diff --git a/main.py b/main.py index 06bd56d..43d716d 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,9 @@ -import sys, time +import sys from mvc.models import VimModel from mvc.views import VimView, CursesAdapter from mvc.controllers import Controller, ReturnCode from mvc.controllers import EditStrategy, CommandStrategy, NormalStrategy +import tools def main(): adapter = CursesAdapter() @@ -15,7 +16,7 @@ def main(): # Первинчая загрузка файла для редактирования if len(sys.argv) > 1: model.LoadFile(sys.argv[1]) - else: model.LoadFile("File not found but i create it"+str(int(time.time()))+".txt") + else: model.LoadFile("File not found but i create it"+str(tools.UnixSec())+".txt") while True: view.Render(model.displayBuffer, diff --git a/mvc/controllers.py b/mvc/controllers.py index c551658..8ec8405 100644 --- a/mvc/controllers.py +++ b/mvc/controllers.py @@ -1,3 +1,4 @@ +import tools from abc import ABC, abstractmethod from mvc.views import CursesAdapter from mvc.models import VimModel, ReturnCode @@ -90,11 +91,36 @@ class NormalStrategy(BaseStrategy): def HandleInput(self, symbolCode) -> int: """Обработка ввода пользователя""" - result = self.handle_common_input(symbolCode) - if result != ReturnCode.CONTINUE: return result - + self.model.UpdateKeyLog(symbolCode) match symbolCode: case self.adapter.KEY_TWO_DOTS: return ReturnCode.SET_COMMAND_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_NULL | self.adapter.KEY_XOR: self.model.ToLineStart() + case self.adapter.KEY_DOLLAR: self.model.ToLineEnd() + case self.adapter.KEY_p: self.model.Paste() + case self.adapter.KEY_w: + if self.model.CombinationCheck("diw", 3): self.model.DeleteWord() + elif self.model.CombinationCheck("yw", 3): self.model.CopyWord() + else: self.model.ToWordEnd() + case self.adapter.KEY_b: self.model.ToWordStart() + case self.adapter.KEY_d: + if self.model.CombinationCheck("dd", 3): self.model.CutLine() + case self.adapter.KEY_y: + if self.model.CombinationCheck("yy", 3): self.model.CopyLine() + case self.adapter.KEY_x: self.model.DeleteNext() + case self.adapter.KEY_G: + num, ind = tools.ExtracrtNumBeforeLastKey(''.join([chr(item[0]) for item in self.model.keyLog])) + if num != None and ind != None: + self.model.MoveToLine(num) + else: + self.model.ToFileEnd() + case self.adapter.KEY_g: + if self.model.CombinationCheck("gg", 3): self.model.ToFileStart() + case self.adapter.KEY_PG_UP: self.model.PageUp() + case self.adapter.KEY_PG_DOWN: self.model.PageDown() self.model.Scroll() return ReturnCode.GOOD \ No newline at end of file diff --git a/mvc/models.py b/mvc/models.py index da0d6db..198a557 100644 --- a/mvc/models.py +++ b/mvc/models.py @@ -1,3 +1,5 @@ +import re +import tools from enum import Enum class ReturnCode(Enum): @@ -19,9 +21,24 @@ class VimModel: self.scrollX = 0 # горизонтальная прокрутка self.file_path = "" # путь к файлу self.mode = "" + self.keyLog = [] # лог нажатий клавиш (кортежи вида (код символа, юникс время нажатия)) self.commandBuffer = [] # буффер для команды self.exchangeBuffer = [] # буффер обмена + def UpdateKeyLog(self, symbolCode: int) -> None: + if len(self.keyLog) > 5000: self.keyLog.clear() + self.keyLog.append((symbolCode, tools.UnixSec())) + + def CombinationCheck(self, comb: str, interval: int) -> None: + """Проверяет была ли нажата комбинация клавиш. + Принимает фактический ввод, команду с которой сравниваем и интервал времени на команду""" + if len(self.keyLog) > len(comb) - 1: + mbComb = self.keyLog[-len(comb):] + if all(chr(mbComb[i][0]) == char for i, char in enumerate(comb)) \ + and mbComb[len(mbComb)-1][1] - mbComb[0][1] < interval: + return True + return False + def ModeBar(self) -> str: modeBar = f"MODE: {self.mode} | FILE: {self.file_path} | LINE: {self.currentLine+1}/{len(self.displayBuffer)}" if self.mode == "COMMAND": @@ -47,15 +64,89 @@ class VimModel: self.displayBuffer[self.currentLine].insert(self.currentCol, chr(symbolCode)) self.currentCol += 1 + def ToLineStart(self) -> None: + self.currentCol = 0 + + def ToLineEnd(self) -> None: + self.currentCol = len(self.displayBuffer[self.currentLine]) + + def ToWordEnd(self) -> None: + line = ''.join(self.displayBuffer[self.currentLine]) + # Находим ближайший непробельный символ + non_space_index = next((i for i, char in enumerate(line[self.currentCol:]) if char != ' '), None) + if non_space_index is not None: + non_space_index += self.currentCol + right_space_index = line.find(' ', non_space_index) + self.currentCol = right_space_index if right_space_index != -1 else len(line) + + def ToWordStart(self) -> None: + line = ''.join(self.displayBuffer[self.currentLine]) + non_space_index = next((i for i in range(self.currentCol - 1, -1, -1) if line[i] != ' '), None) + if non_space_index is not None: + left_space_index = line.rfind(' ', 0, non_space_index) + self.currentCol = left_space_index + 1 if left_space_index != -1 else 0 + + def ToFileEnd(self) -> None: + self.currentLine = len(self.displayBuffer) - 1 + self.currentCol = len(self.displayBuffer[self.currentLine]) + + def ToFileStart(self) -> None: + self.currentLine = 0 + self.currentCol = 0 + + def DeleteNext(self) -> None: + if self.currentCol + 1 < len(self.displayBuffer[self.currentLine]): + del self.displayBuffer[self.currentLine][self.currentCol + 1] + + def PageUp(self) -> None: + self.currentCol = 0 + if self.currentLine > self.displayLinesCount: + self.currentLine -= self.displayLinesCount + else: + self.currentLine = 0 + + def PageDown(self) -> None: + self.currentCol = 0 + if self.currentLine + self.displayLinesCount < len(self.displayBuffer): + self.currentLine += self.displayLinesCount + else: + self.currentLine = len(self.displayBuffer) - 1 + + def DeleteWord(self) -> None: + start_index, end_index = self.WordUnderCursor() + line = self.displayBuffer[self.currentLine] + if end_index < len(line) and line[end_index] == ' ': + end_index += 1 + self.displayBuffer[self.currentLine] = line[:start_index] + line[end_index:] + self.currentCol = start_index + + def CopyWord(self) -> None: + start_index, end_index = self.WordUnderCursor() + line = self.displayBuffer[self.currentLine] + self.exchangeBuffer = line[start_index:end_index+1] + + def Paste(self) -> None: + self.displayBuffer[self.currentLine][self.currentCol+1:self.currentCol+1] = self.exchangeBuffer + + def CutLine(self) -> None: + self.exchangeBuffer = self.displayBuffer[self.currentLine] + self.displayBuffer[self.currentLine] = [] + self.currentCol = 0 + + def CopyLine(self) -> None: + self.exchangeBuffer = self.displayBuffer[self.currentLine] + + def MoveToLine(self, numberLine: int) -> None: + numberLine += 1 + if numberLine >= 0 and numberLine < len(self.displayBuffer): + self.currentLine = numberLine + self.currentCol = 0 + def EnterCommand(self): """Обработка введенной команды""" cmd = ''.join(self.commandBuffer) self.commandBuffer.clear() match cmd: - case "$": # Перемещение в конец строки - self.currentCol = len(self.displayBuffer[self.currentLine]) - case "^" | "0": # Перемещение в начало строки - self.currentCol = 0 case "q": # Выход из программы return ReturnCode.EXIT_CODE case "q!": # Выход без сохранения @@ -75,75 +166,6 @@ class VimModel: self.currentCol = 0 self.displayBuffer[self.currentLine] = [] return ReturnCode.SET_EDIT_MODE - case "RIGHT": # Перемещение курсора на 1 позицию вправо - self.currentCol += 1 - case "LEFT": # Перемещение курсора на 1 позицию влево - if self.currentCol > 0: - self.currentCol -= 1 - case "UP": # Перемещение курсора на 1 позицию вверх - if self.currentLine > 0: - self.currentLine -= 1 - if self.currentCol > len(self.displayBuffer[self.currentLine]): - self.currentCol = len(self.displayBuffer[self.currentLine]) - case "DOWN": # Перемещение курсора на 1 позицию вниз - if self.currentLine < len(self.displayBuffer) - 1: - self.currentLine += 1 - if self.currentCol > len(self.displayBuffer[self.currentLine]): - self.currentCol = len(self.displayBuffer[self.currentLine]) - case "w": # Перемещение курсора в конец слова справа от курсора - line = ''.join(self.displayBuffer[self.currentLine]) - # Находим ближайший непробельный символ - non_space_index = next((i for i, char in enumerate(line[self.currentCol:]) if char != ' '), None) - if non_space_index is not None: - non_space_index += self.currentCol - right_space_index = line.find(' ', non_space_index) - self.currentCol = right_space_index if right_space_index != -1 else len(line) - case "b": # Перемещает курсор в начало слова слева от курсора - line = ''.join(self.displayBuffer[self.currentLine]) - non_space_index = next((i for i in range(self.currentCol - 1, -1, -1) if line[i] != ' '), None) - if non_space_index is not None: - left_space_index = line.rfind(' ', 0, non_space_index) - self.currentCol = left_space_index + 1 if left_space_index != -1 else 0 - case "gg": # Переход в начало файла - self.currentLine = 0 - self.currentCol = 0 - case "G": # Переход в конец файла - self.currentLine = len(self.displayBuffer) - 1 - self.currentCol = len(self.displayBuffer[self.currentLine]) - case "x": # Удаляет символ после курсора - if self.currentCol + 1 < len(self.displayBuffer[self.currentLine]): - del self.displayBuffer[self.currentLine][self.currentCol + 1] - case "yy": # копирует текущую строку - self.exchangeBuffer = self.displayBuffer[self.currentLine] - case "yw": # Копирует слово под курсором - start_index, end_index = self.WordUnderCursor() - line = self.displayBuffer[self.currentLine] - self.exchangeBuffer = line[start_index:end_index+1] - case "diw": # Удаляет слово под курсором - start_index, end_index = self.WordUnderCursor() - line = self.displayBuffer[self.currentLine] - if end_index < len(line) and line[end_index] == ' ': - end_index += 1 - self.displayBuffer[self.currentLine] = line[:start_index] + line[end_index:] - self.currentCol = start_index - case "dd": # Вырезает текущую строку - self.exchangeBuffer = self.displayBuffer[self.currentLine] - self.displayBuffer[self.currentLine] = [] - self.currentCol = 0 - case "p": # Вставить после курсора - self.displayBuffer[self.currentLine][self.currentCol+1:self.currentCol+1] = self.exchangeBuffer - case "PG_UP": # Перейти на экран вверх - self.currentCol = 0 - if self.currentLine > self.displayLinesCount: - self.currentLine -= self.displayLinesCount - else: - self.currentLine = 0 - case "PG_DOWN": # Перейти на экран вниз - self.currentCol = 0 - if self.currentLine + self.displayLinesCount < len(self.displayBuffer): - self.currentLine += self.displayLinesCount - else: - self.currentLine = len(self.displayBuffer) - 1 case "h": self.LoadFile("config/usage.txt") return ReturnCode.SET_BASIC_MODE diff --git a/mvc/views.py b/mvc/views.py index 1468b27..5538c1a 100644 --- a/mvc/views.py +++ b/mvc/views.py @@ -2,16 +2,30 @@ import curses class CursesAdapter: def __init__(self) -> None: - self.KEY_LEFT = curses.KEY_LEFT - self.KEY_RIGHT = curses.KEY_RIGHT - self.KEY_UP = curses.KEY_UP - self.KEY_DOWN = curses.KEY_DOWN - self.KEY_BACKSPACE_1 = 127 self.KEY_BACKSPACE_2 = 8 self.KEY_ENTER = 10 self.KEY_CTRL_S = 19 self.KEY_ESCAPE = 27 + self.KEY_DOLLAR = 36 + self.KEY_NULL = 48 self.KEY_TWO_DOTS = 59 + self.KEY_G = 71 + self.KEY_XOR = 94 + self.KEY_b = 98 + self.KEY_d = 100 + self.KEY_g = 103 + self.KEY_p = 112 + self.KEY_w = 119 + self.KEY_x = 120 + self.KEY_y = 121 + self.KEY_BACKSPACE_1 = 127 + self.KEY_LEFT = curses.KEY_LEFT + self.KEY_RIGHT = curses.KEY_RIGHT + self.KEY_UP = curses.KEY_UP + self.KEY_DOWN = curses.KEY_DOWN + self.KEY_PG_UP = 450 + self.KEY_PG_DOWN = 456 + self.screen = curses.initscr() self.screen.keypad(True) diff --git a/tools.py b/tools.py new file mode 100644 index 0000000..e19ba24 --- /dev/null +++ b/tools.py @@ -0,0 +1,14 @@ +import time, re + +def ExtracrtNumBeforeLastKey(s): + numbers_list = list(re.finditer(r'\d+', s)) + if numbers_list: + last_number_match = numbers_list[-1] + number = last_number_match.group() + i = last_number_match.start() + if i + len(number) == len(s) - 1: + return int(number), i + return None, None + +def UnixSec() -> int: + return int(time.time()) \ No newline at end of file