diff --git a/main.py b/main.py index 4f75e69..282aecd 100644 --- a/main.py +++ b/main.py @@ -1,13 +1,13 @@ from mvc.models import VimModel from mvc.views import VimView -from mvc.controllers import Controller, EditStrategy +from mvc.controllers import Controller, ReturnCode +from mvc.controllers import EditStrategy, CommandStrategy, NormalStrategy def main(): - model = VimModel() - view = VimView() + model, view = VimModel(), VimView() # начальный режим - редактирование - strategy = EditStrategy(model, view.curses_adapter) + strategy = NormalStrategy(model, view.curses_adapter) controller = Controller(strategy) # Загрузка файла для редактирования @@ -18,10 +18,17 @@ def main(): view.Render(model.displayBuffer, model.currentLine, model.currentCol, model.scrollX, model.scrollY, - model.file_path) + model.ModeBar()) symbolCode = view.curses_adapter.GetChar() - if not controller.HandleInput(symbolCode): break + + match controller.HandleInput(symbolCode): + case ReturnCode.SET_BASIC_MODE: + controller.SetStrategy(NormalStrategy(model, view.curses_adapter)) + case ReturnCode.SET_COMMAND_MODE: + controller.SetStrategy(CommandStrategy(model, view.curses_adapter)) + case ReturnCode.EXIT_CODE: + break view.curses_adapter.Cleanup() diff --git a/mvc/controllers.py b/mvc/controllers.py index 47d2813..2a7dbfe 100644 --- a/mvc/controllers.py +++ b/mvc/controllers.py @@ -1,33 +1,67 @@ from abc import ABC, abstractmethod -from mvc.views import CursesAdapter -from mvc.models import VimModel -class IStrategy(ABC): - def __init__(self, model: VimModel, adapter: CursesAdapter): +from mvc.views import CursesAdapter +from mvc.models import VimModel, ReturnCode + +def isAscii(symbolCode: int) -> bool: + if symbolCode > 126 or symbolCode < 32: return False + else: return True + +class Strategy(ABC): + def __init__(self, model: VimModel, adapter: CursesAdapter, mode: str): self.model = model self.adapter = adapter + self.mode = model.mode = mode + + def isAscii(symbolCode: int) -> bool: + if symbolCode > 126 or symbolCode < 32: return False + else: return True @abstractmethod - def HandleInput(self, symbolCode: int) -> bool: + def HandleInput(self, symbolCode: int) -> int: pass class Controller: - def __init__(self, strategy: IStrategy): + def __init__(self, strategy: Strategy): self.strategy = strategy - def SetController(self, strategy: IStrategy): + def SetStrategy(self, strategy: Strategy): self.strategy = strategy def HandleInput(self, symbolCode: int): return self.strategy.HandleInput(symbolCode) -class EditStrategy(IStrategy): +class CommandStrategy(Strategy): + """Режим ввода коман""" def __init__(self, model: VimModel, adapter: CursesAdapter): - self.model = model - self.adapter = adapter - - def HandleInput(self, symbolCode): + super().__init__(model, adapter, mode="COMMAND") + + def HandleInput(self, symbolCode) -> int: + """Обработка ввода пользователя""" + match symbolCode: + case self.adapter.KEY_ESCAPE: return ReturnCode.SET_BASIC_MODE + case self.adapter.KEY_LEFT: self.model.MoveLeft() + case self.adapter.KEY_RIGHT: self.model.MoveRight() + case self.adapter.KEY_UP: self.model.MoveUp() + case self.adapter.KEY_DOWN: self.model.MoveDown() + case self.adapter.KEY_CTRL_S: self.model.SaveFile() + case self.adapter.KEY_ENTER: return self.model.EnterCommand() + case self.adapter.KEY_BACKSPACE_1: self.model.BackspaceCommand() + case self.adapter.KEY_BACKSPACE_2: self.model.BackspaceCommand() + case _: + if not isAscii(symbolCode): return ReturnCode.GOOD + self.model.InsertCommandSymbol(symbolCode) + + self.model.Scroll(self.adapter.lines, self.adapter.cols) + return ReturnCode.GOOD + + +class EditStrategy(Strategy): + """Режим редактирования""" + def __init__(self, model: VimModel, adapter: CursesAdapter): + super().__init__(model, adapter, mode="EDIT") + + def HandleInput(self, symbolCode) -> int: """Обработка ввода пользователя""" match symbolCode: - case self.adapter.KEY_ESCAPE: return False case self.adapter.KEY_LEFT: self.model.MoveLeft() case self.adapter.KEY_RIGHT: self.model.MoveRight() case self.adapter.KEY_UP: self.model.MoveUp() @@ -37,10 +71,26 @@ class EditStrategy(IStrategy): case self.adapter.KEY_BACKSPACE_2: self.model.Backspace() case self.adapter.KEY_CTRL_S: self.model.SaveFile() case _: - if symbolCode > 127 or symbolCode < 0: - print("Only 1-byte symbols!") - return False + if not isAscii(symbolCode): return ReturnCode.GOOD self.model.InsertSymbol(symbolCode) self.model.Scroll(self.adapter.lines, self.adapter.cols) - return True \ No newline at end of file + return ReturnCode.GOOD + +class NormalStrategy(Strategy): + """Режим навигации""" + def __init__(self, model: VimModel, adapter: CursesAdapter): + super().__init__(model, adapter, mode="NORMAL") + + def HandleInput(self, symbolCode) -> int: + """Обработка ввода пользователя""" + match symbolCode: + case self.adapter.KEY_LEFT: self.model.MoveLeft() + case self.adapter.KEY_RIGHT: self.model.MoveRight() + case self.adapter.KEY_UP: self.model.MoveUp() + case self.adapter.KEY_DOWN: self.model.MoveDown() + case self.adapter.KEY_CTRL_S: self.model.SaveFile() + case self.adapter.KEY_TWO_DOTS: return ReturnCode.SET_COMMAND_MODE + + self.model.Scroll(self.adapter.lines, self.adapter.cols) + return ReturnCode.GOOD \ No newline at end of file diff --git a/mvc/models.py b/mvc/models.py index a403768..a8bfc86 100644 --- a/mvc/models.py +++ b/mvc/models.py @@ -1,3 +1,11 @@ +from enum import Enum + +class ReturnCode(Enum): + GOOD = -101 + EXIT_CODE = -100 + SET_BASIC_MODE = -99 + SET_COMMAND_MODE = -98 + class VimModel: def __init__(self): self.displayBuffer = [] # буфер для хранения всех строк @@ -6,6 +14,14 @@ class VimModel: self.scrollY = 0 # вертикальная прокрутка self.scrollX = 0 # горизонтальная прокрутка self.file_path = "" # путь к файлу + self.mode = "" + self.commandBuffer = [] # буффер для команды + + def ModeBar(self) -> str: + modeBar = f"MODE: {self.mode} | FILE: {self.file_path} | LINE: {self.currentLine+1}/{len(self.displayBuffer)}" + if self.mode == "COMMAND": + modeBar += f" | COMMAND BUFFER: {''.join(self.commandBuffer)}" + return modeBar def Scroll(self, displayLines: int, displayCols: int) -> None: if self.currentLine < self.scrollY: @@ -18,11 +34,24 @@ class VimModel: elif self.currentCol >= self.scrollX + displayCols: self.scrollX = self.currentCol - displayCols + 1 + def InsertCommandSymbol(self, symbolCode: int) -> None: + self.commandBuffer.append(chr(symbolCode)) + def InsertSymbol(self, symbolCode: int) -> None: if self.currentCol <= len(self.displayBuffer[self.currentLine]): # проверяем, не превышает ли индекс колонки длину строки self.displayBuffer[self.currentLine].insert(self.currentCol, chr(symbolCode)) self.currentCol += 1 + def EnterCommand(self): + cmd = ''.join(self.commandBuffer) + self.commandBuffer.clear() + match cmd: + case "$": + self.currentCol = len(self.displayBuffer[self.currentLine]) + return ReturnCode.GOOD + case "q": + return ReturnCode.EXIT_CODE + def Enter(self) -> None: # Разделяем текущую строку на две части new_line = self.displayBuffer[self.currentLine][self.currentCol:] @@ -31,6 +60,10 @@ class VimModel: self.displayBuffer.insert(self.currentLine, new_line) # Вставляем новую строку self.currentCol = 0 # Сбрасываем индекс колонки + def BackspaceCommand(self) -> None: + if len(self.commandBuffer) > 0: + self.commandBuffer.pop() + def Backspace(self) -> None: if self.currentCol > 0: # Если символ существует в текущей строке self.currentCol -= 1 diff --git a/mvc/views.py b/mvc/views.py index 9932201..00bb3fb 100644 --- a/mvc/views.py +++ b/mvc/views.py @@ -8,7 +8,7 @@ class VimView: displayBuffer: list[list[str]], currentLine: int, currentCol: int, scrollX: int, scrollY: int, - file_path: str): + modeBarData: str): """Отрисовка текущего состояния""" self.curses_adapter.screen.clear() @@ -25,22 +25,14 @@ class VimView: self.curses_adapter.SetString(i, 0, '') # update mode bar - self.EditModeBar(currentLine, len(displayBuffer), file_path) + self.SetModeBar(modeBarData) # update cursor self.curses_adapter.SetCursor(currentLine - scrollY, currentCol - scrollX) self.curses_adapter.Refresh() - def EditModeBar(self, currentLine, totalLines, fileName): + def SetModeBar(self, modeBarData: str): """Print edit mode information panel""" - self.curses_adapter.screen.addstr(self.curses_adapter.lines - 1, 0, ' ' * (self.curses_adapter.cols - 1)) # Очистка строки - self.curses_adapter.screen.addstr(self.curses_adapter.lines - 1, 0, "FILE: ") - self.curses_adapter.screen.addstr(fileName, curses.color_pair(1)) # Имя файла - self.curses_adapter.screen.addstr(" | MODE:") - self.curses_adapter.screen.addstr(" EDIT", curses.color_pair(1)) - self.curses_adapter.screen.addstr(" | LINE: ") - self.curses_adapter.screen.addstr(str(currentLine + 1), curses.color_pair(1)) - self.curses_adapter.screen.addstr("/") - self.curses_adapter.screen.addstr(str(totalLines), curses.color_pair(1)) # Общее количество строк + self.curses_adapter.SetString(self.curses_adapter.lines - 1, 0, modeBarData) class CursesAdapter: def __init__(self) -> None: @@ -53,6 +45,7 @@ class CursesAdapter: self.KEY_ENTER = 10 self.KEY_CTRL_S = 19 self.KEY_ESCAPE = 27 + self.KEY_TWO_DOTS = 59 self.screen = curses.initscr()