148 lines
5.7 KiB
Python
148 lines
5.7 KiB
Python
from abc import ABC, abstractmethod
|
||
import curses
|
||
import mvc.models
|
||
|
||
class CursesAdapter:
|
||
def __init__(self) -> None:
|
||
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_U = 85
|
||
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)
|
||
self.cols = curses.COLS
|
||
self.lines = curses.LINES
|
||
curses.noecho()
|
||
curses.curs_set(1) # Make cursor visible
|
||
curses.start_color()
|
||
curses.init_pair(1, curses.COLOR_BLUE, curses.COLOR_BLACK) # Фиолетовый текст на черном фоне
|
||
curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLUE) # Фиолетовый текст на черном фоне
|
||
self.colorPairs = [curses.color_pair(1), curses.color_pair(2)]
|
||
|
||
def Refresh(self) -> None:
|
||
"""Apply changes"""
|
||
self.screen.refresh()
|
||
|
||
def Cleanup(self) -> None:
|
||
curses.endwin()
|
||
|
||
def SetCursor(self, x: int, y: int):
|
||
"""set cursor position (x, y)"""
|
||
self.screen.move(x, y)
|
||
|
||
def SetChar(self, x: int, y: int, code: int):
|
||
"""set char position (x, y)"""
|
||
self.screen.addch(x, y, code)
|
||
|
||
def SetColorString(self, x: int, y: int, data: str, attr: int):
|
||
self.screen.addstr(x, y, data, attr)
|
||
|
||
def SetString(self, x: int, y: int, data: str):
|
||
"""set string begin position (x, y)"""
|
||
self.screen.addstr(x, y, data)
|
||
|
||
def GetChar(self) -> int:
|
||
"""Wait users input"""
|
||
return self.screen.getch()
|
||
|
||
class Observer:
|
||
@abstractmethod
|
||
def Update(self):
|
||
pass
|
||
|
||
class VimView(Observer):
|
||
def __init__(self, adapter: CursesAdapter) -> None:
|
||
self.curses_adapter = adapter
|
||
self.model = None
|
||
|
||
def SetModel(self, model: mvc.models.VimModel):
|
||
self.model = model
|
||
model.attach(self)
|
||
|
||
def Update(self):
|
||
self.Render(
|
||
self.model.displayBuffer,
|
||
self.model.currentLine,
|
||
self.model.currentCol,
|
||
self.model.scrollX,
|
||
self.model.scrollY,
|
||
self.model.ModeBar(),
|
||
self.model.showLineNumbers
|
||
)
|
||
|
||
def Render(self,
|
||
displayBuffer: list[list[str]],
|
||
currentLine: int, currentCol: int,
|
||
scrollX: int, scrollY: int,
|
||
modeBarData: str,
|
||
show_line_numbers: bool = False):
|
||
"""Отрисовка текущего состояния"""
|
||
|
||
self.curses_adapter.screen.clear()
|
||
|
||
# Ширина нумерации строк (6 символов + 1 пробел)
|
||
line_number_width = 7 if show_line_numbers else 0
|
||
|
||
# Отображение видимой части текста
|
||
for i in range(self.curses_adapter.lines - 1):
|
||
if i + scrollY < len(displayBuffer):
|
||
line = ''.join(displayBuffer[i + scrollY])
|
||
# Если нужно отображать номера строк, добавляем их перед текстом
|
||
if show_line_numbers:
|
||
line_number = f"{i + scrollY + 1:6} " # 6 символов на номер строки
|
||
# Выводим номер строки фиолетовым цветом
|
||
self.curses_adapter.SetColorString(i, 0, line_number, self.curses_adapter.colorPairs[0])
|
||
|
||
# Выводим текст с учетом прокрутки и ширины нумерации
|
||
visible_text_start = scrollX # Начало видимой части текста
|
||
visible_text_end = scrollX + self.curses_adapter.cols - line_number_width # Конец видимой части текста
|
||
visible_text = line[visible_text_start:visible_text_end]
|
||
self.curses_adapter.SetString(i, line_number_width, visible_text)
|
||
else:
|
||
self.curses_adapter.SetString(i, 0, '')
|
||
|
||
# Обновление панели режима
|
||
self.SetModeBar(modeBarData)
|
||
|
||
# Установка курсора с учетом прокрутки и нумерации строк
|
||
cursor_x = currentLine - scrollY
|
||
cursor_y = currentCol - scrollX + line_number_width
|
||
|
||
# Проверка, чтобы курсор не вышел за пределы экрана
|
||
if 0 <= cursor_x < self.curses_adapter.lines - 1 and 0 <= cursor_y < self.curses_adapter.cols:
|
||
self.curses_adapter.SetCursor(cursor_x, cursor_y)
|
||
|
||
self.curses_adapter.Refresh()
|
||
|
||
def SetModeBar(self, modeBarData: str):
|
||
"""Print edit mode information panel"""
|
||
if len(modeBarData) > self.curses_adapter.cols - 1:
|
||
scrollX = len(modeBarData) - self.curses_adapter.cols
|
||
self.curses_adapter.SetColorString(self.curses_adapter.lines - 1, 0,
|
||
modeBarData[scrollX + 1:], self.curses_adapter.colorPairs[1])
|
||
else:
|
||
self.curses_adapter.SetColorString(self.curses_adapter.lines - 1, 0,
|
||
modeBarData, self.curses_adapter.colorPairs[1])
|