доделал режим навигации и редактирования

master
serr 2025-02-05 10:59:54 +03:00
parent b420a4e667
commit 6ed9b61e5f
5 changed files with 161 additions and 84 deletions

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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)

14
tools.py Normal file
View File

@ -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())