Compare commits

...
20 Commits
Author SHA1 Message Date
JosephFerano b2d8d37a60 Odin window manager 2024-10-15 10:54:26 +07:00
JosephFerano 00be1d2e23 Odin version 2024-09-07 10:55:49 +07:00
JosephFerano 56f3048d2d Highlight string literals green with treesitter 2023-11-17 19:11:06 +07:00
JosephFerano 039b671973 Playing around with treesitter 2023-11-11 15:15:47 +07:00
JosephFerano 3112ac37e2 Update pip file 2023-11-09 12:27:17 +07:00
JosephFerano dfdadef143 Add buffer saving with keybinding 2023-11-09 12:22:47 +07:00
JosephFerano e8ea80ff66 Keymap stacks work as expected with proper fallthrough emulating normal/insert 2023-11-09 11:06:12 +07:00
JosephFerano 7228bf3201 Parse config keybindings and iterate over a stack of keymappings 2023-11-08 19:11:21 +07:00
JosephFerano 5cb2fd0fd7 Extract more functions and add ctrl+key bindings 2023-11-06 18:39:34 +07:00
JosephFerano 27ef7d7c93 Fix bug moving down/up cols and try using a config.py 2023-11-06 16:47:07 +07:00
JosephFerano d690d7a2a3 Extract more functions 2023-11-05 11:44:08 +07:00
JosephFerano 8356ca6a25 Don't render empty text 2023-11-05 10:28:39 +07:00
JosephFerano 44ffba0aa4 Checking in nim version 2023-11-05 10:14:18 +07:00
JosephFerano 4d5ad1a562 Stupid simple keymap 2023-11-04 17:42:10 +07:00
JosephFerano cd8c8739b0 Start moving code to separate functions to prototype func eval 2023-11-04 17:13:31 +07:00
JosephFerano 43450a8c37 Fix bug on K_DELETE when the line is empty 2023-11-02 18:23:07 +07:00
JosephFerano 53ddd7be9f Basic text editing implemented in pygame 2023-11-02 17:04:40 +07:00
JosephFerano 6ccf44bef5 Get typing mostly working 2023-09-21 23:34:38 +07:00
JosephFerano 9d725d0b2f Got the red to show up 2023-09-19 23:31:57 +07:00
JosephFerano b1ab0f6d98 Render string on screen 2023-09-19 23:31:05 +07:00
22 changed files with 1270 additions and 129 deletions

No files matched your search

