VIM-like-text-editor/mvc/models.py

263 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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.commandBuffer = [] # буффер для команды
self.exchangeBuffer = [] # буффер обмена
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 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!": # Выход без сохранения
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 "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
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 = []