236 lines
8.4 KiB
Python
Executable File
236 lines
8.4 KiB
Python
Executable File
import pygame as pg
|
|
from pygame import Rect
|
|
|
|
from piece_table import PieceTable
|
|
|
|
import os
|
|
import json
|
|
import sys
|
|
import time
|
|
import copy
|
|
from dataclasses import dataclass
|
|
from typing import List, Optional
|
|
|
|
from code import InteractiveConsole, InteractiveInterpreter
|
|
|
|
@dataclass(slots=True)
|
|
class Cursor:
|
|
width: float
|
|
height: float
|
|
line_num: int = 0
|
|
col: int = 0
|
|
want_col: int = 0
|
|
|
|
@dataclass(slots=True)
|
|
class Line:
|
|
# Making this a PieceTable rather than a string is rather pointless right now
|
|
# But it's easier to operate on rows for the time being
|
|
piece: PieceTable
|
|
length: int
|
|
|
|
@dataclass(slots=True)
|
|
class Buffer:
|
|
lines: List[Line]
|
|
cursor: Cursor
|
|
filepath: Optional[str] = None
|
|
|
|
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, filepath: str, cursor: Cursor) -> Buffer:
|
|
lines = []
|
|
for line in text.split('\n'):
|
|
lines.append(line_create(line))
|
|
return Buffer(lines, cursor, filepath)
|
|
|
|
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
|
|
|
|
|
|
# oswinx, oswiny = 1940,50
|
|
oswinx, oswiny = 20,50
|
|
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (oswinx,oswiny)
|
|
os.environ['SDL_VIDEO_X11_WMCLASS'] = "pygame"
|
|
clock = pg.time.Clock()
|
|
|
|
pg.init()
|
|
pg.font.init()
|
|
text_renderer = pg.font.Font('JetBrainsMono-Regular.otf', 14)
|
|
screen = pg.display.set_mode((900, 900), pg.RESIZABLE)
|
|
pg.key.set_repeat(240, 10)
|
|
|
|
def load_file(path):
|
|
with open(path, 'r', encoding='utf-8') as f:
|
|
text = f.read()
|
|
f.close()
|
|
return text
|
|
|
|
@dataclass
|
|
class GlobalContext:
|
|
text: str
|
|
current_buffer: Buffer
|
|
cursor: Cursor
|
|
|
|
# global ctx
|
|
global ctx
|
|
cursor = Cursor(width=8, height=20)
|
|
if len(sys.argv) <= 1:
|
|
ctx = GlobalContext("", buffer_create("", cursor), cursor)
|
|
else:
|
|
text = load_file(sys.argv[1])
|
|
ctx = GlobalContext(text, buffer_create(text, sys.argv[1], cursor), cursor)
|
|
from config import *
|
|
from editing import *
|
|
from keybindings import *
|
|
from treesitter import *
|
|
def main():
|
|
running = True
|
|
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 *')
|
|
# We are hardcoding precedence here
|
|
while running:
|
|
dt = clock.tick(120) / 1000.0
|
|
screen.fill('white')
|
|
frame_count += 1
|
|
for event in pg.event.get():
|
|
# print(pg.event.event_name(event.type))
|
|
if event.type == pg.QUIT:
|
|
running = False
|
|
if event.type == pg.KEYDOWN:
|
|
# print(pg.key.name(event.key))
|
|
cursor_on = True
|
|
cursor_flash = 0
|
|
curr_line = buffer_line_current(current_buffer)
|
|
kb_to_test = Keybinding(pg.key.name(event.key), event.mod)
|
|
# print(kb_to_test, event.key, pg.key.name(event.key))
|
|
passthrough = True
|
|
for i in range(len(keymap_stack) - 1, -1, -1):
|
|
keymap = keymap_stack[i]
|
|
if kb_to_test in keymap.mapping:
|
|
keymap.mapping[kb_to_test]()
|
|
if not keymap.pinned:
|
|
keymap_stack.pop()
|
|
passthrough = False
|
|
break
|
|
else:
|
|
if keymap.pop_not_found:
|
|
keymap_stack.pop()
|
|
if not keymap.fallthrough:
|
|
passthrough = False
|
|
if len(kb_to_test.key) == 1:
|
|
print(f"{kb_to_test} not found")
|
|
break
|
|
# No keybinding found so send the directly to the buffer
|
|
# I think emacs instead binds every key to a function that sends the
|
|
# letter, we can evaluate if that's better later on
|
|
if passthrough and event.mod < 2 and event.unicode:
|
|
buffer_insert_char(event.unicode)
|
|
# if event.key == pg.K_RETURN:
|
|
# src = buffer_line_current(current_buffer).piece.get_text()
|
|
# try:
|
|
# eval(src[3:])
|
|
# except:
|
|
# print('error evaluating line: ' + src[3:])
|
|
# console.runsource(src[3:])
|
|
|
|
cur_col = 0
|
|
for i,curr_line in enumerate(current_buffer.lines):
|
|
line_txt = curr_line.piece.get_text()
|
|
ts_nodes = get_ts_nodes(curr_line.piece.get_text())
|
|
color_ranges = [(0, len(line_txt), 'black')]
|
|
# color_ranges = []
|
|
for node in ts_nodes:
|
|
if node.grammar_name == 'string_literal':
|
|
start_col, end_col = node.start_point[1], node.end_point[1]
|
|
print(node.start_point, node.end_point, node.text)
|
|
prev_start,prev_end,prev_col = color_ranges[-1]
|
|
color = 'green'
|
|
if start_col > 0:
|
|
color_ranges[-1] = (prev_start, start_col - 1,prev_col)
|
|
color_ranges.append((start_col, end_col, color))
|
|
if end_col < len(line_txt):
|
|
color_ranges.append((end_col + 1, end_col, color))
|
|
else:
|
|
color_ranges[0] = (start_col, end_col, color)
|
|
|
|
char_width = 8
|
|
for start,end,color in color_ranges:
|
|
render = text_renderer.render(line_txt[start:end], True, color)
|
|
screen.blit(render, (start * char_width, i * line_height))
|
|
|
|
continue
|
|
# We need to iterate over the treesitter tree, I think that for now
|
|
# it can be as easy as going until you find the 'string' grammar name
|
|
# So we need to check three conditions to decide when to draw
|
|
# 1: Did we hit a newline?
|
|
# 2: Did we hit EOF?
|
|
# 3: Did we run into a string
|
|
|
|
# We need to figure out a way to ask treesitter if some
|
|
# tokens are located at the current line that you're
|
|
# checking. Since we're operating line by line. Since we
|
|
# don't iterate character by character, we can't check if
|
|
# we ran into a single or double quote which I guess we
|
|
# could then use treesitter to part. It doesn't make sense
|
|
# to do that anyway because while that might solve this
|
|
# string task, ideally it's generalized so that as we walk
|
|
# the lines we're rendering, we're also checking
|
|
# treesitter grammar. But this is tricky because
|
|
# treesitter doesn't do anything line by line.
|
|
|
|
# So is what we're doing compatible then? maybe we should
|
|
# consider iterating over the lines in a different way and
|
|
# our current data structure won't scale at all. Maybe we
|
|
# should just use the piece table wholesale after all and
|
|
# not use insert mode to generate a temp buffer? That
|
|
# sounds like it'll complicate things a lot.
|
|
|
|
# Another thing would be to collect all the needed tokens?
|
|
|
|
line_syntax = syntax_highlights.get_line(i)
|
|
for grammar in line_syntax:
|
|
if grammar.name == 'string':
|
|
color = 'green'
|
|
else:
|
|
color = 'black'
|
|
string = curr_line.piece.string_at(grammar.start, grammar.end - grammar.start)
|
|
render = text_renderer.render(string, True, color)
|
|
screen.blit(render, (0, i * line_height))
|
|
|
|
if cursor_on and cursor_flash < 0.5:
|
|
cursor_draw(cursor)
|
|
elif cursor_on:
|
|
cursor_on = False
|
|
cursor_flash = 0
|
|
elif not cursor_on and cursor_flash > 0.5:
|
|
cursor_on = True
|
|
cursor_flash = 0
|
|
cursor_flash += dt
|
|
|
|
pg.display.flip()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|