diff --git a/editing.py b/editing.py new file mode 100644 index 0000000..f320f9c --- /dev/null +++ b/editing.py @@ -0,0 +1,17 @@ +from fide import * + +global ctx +ctx = GlobalContext() + +# def cursor_go_left(): +def go_left(): + buf = ctx.current_buffer + cursor = ctx.current_buffer.cursor + if cursor.col == 0: + if cursor.line_num > 0: + cursor.col = buf.lines[cursor.line_num-1].length + cursor.want_col = cursor.col + cursor.line_num -= 1 + else: + cursor.col = max(0, cursor.col - 1) + cursor.want_col = cursor.col diff --git a/fide.py b/fide.py index 70c2b56..f972d1e 100755 --- a/fide.py +++ b/fide.py @@ -9,24 +9,96 @@ import sys import time import copy from dataclasses import dataclass +from typing import List +from code import InteractiveConsole, InteractiveInterpreter +from editing import * + +@dataclass(slots=True) class Cursor: - def __init__(self, row, col): - self.row: int = 0 - self.col: int = 0 - self.want_col: int = 0 + width: float + height: float + line_num: int = 0 + col: int = 0 + want_col: int = 0 - def pos(self): - return (self.row, self.col) - -class Row: - def __init__(self, text): - self.text = PieceTable(text) - self.length = len(text) +@dataclass(slots=True) +class Line: + piece: PieceTable + length: int +@dataclass(slots=True) class Buffer: - def __init__(self, raw_text): - pass + lines: List[Line] + cursor: Cursor + + +def cursor_pos(cursor: Cursor): + return (cursor.line_num, cursor.col) + +def cursor_draw(cursor: Cursor): + rect = Rect(cursor.col * cursor.width, cursor.line_num * cursor.height, 3, cursor.height) + pg.draw.rect(screen, "black", rect, width=2) + +def line_create(text: str) -> Line: + return Line(PieceTable(text), len(text)) + +def buffer_create(text: str, cursor: Cursor) -> Buffer: + lines = [] + for line in text.split('\n'): + lines.append(line_create(line)) + return Buffer(lines, cursor) + +def buffer_line_current(buf: Buffer) -> Line: + return buf.lines[buf.cursor.line_num] + +def buffer_line_num(buf: Buffer) -> int: + return buf.cursor.line_num + +def buffer_total_lines(buf: Buffer) -> int: + return len(buf.lines) + +def buffer_line_len(buf: Buffer) -> int: + return buffer_line_current(buf).length + +def buffer_insert_line_below(buf: Buffer, text): + line_num = buffer_line_num(buf) + buf.lines.insert(line_num + 1, line_create(text)) + +def buffer_newline_at_pos(buf): + curr_line = buffer_line_current(buf) + cursor = buf.cursor + if cursor.col < buffer_line_len(buf): + remainder = curr_line.length - cursor.col + line = curr_line.piece.string_at(cursor.col, remainder) + curr_line.piece.delete(cursor.col, remainder) + curr_line.length -= remainder + else: + line = "" + buffer_insert_line_below(buf, line) + cursor.line_num += 1 + # Eventually when we need to be indentation aware + cursor.col = 0 + cursor.want_col = 0 + + +def buffer_delete_character_at_pos(buf): + cursor = buf.cursor + if cursor_pos(cursor) != (0,0): + if cursor.col == 0: + curr_line = buf.lines.pop(cursor.line_num) + cursor.line_num -= 1 + above_row = buf.lines[cursor.line_num] + above_row.piece.insert(curr_line.piece.get_text(), above_row.length) + cursor.col = above_row.length + cursor.want_col = cursor.col + above_row.length += curr_line.length + else: + curr_line = buffer_line_current(buf) + curr_line.piece.delete(cursor.col - 1, 1) + curr_line.length -= 1 + cursor.col = max(0, cursor.col - 1) + cursor.want_col = cursor.col oswinx, oswiny = 1940,50 os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (oswinx,oswiny) @@ -38,28 +110,30 @@ text_renderer = pg.font.SysFont('JetBrains Mono', 14) screen = pg.display.set_mode((900, 900), pg.RESIZABLE) pg.key.set_repeat(240, 10) -rows = [] -cursor = Cursor(0, 0) -line_height = 20 -char_width = 8 +def load_file(path): + with open(path, 'r', encoding='utf-8') as f: + text = f.read() + f.close() + return text -with open('../gux/test.c', 'r', encoding='utf-8') as f: - text = f.read() - for line in text.split('\n'): - rows.append(Row(line)) - # print(current_buffer[120:125]) - f.close() +@dataclass +class GlobalContext: + cursor = Cursor(width=8, height=20) + text = load_file('../gux/test.c') + current_buffer = buffer_create(text, cursor) -def draw_cursor(): - rect = Rect(cursor.col * char_width, cursor.row * line_height, 3, line_height) - pg.draw.rect(screen, "black", rect, width=2) - -if __name__ == '__main__': +# global ctx +def main(): running = True - # print(current_buffer.display()) frame_count = 0 cursor_flash = 0 cursor_on = True + line_height = 20 + + cursor = ctx.cursor + current_buffer = ctx.current_buffer + console = InteractiveInterpreter(locals()) + console.runsource('from editing import *') while running: dt = clock.tick(120) / 1000.0 screen.fill('white') @@ -68,101 +142,73 @@ if __name__ == '__main__': if event.type == pg.QUIT: running = False if event.type == pg.KEYDOWN: - # print(frame_count, event.unicode, pg.key.name(event.key), event.mod, end="") - # print("") - # print(event.mod) cursor_on = True cursor_flash = 0 - row = rows[cursor.row] - if event.key == pg.K_RETURN: - if cursor.col < row.length: - remainder = row.length - cursor.col - line = row.text.string_at(cursor.col, remainder) - row.text.delete(cursor.col, remainder) - row.length -= remainder - else: - line = "" - rows.insert(cursor.row+1, Row(str(line))) - cursor.row += 1 - # Eventually when we need to be indentation aware - cursor.col = 0 - cursor.want_col = 0 - elif event.key == pg.K_BACKSPACE: - if cursor.pos() != (0,0): - if cursor.col == 0: - row = rows.pop(cursor.row) - cursor.row -= 1 - above_row = rows[cursor.row] - above_row.text.insert(row.text.get_text(), above_row.length) - cursor.col = above_row.length - cursor.want_col = cursor.col - above_row.length += row.length - else: - row.text.delete(cursor.col - 1, 1) - row.length -= 1 - cursor.col = max(0, cursor.col - 1) - cursor.want_col = cursor.col - elif event.key == pg.K_DELETE: - if cursor.col >= rows[cursor.row].length: - if cursor.row < len(rows) - 1: - below_row = rows.pop(cursor.row+1) - if below_row.length > 0: - row.text.insert(below_row.text.get_text(), row.length) - row.length += below_row.length - cursor.want_col = cursor.col - else: - if row.length >= 1: - row.text.delete(cursor.col, 1) - row.length -= 1 - cursor.want_col = cursor.col - elif event.key == pg.K_LEFT: - if cursor.col == 0: - if cursor.row > 0: - cursor.col = rows[cursor.row-1].length - cursor.want_col = cursor.col - cursor.row -= 1 - else: - cursor.col = max(0, cursor.col - 1) - cursor.want_col = cursor.col - elif event.key == pg.K_RIGHT: - if cursor.col == rows[cursor.row].length: - if cursor.row < len(rows): - cursor.col = 0 - cursor.want_col = cursor.col - cursor.row += 1 - else: - cursor.col = min(rows[cursor.row].length, cursor.col + 1) - cursor.want_col = cursor.col - elif event.key == pg.K_UP: - cursor.row = max(0, cursor.row - 1) - cursor.col = min(cursor.want_col, rows[cursor.row].length) - elif event.key == pg.K_DOWN: - cursor.row = min(len(rows) - 1, cursor.row + 1) - cursor.col = min(cursor.want_col, rows[cursor.row].length) - elif event.key == pg.K_SPACE: - row.text.insert(u' ', cursor.col) - row.length += 1 - cursor.col += 1 - cursor.want_col = cursor.col - elif event.key >= 33 and event.key <= 126 and event.mod < 2: - row.text.insert(event.unicode, cursor.col) - row.length += 1 - cursor.col += 1 - cursor.want_col = cursor.col - elif event.mod == 64: # Right Ctrl + curr_line = buffer_line_current(current_buffer) + if event.mod == 64: # Right Ctrl if event.key == pg.K_e: - cursor.col = rows[cursor.row].length + cursor.col = buffer_line_len(buf) cursor.want_col = cursor.col if event.key == pg.K_a: cursor.col = 0 cursor.want_col = cursor.col + if event.key == pg.K_RETURN: + src = buffer_line_current(current_buffer).piece.get_text() + console.runsource(src[3:]) + # console.runcode() + elif event.key == pg.K_RETURN: + buffer_newline_at_pos(current_buffer) + elif event.key == pg.K_BACKSPACE: + buffer_delete_character_at_pos(current_buffer) + elif event.key == pg.K_DELETE: + if cursor.col >= buffer_line_len(current_buffer): + if cursor.line_num < len(current_buffer.lines) - 1: + below_row = current_buffer.lines.pop(cursor.line_num+1) + if below_row.length > 0: + curr_line.piece.insert(below_row.piece.get_text(), curr_line.length) + curr_line.length += below_row.length + cursor.want_col = cursor.col + else: + if curr_line.length >= 1: + curr_line.piece.delete(cursor.col, 1) + curr_line.length -= 1 + cursor.want_col = cursor.col + elif event.key == pg.K_LEFT: + cursor_go_left(current_buffer) + elif event.key == pg.K_RIGHT: + if cursor.col == buffer_line_len(current_buffer): + if cursor.line_num < buffer_total_lines(current_buffer) - 1: + cursor.col = 0 + cursor.want_col = cursor.col + cursor.line_num += 1 + else: + cursor.col = min(buffer_line_len(current_buffer), cursor.col + 1) + cursor.want_col = cursor.col + elif event.key == pg.K_UP: + cursor.line_num = max(0, cursor.line_num - 1) + line_len = buffer_line_len(current_buffer) + cursor.col = min(cursor.want_col, line_len) + elif event.key == pg.K_DOWN: + line_len = buffer_line_len(current_buffer) + cursor.line_num = min(len(current_buffer.lines) - 1, cursor.line_num + 1) + cursor.col = min(cursor.want_col, line_len) + elif event.key == pg.K_SPACE: + curr_line.piece.insert(u' ', cursor.col) + curr_line.length += 1 + cursor.col += 1 + cursor.want_col = cursor.col + elif event.key >= 33 and event.key <= 126 and event.mod < 2: + curr_line.piece.insert(event.unicode, cursor.col) + curr_line.length += 1 + cursor.col += 1 + cursor.want_col = cursor.col - for i,row in enumerate(rows): - render = text_renderer.render(row.text.get_text(), True, 'black') + for i,curr_line in enumerate(current_buffer.lines): + render = text_renderer.render(curr_line.piece.get_text(), True, 'black') screen.blit(render, (0, i * line_height)) if cursor_on and cursor_flash < 0.5: - draw_cursor() + cursor_draw(cursor) elif cursor_on: cursor_on = False cursor_flash = 0 @@ -172,3 +218,6 @@ if __name__ == '__main__': cursor_flash += dt pg.display.flip() + +if __name__ == '__main__': + main()