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 keybindings import *
|
||||
|
||||
keymap = {
|
||||
'left': cursor_go_left,
|
||||
'right': cursor_go_right,
|
||||
'up': cursor_go_up,
|
||||
'down': cursor_go_down,
|
||||
'C-e': cursor_go_line_end,
|
||||
'C-a': cursor_go_line_beginning,
|
||||
}
|
||||
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: 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.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))
|
||||
@ -127,3 +135,9 @@ def buffer_insert_text_at_cursor(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'
|
||||
|
98
fide.py
98
fide.py
@ -30,6 +30,7 @@ class Line:
|
||||
class Buffer:
|
||||
lines: List[Line]
|
||||
cursor: Cursor
|
||||
mode: str = 'insert'
|
||||
|
||||
def cursor_pos(cursor: Cursor):
|
||||
return (cursor.line_num, cursor.col)
|
||||
@ -59,26 +60,6 @@ def buffer_total_lines(buf: Buffer) -> int:
|
||||
def buffer_line_len(buf: Buffer) -> int:
|
||||
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 = 20,50
|
||||
@ -109,6 +90,7 @@ global ctx
|
||||
ctx = GlobalContext()
|
||||
from config import *
|
||||
from editing import *
|
||||
from keybindings import *
|
||||
def main():
|
||||
running = True
|
||||
frame_count = 0
|
||||
@ -119,6 +101,7 @@ def main():
|
||||
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')
|
||||
@ -132,41 +115,52 @@ def main():
|
||||
cursor_on = True
|
||||
cursor_flash = 0
|
||||
curr_line = buffer_line_current(current_buffer)
|
||||
if event.mod == 64: # Right Ctrl
|
||||
# this is inefficient, might as well just pass the mod number
|
||||
# and make the mapping mod_num -> config_key
|
||||
mod_key = translate_keyname('left ctrl')
|
||||
kb_to_test = Keybinding(pg.key.name(event.key), event.mod)
|
||||
# print(kb_to_test, event.key, pg.key.name(event.key))
|
||||
for i in range(len(keymap_stack) - 1, -1, -1):
|
||||
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:
|
||||
# print(mod_key, event.key, event.unicode)
|
||||
key = mod_key + pg.key.name(event.key)
|
||||
if key in keymap:
|
||||
keymap[key]()
|
||||
# if event.key == pg.K_e:
|
||||
# cursor_go_line_end()
|
||||
# if event.key == pg.K_a:
|
||||
# cursor_go_line_beginning()
|
||||
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:])
|
||||
elif pg.key.name(event.key) == 'escape':
|
||||
pass
|
||||
elif event.key == pg.K_RETURN:
|
||||
buffer_newline_at_pos()
|
||||
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 keymap:
|
||||
keymap[pg.key.name(event.key)]()
|
||||
# if event.mod == 64: # Right Ctrl
|
||||
# # this is inefficient, might as well just pass the mod number
|
||||
# # and make the mapping mod_num -> config_key
|
||||
# mod_key = translate_keyname('left ctrl')
|
||||
|
||||
# if event.unicode:
|
||||
# # print(mod_key, event.key, event.unicode)
|
||||
# key = mod_key + pg.key.name(event.key)
|
||||
# if key in keymappings[current_buffer.mode]:
|
||||
# keymappings[current_buffer.mode][key]()
|
||||
# # if event.key == pg.K_e:
|
||||
# # cursor_go_line_end()
|
||||
# # if event.key == pg.K_a:
|
||||
# # cursor_go_line_beginning()
|
||||
# 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:])
|
||||
# elif pg.key.name(event.key) == 'escape':
|
||||
# pass
|
||||
# elif event.key == pg.K_RETURN:
|
||||
# buffer_newline_at_pos()
|
||||
# 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
|
||||
# elif event.mod < 2:
|
||||
elif event.unicode:
|
||||
buffer_insert_char(event.unicode)
|
||||
# elif event.unicode:
|
||||
# buffer_insert_char(event.unicode)
|
||||
|
||||
for i,curr_line in enumerate(current_buffer.lines):
|
||||
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