Parse config keybindings and iterate over a stack of keymappings
This commit is contained in:
parent
5cb2fd0fd7
commit
7228bf3201
24
DESIGN.org
Normal file
24
DESIGN.org
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
* Keymapping 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
|
||||||
|
|
25
config.py
25
config.py
@ -1,10 +1,19 @@
|
|||||||
from editing import *
|
from editing import *
|
||||||
|
from keybindings import *
|
||||||
|
|
||||||
keymap = {
|
kb('insert', 'left' , cursor_go_left)
|
||||||
'left': cursor_go_left,
|
kb('insert', 'right', cursor_go_right)
|
||||||
'right': cursor_go_right,
|
kb('insert', 'up' , cursor_go_up)
|
||||||
'up': cursor_go_up,
|
kb('insert', 'down' , cursor_go_down)
|
||||||
'down': cursor_go_down,
|
kb('insert', 'C-e' , cursor_go_line_end)
|
||||||
'C-e': cursor_go_line_end,
|
kb('insert', 'C-a' , cursor_go_line_beginning)
|
||||||
'C-a': cursor_go_line_beginning,
|
kb('insert', 'escape', lambda: push(keymappings['normal']))
|
||||||
}
|
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)
|
||||||
|
14
editing.py
14
editing.py
@ -53,6 +53,14 @@ def cursor_go_line_end():
|
|||||||
buf.cursor.col = buffer_line_len(buf)
|
buf.cursor.col = buffer_line_len(buf)
|
||||||
buf.cursor.want_col = buf.cursor.col
|
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):
|
def buffer_insert_line_below(buf: Buffer, text):
|
||||||
line_num = buffer_line_num(buf)
|
line_num = buffer_line_num(buf)
|
||||||
buf.lines.insert(line_num + 1, line_create(text))
|
buf.lines.insert(line_num + 1, line_create(text))
|
||||||
@ -127,3 +135,9 @@ def buffer_insert_text_at_cursor(text):
|
|||||||
curr_line.length += text_len
|
curr_line.length += text_len
|
||||||
cursor.col += text_len
|
cursor.col += text_len
|
||||||
cursor.want_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'
|
||||||
|
98
fide.py
98
fide.py
@ -30,6 +30,7 @@ class Line:
|
|||||||
class Buffer:
|
class Buffer:
|
||||||
lines: List[Line]
|
lines: List[Line]
|
||||||
cursor: Cursor
|
cursor: Cursor
|
||||||
|
mode: str = 'insert'
|
||||||
|
|
||||||
def cursor_pos(cursor: Cursor):
|
def cursor_pos(cursor: Cursor):
|
||||||
return (cursor.line_num, cursor.col)
|
return (cursor.line_num, cursor.col)
|
||||||
@ -59,26 +60,6 @@ def buffer_total_lines(buf: Buffer) -> int:
|
|||||||
def buffer_line_len(buf: Buffer) -> int:
|
def buffer_line_len(buf: Buffer) -> int:
|
||||||
return buffer_line_current(buf).length
|
return buffer_line_current(buf).length
|
||||||
|
|
||||||
def translate_keyname(keyname: str) -> str:
|
|
||||||
match keyname:
|
|
||||||
case 'left ctrl':
|
|
||||||
return 'C-'
|
|
||||||
case 'left alt':
|
|
||||||
return 'A-'
|
|
||||||
case 'left meta':
|
|
||||||
return 'M-'
|
|
||||||
case 'left shift':
|
|
||||||
return 'S-'
|
|
||||||
|
|
||||||
case 'right ctrl':
|
|
||||||
return 'RC-'
|
|
||||||
case 'right alt':
|
|
||||||
return 'RA-'
|
|
||||||
case 'right meta':
|
|
||||||
return 'RM-'
|
|
||||||
case 'right shift':
|
|
||||||
return 'RS-'
|
|
||||||
|
|
||||||
|
|
||||||
# oswinx, oswiny = 1940,50
|
# oswinx, oswiny = 1940,50
|
||||||
oswinx, oswiny = 20,50
|
oswinx, oswiny = 20,50
|
||||||
@ -109,6 +90,7 @@ global ctx
|
|||||||
ctx = GlobalContext()
|
ctx = GlobalContext()
|
||||||
from config import *
|
from config import *
|
||||||
from editing import *
|
from editing import *
|
||||||
|
from keybindings import *
|
||||||
def main():
|
def main():
|
||||||
running = True
|
running = True
|
||||||
frame_count = 0
|
frame_count = 0
|
||||||
@ -119,6 +101,7 @@ def main():
|
|||||||
current_buffer = ctx.current_buffer
|
current_buffer = ctx.current_buffer
|
||||||
console = InteractiveInterpreter(locals())
|
console = InteractiveInterpreter(locals())
|
||||||
console.runsource('from editing import *')
|
console.runsource('from editing import *')
|
||||||
|
# We are hardcoding precedence here
|
||||||
while running:
|
while running:
|
||||||
dt = clock.tick(120) / 1000.0
|
dt = clock.tick(120) / 1000.0
|
||||||
screen.fill('white')
|
screen.fill('white')
|
||||||
@ -132,41 +115,52 @@ def main():
|
|||||||
cursor_on = True
|
cursor_on = True
|
||||||
cursor_flash = 0
|
cursor_flash = 0
|
||||||
curr_line = buffer_line_current(current_buffer)
|
curr_line = buffer_line_current(current_buffer)
|
||||||
if event.mod == 64: # Right Ctrl
|
kb_to_test = Keybinding(pg.key.name(event.key), event.mod)
|
||||||
# this is inefficient, might as well just pass the mod number
|
# print(kb_to_test, event.key, pg.key.name(event.key))
|
||||||
# and make the mapping mod_num -> config_key
|
for i in range(len(keymap_stack) - 1, -1, -1):
|
||||||
mod_key = translate_keyname('left ctrl')
|
keymap = keymap_stack[i]
|
||||||
|
# check if a modifier is pressed
|
||||||
|
if kb_to_test in keymap.mapping:
|
||||||
|
keymap.mapping[kb_to_test]()
|
||||||
|
# if mod_key and event.unicode:
|
||||||
|
# print(mod_key + pg.key.name(event.key))
|
||||||
|
# print(pg.key.name(event.key))
|
||||||
|
|
||||||
if event.unicode:
|
# if event.mod == 64: # Right Ctrl
|
||||||
# print(mod_key, event.key, event.unicode)
|
# # this is inefficient, might as well just pass the mod number
|
||||||
key = mod_key + pg.key.name(event.key)
|
# # and make the mapping mod_num -> config_key
|
||||||
if key in keymap:
|
# mod_key = translate_keyname('left ctrl')
|
||||||
keymap[key]()
|
|
||||||
# if event.key == pg.K_e:
|
# if event.unicode:
|
||||||
# cursor_go_line_end()
|
# # print(mod_key, event.key, event.unicode)
|
||||||
# if event.key == pg.K_a:
|
# key = mod_key + pg.key.name(event.key)
|
||||||
# cursor_go_line_beginning()
|
# if key in keymappings[current_buffer.mode]:
|
||||||
if event.key == pg.K_RETURN:
|
# keymappings[current_buffer.mode][key]()
|
||||||
src = buffer_line_current(current_buffer).piece.get_text()
|
# # if event.key == pg.K_e:
|
||||||
# try:
|
# # cursor_go_line_end()
|
||||||
# eval(src[3:])
|
# # if event.key == pg.K_a:
|
||||||
# except:
|
# # cursor_go_line_beginning()
|
||||||
# print('error evaluating line: ' + src[3:])
|
# if event.key == pg.K_RETURN:
|
||||||
console.runsource(src[3:])
|
# src = buffer_line_current(current_buffer).piece.get_text()
|
||||||
elif pg.key.name(event.key) == 'escape':
|
# # try:
|
||||||
pass
|
# # eval(src[3:])
|
||||||
elif event.key == pg.K_RETURN:
|
# # except:
|
||||||
buffer_newline_at_pos()
|
# # print('error evaluating line: ' + src[3:])
|
||||||
elif event.key == pg.K_BACKSPACE:
|
# console.runsource(src[3:])
|
||||||
buffer_delete_character_under_cursor()
|
# elif pg.key.name(event.key) == 'escape':
|
||||||
elif event.key == pg.K_DELETE:
|
# pass
|
||||||
buffer_delete_next_character()
|
# elif event.key == pg.K_RETURN:
|
||||||
elif pg.key.name(event.key) in keymap:
|
# buffer_newline_at_pos()
|
||||||
keymap[pg.key.name(event.key)]()
|
# elif event.key == pg.K_BACKSPACE:
|
||||||
|
# buffer_delete_character_under_cursor()
|
||||||
|
# elif event.key == pg.K_DELETE:
|
||||||
|
# buffer_delete_next_character()
|
||||||
|
# elif pg.key.name(event.key) in keymappings[current_buffer.mode]:
|
||||||
|
# keymappings[current_buffer.mode][pg.key.name(event.key)]()
|
||||||
# pass
|
# pass
|
||||||
# elif event.mod < 2:
|
# elif event.mod < 2:
|
||||||
elif event.unicode:
|
# elif event.unicode:
|
||||||
buffer_insert_char(event.unicode)
|
# buffer_insert_char(event.unicode)
|
||||||
|
|
||||||
for i,curr_line in enumerate(current_buffer.lines):
|
for i,curr_line in enumerate(current_buffer.lines):
|
||||||
render = text_renderer.render(curr_line.piece.get_text(), True, 'black')
|
render = text_renderer.render(curr_line.piece.get_text(), True, 'black')
|
||||||
|
11
fide.todo
Normal file
11
fide.todo
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#-*- 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
|
||||||
|
* TODO Keymap precedence system
|
||||||
|
|
100
keybindings.py
Normal file
100
keybindings.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
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
|
||||||
|
passthrough: bool
|
||||||
|
pop_not_found: bool
|
||||||
|
mapping: Dict[[int,str],Callable] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
global keymappings
|
||||||
|
global keymap_stack
|
||||||
|
|
||||||
|
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():
|
||||||
|
key = symbol_to_num(key)
|
||||||
|
|
||||||
|
return Keybinding(key, modifiers)
|
||||||
|
|
||||||
|
|
||||||
|
keymap_stack = [
|
||||||
|
Keymap('insert', pinned=True, passthrough=False, pop_not_found=False),
|
||||||
|
Keymap('normal', pinned=True, passthrough=False, pop_not_found=False),
|
||||||
|
]
|
||||||
|
|
||||||
|
keymappings = {
|
||||||
|
'insert': keymap_stack[0],
|
||||||
|
'normal': keymap_stack[1],
|
||||||
|
}
|
||||||
|
|
||||||
|
def kb(mode: str, binding: str, f: Callable):
|
||||||
|
b = parse_keybinding(binding)
|
||||||
|
keymappings[mode].mapping[b] = f
|
||||||
|
|
||||||
|
def keymap_push(keymap: Keymap):
|
||||||
|
keymap_stack.append(keymap_push)
|
||||||
|
|
||||||
|
def keymap_pop():
|
||||||
|
keymap_stack.pop()
|
Loading…
x
Reference in New Issue
Block a user