import re import tools from enum import Enum class ReturnCode(Enum): GOOD = -101 EXIT_CODE = -100 SET_BASIC_MODE = -99 SET_COMMAND_MODE = -98 SET_EDIT_MODE = -97 CONTINUE = -96 class VimModel: def __init__(self, displayLinesCount: int, displayColsCount: int): self.displayLinesCount = displayLinesCount self.displayColsCount = displayColsCount self.displayBuffer = [] # буфер для хранения всех строк self.currentLine = 0 # текущий индекс строки self.currentCol = 0 # текущий индекс колонки self.scrollY = 0 # вертикальная прокрутка 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": modeBar += f" | COMMAND BUFFER: {''.join(self.commandBuffer)}" return modeBar def Scroll(self) -> None: if self.currentLine < self.scrollY: self.scrollY = self.currentLine elif self.currentLine >= self.scrollY + self.displayLinesCount - 1: self.scrollY = self.currentLine - self.displayLinesCount + 2 if self.currentCol < self.scrollX: self.scrollX = self.currentCol elif self.currentCol >= self.scrollX + self.displayColsCount: self.scrollX = self.currentCol - self.displayColsCount + 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 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 "q": # Выход из программы return ReturnCode.EXIT_CODE case "q!": # Выход без сохранения return ReturnCode.EXIT_CODE case "wq!": # Записать в текущий файл + выйти self.SaveFile() return ReturnCode.EXIT_CODE case "i": # Вход в режим редактирования return ReturnCode.SET_EDIT_MODE case "I": # Переходит в начало строки и начинает ввод текста self.currentCol = 0 return ReturnCode.SET_EDIT_MODE case "A": # Переходит в конец строки и начинает ввод текста self.currentCol = len(self.displayBuffer[self.currentLine]) return ReturnCode.SET_EDIT_MODE case "S": # Удаление строки на которой курсор и вход в режим редактирования self.currentCol = 0 self.displayBuffer[self.currentLine] = [] return ReturnCode.SET_EDIT_MODE case "h": self.LoadFile("config/usage.txt") return ReturnCode.SET_BASIC_MODE case _: # Открывает файл if len(cmd) > 2 and cmd[:2] == 'o ': filename = cmd[2:] self.LoadFile(filename) return ReturnCode.SET_BASIC_MODE # Запись в файл elif len(cmd) > 2 and cmd[:2] == 'w ': filename = cmd[2:] self.WriteFile(filename) # Переход на строку по указанному номеру elif len(cmd) > 1 and cmd[-1] == 'G': if cmd[:-1].isdigit(): numberLine = int(cmd[:-1]) if numberLine >= 0 and numberLine < len(self.displayBuffer): self.currentLine = numberLine self.currentCol = 0 return ReturnCode.GOOD def WordUnderCursor(self)-> tuple[int, int]: """Возвращает индекс начала и индекс конца слова под курсором""" line = ''.join(self.displayBuffer[self.currentLine]) start_index = line.rfind(' ', 0, self.currentCol) start_index = 0 if start_index == -1 else start_index + 1 end_index = line.find(' ', self.currentCol) end_index = len(line) if end_index == -1 else end_index return start_index, end_index def Enter(self) -> None: # Разделяем текущую строку на две части new_line = self.displayBuffer[self.currentLine][self.currentCol:] self.displayBuffer[self.currentLine] = self.displayBuffer[self.currentLine][:self.currentCol] self.currentLine += 1 # Переходим на следующую строку 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 del self.displayBuffer[self.currentLine][self.currentCol] # Удаляем символ elif self.currentLine > 0: # Если текущая строка не первая # Объединяем текущую строку с предыдущей prev_line_length = len(self.displayBuffer[self.currentLine - 1]) self.displayBuffer[self.currentLine - 1].extend(self.displayBuffer[self.currentLine]) del self.displayBuffer[self.currentLine] self.currentLine -= 1 self.currentCol = prev_line_length # Переходим в конец предыдущей строки def MoveLeft(self) -> None: if self.currentCol > 0: self.currentCol -= 1 elif self.currentLine > 0: self.currentLine -= 1 self.currentCol = len(self.displayBuffer[self.currentLine]) def MoveRight(self) -> None: if self.currentCol < len(self.displayBuffer[self.currentLine]): self.currentCol += 1 elif self.currentLine < len(self.displayBuffer) - 1: self.currentLine += 1 self.currentCol = 0 def MoveUp(self) -> None: if self.currentLine > 0: self.currentLine -= 1 self.currentCol = min(self.currentCol, len(self.displayBuffer[self.currentLine])) def MoveDown(self) -> None: if self.currentLine < len(self.displayBuffer) - 1: self.currentLine += 1 self.currentCol = min(self.currentCol, len(self.displayBuffer[self.currentLine])) def LoadFile(self, file_path) -> None: """Загрузка файла для редактирования""" self.Reset() self.file_path = file_path self.mode = "NORMAL" try: with open(file_path, "r") as file: self.displayBuffer = [list(line.rstrip('\n')) for line in file.readlines()] except FileNotFoundError: print(f"File '{file_path}' not found. Starting with empty buffer.") self.displayBuffer = [] self.displayBuffer.append([]) def SaveFile(self) -> None: """Сохранение текущего файла""" self.WriteFile(self.file_path) def WriteFile(self, file_path) -> None: """Запись в файл по указанному пути""" try: with open(file_path, "w") as file: for line in self.displayBuffer: file.write(''.join(line) + '\n') print(f"In file '{file_path}' written successfully.") except Exception as e: print(f"Error writing file: {str(e)}") def Reset(self) -> None: self.displayBuffer = [] self.currentLine = 0 self.currentCol = 0 self.scrollY = 0 self.scrollX = 0 self.file_path = "" self.mode = "" self.commandBuffer = [] self.exchangeBuffer = []