2025-02-05 10:59:54 +03:00
|
|
|
|
import tools
|
2025-02-04 18:36:12 +03:00
|
|
|
|
from enum import Enum
|
2025-02-11 10:37:35 +03:00
|
|
|
|
from typing import List
|
|
|
|
|
from mystring import MyString as MyString
|
2025-02-04 18:36:12 +03:00
|
|
|
|
|
|
|
|
|
class ReturnCode(Enum):
|
|
|
|
|
GOOD = -101
|
|
|
|
|
EXIT_CODE = -100
|
|
|
|
|
SET_BASIC_MODE = -99
|
|
|
|
|
SET_COMMAND_MODE = -98
|
2025-02-04 18:46:16 +03:00
|
|
|
|
SET_EDIT_MODE = -97
|
2025-02-04 19:06:51 +03:00
|
|
|
|
CONTINUE = -96
|
2025-02-04 18:36:12 +03:00
|
|
|
|
|
2025-02-04 13:16:23 +03:00
|
|
|
|
class VimModel:
|
2025-02-11 10:37:35 +03:00
|
|
|
|
observers: List
|
|
|
|
|
displayLinesCount: int
|
|
|
|
|
displayColsCount: int
|
|
|
|
|
showLineNumbers: bool
|
|
|
|
|
lastSearch: tuple[str, int] # tuple (search str, direction)
|
2025-02-11 11:45:28 +03:00
|
|
|
|
displayBuffer: List[List[MyString]]
|
|
|
|
|
dump: List[List[MyString]]
|
2025-02-11 10:37:35 +03:00
|
|
|
|
currentLine: int
|
|
|
|
|
currentCol: int
|
|
|
|
|
scrollY: int
|
|
|
|
|
scrollX: int
|
|
|
|
|
file_path: MyString # filepath
|
|
|
|
|
mode: MyString
|
|
|
|
|
inputAfterCursor: bool
|
|
|
|
|
keyLog: List[tuple[int, int]] # key log for check combinations (tuples (symbol code, tap unix time))
|
2025-02-11 11:45:28 +03:00
|
|
|
|
commandBuffer: List[MyString] # buffer for command
|
|
|
|
|
exchangeBuffer: List[MyString] # buffer for exchange
|
2025-02-11 10:37:35 +03:00
|
|
|
|
|
2025-02-05 00:24:14 +03:00
|
|
|
|
def __init__(self, displayLinesCount: int, displayColsCount: int):
|
2025-02-10 20:57:28 +03:00
|
|
|
|
self.observers = []
|
2025-02-05 00:24:14 +03:00
|
|
|
|
self.displayLinesCount = displayLinesCount
|
|
|
|
|
self.displayColsCount = displayColsCount
|
2025-02-05 22:36:12 +03:00
|
|
|
|
self.showLineNumbers = True
|
2025-02-05 23:18:49 +03:00
|
|
|
|
self.lastSearch = ()
|
2025-02-11 10:37:35 +03:00
|
|
|
|
self.displayBuffer = []
|
2025-02-05 21:42:04 +03:00
|
|
|
|
self.dump = []
|
2025-02-11 10:37:35 +03:00
|
|
|
|
self.currentLine = 0
|
|
|
|
|
self.currentCol = 0
|
|
|
|
|
self.scrollY = 0
|
|
|
|
|
self.scrollX = 0
|
|
|
|
|
self.file_path = MyString()
|
|
|
|
|
self.mode = MyString()
|
2025-02-05 23:46:41 +03:00
|
|
|
|
self.inputAfterCursor = False
|
2025-02-11 10:37:35 +03:00
|
|
|
|
self.keyLog = []
|
|
|
|
|
self.commandBuffer = []
|
|
|
|
|
self.exchangeBuffer = []
|
2025-02-04 18:36:12 +03:00
|
|
|
|
|
2025-02-10 20:57:28 +03:00
|
|
|
|
def attach(self, observer):
|
|
|
|
|
self.observers.append(observer)
|
|
|
|
|
|
|
|
|
|
def detach(self, observer):
|
|
|
|
|
self.observers.remove(observer)
|
|
|
|
|
|
|
|
|
|
def notify(self):
|
|
|
|
|
for observer in self.observers:
|
|
|
|
|
observer.Update()
|
|
|
|
|
|
2025-02-05 23:46:41 +03:00
|
|
|
|
def InputAfterCursor(self) -> None:
|
|
|
|
|
self.inputAfterCursor = not self.inputAfterCursor
|
|
|
|
|
|
2025-02-05 10:59:54 +03:00
|
|
|
|
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
|
|
|
|
|
|
2025-02-04 18:36:12 +03:00
|
|
|
|
def ModeBar(self) -> str:
|
2025-02-11 10:37:35 +03:00
|
|
|
|
modeBar = f"MODE: {self.mode.data()} | FILE: {self.file_path.data()} | LINE: {self.currentLine+1}/{len(self.displayBuffer)}"
|
|
|
|
|
if self.mode == MyString("COMMAND"):
|
2025-02-11 11:45:28 +03:00
|
|
|
|
modeBar += f" | COMMAND BUFFER: {''.join(item.data() for item in self.commandBuffer)}"
|
2025-02-04 18:36:12 +03:00
|
|
|
|
return modeBar
|
2025-02-04 13:16:23 +03:00
|
|
|
|
|
2025-02-05 00:24:14 +03:00
|
|
|
|
def Scroll(self) -> None:
|
2025-02-05 22:21:14 +03:00
|
|
|
|
# Учитываем ширину нумерации строк (6 символов + 1 пробел)
|
|
|
|
|
line_number_width = 7 if self.showLineNumbers else 0
|
|
|
|
|
# Вертикальная прокрутка
|
2025-02-04 14:14:46 +03:00
|
|
|
|
if self.currentLine < self.scrollY:
|
|
|
|
|
self.scrollY = self.currentLine
|
2025-02-05 00:24:14 +03:00
|
|
|
|
elif self.currentLine >= self.scrollY + self.displayLinesCount - 1:
|
|
|
|
|
self.scrollY = self.currentLine - self.displayLinesCount + 2
|
2025-02-04 14:14:46 +03:00
|
|
|
|
|
2025-02-05 22:21:14 +03:00
|
|
|
|
# Горизонтальная прокрутка с учетом нумерации строк
|
|
|
|
|
# Видимая область для текста уменьшается на ширину нумерации строк
|
|
|
|
|
visible_text_width = self.displayColsCount - line_number_width
|
|
|
|
|
|
2025-02-04 14:14:46 +03:00
|
|
|
|
if self.currentCol < self.scrollX:
|
|
|
|
|
self.scrollX = self.currentCol
|
2025-02-05 22:21:14 +03:00
|
|
|
|
elif self.currentCol >= self.scrollX + visible_text_width:
|
|
|
|
|
self.scrollX = self.currentCol - visible_text_width + 1
|
2025-02-04 14:14:46 +03:00
|
|
|
|
|
2025-02-04 18:36:12 +03:00
|
|
|
|
def InsertCommandSymbol(self, symbolCode: int) -> None:
|
2025-02-11 11:45:28 +03:00
|
|
|
|
self.commandBuffer.append(MyString(chr(symbolCode)))
|
2025-02-04 18:36:12 +03:00
|
|
|
|
|
2025-02-04 14:14:46 +03:00
|
|
|
|
def InsertSymbol(self, symbolCode: int) -> None:
|
|
|
|
|
if self.currentCol <= len(self.displayBuffer[self.currentLine]): # проверяем, не превышает ли индекс колонки длину строки
|
2025-02-11 11:45:28 +03:00
|
|
|
|
self.displayBuffer[self.currentLine].insert(self.currentCol, MyString(chr(symbolCode)))
|
2025-02-04 14:14:46 +03:00
|
|
|
|
self.currentCol += 1
|
2025-02-05 23:46:41 +03:00
|
|
|
|
|
|
|
|
|
def InsertSymbolAfterCursor(self, symbolCode: int) -> None:
|
|
|
|
|
if self.currentCol <= len(self.displayBuffer[self.currentLine]): # проверяем, не превышает ли индекс колонки длину строки
|
2025-02-11 11:45:28 +03:00
|
|
|
|
self.displayBuffer[self.currentLine].insert(self.currentCol, MyString(chr(symbolCode)))
|
2025-02-04 14:14:46 +03:00
|
|
|
|
|
2025-02-05 10:59:54 +03:00
|
|
|
|
def ToLineStart(self) -> None:
|
|
|
|
|
self.currentCol = 0
|
|
|
|
|
|
|
|
|
|
def ToLineEnd(self) -> None:
|
|
|
|
|
self.currentCol = len(self.displayBuffer[self.currentLine])
|
|
|
|
|
|
|
|
|
|
def ToWordEnd(self) -> None:
|
2025-02-11 11:45:28 +03:00
|
|
|
|
line = ''.join(item.data() for item in self.displayBuffer[self.currentLine])
|
2025-02-05 10:59:54 +03:00
|
|
|
|
# Находим ближайший непробельный символ
|
|
|
|
|
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:
|
2025-02-11 11:45:28 +03:00
|
|
|
|
line = ''.join(item.data() for item in self.displayBuffer[self.currentLine])
|
2025-02-05 10:59:54 +03:00
|
|
|
|
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:
|
2025-02-05 21:13:50 +03:00
|
|
|
|
self.currentLine -= self.displayLinesCount - 2
|
2025-02-05 10:59:54 +03:00
|
|
|
|
else:
|
|
|
|
|
self.currentLine = 0
|
|
|
|
|
|
|
|
|
|
def PageDown(self) -> None:
|
|
|
|
|
self.currentCol = 0
|
|
|
|
|
if self.currentLine + self.displayLinesCount < len(self.displayBuffer):
|
2025-02-05 21:13:50 +03:00
|
|
|
|
self.currentLine += self.displayLinesCount - 2
|
2025-02-05 10:59:54 +03:00
|
|
|
|
else:
|
|
|
|
|
self.currentLine = len(self.displayBuffer) - 1
|
|
|
|
|
|
|
|
|
|
def DeleteWord(self) -> None:
|
|
|
|
|
start_index, end_index = self.WordUnderCursor()
|
|
|
|
|
line = self.displayBuffer[self.currentLine]
|
2025-02-11 11:45:28 +03:00
|
|
|
|
if end_index < len(line) and line[end_index] == MyString(' '):
|
2025-02-05 10:59:54 +03:00
|
|
|
|
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:
|
2025-02-05 21:13:50 +03:00
|
|
|
|
numberLine -= 1
|
2025-02-05 10:59:54 +03:00
|
|
|
|
if numberLine >= 0 and numberLine < len(self.displayBuffer):
|
|
|
|
|
self.currentLine = numberLine
|
|
|
|
|
self.currentCol = 0
|
|
|
|
|
|
2025-02-04 18:36:12 +03:00
|
|
|
|
def EnterCommand(self):
|
2025-02-04 20:01:25 +03:00
|
|
|
|
"""Обработка введенной команды"""
|
2025-02-11 11:45:28 +03:00
|
|
|
|
cmd = ''.join(item.data() for item in self.commandBuffer)
|
2025-02-04 18:36:12 +03:00
|
|
|
|
self.commandBuffer.clear()
|
|
|
|
|
match cmd:
|
2025-02-04 20:50:47 +03:00
|
|
|
|
case "q": # Выход из программы
|
2025-02-05 22:36:12 +03:00
|
|
|
|
if self.displayBuffer == self.dump:
|
|
|
|
|
return ReturnCode.EXIT_CODE
|
2025-02-05 01:26:37 +03:00
|
|
|
|
case "q!": # Выход без сохранения
|
|
|
|
|
return ReturnCode.EXIT_CODE
|
2025-02-05 21:13:50 +03:00
|
|
|
|
case "wq!" | "x": # Записать в текущий файл + выйти
|
2025-02-05 01:26:37 +03:00
|
|
|
|
self.SaveFile()
|
2025-02-05 01:24:57 +03:00
|
|
|
|
return ReturnCode.EXIT_CODE
|
2025-02-05 21:42:04 +03:00
|
|
|
|
case "w": # Сохраняет файл
|
2025-02-05 21:13:50 +03:00
|
|
|
|
self.SaveFile()
|
2025-02-04 20:50:47 +03:00
|
|
|
|
case "i": # Вход в режим редактирования
|
2025-02-04 20:01:25 +03:00
|
|
|
|
return ReturnCode.SET_EDIT_MODE
|
2025-02-04 21:45:10 +03:00
|
|
|
|
case "I": # Переходит в начало строки и начинает ввод текста
|
|
|
|
|
self.currentCol = 0
|
|
|
|
|
return ReturnCode.SET_EDIT_MODE
|
2025-02-04 21:48:05 +03:00
|
|
|
|
case "A": # Переходит в конец строки и начинает ввод текста
|
|
|
|
|
self.currentCol = len(self.displayBuffer[self.currentLine])
|
|
|
|
|
return ReturnCode.SET_EDIT_MODE
|
2025-02-04 20:50:47 +03:00
|
|
|
|
case "S": # Удаление строки на которой курсор и вход в режим редактирования
|
2025-02-04 20:28:41 +03:00
|
|
|
|
self.currentCol = 0
|
2025-02-04 18:46:16 +03:00
|
|
|
|
self.displayBuffer[self.currentLine] = []
|
|
|
|
|
return ReturnCode.SET_EDIT_MODE
|
2025-02-05 23:46:41 +03:00
|
|
|
|
case "o":
|
|
|
|
|
self.InputAfterCursor()
|
|
|
|
|
return ReturnCode.SET_EDIT_MODE
|
2025-02-05 01:47:34 +03:00
|
|
|
|
case "h":
|
|
|
|
|
self.LoadFile("config/usage.txt")
|
|
|
|
|
return ReturnCode.SET_BASIC_MODE
|
2025-02-05 23:18:49 +03:00
|
|
|
|
case "n":
|
|
|
|
|
if self.lastSearch != ():
|
|
|
|
|
index = tools.findSublistIndex(self.displayBuffer,
|
|
|
|
|
list(self.lastSearch[0]),
|
|
|
|
|
self.currentLine,
|
|
|
|
|
direction=self.lastSearch[1])
|
|
|
|
|
if index != -1:
|
|
|
|
|
self.currentLine = index
|
|
|
|
|
self.currentCol = 0
|
|
|
|
|
self.lastSearch = (self.lastSearch[0], self.lastSearch[1])
|
|
|
|
|
case "N":
|
|
|
|
|
if self.lastSearch != ():
|
|
|
|
|
index = tools.findSublistIndex(self.displayBuffer,
|
|
|
|
|
list(self.lastSearch[0]),
|
|
|
|
|
self.currentLine,
|
|
|
|
|
direction=(self.lastSearch[1]+1)%2)
|
|
|
|
|
if index != -1:
|
|
|
|
|
self.currentLine = index
|
|
|
|
|
self.currentCol = 0
|
|
|
|
|
self.lastSearch = (self.lastSearch[0], (self.lastSearch[1]+1)%2)
|
2025-02-05 21:42:04 +03:00
|
|
|
|
case "e!":
|
|
|
|
|
self.displayBuffer = [sublist.copy() for sublist in self.dump]
|
2025-02-11 11:45:28 +03:00
|
|
|
|
self.currentCol = 0
|
|
|
|
|
self.currentLine = 0
|
2025-02-05 22:21:14 +03:00
|
|
|
|
case "set num":
|
|
|
|
|
self.showLineNumbers = not self.showLineNumbers
|
2025-02-05 01:02:33 +03:00
|
|
|
|
case _:
|
2025-02-05 21:19:08 +03:00
|
|
|
|
# Открывает файл filename
|
2025-02-05 01:02:33 +03:00
|
|
|
|
if len(cmd) > 2 and cmd[:2] == 'o ':
|
|
|
|
|
filename = cmd[2:]
|
2025-02-11 10:37:35 +03:00
|
|
|
|
self.LoadFile(MyString(filename))
|
2025-02-05 01:02:33 +03:00
|
|
|
|
return ReturnCode.SET_BASIC_MODE
|
2025-02-05 21:19:08 +03:00
|
|
|
|
# Запись в файл filename
|
2025-02-05 01:23:06 +03:00
|
|
|
|
elif len(cmd) > 2 and cmd[:2] == 'w ':
|
|
|
|
|
filename = cmd[2:]
|
2025-02-11 10:37:35 +03:00
|
|
|
|
self.WriteFile(MyString(filename))
|
2025-02-05 21:19:08 +03:00
|
|
|
|
# Заменяет символ на указанный
|
|
|
|
|
elif len(cmd) == 3 and cmd[:2] == 'r ':
|
|
|
|
|
self.displayBuffer[self.currentLine][self.currentCol] = cmd[2]
|
2025-02-05 21:29:37 +03:00
|
|
|
|
# Переход на строку по введенному номеру
|
|
|
|
|
elif cmd.isdigit():
|
|
|
|
|
self.MoveToLine(int(cmd))
|
2025-02-05 23:08:48 +03:00
|
|
|
|
# Поиск строки от курсора до конца файла
|
|
|
|
|
elif len(cmd) > 1 and cmd[0] == '/':
|
|
|
|
|
index = tools.findSublistIndex(self.displayBuffer,
|
|
|
|
|
list(cmd[1:]),
|
|
|
|
|
self.currentLine,
|
|
|
|
|
direction=1)
|
|
|
|
|
if index != -1:
|
|
|
|
|
self.currentLine = index
|
|
|
|
|
self.currentCol = 0
|
2025-02-05 23:18:49 +03:00
|
|
|
|
self.lastSearch = (cmd[1:], 1)
|
2025-02-05 23:08:48 +03:00
|
|
|
|
# Поиск строки от курсора до начала файла
|
|
|
|
|
elif len(cmd) > 1 and cmd[0] == '?':
|
|
|
|
|
index = tools.findSublistIndex(self.displayBuffer,
|
|
|
|
|
list(cmd[1:]),
|
|
|
|
|
self.currentLine,
|
|
|
|
|
direction=0)
|
|
|
|
|
if index != -1:
|
|
|
|
|
self.currentLine = index
|
|
|
|
|
self.currentCol = 0
|
2025-02-05 23:18:49 +03:00
|
|
|
|
self.lastSearch = (cmd[1:], 0)
|
2025-02-04 22:05:12 +03:00
|
|
|
|
|
2025-02-04 21:26:55 +03:00
|
|
|
|
return ReturnCode.GOOD
|
2025-02-04 18:36:12 +03:00
|
|
|
|
|
2025-02-04 23:12:19 +03:00
|
|
|
|
def WordUnderCursor(self)-> tuple[int, int]:
|
2025-02-05 00:24:14 +03:00
|
|
|
|
"""Возвращает индекс начала и индекс конца слова под курсором"""
|
2025-02-11 11:45:28 +03:00
|
|
|
|
line = ''.join(item.data() for item in self.displayBuffer[self.currentLine])
|
2025-02-04 23:12:19 +03:00
|
|
|
|
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
|
|
|
|
|
|
2025-02-04 13:49:40 +03:00
|
|
|
|
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 # Сбрасываем индекс колонки
|
|
|
|
|
|
2025-02-04 18:36:12 +03:00
|
|
|
|
def BackspaceCommand(self) -> None:
|
|
|
|
|
if len(self.commandBuffer) > 0:
|
|
|
|
|
self.commandBuffer.pop()
|
|
|
|
|
|
2025-02-04 13:49:40 +03:00
|
|
|
|
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:
|
2025-02-04 13:37:47 +03:00
|
|
|
|
if self.currentCol > 0:
|
|
|
|
|
self.currentCol -= 1
|
|
|
|
|
elif self.currentLine > 0:
|
|
|
|
|
self.currentLine -= 1
|
|
|
|
|
self.currentCol = len(self.displayBuffer[self.currentLine])
|
|
|
|
|
|
2025-02-04 13:49:40 +03:00
|
|
|
|
def MoveRight(self) -> None:
|
2025-02-04 13:37:47 +03:00
|
|
|
|
if self.currentCol < len(self.displayBuffer[self.currentLine]):
|
|
|
|
|
self.currentCol += 1
|
|
|
|
|
elif self.currentLine < len(self.displayBuffer) - 1:
|
|
|
|
|
self.currentLine += 1
|
|
|
|
|
self.currentCol = 0
|
|
|
|
|
|
2025-02-04 13:49:40 +03:00
|
|
|
|
def MoveUp(self) -> None:
|
2025-02-04 13:37:47 +03:00
|
|
|
|
if self.currentLine > 0:
|
|
|
|
|
self.currentLine -= 1
|
|
|
|
|
self.currentCol = min(self.currentCol, len(self.displayBuffer[self.currentLine]))
|
|
|
|
|
|
2025-02-04 13:49:40 +03:00
|
|
|
|
def MoveDown(self) -> None:
|
2025-02-04 13:37:47 +03:00
|
|
|
|
if self.currentLine < len(self.displayBuffer) - 1:
|
|
|
|
|
self.currentLine += 1
|
|
|
|
|
self.currentCol = min(self.currentCol, len(self.displayBuffer[self.currentLine]))
|
|
|
|
|
|
2025-02-05 21:42:04 +03:00
|
|
|
|
def Dump(self) -> None:
|
|
|
|
|
"""Обновляет дамп данных"""
|
|
|
|
|
self.dump = [sublist.copy() for sublist in self.displayBuffer]
|
|
|
|
|
|
2025-02-05 23:08:48 +03:00
|
|
|
|
def RecoverLine(self) -> None:
|
|
|
|
|
self.displayBuffer[self.currentLine] = self.dump[self.currentLine].copy()
|
|
|
|
|
self.currentCol = 0
|
|
|
|
|
|
2025-02-04 14:14:46 +03:00
|
|
|
|
def LoadFile(self, file_path) -> None:
|
2025-02-04 13:16:23 +03:00
|
|
|
|
"""Загрузка файла для редактирования"""
|
2025-02-05 01:02:33 +03:00
|
|
|
|
self.Reset()
|
2025-02-04 13:16:23 +03:00
|
|
|
|
self.file_path = file_path
|
2025-02-11 10:37:35 +03:00
|
|
|
|
self.mode = MyString("NORMAL")
|
2025-02-04 13:16:23 +03:00
|
|
|
|
try:
|
2025-02-11 10:37:35 +03:00
|
|
|
|
with open(file_path.data(), "r") as file:
|
2025-02-11 11:45:28 +03:00
|
|
|
|
self.displayBuffer = [[MyString(char) for char in line.rstrip('\n')] for line in file.readlines()]
|
2025-02-05 21:42:04 +03:00
|
|
|
|
self.Dump()
|
2025-02-04 13:16:23 +03:00
|
|
|
|
except FileNotFoundError:
|
2025-02-11 10:37:35 +03:00
|
|
|
|
print(f"File '{file_path.data()}' not found. Starting with empty buffer.")
|
2025-02-04 13:16:23 +03:00
|
|
|
|
self.displayBuffer = []
|
2025-02-05 01:47:34 +03:00
|
|
|
|
self.displayBuffer.append([])
|
2025-02-04 13:16:23 +03:00
|
|
|
|
|
2025-02-04 14:14:46 +03:00
|
|
|
|
def SaveFile(self) -> None:
|
2025-02-05 01:23:06 +03:00
|
|
|
|
"""Сохранение текущего файла"""
|
|
|
|
|
self.WriteFile(self.file_path)
|
|
|
|
|
|
|
|
|
|
def WriteFile(self, file_path) -> None:
|
|
|
|
|
"""Запись в файл по указанному пути"""
|
2025-02-04 13:16:23 +03:00
|
|
|
|
try:
|
2025-02-11 10:37:35 +03:00
|
|
|
|
with open(file_path.data(), "w") as file:
|
2025-02-04 13:16:23 +03:00
|
|
|
|
for line in self.displayBuffer:
|
2025-02-11 11:45:28 +03:00
|
|
|
|
file.write(''.join(item.data() for item in line) + '\n')
|
2025-02-05 22:36:12 +03:00
|
|
|
|
self.Dump()
|
2025-02-11 10:37:35 +03:00
|
|
|
|
print(f"In file '{file_path.data()}' written successfully.")
|
2025-02-04 13:16:23 +03:00
|
|
|
|
except Exception as e:
|
2025-02-05 01:23:06 +03:00
|
|
|
|
print(f"Error writing file: {str(e)}")
|
2025-02-05 01:02:33 +03:00
|
|
|
|
|
|
|
|
|
def Reset(self) -> None:
|
2025-02-05 23:46:41 +03:00
|
|
|
|
self.showLineNumbers = True
|
|
|
|
|
self.lastSearch = ()
|
|
|
|
|
self.displayBuffer = [] # буфер для хранения всех строк
|
2025-02-05 21:42:04 +03:00
|
|
|
|
self.dump = []
|
2025-02-05 23:46:41 +03:00
|
|
|
|
self.currentLine = 0 # текущий индекс строки
|
|
|
|
|
self.currentCol = 0 # текущий индекс колонки
|
|
|
|
|
self.scrollY = 0 # вертикальная прокрутка
|
|
|
|
|
self.scrollX = 0 # горизонтальная прокрутка
|
2025-02-11 10:37:35 +03:00
|
|
|
|
self.file_path = MyString() # путь к файлу
|
|
|
|
|
self.mode = MyString()
|
2025-02-05 23:46:41 +03:00
|
|
|
|
self.inputAfterCursor = False
|
|
|
|
|
self.keyLog = [] # лог нажатий клавиш (кортежи вида (код символа, юникс время нажатия))
|
|
|
|
|
self.commandBuffer = [] # буффер для команды
|
|
|
|
|
self.exchangeBuffer = [] # буффер обмена
|