+4 -2
View File
@@ -1,2 +1,4 @@
/rust-version/ /cosmic-text/
/main /__pycache__/
/nim-version/fide
/build/
+36
View File
@@ -0,0 +1,36 @@
* Keymapping system
** X11 Window Manager
- Run launcher
- Basic Window Management
- Buffer-or-x11win
** If only it had a good text editor...
- SDL2
** IMTUI Lib
- Autolayout system
** Custom Language VM
- LSP
- Treesitter
- Coroutines with the event loop
** Keymap System
*** Keymap Stacking
There shouldn't be modes, there would be keymaps and a stack. Insert mode would
be at the bottom of the stack. Adding a new mode means pushing onto the stack,
and when a keybinding event is raised, you iterate over the stack from top to
bottom to find the correct keybinding. The vim nomenclature wouldn't work in
this case as you don't "leave insert mode". Rather, you pop the "normal mode"
keymap ontop of the "insert mode" keymap.
*** Passthrough/Blocking
The keymap object should have additional settings that dictate its behavior when
a key is found and not found.
**** Found
***** Pop
The default could be to pop from the stack.
***** Keep
Keep the keymap on the stack. This is useful for both normal mode and transient
like modes where you need to explicitly pop it from the stack.
**** Not found
***** Passthrough
Allow the loop to go to the next keymap
***** Block
Ignore missing key, don't pop the keymap, maybe report an error
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
from editing import *
from keybindings import *
kb('insert', 'left' , cursor_go_left)
kb('insert', 'right', cursor_go_right)
kb('insert', 'up' , cursor_go_up)
kb('insert', 'down' , cursor_go_down)
kb('insert', 'C-e' , cursor_go_line_end)
kb('insert', 'C-a' , cursor_go_line_beginning)
kb('insert', 'escape', lambda: keymap_push(keymappings['normal']))
kb('insert', 'backspace', buffer_delete_character_under_cursor)
kb('insert', 'delete', buffer_delete_next_character)
kb('insert', 'return', buffer_newline_at_pos)
kb('insert', 'C-s', buffer_save)
kb('normal', 'i', keymap_pop)
kb('normal', 'H', cursor_go_buffer_beginning)
kb('normal', 'L', cursor_go_buffer_end)
kb('normal', 'h', cursor_go_left)
kb('normal', 'j', cursor_go_down)
kb('normal', 'k', cursor_go_up)
kb('normal', 'l', cursor_go_right)
kb('normal', '^', cursor_go_line_beginning)
kb('normal', '$', cursor_go_line_end)
kb('normal', 'C-s', buffer_save)
+1 -1
View File
@@ -6,7 +6,7 @@ CC = g++
#COMPILER_FLAGS specifies the additional compilation options we're using #COMPILER_FLAGS specifies the additional compilation options we're using
# -w suppresses all warnings # -w suppresses all warnings
COMPILER_FLAGS = -w COMPILER_FLAGS = -Wall -Wpedantic
#LINKER_FLAGS specifies the libraries we're linking against #LINKER_FLAGS specifies the libraries we're linking against
LINKER_FLAGS = -lSDL2 -lSDL2_image LINKER_FLAGS = -lSDL2 -lSDL2_image
+43
View File
@@ -0,0 +1,43 @@
#-*- mode: org -*-
#+TODO: TODO | DONE
#+STARTUP: show2levels
* DONE Create SDL hello world
* DONE Research which data structure to use (ext/rope.h)
* DONE Figure out how to render text in SDL (font texture atlas)
* DONE Insert text into rope from keyboard presses
* DONE Render rope
* DONE Handle colors and font texture blitting
* DONE Get backspace working
* TODO Scale text with C-+ and C--
* TODO Read a file from disk and display it
* TODO Create a cursor
* TODO Add basic keybinding system
* TODO Move cursor with arrow keys
* TODO Save file
* TODO Render each line in a different color
To help figuring out how to color things for syntax highlighting
* TODO Install treesitter package
* TODO Color different parts of the buffer based on AST
* TODO Create vertical/horizontal window splits
* TODO Make splits resizable
* TODO Buffer lists per pane
* TODO Pane owned sub-panes
* TODO Promote or demote panes as sub-panes
* TODO Pinnable/dedicated buffers
Panes and their sub-panes can have buffers pinned to them that require a
keybinding to override.
* TODO Custom buffer and pane variables
* TODO Floating splits
* TODO Buffers tags/ids
* TODO Pane attributes that modify it's behavior
- [ ] Pinnable
- [ ] Toggleable
- [ ] Overtake?
- [ ] Popup
* TODO Narrowing/widening
* TODO Read-only mode
* TODO Implement key chording
* TODO Implement modal editing modes
* TODO Keymap layering stack
View File
File renamed without changes.
BIN
View File
Binary file not shown.
+191
View File
@@ -0,0 +1,191 @@
#include <SDL2/SDL.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_render.h>
#include <stdio.h>
#include <iostream>
#include <ext/rope>
using namespace __gnu_cxx;
using namespace std;
//Screen dimension constants
const int SCREEN_WIDTH = 1024;
const int SCREEN_HEIGHT = 768;
char *test_string = "This is a test string for fide";
enum KeyPressSurfaces
{
KEY_PRESS_SURFACE_DEFAULT,
KEY_PRESS_SURFACE_UP,
KEY_PRESS_SURFACE_DOWN,
KEY_PRESS_SURFACE_LEFT,
KEY_PRESS_SURFACE_RIGHT,
KEY_PRESS_SURFACE_TOTAL
};
SDL_Rect get_srcrect(char key) {
SDL_Rect srcrect;
srcrect.x = (key % 16) * 70;
srcrect.y = (key / 16) * 80;
srcrect.w = 80;
srcrect.h = 80;
return srcrect;
}
void render_char(char key, int row, int col, SDL_Renderer* renderer, SDL_Texture* font) {
SDL_Rect dstrect;
dstrect.x = col;
dstrect.y = row;
dstrect.w = 30;
dstrect.h = 30;
SDL_Rect srcrect = get_srcrect(key);
SDL_RenderCopy(renderer, font, &srcrect, &dstrect);
}
void Draw(char keyPressed, SDL_Renderer* renderer, SDL_Texture* fontTexture) {
SDL_Rect srcrect;
SDL_Rect dstrect;
if (keyPressed > 0) {
srcrect.x = (keyPressed % 16) * 70;
srcrect.y = (keyPressed / 16) * 80;
} else {
srcrect.x = 0;
srcrect.y = 0;
}
srcrect.w = 80;
srcrect.h = 80;
dstrect.x = SCREEN_WIDTH/2 - 40;
dstrect.y = SCREEN_HEIGHT/2 - 40;
dstrect.w = 100;
dstrect.h = 100;
// SDL_BlitSurface(imageSurface, &srcrect, screenSurface, &dstrect);
//Update the surface
// SDL_UpdateWindowSurface( window );
SDL_RenderCopy(renderer, fontTexture, &srcrect, &dstrect);
}
int main(int argc, char *argv[]) {
SDL_Window* window = NULL;
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
printf("error initializing SDL: %s\n", SDL_GetError());
return 1;
}
window = SDL_CreateWindow( "SDL Tutorial",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
SCREEN_WIDTH,
SCREEN_HEIGHT,
SDL_WINDOW_SHOWN );
if (window == NULL) {
printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() );
return 1;
}
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
SDL_SetRenderDrawColor(renderer, 130, 163, 255, 1);
SDL_Surface* fontSurface = IMG_Load("output.png");
if (fontSurface == NULL) {
cout << "Could not find spritesheet" << endl;
return 1;
}
SDL_Texture* fontTexture = SDL_CreateTextureFromSurface(renderer, fontSurface);
// SDL_Surface* screenSurface = NULL;
// //Get window surface
// screenSurface = SDL_GetWindowSurface( window );
//Fill the surface white
// auto rgbMap = SDL_MapRGB( screenSurface->format, 0x82, 0xA3, 0xFF);
SDL_Event e;
bool quit = false;
int keyPressed = -1;
int rowCurr = 0;
int colCurr = 0;
float scale = 50;
rope<char> buffer;
while (quit == false) {
while (SDL_PollEvent( &e)) {
if (e.type == SDL_QUIT) {
quit = true;
} else if (e.type == SDL_KEYDOWN) {
char key = e.key.keysym.sym;
char mod = e.key.keysym.mod;
if (key == '\r') {
buffer.push_back('\n');
}
if (key == '-' && mod & KMOD_LCTRL) {
cout << "Minu" << endl;
}
if (key == '=' && mod & KMOD_LCTRL) {
cout << "Ploos" << endl;
}
if (key >= (char)'!' || key <= (char)'~') {
// keyPressed = key;
buffer.push_back(key);
}
if (key == SDLK_BACKSPACE) {
if (buffer.size() >= 2) {
buffer.pop_back();
buffer.pop_back();
}
}
cout << key << endl;
// switch (e.key.keysym.sym) {
// case SDLK_
// }
}
}
// SDL_FillRect( screenSurface, NULL, rgbMap);
SDL_RenderClear(renderer);
SDL_SetTextureColorMod(fontTexture, 0x0, 0, 0);
rope<char>::iterator it;
for (it = buffer.mutable_begin(); it != buffer.mutable_end(); it++) {
if (*it == '\n') {
rowCurr += 1;
colCurr = 0;
continue;
}
if (*it == '\r') {
continue;
}
if (*it >= (char)'!' || *it <= (char)'~') {
render_char(*it, rowCurr * 70, colCurr++ * 50, renderer, fontTexture);
}
}
colCurr = 0;
rowCurr = 0;
// Draw(keyPressed, renderer, fontTexture);
SDL_RenderPresent(renderer);
}
//Hack to get window to stay up
// SDL_FreeSurface(screenSurface);
SDL_FreeSurface(fontSurface);
SDL_DestroyTexture(fontTexture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow( window );
//Quit SDL subsystems
SDL_Quit();
return 0;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+150
View File
@@ -0,0 +1,150 @@
from fide import *
def cursor_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
def cursor_go_right():
buf = ctx.current_buffer
cursor = ctx.current_buffer.cursor
if cursor.col == buffer_line_len(buf):
if cursor.line_num < buffer_total_lines(buf) - 1:
cursor.col = 0
cursor.want_col = cursor.col
cursor.line_num += 1
else:
cursor.col = min(buffer_line_len(buf), cursor.col + 1)
cursor.want_col = cursor.col
def cursor_go_down():
buf = ctx.current_buffer
cursor = ctx.current_buffer.cursor
if cursor.line_num == len(buf.lines):
return
cursor.line_num = min(len(buf.lines) - 1, cursor.line_num + 1)
line_len = buf.lines[cursor.line_num].length
cursor.col = min(cursor.want_col, line_len)
def cursor_go_up():
buf = ctx.current_buffer
cursor = ctx.current_buffer.cursor
if cursor.line_num == 0:
return
cursor.line_num = max(0, cursor.line_num - 1)
line_len = buf.lines[cursor.line_num].length
# TODO: This should be global
cursor.col = min(cursor.want_col, line_len)
def cursor_go_line_beginning():
buf = ctx.current_buffer
buf.cursor.col = 0
buf.cursor.want_col = buf.cursor.col
def cursor_go_line_end():
buf = ctx.current_buffer
buf.cursor.col = buffer_line_len(buf)
buf.cursor.want_col = buf.cursor.col
def cursor_go_buffer_beginning():
buf = ctx.current_buffer
buf.cursor.line_num = 0
def cursor_go_buffer_end():
buf = ctx.current_buffer
buf.cursor.line_num = len(buf.lines) - 1
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 = ctx.current_buffer
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_under_cursor():
buf = ctx.current_buffer
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
def buffer_delete_next_character():
buf = ctx.current_buffer
cursor = buf.cursor
curr_line = buffer_line_current(buf)
if cursor.col >= buffer_line_len(buf):
if cursor.line_num < len(buf.lines) - 1:
below_row = buf.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
def buffer_insert_char(c):
cursor = ctx.current_buffer.cursor
curr_line = buffer_line_current(ctx.current_buffer)
curr_line.piece.insert(c, cursor.col)
curr_line.length += 1
cursor.col += 1
cursor.want_col = cursor.col
def buffer_insert_text_at_cursor(text):
cursor = ctx.current_buffer.cursor
curr_line = buffer_line_current(ctx.current_buffer)
curr_line.piece.insert(text, cursor.col)
text_len = len(text)
curr_line.length += text_len
cursor.col += text_len
cursor.want_col = text_len
# def buffer_switch_normal_mode():
# ctx.current_buffer.mode = 'normal'
# def buffer_switch_insert_mode():
# ctx.current_buffer.mode = 'insert'
def buffer_save():
buf = ctx.current_buffer
# if buf.filepath is not None:
with open(buf.filepath, 'w') as f:
text = '\n'.join(map(lambda l: l.piece.get_text(), buf.lines))
f.write(text)
Executable
+235
View File
@@ -0,0 +1,235 @@
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()
+22
View File
@@ -0,0 +1,22 @@
#-*- mode: org -*-
#+TODO: TODO | DONE
#+STARTUP: show2levels
* DONE Render lines
* DONE Basic buffer implementation
* DONE Basic text editing functions
* DONE Basic keybindings system
* DONE Parse keymaps so config matches pygame keys
* DONE Keymap fallthrough/precedence stacking
* DONE Pass the file you want to edit as a command line argument
* DONE Save buffer function with a keybinding
* DONE Install, Import and play around with Treesitter
* DONE Highlight string literals with treesitter
* TODO Incorporate color regions into the buffer/line objects
* TODO Create a simple color map based on treesitter keywords
* TODO Walk the AST to generate the color regions
* TODO Update treesitter AST as the user edits the file
* TODO Add scrolling based on cursor position
* TODO Add reverse parsing of keymaps to show users what they're typing
+106
View File
@@ -0,0 +1,106 @@
from fide import *
from typing import List, Dict, Callable
from dataclasses import dataclass, field
@dataclass(slots=True, frozen=True)
class Keybinding:
key: str
modifiers: int
@dataclass(slots=True)
class Keymap:
name: str
pinned: bool
fallthrough: bool
pop_not_found: bool
mapping: Dict[[int,str],Callable] = field(default_factory=dict)
global keymappings
global keymap_stack
global reverse_binding
def modsym_to_flag(sym: str):
match sym:
case 'S': return 1
case 'C': return 64
case 'RC': return 128
case 'A': return 256
case 'RA': return 512
case 'M': return 1024
# TODO Get which keycode these are
case 'right meta':
return 'RM'
case 'right shift':
return 'RS'
return None
def symbol_to_num(sym: str):
match sym:
case '!': return '1'
case '@': return '2'
case '#': return '3'
case '$': return '4'
case '%': return '5'
case '^': return '6'
case '&': return '7'
case '*': return '8'
case '(': return '9'
case ')': return '0'
# TODO Get which keycode these are
return sym
def parse_keybinding(keybinding: str) -> str:
keys = keybinding.replace(' ', '').split('-')
if not keys:
# TODO: We need to return some kind of error
return None
keys_len = len(keys)
if keys_len > 1: # We have modifiers
modifiers = 0
for i in range(keys_len - 1):
flag = modsym_to_flag(keys[i])
if flag:
modifiers |= flag
else:
# TODO: Also throw some kind of parsing error
return None
else:
modifiers = 0
if len(keys[-1]) > 1: # We have a multi-stroke keybinding, we don't handle that yet
key = keys[-1]
else:
key = keys[-1][0]
if key.isupper():
modifiers |= 1
key = key.lower()
if not key.isalnum():
modifiers |= 1
key = symbol_to_num(key)
return Keybinding(key, modifiers)
keymap_stack = [
Keymap('insert', pinned=True, fallthrough=True, pop_not_found=False),
Keymap('normal', pinned=True, fallthrough=False, pop_not_found=False),
]
keymappings = {
'insert': keymap_stack[0],
'normal': keymap_stack[1],
}
# Do we even need this?
reverse_binding = {}
def kb(mode: str, binding: str, f: Callable):
keybinding = parse_keybinding(binding)
keymappings[mode].mapping[keybinding] = f
reverse_binding[keybinding] = binding
def keymap_push(keymap: Keymap):
keymap_stack.append(keymap)
def keymap_pop():
keymap_stack.pop()
-126
View File
@@ -1,126 +0,0 @@
#include <SDL2/SDL.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_render.h>
#include <stdio.h>
#include <iostream>
using namespace std;
//Screen dimension constants
const int SCREEN_WIDTH = 1024;
const int SCREEN_HEIGHT = 768;
enum KeyPressSurfaces
{
KEY_PRESS_SURFACE_DEFAULT,
KEY_PRESS_SURFACE_UP,
KEY_PRESS_SURFACE_DOWN,
KEY_PRESS_SURFACE_LEFT,
KEY_PRESS_SURFACE_RIGHT,
KEY_PRESS_SURFACE_TOTAL
};
int main(int argc, char *argv[]) {
SDL_Window* window = NULL;
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
printf("error initializing SDL: %s\n", SDL_GetError());
return 1;
}
window = SDL_CreateWindow( "SDL Tutorial",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
SCREEN_WIDTH,
SCREEN_HEIGHT,
SDL_WINDOW_SHOWN );
if (window == NULL) {
printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() );
return 1;
}
SDL_Renderer* renderer =
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
SDL_SetRenderDrawColor(renderer, 130, 163, 255, 1);
SDL_Surface* fontSurface = IMG_Load("font.png");
if (fontSurface == NULL) {
cout << "Could not find spritesheet" << endl;
return 1;
}
SDL_Texture* fontTexture = SDL_CreateTextureFromSurface(renderer, fontSurface);
// SDL_Surface* screenSurface = NULL;
// //Get window surface
// screenSurface = SDL_GetWindowSurface( window );
//Fill the surface white
// auto rgbMap = SDL_MapRGB( screenSurface->format, 0x82, 0xA3, 0xFF);
int test = 0;
SDL_Event e;
bool quit = false;
int keyPressed = -1;
while (quit == false) {
while (SDL_PollEvent( &e)) {
if (e.type == SDL_QUIT) {
quit = true;
}
else if (e.type == SDL_KEYDOWN) {
char key = e.key.keysym.sym;
if (key >= (char)'!' || key <= (char)'~') {
keyPressed = key;
}
// switch (e.key.keysym.sym) {
// case SDLK_
// }
}
}
// SDL_FillRect( screenSurface, NULL, rgbMap);
SDL_Rect srcrect;
SDL_Rect dstrect;
if (keyPressed > 0) {
srcrect.x = (keyPressed % 16) * 70;
srcrect.y = (keyPressed / 16) * 80;
} else {
srcrect.x = 0;
srcrect.y = 0;
}
srcrect.w = 80;
srcrect.h = 80;
dstrect.x = SCREEN_WIDTH/2 - 40;
dstrect.y = SCREEN_HEIGHT/2 - 40;
dstrect.w = 100;
dstrect.h = 100;
// SDL_BlitSurface(imageSurface, &srcrect, screenSurface, &dstrect);
//Update the surface
// SDL_UpdateWindowSurface( window );
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, fontTexture, &srcrect, &dstrect);
SDL_RenderPresent(renderer);
}
//Hack to get window to stay up
// SDL_FreeSurface(screenSurface);
SDL_FreeSurface(fontSurface);
SDL_DestroyTexture(fontTexture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow( window );
//Quit SDL subsystems
SDL_Quit();
return 0;
}
+1
View File
@@ -0,0 +1 @@
require "sdl2"
+124
View File
@@ -0,0 +1,124 @@
import os
import std/strformat
import std/strutils
import sdl2
import sdl2/ttf
const
WindowWidth = 900
WindowHeight = 900
OSWinX: cint = 1940
OSWinY: cint = 50
LineHeight = 20
type
Cursor = object
width: float
height: float
line_num: int = 0
col: int = 0
want_col: int = 0
Buffer = object
lines: seq[string]
cursor: Cursor
func bufferNew(text: string, cursor: Cursor): Buffer =
return Buffer(lines: text.split("\n"), cursor: cursor)
type SDLException = object of Defect
template sdlFailIf(condition: typed, reason: string) =
if condition: raise SDLException.newException(
reason & ", SDL error " & $getError()
)
proc main =
sdlFailIf(not sdl2.init(INIT_VIDEO or INIT_TIMER or INIT_EVENTS)):
"SDL2 initialization failed"
defer: sdl2.quit()
let window = createWindow(
title = "TODE",
x = OSWinX,
y = OSWinY,
w = WindowWidth,
h = WindowHeight,
flags = SDL_WINDOW_SHOWN or SDL_WINDOW_RESIZABLE
)
sdlFailIf window.isNil: "window could not be created"
defer: window.destroy()
let renderer = createRenderer(
window = window,
index = -1,
flags = Renderer_Accelerated or Renderer_PresentVsync or Renderer_TargetTexture
)
sdlFailIf renderer.isNil: "renderer could not be created"
defer: renderer.destroy()
sdlFailIf(not ttfInit()): "SDL_TTF initialization failed"
defer: ttfQuit()
let font = ttf.openFont("liberation-sans.ttf", 18)
sdlFailIf font.isNil: "font could not be created"
sdlFailIf(not setHint("SDL_RENDER_SCALE_QUALITY", "2")):
"Linear texture filtering could not be enabled"
var
running = true
dt: float32
counter: uint64
previousCounter: uint64
let
textFile = readFile("../../gux/test.c")
cursor = Cursor(width: 8, height: LineHeight)
buffer = bufferNew(textFile, cursor)
counter = getPerformanceCounter()
while running:
previousCounter = counter
counter = getPerformanceCounter()
dt = (counter - previousCounter).float / getPerformanceFrequency().float
var event = defaultEvent
while pollEvent(event):
case event.kind
of QuitEvent:
running = false
break
of KeyDown:
discard
of KeyUp:
discard
else:
discard
renderer.setDrawColor 255, 255, 255, 255 # black
renderer.clear()
for i, line in buffer.lines:
if isEmptyOrWhitespace(line):
continue
let
textColor = color(0, 0, 0, 0)
surface = ttf.renderTextBlended(font, line, textColor)
texture = renderer.createTextureFromSurface(surface)
sdlFailIf surface.isNil: "Could not render text surface"
var r = rect(0, cint(i * 20), surface.w, surface.h)
renderer.copy texture, nil, addr r
surface.freeSurface
defer: texture.destroy
renderer.present()
main()
Binary file not shown.
+185
View File
@@ -0,0 +1,185 @@
package tide
import "core:os"
import "core:log"
import "core:math"
import "core:strings"
import fmt "core:fmt"
import sdl "vendor:sdl2"
import ttf "vendor:sdl2/ttf"
import txtedit "core:text/edit"
AppState :: struct {
buffer_dirty: bool,
}
SdlCtx :: struct {
window: ^sdl.Window,
surface: ^sdl.Surface,
renderer: ^sdl.Renderer,
font: ^ttf.Font,
ftex: ^sdl.Texture,
text_surface: ^sdl.Surface,
buffer_text: ^BufferText,
// font_data: ^FontData,
should_close: bool,
app_start: f64,
frame_start: f64,
frame_end: f64,
frame_elapsed: f64,
}
BufferText :: struct {
builder: strings.Builder,
}
FontData :: struct {
font: ^ttf.Font,
texture: ^sdl.Texture,
rect: sdl.Rect,
font_w: int,
font_h: int,
}
WIN_TITLE :: "FIDE"
WIN_X := i32(0)
WIN_Y := i32(100)
WIN_W := i32(950)
WIN_H := i32(1000)
WIN_FLAGS :: sdl.WindowFlags{.SHOWN}
state := AppState{}
ctx := SdlCtx{}
sdl_init :: proc() -> (ok: bool) {
ctx.app_start = f64(sdl.GetPerformanceCounter()) / f64(sdl.GetPerformanceFrequency())
if sdl.Init(sdl.INIT_VIDEO) != 0 {
return false
}
ctx.window = sdl.CreateWindow(WIN_TITLE, WIN_X, WIN_Y, WIN_W, WIN_H, WIN_FLAGS)
ctx.renderer = sdl.CreateRenderer(ctx.window, -1, {.ACCELERATED, .PRESENTVSYNC})
if ctx.renderer == nil {
log.errorf("sdl2.CreateRenderer failed.")
return false
}
if ttf.Init() != 0 {
log.errorf("sdl.TTF_Init failed.")
return false
}
font_path := cstring("../JetBrainsMono-Regular.otf")
ctx.font = ttf.OpenFont(font_path, 32)
if ctx.font == nil {
log.errorf("sdl.TTF_OpenFont failed.")
return false
}
bt := new(BufferText)
bt.builder = strings.builder_make(0, 512)
ctx.buffer_text = bt
append(&bt.builder.buf, "// Code goes here...")
str := strings.to_cstring(&ctx.buffer_text.builder)
color := sdl.Color{ 0, 0, 0, 255}
ctx.text_surface = ttf.RenderUTF8_Blended_Wrapped(ctx.font, str, color, 900);
ctx.ftex = sdl.CreateTextureFromSurface(ctx.renderer, ctx.text_surface);
state.buffer_dirty = true
return true
}
process_input :: proc() {
e: sdl.Event
for sdl.PollEvent(&e) {
#partial switch(e.type) {
case .QUIT:
ctx.should_close = true
case .KEYDOWN:
#partial switch(e.key.keysym.sym) {
case .ESCAPE:
ctx.should_close = true
case .BACKSPACE:
if strings.builder_len(ctx.buffer_text.builder) > 0 {
strings.pop_rune(&ctx.buffer_text.builder)
state.buffer_dirty = true
}
case .RETURN:
strings.write_rune(&ctx.buffer_text.builder, '\n')
state.buffer_dirty = true
}
case .TEXTINPUT:
key := string(cstring(raw_data(e.text.text[:])))
strings.write_string(&ctx.buffer_text.builder, key)
state.buffer_dirty = true
}
}
}
update :: proc() {
if state.buffer_dirty {
color := sdl.Color{ 0, 0, 0, 255}
str: cstring
if strings.builder_len(ctx.buffer_text.builder) > 0 {
str = strings.to_cstring(&ctx.buffer_text.builder)
} else {
str = cstring(" ")
}
ctx.text_surface = ttf.RenderUTF8_Blended_Wrapped(ctx.font, str, color, 900);
if (ctx.text_surface == nil) {
log.errorf(sdl.GetErrorString())
os.exit(1)
}
ctx.ftex = sdl.CreateTextureFromSurface(ctx.renderer, ctx.text_surface);
}
}
draw :: proc() {
sdl.SetRenderDrawColor(ctx.renderer, 250, 250, 250, 0xff)
sdl.RenderClear(ctx.renderer)
if ctx.ftex != nil {
src := sdl.Rect { 0, 0, ctx.text_surface.w, ctx.text_surface.h }
dest := sdl.Rect { 0, 0, ctx.text_surface.w, ctx.text_surface.h }
sdl.RenderCopy(ctx.renderer, ctx.ftex, &src, &dest)
}
sdl.RenderPresent(ctx.renderer)
}
main :: proc() {
context.logger = log.create_console_logger()
if ok := sdl_init(); !ok {
log.errorf(sdl.GetErrorString())
os.exit(1)
}
ctx.frame_start = ctx.app_start
ctx.frame_elapsed = 0.001
for !ctx.should_close {
process_input()
update()
draw()
ctx.frame_end = f64(sdl.GetPerformanceCounter()) / f64(sdl.GetPerformanceFrequency())
ctx.frame_elapsed = ctx.frame_end - ctx.frame_start
ctx.frame_start = ctx.frame_end
state.buffer_dirty = false
}
ttf.CloseFont(ctx.font)
sdl.DestroyWindow(ctx.window)
sdl.Quit()
}
+71
View File
@@ -0,0 +1,71 @@
package wm
import fmt "core:fmt"
import "core:os"
import "core:log"
import "core:math"
import "core:strings"
import x "vendor:x11/xlib"
main :: proc () {
display: ^x.Display
root: x.Window
attr: x.XWindowAttributes
start: x.XButtonEvent
display = x.OpenDisplay(nil)
if display == nil {
fmt.println("Could not open display")
os.exit(1)
}
x.GrabKey(display, i32(x.KeysymToKeycode(display, x.StringToKeysym("F1"))), {.Mod1Mask},
x.DefaultRootWindow(display), true, .GrabModeAsync, .GrabModeAsync);
// x.GrabButton(display, 1, Mod1Mask, DefaultRootWindow(dpy), true,
// ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
// XGrabButton(display, 3, Mod1Mask, DefaultRootWindow(display), true,
// ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
// x.SelectInput
// display,
// root,
// {.SubstructureRedirect, .SubstructureNotify});
// x.Synchronize(display, false);
for {
e: x.XEvent
x.NextEvent(display, &e)
fmt.println("Received Event: ", e)
#partial switch e.type {
case .KeyPress:
x.RaiseWindow(display, e.xkey.subwindow)
case .ButtonPress:
x.GetWindowAttributes(display, e.xbutton.subwindow, &attr)
start = e.xbutton
case .MotionNotify:
xdiff := e.xbutton.x_root - start.x_root;
ydiff := e.xbutton.y_root - start.y_root;
xpos := attr.x + (start.button==.Button1 ? xdiff : 0)
ypos := attr.y + (start.button==.Button1 ? ydiff : 0)
width := u32(max(1, attr.width + (start.button==.Button2 ? xdiff : 0)))
height := u32(max(1, attr.height + (start.button==.Button2 ? ydiff : 0)))
x.MoveResizeWindow(display, start.subwindow, xpos, ypos, width, height)
case .ButtonRelease:
// start.subwindow = nil;
}
// else if(ev.xbutton.subwindow != None)
// else if(ev.type == MotionNotify && start.subwindow != None)
// {
// int xdiff = ev.xbutton.x_root - start.x_root;
// int ydiff = ev.xbutton.y_root - start.y_root;
// XMoveResizeWindow(dpy, start.subwindow,
// attr.x + (start.button==1 ? xdiff : 0),
// attr.y + (start.button==1 ? ydiff : 0),
// MAX(1, attr.width + (start.button==3 ? xdiff : 0)),
// MAX(1, attr.height + (start.button==3 ? ydiff : 0)));
// }
// else if(ev.type == ButtonRelease)
// start.subwindow = None;
}
}
+2
View File
@@ -0,0 +1,2 @@
pygame-ce==2.3.2
piece_table=0.0.3
+75
View File
@@ -0,0 +1,75 @@
from tree_sitter import Language, Parser, Node, TreeCursor, Tree
from typing import Tuple, List
from dataclasses import dataclass
# Language.build_library(
# # Store the library in the `build` directory
# 'build/compiled-languages.so',
# # Include one or more languages
# [
# '/home/joe/Repositories/tree-sitter/languages/tree-sitter-cpp',
# '/home/joe/Repositories/tree-sitter/languages/tree-sitter-c',
# '/home/joe/Repositories/tree-sitter/languages/tree-sitter-rust',
# '/home/joe/Repositories/tree-sitter/languages/tree-sitter-python',
# # 'vendor/tree-sitter-javascript',
# # 'vendor/tree-sitter-python'
# ]
# )
C_LANGUAGE = Language('build/compiled-languages.so', 'c')
# CPP_LANGUAGE = Language('build/compiled-languages.so', 'cpp')
# PYTHON_LANGUAGE = Language('build/compiled-languages.so', 'python')
# PY_LANGUAGE = Language('build/my-languages.so', 'python')
c_parser: Parser = Parser()
c_parser.set_language(C_LANGUAGE)
# cpp_parser = Parser()
# cpp_parser.set_language(CPP_LANGUAGE)
# python_parser = Parser()
# python_parser.set_language(PYTHON_LANGUAGE)
def traverse_tree(tree: Tree):
cursor = tree.walk()
reached_root = False
while reached_root == False:
yield cursor.node
if cursor.goto_first_child():
continue
if cursor.goto_next_sibling():
continue
retracing = True
while retracing:
if not cursor.goto_parent():
retracing = False
reached_root = True
if cursor.goto_next_sibling():
retracing = False
# with open('treesitter.py', 'r') as f:
# def dfs(node: Node, indent_level):
# indent = "".join([' ' * (indent_level * 4)])
# # if node.grammar_name != node.type:
# # print(node)
# print(indent, '-', f"({node.type})", # node.range,
# node.start_point, node.end_point, node.text)
# for n in node.children:
# dfs(n, indent_level + 1)
# dfs(root, 0)
@dataclass
class ParsedRange:
name: str
line_num: int
crange: Tuple[int, int]
# def get_ts_nodes(text: str) -> List[ParsedRange]:
def get_ts_nodes(text: str) -> List[Node]:
tree = c_parser.parse(bytes(text, 'utf-8'))
# root: Node = tree.root_node
# query = C_LANGUAGE.query(root.sexp())
return [n for n in traverse_tree(tree)]