Wim is a terminal-based text editor developed as a college project.
-
Mateusz Piasecki
-
Hubert Klimowicz
-
To enter command mode press
Ctrl + ; -
To explore commands history press
↑or↓ -
To execute typed command press
Enter -
To exit command mode press
Esc -
To save changes press
Ctrl + S -
To exit application press
Ctrl + Q
-
mv [DIRECTION - l/r/u/d] [COUNT]moves cursor by a given number of positions in a given direction -
i "[TEXT]"inserts text at the current cursor positions -
d [COUNT]deletes a given number of characters at the current cursor position -
f "[PATTERN]"moves cursor to the next occurance of a given pattern if found -
ssaves changes -
sa "[PATH]"saves file under given path -
l [PATH]loads file from a given path -
mc [NAME] "[COMAND / MACRO]" "[COMMAND / MACRO]" ..."creates macro with a given name
Example of macro definition
mc macro1 "f \"abc\"" "d 3" "i \"replacement\""
This macro will replace first encountered "abc" pattern and replace it with "replacement".
NOTE that created macros are saved in a file and thus available even after restarting the application
-
md [NAME]deletes macro with a given name -
[MACRO NAME]executes defined macro
Celem projektu było stworzenie konsolowego edytora tekstu. Edytor miał umożliwiać użytkownikowi definiowanie własnych makr usprawniających pracę z tekstem.
-
Model - Udostępnia interfejs umożliwiający edycję i zarządzanie plikiem tekstowym.
-
Commandbuffer - Logiczna reprezentacja pola do wpisywania komend.
-
IView - wirtualny interfejs służący do prezentacji stanu programu.
-
AppManager - Klasa zarządzająca aplikacją. Posiada instancje klasy Model, CommandBuffer oraz listę subskrybujących widoków klasy IView. Śledzi tryb w jakim znajduje się program (Edit/Command) i na tej podstawie odpowiednio przekierowuje obsługę zdarzeń wejściowych.
-
ICommand - Klasa bazowa dla polimorficznej implementacji każdej komendy. Można je utworzyć z tekstu, wypisać na strumień wyjściowy oraz wywołać.
-
Macro - Reprezentuje pojedyncze makro, jako zbiór sekwencyjnie wykonywanych komend.
-
MacroDatabase - Przechowuje wszystkie zapisywane makra i zarządza zapisywaniem ich do pliku.
-
CommandParser - Służy do rozpoznawania rodzaju komendy i utworzenia komendy z tekstu.
-
StringDivider - Główna logika parsowania. Służy do rozpoznawania początku i końca zadanego typu podsłowa (argumentu komendy) i zwracania kolejnych argumentów. Użycie polega na wywołaniu metody, przekazując zmienną typu tekstowego, do którego argument może być zapisany.
-
StringEscaper - Klasa odpowiedzialna za kodowanie i dekodowanie znaków
"i\, do przetwarzania tekstu otoczonego znakami". -
...Command - Wszystkie klasy o nazwach kończących się na
Command(pozaICommand) są oddzielnymi komendami, które można parsować i wywołać.
Klasy zależne od systemu operacyjnego i konkretnej formy prezentacji:
Oryginalnie projekt został stworzony tak, aby działał w systemie operacyjnym Windows i komunikował się z użytkownikiem za pomocą terminala. Został jednak zaprojektowany w taki sposób, aby stworzenie interfejsu graficznego, bądź przeniesienie go na Linuxa wymagało minimalnej ingerencji w istniejącyc kod. W tym celu wydzieliliśmy specyficzne fragmenty kodu.
-
TerminalContoller - pobiera input od użytkownika i przekazuje go do subskrybującego AppManager'a.
-
TerminalView - dziedziczy po klasie View i służy do wyswietlania stanu programu w terminalu.
- Pisanie parsera jest trudne. Należy starannie rozważyć wszystkie przypadki brzegowe co do tekstu, który użytkownik może wpisać - jest ich bardzo dużo i z bardzo wysokim prawdopodbieństwem nie wszystkie będą przez programistę zauważone. Pisanie parsera poprzez zastosowanie tzw. builder pattern (
StringDivider) wygląda czysto z zewnątrz, natomiast może potencjalnie ograniczać programistę i wymuszać na nim pisanie licznych metod do klasy. - Co do architektury programu jako całości, w tym projektowania obiektowego, należy umieć przewidzieć jak najwięcej rzeczy - tj. należy przewidzieć, czy wcześniej postawiony problem można w ogóle rozwiązać w proponowany sposób i na jakie problemy można natrafić:
- podczas implementacji: czy nie korzystamy z pól, które mają być prywatne, czy nasza implementacja faktycznie rozwiązuje zadany problem
- podczas zmian decyzji projektowych: czy ta zmiana wymusi zmiany w innych fragmentach kodu (i jakie?), czy ta zmiana sprawi, że inny fragment kodu przestanie działać?
Niestety, projekty na studiach nie oddają pełnej skali tego problemu, ponieważ postawiony problem jest często jasno zdefiniowany, a wymagania nie zmieniają się. Ponadto, projekty realizowane na studiach są z natury nieduże. Najlepsze, co możemy zrobić w dużym projekcie, to eksperymentować z wcześniej przemyślanymi pomysłami na rozwiązanie wcześniej podzelonego problemu, z różnymi zewnętrznymi serwisami itp.
Podczas realizacji projektu dwukrotnie natrafiliśmy na tak zwane "circular dependency". Winikało to z tego, że nie do końca przemyśleliśmy strukturę projektu przed przytąpieniem do pisania programu. W rezultacie zmarnowaliśmy część czasu na refactoring, którego można było uniknąć. Ta sytuacja jest dobrą nauczką na przyszłość aby gruntownie zaplanować projekt przed przystąpieniem do jego realizacji.



