Skip to content

Commit

Permalink
Added popup which shows next available keys and their commands
Browse files Browse the repository at this point in the history
  • Loading branch information
Nimaoth committed Feb 3, 2025
1 parent 98c8fef commit 0e7a85c
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 66 deletions.
75 changes: 29 additions & 46 deletions config/default_config.nim
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,15 @@ proc loadDefaultKeybindings*(clearExisting: bool = false) {.expose("load-default
addCommand "editor", "<C-x><C-x>", "quit"
addCommand "editor", "<CAS-r>", "reload-plugin"

addCommand "editor", "<LEADER><*-T>1", "load-theme", "synthwave-color-theme"
addCommand "editor", "<LEADER><*-T>2", "load-theme", "tokyo-night-storm-color-theme"

addCommand "editor", "<LEADER>ft", "toggle-flag", "editor.log-frame-time"
# addCommand "editor", "<C-SPACE>pp", proc() =
# toggleFlag "editor.poll"
# echo "-> ", getFlag("editor.poll")
addCommand "editor", "<LEADER>fl", "toggle-flag", "logging"
addCommand "editor", "<LEADER>ffs", "toggle-flag", "render-selected-value"
addCommand "editor", "<LEADER>ffr", "toggle-flag", "log-render-duration"
addCommand "editor", "<LEADER>ffd", "toggle-flag", "render-debug-info"
addCommand "editor", "<LEADER>ffo", "toggle-flag", "render-execution-output"
addCommand "editor", "<LEADER>ffg", "toggle-flag", "text.print-scopes"
addCommand "editor", "<LEADER>ffm", "toggle-flag", "text.print-matches"
addCommand "editor", "<LEADER>ffh", "toggle-flag", "text.show-node-highlight"
addCommand "editor", "<LEADER>iii", "toggleShowDrawnNodes"
addCommand "editor", "<LEADER>ot", "toggle-flag", "editor.log-frame-time"
addCommand "editor", "<LEADER>ol", "toggle-flag", "logging"
addCommand "editor", "<LEADER>os", "toggle-flag", "render-selected-value"
addCommand "editor", "<LEADER>or", "toggle-flag", "log-render-duration"
addCommand "editor", "<LEADER>od", "toggle-flag", "render-debug-info"
addCommand "editor", "<LEADER>oo", "toggle-flag", "render-execution-output"
addCommand "editor", "<LEADER>og", "toggle-flag", "text.print-scopes"
addCommand "editor", "<LEADER>om", "toggle-flag", "text.print-matches"
addCommand "editor", "<LEADER>oh", "toggle-flag", "text.show-node-highlight"
addCommandBlockDesc "editor", "<C-5>", "":
setOption("text.node-highlight-parent-index", clamp(getOption[int]("text.node-highlight-parent-index") - 1, 0, 100000))
echo "text.node-highlight-parent-index: ", getOption[int]("text.node-highlight-parent-index")
Expand All @@ -77,21 +70,19 @@ proc loadDefaultKeybindings*(clearExisting: bool = false) {.expose("load-default
setOption("text.node-highlight-sibling-index", clamp(getOption[int]("text.node-highlight-sibling-index") + 1, -100000, 100000))

# addCommand "editor", "<S-SPACE><*-l>", ""
addCommand "editor", "<LEADER>ff", "log-options"
# addCommand "editor", "<LEADER>ff", "log-options"
addCommand "editor", "<ESCAPE>", "escape"

# Window stuff <LEADER>w
withKeys "<LEADER>w", "<C-w>":
addCommand "editor", "<*-F>-", "change-font-size", -1
addCommand "editor", "<*-F>+", "change-font-size", 1
addCommand "editor", "<*-A>-", "change-animation-speed", 1 / 1.5
addCommand "editor", "<*-A>+", "change-animation-speed", 1.5
addCommand "editor", "<*-f>-", "change-font-size", -1
addCommand "editor", "<*-f>+", "change-font-size", 1
addCommand "editor", "<*-k>n", "change-layout-prop", "main-split", -0.05
addCommand "editor", "<*-k>t", "change-layout-prop", "main-split", 0.05
addCommand "editor", "b", "toggle-status-bar-location"
addCommand "editor", "1", "set-layout", "horizontal"
addCommand "editor", "2", "set-layout", "vertical"
addCommand "editor", "3", "set-layout", "fibonacci"
addCommand "editor", "<*-l>n", "change-layout-prop", "main-split", -0.05
addCommand "editor", "<*-l>t", "change-layout-prop", "main-split", 0.05
addCommand "editor", "v", "create-view"
addCommand "editor", "a", "create-keybind-autocomplete-view"
addCommand "editor", "x", "close-current-view", keepHidden=true, restoreHidden=false
Expand Down Expand Up @@ -150,29 +141,8 @@ proc loadDefaultKeybindings*(clearExisting: bool = false) {.expose("load-default
logs(scrollToBottom = true)
nextView()

withKeys "<LEADER>s":
addCommandBlock "editor", "l<*-n>1":
setOption("editor.text.line-numbers", LineNumbers.None)
requestRender(true)

addCommandBlock "editor", "l<*-n>2":
setOption("editor.text.line-numbers", LineNumbers.Absolute)
requestRender(true)

addCommandBlock "editor", "l<*-n>3":
setOption("editor.text.line-numbers", LineNumbers.Relative)
requestRender(true)

addCommandBlock "editor", "dl": lspLogVerbose(true)
addCommandBlock "editor", "dL": lspLogVerbose(false)

addCommand "editor", "<LEADER>lf", "load-file"
addCommand "editor", "<LEADER>sf", "write-file"
addCommand "editor", "<LEADER>rSS", "write-file", "", true
addCommand "editor", "<LEADER>rSA", "save-app-state"
addCommand "editor", "<LEADER>rSC", "remove-from-local-storage"
addCommand "editor", "<LEADER>rCC", "clear-workspace-caches"
addCommand "editor", "<LEADER>rCC", "clear-workspace-caches"
addCommand "editor", "<LEADER>fl", "load-file"
addCommand "editor", "<LEADER>fs", "write-file"

addCommand "command-line-low", "<ESCAPE>", "exit-command-line"
addCommand "command-line-low", "<ENTER>", "execute-command-line"
Expand Down Expand Up @@ -276,6 +246,19 @@ proc loadDefaultKeybindings*(clearExisting: bool = false) {.expose("load-default
for i in 0..10:
nextVariable()

addCommandDescription "editor", "<LEADER>g", "Global pickers"
addCommandDescription "editor.text", "<LEADER>g", "Document pickers"
addCommandDescription "editor", "<LEADER>a", "Debugger"
addCommandDescription "editor", "<LEADER>a", "Debugger"
addCommandDescription "editor", "<LEADER>o", "Options"
addCommandDescription "editor", "<LEADER>ff", "Rendering"
addCommandDescription "editor", "<LEADER>w", "Window"
addCommandDescription "editor", "<LEADER>wf", "Change font size"
addCommandDescription "editor", "<LEADER>wk", "Split size ratio"
addCommandDescription "editor", "<LEADER>r", "Run"
addCommandDescription "editor", "<LEADER>f", "File"
addCommandDescription "editor", "<LEADER>m", "Toggle fullscreen"

# addCommand "editor.text", "<C-SPACE>ts", "reload-treesitter"

# setHandleInputs("editor.model", true)
Expand Down
Binary file modified config/wasm/keybindings_plugin.wasm
Binary file not shown.
13 changes: 13 additions & 0 deletions scripting/events_api_wasm.nim
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,16 @@ proc removeCommand*(context: string; keys: string) {.gcsafe, raises: [].} =
let res {.used.} = events_removeCommand_void_EventHandlerService_string_string_wasm(
argsJsonString.cstring)


proc events_addCommandDescription_void_EventHandlerService_string_string_string_wasm(
arg: cstring): cstring {.importc.}
proc addCommandDescription*(context: string; keys: string;
description: string = "") {.gcsafe, raises: [].} =
var argsJson = newJArray()
argsJson.add context.toJson()
argsJson.add keys.toJson()
argsJson.add description.toJson()
let argsJsonString = $argsJson
let res {.used.} = events_addCommandDescription_void_EventHandlerService_string_string_string_wasm(
argsJsonString.cstring)

49 changes: 48 additions & 1 deletion src/app.nim
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import std/[sequtils, strformat, strutils, tables, options, os, json, macros, sugar, streams, deques]
import misc/[id, util, timer, event, myjsonutils, traits, rect_utils, custom_logger, custom_async,
array_set, delayed_task, disposable_ref, regex]
array_set, delayed_task, disposable_ref, regex, custom_unicode]
import ui/node
import scripting/[expose, scripting_base]
import platform/[platform]
Expand Down Expand Up @@ -140,6 +140,10 @@ type

closeUnusedDocumentsTask: DelayedTask

showNextPossibleInputsTask: DelayedTask
nextPossibleInputs*: seq[tuple[input: string, description: string, continues: bool]]
showNextPossibleInputs*: bool

var gEditor* {.exportc.}: App = nil

proc handleLog(self: App, level: Level, args: openArray[string])
Expand Down Expand Up @@ -724,6 +728,11 @@ proc newApp*(backend: api.Backend, platform: Platform, services: Services, optio
self.closeUnusedDocumentsTask = startDelayed(closeUnusedDocumentsTimerS * 1000, repeat=true):
self.closeUnusedDocuments()

let showNextPossibleInputsDelay = self.config.getOption("editor.which-key-delay", 500)
self.showNextPossibleInputsTask = startDelayedPaused(showNextPossibleInputsDelay, repeat=false):
self.showNextPossibleInputs = self.nextPossibleInputs.len > 0
self.platform.requestRender()

self.runEarlyCommandsFromAppOptions()
self.runConfigCommands("startup-commands")

Expand Down Expand Up @@ -2126,6 +2135,40 @@ proc recordInputToHistory*(self: App, input: string) =
if self.inputHistory.len > maxLen:
self.inputHistory = self.inputHistory[(self.inputHistory.len - maxLen)..^1]

proc updateNextPossibleInputs*(self: App) =
self.nextPossibleInputs.setLen(0)
for handler in self.currentEventHandlers:
if not handler.inProgress:
continue

let nextPossibleInputs = handler.getNextPossibleInputs()
for x in nextPossibleInputs:
for next in x[2]:
if x[1] == {Shift} and x[0] in 0..Rune.high.int and x[0].Rune.isAlpha:
continue

let actions = handler.dfa.getActions(next)
if actions.len > 1:
var desc = &"... ({handler.config.context})"
handler.config.stateToDescription.withValue(next.current, val):
desc = val[] & "..."
self.nextPossibleInputs.add (inputToString(x[0], x[1]), desc, true)
elif actions.len > 0:
var desc = &"{actions[0][0]} {actions[0][1]}"
handler.config.stateToDescription.withValue(next.current, val):
desc = val[]
self.nextPossibleInputs.add (inputToString(x[0], x[1]), desc, false)

if self.nextPossibleInputs.len > 0 and not self.showNextPossibleInputs:
self.showNextPossibleInputsTask.interval = self.config.getOption("editor.which-key-delay", 500)
self.showNextPossibleInputsTask.reschedule()

elif self.nextPossibleInputs.len == 0:
self.showNextPossibleInputs = false

if self.showNextPossibleInputs:
self.platform.requestRender()

proc handleKeyPress*(self: App, input: int64, modifiers: Modifiers) =
# logScope lvlDebug, &"handleKeyPress {inputToString(input, modifiers)}"
self.logNextFrameTime = true
Expand All @@ -2151,6 +2194,8 @@ proc handleKeyPress*(self: App, input: int64, modifiers: Modifiers) =
except:
discard

self.updateNextPossibleInputs()

proc handleKeyRelease*(self: App, input: int64, modifiers: Modifiers) =
discard

Expand All @@ -2176,6 +2221,8 @@ proc handleRune*(self: App, input: int64, modifiers: Modifiers) =
except:
discard

self.updateNextPossibleInputs()

proc handleDropFile*(self: App, path, content: string) =
let document = newTextDocument(self.services, path, content)
self.editors.documents.add document
Expand Down
74 changes: 66 additions & 8 deletions src/events.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import std/[tables, sequtils, strutils]
import std/[tables, sequtils, strutils, sugar, unicode]
import misc/[custom_logger, util, custom_async]
import scripting/expose
import input, service, dispatch_tables
Expand Down Expand Up @@ -31,6 +31,8 @@ type
consumeAllInput*: bool
revision: int
leaders: seq[string]
descriptions*: Table[string, string]
stateToDescription*: Table[int, string]

EventHandler* = ref object
states: seq[CommandState]
Expand Down Expand Up @@ -93,7 +95,24 @@ proc combineCommands(config: EventHandlerConfig, commands: var Table[string, Tab
proc buildDFA*(config: EventHandlerConfig): CommandDFA {.gcsafe, raises: [].} =
var commands = initTable[string, Table[string, string]]()
config.combineCommands(commands)
return buildDFA(commands, config.leaders)
result = buildDFA(commands, config.leaders)

let leaders = collect(newSeq):
for leader in config.leaders:
let (keys, _, _, _, _) = parseNextInput(leader.toRunes, 0)
for key in keys:
(key.inputCodes.a, key.mods)

config.stateToDescription.clear()
for leader in leaders:
for (keys, desc) in config.descriptions.pairs:
var states: seq[CommandState]
for (inputCode, mods, _) in parseInputs(keys, [leader]):
let oldStates = states
states = result.stepAll(states, inputCode.a, mods)

for s in states:
config.stateToDescription[s.current] = desc

proc maxRevision*(config: EventHandlerConfig): int =
result = config.revision
Expand Down Expand Up @@ -130,6 +149,10 @@ proc addCommand*(config: EventHandlerConfig, context: string, keys: string, acti
config.commands[context][keys] = Command(command: action, source: source)
config.revision += 1

proc addCommandDescription*(config: EventHandlerConfig, keys: string, description: string) =
config.descriptions[keys] = description
config.revision += 1

proc removeCommand*(config: EventHandlerConfig, keys: string) =
config.commands.del(keys)
config.revision += 1
Expand All @@ -150,6 +173,9 @@ proc setLeaders*(config: EventHandlerConfig, leaders: openArray[string]) =
config.leaders = @leaders
config.revision += 1

proc getNextPossibleInputs*(handler: EventHandler): auto =
handler.dfa.getNextPossibleInputs(handler.states)

template eventHandler*(inConfig: EventHandlerConfig, handlerBody: untyped): untyped =
block:
var handler = EventHandler()
Expand Down Expand Up @@ -236,7 +262,7 @@ template assignEventHandler*(target: untyped, inConfig: EventHandlerConfig, hand
handlerBody
target = handler

proc reset*(handler: var EventHandler) =
proc resetHandler*(handler: var EventHandler) =
handler.states = @[]

proc inProgress*(states: openArray[CommandState]): bool =
Expand All @@ -246,6 +272,7 @@ proc inProgress*(states: openArray[CommandState]): bool =
return false

proc inProgress*(handler: EventHandler): bool = handler.states.inProgress
proc states*(handler: EventHandler): auto = handler.states

proc anyInProgress*(handlers: openArray[EventHandler]): bool =
for h in handlers:
Expand All @@ -271,7 +298,7 @@ proc handleEvent*(handler: var EventHandler, input: int64, modifiers: Modifiers,
# debugf"handleEvent {handler.config.context} {(inputToString(input, modifiers))}"

if not handler.inProgress:
handler.reset()
handler.resetHandler()
if not prevStates.inProgress:
return Ignored
else:
Expand All @@ -283,10 +310,31 @@ proc handleEvent*(handler: var EventHandler, input: int64, modifiers: Modifiers,
elif handler.states.anyIt(handler.dfa.isTerminal(it.current)):
if handler.states.len != 1:
return Failed

let (action, arg) = handler.dfa.getAction(handler.states[0])
handler.reset()
# handler.state.current = handler.dfa.getDefaultState(handler.state.current) # todo
return handler.handleAction(action, arg)
let currentState = handler.states[0].current
let nextState = handler.dfa.getDefaultState(currentState)

if nextState != 0:
handler.states = @[CommandState(
current: nextState,
functionIndices: handler.dfa.getFunctionIndices(nextState),
captures: handler.states[0].captures, # todo
)]
else:
handler.resetHandler()

let res = handler.handleAction(action, arg)
case res
of Failed: return Failed
of Ignored: return Ignored
of Canceled: return Canceled
of Progress: return Progress
of Handled:
if handler.inProgress:
return Progress
else:
return Handled

else:
if not handler.handleProgress.isNil:
Expand Down Expand Up @@ -321,7 +369,7 @@ proc handleEvent*(handlers: seq[EventHandler], input: int64, modifiers: Modifier
# Don't reset the current handler
if k != handlerIndex:
var h = h
h.reset()
h.resetHandler()

return Handled
of Progress:
Expand Down Expand Up @@ -438,4 +486,14 @@ proc removeCommand*(self: EventHandlerService, context: string, keys: string) {.
self.getEventHandlerConfig(context).removeCommand(keys)
self.invalidateCommandToKeysMap()

proc addCommandDescription*(self: EventHandlerService, context: string, keys: string, description: string = "") {.expose("events").} =
let context = if context.endsWith("."):
context[0..^2]
else:
context

log lvlWarn, fmt"Adding command description to '{context}': '{keys}' -> '{description}'"

self.getEventHandlerConfig(context).addCommandDescription(keys, description)

addGlobalDispatchTable "events", genDispatchTable("events")
Loading

0 comments on commit 0e7a85c

Please sign in to comment.