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

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.models import VimModel
from mvc.views import VimView, CursesAdapter from mvc.views import VimView, CursesAdapter
from mvc.controllers import Controller, ReturnCode from mvc.controllers import Controller, ReturnCode
from mvc.controllers import EditStrategy, CommandStrategy, NormalStrategy from mvc.controllers import EditStrategy, CommandStrategy, NormalStrategy
import tools
def main(): def main():
adapter = CursesAdapter() adapter = CursesAdapter()
@ -15,7 +16,7 @@ def main():
# Первинчая загрузка файла для редактирования # Первинчая загрузка файла для редактирования
if len(sys.argv) > 1: model.LoadFile(sys.argv[1]) 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: while True:
view.Render(model.displayBuffer, view.Render(model.displayBuffer,

View File

@ -1,3 +1,4 @@
import tools
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from mvc.views import CursesAdapter from mvc.views import CursesAdapter
from mvc.models import VimModel, ReturnCode from mvc.models import VimModel, ReturnCode
@ -90,11 +91,36 @@ class NormalStrategy(BaseStrategy):
def HandleInput(self, symbolCode) -> int: def HandleInput(self, symbolCode) -> int:
"""Обработка ввода пользователя""" """Обработка ввода пользователя"""
result = self.handle_common_input(symbolCode) self.model.UpdateKeyLog(symbolCode)
if result != ReturnCode.CONTINUE: return result
match symbolCode: match symbolCode:
case self.adapter.KEY_TWO_DOTS: return ReturnCode.SET_COMMAND_MODE 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() self.model.Scroll()
return ReturnCode.GOOD return ReturnCode.GOOD

View File

@ -1,3 +1,5 @@
import re
import tools
from enum import Enum from enum import Enum
class ReturnCode(Enum): class ReturnCode(Enum):
@ -19,9 +21,24 @@ class VimModel:
self.scrollX = 0 # горизонтальная прокрутка self.scrollX = 0 # горизонтальная прокрутка
self.file_path = "" # путь к файлу self.file_path = "" # путь к файлу
self.mode = "" self.mode = ""
self.keyLog = [] # лог нажатий клавиш (кортежи вида (код символа, юникс время нажатия))
self.commandBuffer = [] # буффер для команды self.commandBuffer = [] # буффер для команды
self.exchangeBuffer = [] # буффер обмена 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: def ModeBar(self) -> str:
modeBar = f"MODE: {self.mode} | FILE: {self.file_path} | LINE: {self.currentLine+1}/{len(self.displayBuffer)}" modeBar = f"MODE: {self.mode} | FILE: {self.file_path} | LINE: {self.currentLine+1}/{len(self.displayBuffer)}"
if self.mode == "COMMAND": if self.mode == "COMMAND":
@ -47,15 +64,89 @@ class VimModel:
self.displayBuffer[self.currentLine].insert(self.currentCol, chr(symbolCode)) self.displayBuffer[self.currentLine].insert(self.currentCol, chr(symbolCode))
self.currentCol += 1 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): def EnterCommand(self):
"""Обработка введенной команды""" """Обработка введенной команды"""
cmd = ''.join(self.commandBuffer) cmd = ''.join(self.commandBuffer)
self.commandBuffer.clear() self.commandBuffer.clear()
match cmd: match cmd:
case "$": # Перемещение в конец строки
self.currentCol = len(self.displayBuffer[self.currentLine])
case "^" | "0": # Перемещение в начало строки
self.currentCol = 0
case "q": # Выход из программы case "q": # Выход из программы
return ReturnCode.EXIT_CODE return ReturnCode.EXIT_CODE
case "q!": # Выход без сохранения case "q!": # Выход без сохранения
@ -75,75 +166,6 @@ class VimModel:
self.currentCol = 0 self.currentCol = 0
self.displayBuffer[self.currentLine] = [] self.displayBuffer[self.currentLine] = []
return ReturnCode.SET_EDIT_MODE 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": case "h":
self.LoadFile("config/usage.txt") self.LoadFile("config/usage.txt")
return ReturnCode.SET_BASIC_MODE return ReturnCode.SET_BASIC_MODE

View File

@ -2,16 +2,30 @@ import curses
class CursesAdapter: class CursesAdapter:
def __init__(self) -> None: 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_BACKSPACE_2 = 8
self.KEY_ENTER = 10 self.KEY_ENTER = 10
self.KEY_CTRL_S = 19 self.KEY_CTRL_S = 19
self.KEY_ESCAPE = 27 self.KEY_ESCAPE = 27
self.KEY_DOLLAR = 36
self.KEY_NULL = 48
self.KEY_TWO_DOTS = 59 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 = curses.initscr()
self.screen.keypad(True) 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())