From 445ae1634d88c39a82b3556b925a25c19cb71a5f Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Mon, 18 Aug 2025 20:22:24 +0200 Subject: [PATCH 01/47] started working on moving vim motions to new plugin system --- config/keybindings.json | 368 +++++++ nev.nimble | 4 + plugin_api/v0/api.nim | 92 +- plugin_api/v0/plugin_api_guest.nim | 6 + plugin_api/v0/registers.nim | 52 + plugin_api/v0/text_editor.nim | 249 +++++ plugins/vim/config.nims | 13 + plugins/vim/manifest.json | 13 + plugins/vim/nimble.lock | 116 ++ plugins/vim/vim.nim | 1521 ++++++++++++++++++++++++++ plugins/vim/vim.nimble | 9 + scripting/plugin_runtime.nim | 2 +- src/app.nim | 18 +- src/channel.nim | 43 +- src/command_service.nim | 24 +- src/document_editor.nim | 24 +- src/finder/open_editor_previewer.nim | 2 +- src/generated/plugin_api_host.nim | 644 ++++++++++- src/language_server_command_line.nim | 18 + src/layout.nim | 44 +- src/layouts.nim | 36 +- src/plugin_api/plugin_api.nim | 225 +++- src/plugin_api/wasi.nim | 47 + src/scripting_api.nim | 2 +- src/terminal_service.nim | 417 ++++--- src/text/language/debugger.nim | 4 +- src/text/text_editor.nim | 21 +- wit/v0/api.wit | 38 + 28 files changed, 3798 insertions(+), 254 deletions(-) create mode 100644 plugin_api/v0/registers.nim create mode 100644 plugins/vim/config.nims create mode 100644 plugins/vim/manifest.json create mode 100644 plugins/vim/nimble.lock create mode 100644 plugins/vim/vim.nim create mode 100644 plugins/vim/vim.nimble diff --git a/config/keybindings.json b/config/keybindings.json index f7d73f44e..1d816383b 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -685,6 +685,374 @@ "": ["apply-selected-completion"], }, + "vim-new#text_object": { + "iw": ["vim-select-text-object", "vim-word-inner", false, true, "<#text_object.count>"], + "aw": ["vim-select-text-object", "vim-word-outer", false, true, "<#text_object.count>"], + "iW": ["vim-select-text-object", "vim-WORD-inner", false, true, "<#text_object.count>"], + "aW": ["vim-select-text-object", "vim-WORD-outer", false, true, "<#text_object.count>"], + "ip": ["vim-select-text-object", "vim-paragraph-inner", false, true, "<#text_object.count>"], + "ap": ["vim-select-text-object", "vim-paragraph-outer", false, true, "<#text_object.count>"], + + "i{": ["vim-select-surrounding", "vim-surround-{-inner", false, true, "<#text_object.count>"], + "a{": ["vim-select-surrounding", "vim-surround-{-outer", false, true, "<#text_object.count>"], + "i}": ["vim-select-surrounding", "vim-surround-{-inner", false, true, "<#text_object.count>"], + "a}": ["vim-select-surrounding", "vim-surround-{-outer", false, true, "<#text_object.count>"], + "i(": ["vim-select-surrounding", "vim-surround-(-inner", false, true, "<#text_object.count>"], + "a(": ["vim-select-surrounding", "vim-surround-(-outer", false, true, "<#text_object.count>"], + "i)": ["vim-select-surrounding", "vim-surround-(-inner", false, true, "<#text_object.count>"], + "a)": ["vim-select-surrounding", "vim-surround-(-outer", false, true, "<#text_object.count>"], + "i[": ["vim-select-surrounding", "vim-surround-[-inner", false, true, "<#text_object.count>"], + "a[": ["vim-select-surrounding", "vim-surround-[-outer", false, true, "<#text_object.count>"], + "i]": ["vim-select-surrounding", "vim-surround-[-inner", false, true, "<#text_object.count>"], + "a]": ["vim-select-surrounding", "vim-surround-[-outer", false, true, "<#text_object.count>"], + "i\\<": ["vim-select-surrounding", "vim-surround-angle-inner", false, true, "<#text_object.count>"], + "a\\<": ["vim-select-surrounding", "vim-surround-angle-outer", false, true, "<#text_object.count>"], + "i\\>": ["vim-select-surrounding", "vim-surround-angle-inner", false, true, "<#text_object.count>"], + "a\\>": ["vim-select-surrounding", "vim-surround-angle-outer", false, true, "<#text_object.count>"], + "i\"": ["vim-select-surrounding", "vim-surround-\"-inner", false, true, "<#text_object.count>"], + "a\"": ["vim-select-surrounding", "vim-surround-\"-outer", false, true, "<#text_object.count>"], + "i'": ["vim-select-surrounding", "vim-surround-'-inner", false, true, "<#text_object.count>"], + "a'": ["vim-select-surrounding", "vim-surround-'-outer", false, true, "<#text_object.count>"], + }, + + "vim-new#move": { + "": ["vim-move-cursor-column", -1, "<#move.count>"], + "": ["vim-move-cursor-column", 1, "<#move.count>"], + "": ["vim-move-cursor-visual-line", -1, "<#move.count>"], + "": ["vim-move-cursor-visual-line", 1, "<#move.count>"], + "h": ["vim-move-cursor-column", -1, "<#move.count>"], + "j": ["vim-move-cursor-visual-line", 1, "<#move.count>"], + "k": ["vim-move-cursor-visual-line", -1, "<#move.count>"], + "l": ["vim-move-cursor-column", 1, "<#move.count>"], + "gm": ["move-cursor-line-center"], + "gM": ["move-cursor-center"], + + "0": ["vim-move-first", "line"], + "": ["vim-move-first", "visual-line"], + "^": ["vim-move-first", "line-no-indent"], + "$": ["vim-move-to-end-of-line", "<#move.count>"], + "": ["vim-move-to-end-of-visual-line", "<#move.count>"], + "g0": ["vim-move-first", "line"], + "g^": ["vim-move-first", "line-no-indent"], + "g$": ["vim-move-to-end-of-line", "<#move.count>"], + "gg": ["vim-move-file-start", "<#move.count>"], + "G": ["vim-move-file-end", "<#move.count>"], + "%": ["vim-move-to-matching-or-file-offset", "<#move.count>"], + + "w": ["move-selection-next", "vim-word", false, true, "<#move.count>"], + "": ["move-selection-next", "vim-word", false, true, "<#move.count>"], + "W": ["move-selection-next", "vim-WORD", false, true, "<#move.count>"], + "": ["move-selection-next", "vim-WORD", false, true, "<#move.count>"], + "e": ["move-selection-end", "vim-word", false, false, "<#move.count>"], + "E": ["move-selection-end", "vim-WORD", false, false, "<#move.count>"], + "b": ["move-selection-end", "vim-word", true, true, "<#move.count>"], + "": ["move-selection-end", "vim-word", true, true, "<#move.count>"], + "B": ["move-selection-end", "vim-WORD", true, true, "<#move.count>"], + "": ["move-selection-end", "vim-WORD", true, true, "<#move.count>"], + "ge": ["move-selection-next", "vim-word", true, false, "<#move.count>"], + "gE": ["move-selection-next", "vim-WORD", true, false, "<#move.count>"], + "}": ["move-paragraph", false, "<#move.count>"], + "{": ["move-paragraph", true, "<#move.count>"], + "f": ["vim-move-to ", false, "<#move.count>"], + "t": ["vim-move-to ", true, "<#move.count>"], + + "": ["vim-move-cursor-visual-page", 0.5, "<#move.count>", true], + "": ["vim-move-cursor-visual-page", 1, "<#move.count>", true], + "": ["vim-move-cursor-visual-page", -0.5, "<#move.count>", true], + "": ["vim-move-cursor-visual-page", -1, "<#move.count>", true], + "|": ["vim-move-to-column", "<#move.count>"], + "-": ["vim-move-cursor-line-first-char", -1, "<#move.count>"], + "+": ["vim-move-cursor-line-first-char", 1, "<#move.count>"], + "_": ["vim-move-to-start-of-line", "<#move.count>"], + }, + + "vim-new.base": { + ":": ["command-line"], + + "h": ["focus-view-left"], + "": ["focus-view-left"], + "": ["focus-view-left"], + "l": ["focus-view-right"], + "": ["focus-view-right"], + "": ["focus-view-right"], + "k": ["focus-view-up"], + "": ["focus-view-up"], + "": ["focus-view-up"], + "j": ["focus-view-down"], + "": ["focus-view-down"], + "": ["focus-view-down"], + "w": ["focus-next-view", "**"], + "p": ["open-prev-view"], + "": ["open-prev-view"], + "q": ["vim-close-current-view-or-quit"], + "": ["close-active-view"], + "c": ["hide-active-view"], + "C": ["close-active-view"], + "o": ["close-other-views"], + "": ["close-other-views"], + + "": ["open-prev-view"], + + "m": ["toggle-maximize-view-local", "**"], + + "sr": "open-recent-session", + "to": ["select-terminal"], + "ts": [ + "run-in-terminal", + "default", + "", + { + "mode": "insert", + "closeOnTerminate": true, + "group": "scratch", + } + ], + }, + + "vim-new": { + "": ["vim.normal-mode"], + "": ["vim-add-cursor-above"], + "": ["vim-add-cursor-below"], + + // special + "d": ["vim-delete-move <#count>"], + "c": ["vim-change-move <#count>"], + "y": ["vim-yank-move <#count>"], + + "d": ["vim-delete-move <#count>"], + "c": ["vim-change-move <#count>"], + "y": ["vim-yank-move <#count>"], + + "": ["scroll-lines", -2], + "": ["scroll-text-horizontal", -4], + "": ["scroll-lines", 2], + "": ["scroll-text-horizontal", 4], + "": ["scroll-lines", -1], + "": ["scroll-lines", 1], + + "/": ["vim-open-search-bar"], + "gc": ["vim-toggle-line-comment"], + "*": ["vim-set-search-query-from-word"], + "n": ["vim-next-search-result"], + "N": ["vim-prev-search-result"], + + ".": ["replay-commands", "."], + "L": ["vim-select-word-or-add-cursor"], + "H": ["vim-move-last-selection-to-next-search-result"], + + "m": ["vim-add-mark "], + "'": ["vim-goto-mark "], + + "q": ["vim-start-macro "], + "@": ["vim-play-macro "], + "Q": ["vim-stop-macro"], + + "M": ["enter-choose-cursor-mode", "vim-handle-select-word"], + + // movement + "": ["select-prev"], + "": ["select-next"], + "": ["vim.select-last "], + "o": ["vim-reverse-selections"], + "": ["select-next-tab-stop"], + "": ["select-prev-tab-stop"], + "gt": ["vim-goto-next-diagnostic"], + "gn": ["vim-goto-prev-diagnostic"], + "gf": ["vim-goto-next-change"], + "gh": ["vim-goto-prev-change"], + + // copy/paste + "p": ["vim-paste", true, false, "vim-default-register"], + "P": ["vim-paste", true, false], + + // editing + "x": ["vim-delete-right"], + "": ["vim-delete-right"], + "": ["vim-delete-left"], + "": ["vim-delete-left"], + "": ["vim-delete-left"], + "r": ["set-mode", "vim-new.replace"], + + // undo/redo + "u": ["vim.undo", true], + "U": ["vim.redo", false], + + // vcs + "gx": ["close-diff"], + "gc": ["update-diff"], + }, + + "vim-new.normal": { + // special + "a": ["vim.insert-mode", "right"], + "i": ["vim.insert-mode"], + "A": ["vim.insert-mode", "line-end"], + "I": ["vim.insert-mode", "line-no-indent"], + "gI": ["vim.insert-mode", "line-start"], + "o": ["vim-insert-line-below"], + "O": ["vim-insert-line-above"], + "v": ["set-mode", "vim-new.visual"], + "V": ["vim-visual-line-mode"], + + "dd": ["vim-delete-line"], + "cc": ["vim-change-line"], + "yy": ["vim-yank-line"], + "D": ["vim-delete-to-line-end"], + "C": ["vim-change-to-line-end"], + "Y": ["vim-yank-to-line-end"], + + // cursor movement + "gxf": ["vim-increment"], + "gxh": ["vim-decrement"], + "gxc": ["vim-increment-by-index"], + + // editing + "~": ["vim-toggle-case", true], + "\\>\\>": ["vim-indent"], + "\\<\\<": ["vim-unindent"], + "s": ["all", [".set-mode", "vim-new.visual"], [".vim-change-selection", true]], + "J": ["vim-join-lines", true], + "gJ": ["vim-join-lines", false], + + // lsp + "gd": ["goto-definition"], + "gD": ["goto-declaration"], + "gi": ["goto-implementation"], + "gT": ["goto-type-definition"], + "gs": ["goto-symbol"], + "gw": ["goto-workspace-symbol"], + "gr": ["goto-references"], + "go": ["switch-source-header"], + "ga": ["select-code-action"], + "gR": ["rename"], + "K": ["show-hover-for-current"], + "H": ["show-diagnostics-for-current"], + + // scroll + "z": ["vim-scroll-line-to-top-and-move-line-start"], + "zt": ["vim-scroll-line-to-top"], + "z.": ["vim-center-line-and-move-line-start"], + "zz": ["vim-center-line"], + "z-": ["vim-scroll-line-to-bottom-and-move-line-start"], + "zb": ["vim-scroll-line-to-bottom"], + + // vcs + "gy": ["stage-selected", false], + "gr": ["revert-selected", false], + }, + + "vim-new.insert": { + "cf": ["set-mode", "vim-new.normal"], + "": ["insert-text", " "], + "": ["vim-enter"], + "": ["vim-delete-word-back"], + "": ["vim-delete-line-back"], + "": ["get-completions"], + "": ["get-completions"], + "": ["insert-indent"], + "": ["vim-indent"], + "": ["vim-unindent"], + "": ["set-mode", "vim-new.insert-register"], + }, + + "vim-new.visual": { + // special + "A": ["vim.insert-mode", "right"], + "I": ["vim.insert-mode", "first"], + "": ["vim.select "], + "": ["vim-select-move <#count>"], + + "y": ["vim-yank-selection"], + "gy": ["vim-yank-selection-clipboard"], + "d": ["vim-delete-selection", true], + "x": ["vim-delete-selection", true], + "X": ["vim-delete-selection", true], + "": ["vim-delete-selection", true], + "": ["vim-delete-selection", true], + "c": ["vim-change-selection", true], + "s": ["vim-change-selection", true], + "*": ["vim-set-search-query-from-selection"], + + "L": ["vim-set-search-query-or-add-cursor"], + + // copy/paste + "p": ["vim-paste", false, true, "vim-default-register"], + "P": ["vim-paste", false, true], + "y": ["vim-yank-selection"], + "gy": ["vim-yank-selection-clipboard"], + "d": ["vim-delete-selection", true], + "c": ["vim-change-selection", true], + "s": ["vim-change-selection", true], + + // cursor movement + "o": ["vim-invert-selections"], + "gp": ["select-parent-current-ts", false], + "gl": ["vim-add-next-same-node-to-selection"], + "gh": ["vim-move-selection-to-next-same-node"], + "gs": ["vim-add-next-sibling-to-selection"], + "gd": ["vim-move-selection-to-next-sibling"], + "gi": ["vim-shrink-selection"], + "gxx": ["vim-evaluate-selection"], + "gxf": ["vim-increment-selection"], + "gxh": ["vim-decrement-selection"], + "gxc": ["vim-increment-selection-by-index"], + "gxg": "cycle-selected-case", + + // editing + "~": ["vim-toggle-case", false], + "\\>": ["vim-indent"], + "\\<": ["vim-unindent"], + "S": ["vim-surround "], + + // vcs + "gy": ["stage-selected", true], + "gr": ["revert-selected", true], + }, + + "vim-new.visual-line": { + // special + "": ["vim.select "], + "": ["vim-select-move <#count>"], + + // copy/paste + "p": ["vim-paste", false, true, "vim-default-register"], + "P": ["vim-paste", false, true], + "y": ["vim-yank-selection"], + "gy": ["vim-yank-selection-clipboard"], + "d": ["vim-delete-selection", true], + "c": ["vim-change-selection", true], + "s": ["vim-change-selection", true], + + // cursor movement + "o": ["vim-invert-line-selections"], + + // editing + "~": ["vim-toggle-case", false], + "\\>": ["vim-indent"], + "\\<": ["vim-unindent"], + + // vcs + "gy": ["stage-selected", true], + "gr": ["revert-selected", true], + }, + + "vim-new.replace": { + "": ["vim-replace", " "], + "": ["set-mode", "vim-new.normal"], + }, + + "vim-new.insert-register": { + "": ["set-mode", "vim-new.insert"], + "": ["all", [".vim-paste", false, true, "vim-default-register"], [".set-mode", "vim-new.insert"]], + }, + + "vim-new.completion": { + "": ["select-prev-completion"], + "": ["select-next-completion"], + "": ["apply-selected-completion"], + "": ["apply-selected-completion"], + }, + // vscode "vscode.base": { "": ["quit"], diff --git a/nev.nimble b/nev.nimble index b05141882..471f31ea3 100644 --- a/nev.nimble +++ b/nev.nimble @@ -182,6 +182,10 @@ proc buildPlugin(name: string) = task buildWasmModule, "": buildPlugin("test_plugin") # buildPlugin("pong") + # buildPlugin("vim") + +task buildVimPlugin, "": + buildPlugin("vim") task buildWasmModule1, "": withDir "plugins/test_plugin_1": diff --git a/plugin_api/v0/api.nim b/plugin_api/v0/api.nim index ee69ba1f0..53f3a0c46 100644 --- a/plugin_api/v0/api.nim +++ b/plugin_api/v0/api.nim @@ -1,4 +1,4 @@ -import std/[strformat, json, jsonutils, strutils, sequtils] +import std/[strformat, json, jsonutils, strutils, sequtils, sugar] import wit_guest, wit_types, wit_runtime, generational_seq, event, util export wit_types, wit_runtime import async @@ -87,6 +87,81 @@ when pluginWorld == "plugin": except: return def + func `$`*(cursor: Cursor): string = + return $cursor.line & ":" & $cursor.column + + func `$`*(selection: Selection): string = + return $selection.first & "-" & $selection.last + + func `<`*(a: Cursor, b: Cursor): bool = + ## Returns true if the cursor `a` comes before `b` + if a.line < b.line: + return true + elif a.line == b.line and a.column < b.column: + return true + else: + return false + + func `<=`*(a: Cursor, b: Cursor): bool = + return a == b or a < b + + func `>`*(a: Cursor, b: Cursor): bool = + if a.line > b.line: + return true + elif a.line == b.line and a.column > b.column: + return true + else: + return false + + func `>=`*(a: Cursor, b: Cursor): bool = + return a == b or a >= b + + func min*(a: Cursor, b: Cursor): Cursor = + if a < b: + return a + return b + + func max*(a: Cursor, b: Cursor): Cursor = + if a >= b: + return a + return b + + func isBackwards*(selection: Selection): bool = + ## Returns true if the first cursor of the selection is after the second cursor + return selection.first > selection.last + + func normalized*(selection: Selection): Selection = + ## Returns the normalized selection, i.e. where first < last. + ## Switches first and last if backwards. + if selection.isBackwards: + return Selection(first: selection.last, last: selection.first) + else: + return selection + + func `in`*(a: Cursor, b: Selection): bool = + ## Returns true if the cursor is contained within the selection + let b = b.normalized + return a >= b.first and a <= b.last + + func reverse*(selection: Selection): Selection = Selection(first: selection.last, last: selection.first) + + func isEmpty*(selection: Selection): bool = selection.first == selection.last + func allEmpty*(selections: openArray[Selection]): bool = selections.allIt(it.isEmpty) + + func contains*(selection: Selection, cursor: Cursor): bool = (cursor >= selection.first and cursor <= selection.last) + func contains*(selection: Selection, other: Selection): bool = (other.first >= selection.first and other.last <= selection.last) + + func contains*(self: openArray[Selection], cursor: Cursor): bool = self.`any` (s) => s.contains(cursor) + func contains*(self: openArray[Selection], other: Selection): bool = self.`any` (s) => s.contains(other) + + func `or`*(a: Selection, b: Selection): Selection = + let an = a.normalized + let bn = b.normalized + return Selection(first: min(an.first, bn.first), last: max(an.last, bn.last)) + + converter toCursor*(c: tuple[line, column: int]): Cursor = Cursor(line: c.line.int32, column: c.column.int32) + converter toSelection*(c: tuple[line, column: int]): Selection = Selection(first: c.toCursor, last: c.toCursor) + converter toSelection*(c: tuple[first, last: Cursor]): Selection = Selection(first: c.first, last: c.last) proc toSelection*(c: Cursor): Selection = Selection(first: c, last: c) proc defineCommand*(name: WitString; active: bool; docs: WitString; params: WitList[(WitString, WitString)]; returntype: WitString; context: WitString; data: uint32; handler: CommandHandler) = @@ -95,6 +170,21 @@ when pluginWorld == "plugin": proc addModeChangedHandler*(fun: proc(old: WitString, new: WitString) {.cdecl.}) = discard addModeChangedHandler(cast[uint32](fun)) + proc selections*(editor: TextEditor): WitList[Selection] = + return editor.getSelections() + + proc setSelections*(editor: TextEditor, s: openArray[Selection]) = + editor.setSelections(@@s) + + proc lineCount*(editor: TextEditor): int = + editor.content.lines.int + + proc setMode*(editor: TextEditor, mode: string) = + editor.setMode(ws(mode), exclusive = true) + + proc applyMove*(editor: TextEditor, cursor: Cursor, move: string, count: int = 1, wrap: bool = true, includeEol: bool = true): Selection = + return editor.applyMove(cursor.toSelection, move.ws, count, wrap, includeEol)[0] + proc asEditor*(editor: TextEditor): Editor = Editor(id: editor.id) proc asDocument*(document: TextDocument): Document = Document(id: document.id) diff --git a/plugin_api/v0/plugin_api_guest.nim b/plugin_api/v0/plugin_api_guest.nim index 36a68034e..451375721 100644 --- a/plugin_api/v0/plugin_api_guest.nim +++ b/plugin_api/v0/plugin_api_guest.nim @@ -27,6 +27,12 @@ import export types +import + registers + +export + registers + import text_document diff --git a/plugin_api/v0/registers.nim b/plugin_api/v0/registers.nim new file mode 100644 index 000000000..8bc2c40fb --- /dev/null +++ b/plugin_api/v0/registers.nim @@ -0,0 +1,52 @@ + +{.push, hint[DuplicateModuleImport]: off.} +import + std / [options] + +from std / unicode import Rune + +import + results, wit_types, wit_runtime, wit_guest + +{.pop.} +proc registersIsReplayingCommandsImported(): bool {. + wasmimport("is-replaying-commands", "nev:plugins/registers").} +proc isReplayingCommands*(): bool {.nodestroy.} = + ## todo + let res = registersIsReplayingCommandsImported() + result = res.bool + +proc registersSetRegisterTextImported(a0: int32; a1: int32; a2: int32; a3: int32): void {. + wasmimport("set-register-text", "nev:plugins/registers").} +proc setRegisterText*(text: WitString; register: WitString): void {.nodestroy.} = + ## todo + var + arg0: int32 + arg1: int32 + arg2: int32 + arg3: int32 + if text.len > 0: + arg0 = cast[int32](text[0].addr) + else: + arg0 = 0.int32 + arg1 = cast[int32](text.len) + if register.len > 0: + arg2 = cast[int32](register[0].addr) + else: + arg2 = 0.int32 + arg3 = cast[int32](register.len) + registersSetRegisterTextImported(arg0, arg1, arg2, arg3) + +proc registersStartRecordingCommandsImported(a0: int32; a1: int32): void {. + wasmimport("start-recording-commands", "nev:plugins/registers").} +proc startRecordingCommands*(register: WitString): void {.nodestroy.} = + ## todo + var + arg0: int32 + arg1: int32 + if register.len > 0: + arg0 = cast[int32](register[0].addr) + else: + arg0 = 0.int32 + arg1 = cast[int32](register.len) + registersStartRecordingCommandsImported(arg0, arg1) diff --git a/plugin_api/v0/text_editor.nim b/plugin_api/v0/text_editor.nim index f32a99591..cb4f82900 100644 --- a/plugin_api/v0/text_editor.nim +++ b/plugin_api/v0/text_editor.nim @@ -104,6 +104,215 @@ proc command*(editor: TextEditor; name: WitString; arguments: WitString): Result tempErr = cast[CommandError](cast[ptr int32](retArea[4].addr)[]) result = results.Result[WitString, CommandError].err(tempErr) +proc textEditorRecordCurrentCommandImported(a0: uint64; a1: int32; a2: int32): void {. + wasmimport("record-current-command", "nev:plugins/text-editor").} +proc recordCurrentCommand*(editor: TextEditor; registers: WitList[WitString]): void {. + nodestroy.} = + ## todo + var + arg0: uint64 + arg1: int32 + arg2: int32 + arg0 = editor.id + if registers.len > 0: + arg1 = cast[int32](registers[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](registers.len) + textEditorRecordCurrentCommandImported(arg0, arg1, arg2) + +proc textEditorGetUsageImported(a0: uint64; a1: int32): void {. + wasmimport("get-usage", "nev:plugins/text-editor").} +proc getUsage*(editor: TextEditor): WitString {.nodestroy.} = + ## todo + var + retArea: array[8, uint8] + arg0: uint64 + arg0 = editor.id + textEditorGetUsageImported(arg0, cast[int32](retArea[0].addr)) + result = ws(cast[ptr char](cast[ptr int32](retArea[0].addr)[]), + cast[ptr int32](retArea[4].addr)[]) + +proc textEditorGetRevisionImported(a0: uint64): int32 {. + wasmimport("get-revision", "nev:plugins/text-editor").} +proc getRevision*(editor: TextEditor): int32 {.nodestroy.} = + ## todo + var arg0: uint64 + arg0 = editor.id + let res = textEditorGetRevisionImported(arg0) + result = convert(res, int32) + +proc textEditorSetModeImported(a0: uint64; a1: int32; a2: int32; a3: bool): void {. + wasmimport("set-mode", "nev:plugins/text-editor").} +proc setMode*(editor: TextEditor; mode: WitString; exclusive: bool): void {. + nodestroy.} = + ## todo + var + arg0: uint64 + arg1: int32 + arg2: int32 + arg3: bool + arg0 = editor.id + if mode.len > 0: + arg1 = cast[int32](mode[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](mode.len) + arg3 = exclusive + textEditorSetModeImported(arg0, arg1, arg2, arg3) + +proc textEditorModeImported(a0: uint64; a1: int32): void {. + wasmimport("mode", "nev:plugins/text-editor").} +proc mode*(editor: TextEditor): WitString {.nodestroy.} = + ## todo + var + retArea: array[8, uint8] + arg0: uint64 + arg0 = editor.id + textEditorModeImported(arg0, cast[int32](retArea[0].addr)) + result = ws(cast[ptr char](cast[ptr int32](retArea[0].addr)[]), + cast[ptr int32](retArea[4].addr)[]) + +proc textEditorClearTabStopsImported(a0: uint64): void {. + wasmimport("clear-tab-stops", "nev:plugins/text-editor").} +proc clearTabStops*(editor: TextEditor): void {.nodestroy.} = + var arg0: uint64 + arg0 = editor.id + textEditorClearTabStopsImported(arg0) + +proc textEditorSelectNextTabStopImported(a0: uint64): void {. + wasmimport("select-next-tab-stop", "nev:plugins/text-editor").} +proc selectNextTabStop*(editor: TextEditor): void {.nodestroy.} = + var arg0: uint64 + arg0 = editor.id + textEditorSelectNextTabStopImported(arg0) + +proc textEditorSelectPrevTabStopImported(a0: uint64): void {. + wasmimport("select-prev-tab-stop", "nev:plugins/text-editor").} +proc selectPrevTabStop*(editor: TextEditor): void {.nodestroy.} = + var arg0: uint64 + arg0 = editor.id + textEditorSelectPrevTabStopImported(arg0) + +proc textEditorUndoImported(a0: uint64; a1: int32; a2: int32): void {. + wasmimport("undo", "nev:plugins/text-editor").} +proc undo*(editor: TextEditor; checkpoint: WitString): void {.nodestroy.} = + var + arg0: uint64 + arg1: int32 + arg2: int32 + arg0 = editor.id + if checkpoint.len > 0: + arg1 = cast[int32](checkpoint[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](checkpoint.len) + textEditorUndoImported(arg0, arg1, arg2) + +proc textEditorRedoImported(a0: uint64; a1: int32; a2: int32): void {. + wasmimport("redo", "nev:plugins/text-editor").} +proc redo*(editor: TextEditor; checkpoint: WitString): void {.nodestroy.} = + var + arg0: uint64 + arg1: int32 + arg2: int32 + arg0 = editor.id + if checkpoint.len > 0: + arg1 = cast[int32](checkpoint[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](checkpoint.len) + textEditorRedoImported(arg0, arg1, arg2) + +proc textEditorAddNextCheckpointImported(a0: uint64; a1: int32; a2: int32): void {. + wasmimport("add-next-checkpoint", "nev:plugins/text-editor").} +proc addNextCheckpoint*(editor: TextEditor; checkpoint: WitString): void {. + nodestroy.} = + var + arg0: uint64 + arg1: int32 + arg2: int32 + arg0 = editor.id + if checkpoint.len > 0: + arg1 = cast[int32](checkpoint[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](checkpoint.len) + textEditorAddNextCheckpointImported(arg0, arg1, arg2) + +proc textEditorCopyImported(a0: uint64; a1: int32; a2: int32; a3: bool): void {. + wasmimport("copy", "nev:plugins/text-editor").} +proc copy*(editor: TextEditor; register: WitString; inclusiveEnd: bool): void {. + nodestroy.} = + var + arg0: uint64 + arg1: int32 + arg2: int32 + arg3: bool + arg0 = editor.id + if register.len > 0: + arg1 = cast[int32](register[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](register.len) + arg3 = inclusiveEnd + textEditorCopyImported(arg0, arg1, arg2, arg3) + +proc textEditorPasteImported(a0: uint64; a1: int32; a2: int32; a3: bool): void {. + wasmimport("paste", "nev:plugins/text-editor").} +proc paste*(editor: TextEditor; register: WitString; inclusiveEnd: bool): void {. + nodestroy.} = + var + arg0: uint64 + arg1: int32 + arg2: int32 + arg3: bool + arg0 = editor.id + if register.len > 0: + arg1 = cast[int32](register[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](register.len) + arg3 = inclusiveEnd + textEditorPasteImported(arg0, arg1, arg2, arg3) + +proc textEditorApplyMoveImported(a0: uint64; a1: int32; a2: int32; a3: int32; + a4: int32; a5: int32; a6: int32; a7: int32; + a8: bool; a9: bool; a10: int32): void {. + wasmimport("apply-move", "nev:plugins/text-editor").} +proc applyMove*(editor: TextEditor; selection: Selection; move: WitString; + count: int32; wrap: bool; includeEol: bool): WitList[Selection] {. + nodestroy.} = + var + retArea: array[48, uint8] + arg0: uint64 + arg1: int32 + arg2: int32 + arg3: int32 + arg4: int32 + arg5: int32 + arg6: int32 + arg7: int32 + arg8: bool + arg9: bool + arg0 = editor.id + arg1 = selection.first.line + arg2 = selection.first.column + arg3 = selection.last.line + arg4 = selection.last.column + if move.len > 0: + arg5 = cast[int32](move[0].addr) + else: + arg5 = 0.int32 + arg6 = cast[int32](move.len) + arg7 = count + arg8 = wrap + arg9 = includeEol + textEditorApplyMoveImported(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, cast[int32](retArea[0].addr)) + result = wl(cast[ptr typeof(result[0])](cast[ptr int32](retArea[0].addr)[]), + cast[ptr int32](retArea[4].addr)[]) + proc textEditorSetSelectionImported(a0: uint64; a1: int32; a2: int32; a3: int32; a4: int32): void {. wasmimport("set-selection", "nev:plugins/text-editor").} @@ -122,6 +331,22 @@ proc setSelection*(editor: TextEditor; s: Selection): void {.nodestroy.} = arg4 = s.last.column textEditorSetSelectionImported(arg0, arg1, arg2, arg3, arg4) +proc textEditorSetSelectionsImported(a0: uint64; a1: int32; a2: int32): void {. + wasmimport("set-selections", "nev:plugins/text-editor").} +proc setSelections*(editor: TextEditor; s: WitList[Selection]): void {.nodestroy.} = + ## Sets the selections for the given editor. + var + arg0: uint64 + arg1: int32 + arg2: int32 + arg0 = editor.id + if s.len > 0: + arg1 = cast[int32](s[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](s.len) + textEditorSetSelectionsImported(arg0, arg1, arg2) + proc textEditorGetSelectionImported(a0: uint64; a1: int32): void {. wasmimport("get-selection", "nev:plugins/text-editor").} proc getSelection*(editor: TextEditor): Selection {.nodestroy.} = @@ -136,6 +361,30 @@ proc getSelection*(editor: TextEditor): Selection {.nodestroy.} = result.last.line = convert(cast[ptr int32](retArea[8].addr)[], int32) result.last.column = convert(cast[ptr int32](retArea[12].addr)[], int32) +proc textEditorGetSelectionsImported(a0: uint64; a1: int32): void {. + wasmimport("get-selections", "nev:plugins/text-editor").} +proc getSelections*(editor: TextEditor): WitList[Selection] {.nodestroy.} = + ## Returns the selections of the given editor. + var + retArea: array[8, uint8] + arg0: uint64 + arg0 = editor.id + textEditorGetSelectionsImported(arg0, cast[int32](retArea[0].addr)) + result = wl(cast[ptr typeof(result[0])](cast[ptr int32](retArea[0].addr)[]), + cast[ptr int32](retArea[4].addr)[]) + +proc textEditorLineLengthImported(a0: uint64; a1: int32): int32 {. + wasmimport("line-length", "nev:plugins/text-editor").} +proc lineLength*(editor: TextEditor; line: int32): int32 {.nodestroy.} = + ## Return the length of the given line (0 based) + var + arg0: uint64 + arg1: int32 + arg0 = editor.id + arg1 = line + let res = textEditorLineLengthImported(arg0, arg1) + result = convert(res, int32) + proc textEditorAddModeChangedHandlerImported(a0: uint32): int32 {. wasmimport("add-mode-changed-handler", "nev:plugins/text-editor").} proc addModeChangedHandler*(fun: uint32): int32 {.nodestroy.} = diff --git a/plugins/vim/config.nims b/plugins/vim/config.nims new file mode 100644 index 000000000..4bf8df5dd --- /dev/null +++ b/plugins/vim/config.nims @@ -0,0 +1,13 @@ +include "../wasm.config.nims" +switch("d", "pluginApiVersion=0") +switch("nimcache", "../../nimcache/vim") + +# Put custom build configs which shouldn't be commited in local.nims +when withDir(thisDir(), fileExists("local.nims")): + include "local.nims" + +# begin Nimble config (version 2) +--noNimblePath +when withDir(thisDir(), system.fileExists("nimble.paths")): + include "nimble.paths" +# end Nimble config diff --git a/plugins/vim/manifest.json b/plugins/vim/manifest.json new file mode 100644 index 000000000..e7c6d2348 --- /dev/null +++ b/plugins/vim/manifest.json @@ -0,0 +1,13 @@ +{ + "name": "Vim Motions", + "version": "0.0.1", + "authors": ["Nimaoth"], + "repository": "https://github.com/Nimaoth/Nev/plugins/vim", + "wasm": "vim.m.wasm", + // "wasm": "vim.wat", + "autoLoad": true, + "commands": { + "test-command-1": {}, + }, + "permissions": {}, +} diff --git a/plugins/vim/nimble.lock b/plugins/vim/nimble.lock new file mode 100644 index 000000000..cd8864e89 --- /dev/null +++ b/plugins/vim/nimble.lock @@ -0,0 +1,116 @@ +{ + "version": 2, + "packages": { + "vmath": { + "version": "2.0.0", + "vcsRevision": "661bdaac7546e16d087f066adfeb6ea7590106b4", + "url": "https://github.com/Nimaoth/vmath", + "downloadMethod": "git", + "dependencies": [], + "checksums": { + "sha1": "db421090319e1a0ef028535eedfdadae6f3d7294" + } + }, + "bumpy": { + "version": "1.1.2", + "vcsRevision": "d7eaf00c24820ad0317c9926737402e62431e931", + "url": "https://github.com/treeform/bumpy", + "downloadMethod": "git", + "dependencies": [ + "vmath" + ], + "checksums": { + "sha1": "acc789c4e31b3ee969a13dd0c238d808f39b19c7" + } + }, + "c2nim": { + "version": "0.9.19", + "vcsRevision": "5753fa7658ed50432ea6c2df84f0a45e36bcb2ea", + "url": "https://github.com/nim-lang/c2nim", + "downloadMethod": "git", + "dependencies": [], + "checksums": { + "sha1": "1954eb89cc353cf2a4e54804d38d5a1e72f3c98e" + } + }, + "chroma": { + "version": "0.2.7", + "vcsRevision": "b2e71179174e040884ebf6a16cbac711c84620b9", + "url": "https://github.com/treeform/chroma", + "downloadMethod": "git", + "dependencies": [], + "checksums": { + "sha1": "f873f4b97e87a82e27e0b85bf9130fe3e320a2b8" + } + }, + "fusion": { + "version": "1.2", + "vcsRevision": "0b0a0273b8df860cd54e3fcca7bb4c617244ac77", + "url": "https://github.com/nim-lang/fusion", + "downloadMethod": "git", + "dependencies": [], + "checksums": { + "sha1": "408bde8679bce63dfb6f424e107eb37c5eb31510" + } + }, + "unicodedb": { + "version": "0.12.0", + "vcsRevision": "b055310c08db8f879057b4fec15c8301ee93bb2a", + "url": "https://github.com/nitely/nim-unicodedb", + "downloadMethod": "git", + "dependencies": [], + "checksums": { + "sha1": "4452416471e2fe8726eb6070ed6ea7368171cc09" + } + }, + "regex": { + "version": "0.25.0", + "vcsRevision": "cb8b7bfdcdc2272aadf92153c668acd3c901bd6b", + "url": "https://github.com/nitely/nim-regex", + "downloadMethod": "git", + "dependencies": [ + "unicodedb" + ], + "checksums": { + "sha1": "cb2e24397c051e55ad5b72b3c0d4f270a90084df" + } + }, + "nimgen": { + "version": "0.5.4", + "vcsRevision": "e6697aabbe36e42e4dd261cfd0b8a8be16f6f342", + "url": "https://github.com/Nimaoth/nimgen", + "downloadMethod": "git", + "dependencies": [ + "c2nim", + "regex", + "unicodedb" + ], + "checksums": { + "sha1": "d563cc29937e4d2d66ebeb3eaacef95c6284fb4c" + } + }, + "nimwasmtime": { + "version": "0.2.1", + "vcsRevision": "22cfeef719757063ba951079374bee67a917c0bb", + "url": "https://github.com/Nimaoth/nimwasmtime", + "downloadMethod": "git", + "dependencies": [ + "nimgen" + ], + "checksums": { + "sha1": "b583e2c45336f1d6ec6a720f3fd5638bc0337b5f" + } + }, + "results": { + "version": "0.5.0", + "vcsRevision": "57b2392ad69849e3e2d02b74a8a0feee2c3d9570", + "url": "https://github.com/arnetheduck/nim-results", + "downloadMethod": "git", + "dependencies": [], + "checksums": { + "sha1": "901e2ed088160fdcb8fdf3b315ce62baedc3f7ba" + } + } + }, + "tasks": {} +} diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim new file mode 100644 index 000000000..ba78ffe6b --- /dev/null +++ b/plugins/vim/vim.nim @@ -0,0 +1,1521 @@ +# import std/[strutils, macros, genasts, sequtils, sets, algorithm, jsonutils] +import std/[strformat, json, jsonutils, strutils, tables, macros, genasts, streams, sequtils, sets, os, terminal, colors] +import results +import util, custom_unicode, myjsonutils, id, regex, wrap, sugar +# import input_api +import api +from "../../src/scripting_api.nim" as sca import nil +import "../../src/input_api.nim" + +type LogLevel = enum lvlInfo, lvlNotice, lvlDebug, lvlWarn, lvlError + +proc log(level: LogLevel, str: string) = + let color = case level + of lvlDebug: rgb(100, 100, 200) + of lvlInfo: rgb(200, 200, 200) + of lvlNotice: rgb(200, 255, 255) + of lvlWarn: rgb(200, 200, 100) + of lvlError: rgb(255, 150, 150) + # of lvlFatal: rgb(255, 0, 0) + else: rgb(255, 255, 255) + try: + {.gcsafe.}: + stdout.write(ansiForegroundColorCode(color)) + stdout.write("[vim] ") + stdout.write(str) + stdout.write("\r\n") + except IOError: + discard + +template debugf*(x: static string) = + log lvlDebug, fmt(x) + +var yankedLines: bool = false ## Whether the last thing we yanked was in a line mode + +type EditorVimState = object + ## Contains state which can vary per editor + selectLines: bool = false ## Whether entire lines should be selected (e.g. in visual-line mode/when using dd) + deleteInclusiveEnd: bool = true ## Whether the next time we delete some the selection end should be inclusive + cursorIncludeEol: bool = false ## Whether the cursor can be after the last character in a line (e.g. in insert mode) + currentUndoCheckpoint: string = "insert" ## Which checkpoint to undo to (depends on mode) + revisionBeforeImplicitInsertMacro: int + marks: Table[string, seq[(sca.Anchor, sca.Anchor)]] + unresolveMarks: Table[string, seq[sca.Selection]] + +var editorStates: Table[sca.EditorId, EditorVimState] + +const editorContext = "editor.text" + + +type IdentifierCase = enum Camel, Pascal, Kebab, Snake, ScreamingSnake + +proc splitCase(s: string): tuple[cas: IdentifierCase, parts: seq[string]] = + if s == "": + return (IdentifierCase.Camel, @[]) + + if s.find('_') != -1: + result.cas = IdentifierCase.Snake + result.parts = s.split('_').mapIt(custom_unicode.toLower(it)) + for r in s.runes: + if r != '_'.Rune and not r.isLower: + result.cas = IdentifierCase.ScreamingSnake + break + + elif s.find('-') != -1: + result.cas = IdentifierCase.Kebab + result.parts = s.split('-').mapIt(custom_unicode.toLower(it)) + else: + if s[0].isUpperAscii: + result.cas = IdentifierCase.Pascal + else: + result.cas = IdentifierCase.Camel + + result.parts.add "" + for r in s.runes: + if not r.isLower and result.parts.last.len > 0: + result.parts.add "" + result.parts.last.add(custom_unicode.toLower(r)) + +proc joinCase(parts: seq[string], cas: IdentifierCase): string = + assert parts.len > 0 + case cas + of IdentifierCase.Camel: + parts[0] & parts[1..^1].mapIt(it.capitalize).join("") + of IdentifierCase.Pascal: + parts.mapIt(it.capitalize).join("") + of IdentifierCase.Kebab: + parts.join("-") + of IdentifierCase.Snake: + parts.join("_") + of IdentifierCase.ScreamingSnake: + parts.mapIt(custom_unicode.toUpper(it)).join("_") + +proc cycleCase(s: string): string = + if s.len == 0: + return s + let (cas, parts) = s.splitCase() + let nextCase = if cas == IdentifierCase.high: + IdentifierCase.low + else: + cas.succ + return parts.joinCase(nextCase) + +proc exposeImpl*(context: NimNode, name: string, fun: NimNode, active: bool): NimNode = + # defer: + # echo result.repr + + let def = if fun.kind == nnkProcDef: fun else: fun.getImpl + if def.kind != nnkProcDef: + error("expose can only be used on proc definitions", fun) + + let signature = def.copy + signature[6] = newEmptyNode() + + let signatureUntyped = parseExpr(signature.repr) + let jsonWrapperName = (def.name.repr & "Json").ident + let jsonWrapper = createJsonWrapper(signatureUntyped, jsonWrapperName) + + let documentation = def.getDocumentation() + let documentationStr = documentation.map((it) => it.strVal).get("").newLit + + let returnType = if def[3][0].kind == nnkEmpty: "" else: def[3][0].repr + var params: seq[(string, string)] = @[] + for param in def[3][1..^1]: + params.add (param[0].repr, param[1].repr) + + if def == fun: + return genAst(name, def, jsonWrapper, jsonWrapperName, documentationStr, inParams = params, inReturnType = returnType, inActive = active, inContext = context): + def + jsonWrapper + defineCommand(ws(name), + active = inActive, + docs = ws(documentationStr), + params = wl[(WitString, WitString)](nil, 0), + returnType = ws(inReturnType), + context = ws(inContext), + data = 0): + proc(data: uint32, argsString: WitString): WitString {.cdecl.} = + var args = newJArray() + try: + for a in newStringStream($argsString).parseJsonFragments(): + args.add a + let res = jsonWrapperName(args) + return stackWitString($res) + except CatchableError as e: + log lvlError, "Failed to run command '" & name & "': " & e.msg + + return ws"" + + else: + return genAst(name, jsonWrapper, jsonWrapperName, documentationStr, inParams = params, inReturnType = returnType, inAactive = active, inContext = context): + jsonWrapper + defineCommand(stackWitString(name), + active = inActive, + docs = ws(documentationStr), + params = wl[(WitString, WitString)](nil, 0), + returnType = ws(inReturnType), + context = ws(inContext), + data = 0): + proc(data: uint32, args: WitString): WitString {.cdecl.} = + var args = newJArray() + try: + for a in newStringStream($argsString).parseJsonFragments(): + args.add a + let res = jsonWrapperName(args) + return stackWitString($res) + except CatchableError as e: + log lvlError, "Failed to run command '" & name & "': " & e.msg + + return ws"" + +macro expose*(name: string, fun: typed): untyped = + return exposeImpl(newLit"script", name.repr, fun, active=false) + +macro expose*(context, string: string, fun: typed): untyped = + let name = fun.name.repr.splitCase.parts.joinCase(Kebab) + return exposeImpl(context, name, fun, active=false) + +macro exposeActive*(context: string, fun: typed): untyped = + let name = fun.name.repr.splitCase.parts.joinCase(Kebab) + return exposeImpl(context, name, fun, active=true) + +macro callJson*(fun: typed, args: JsonNode): JsonNode = + ## Calls a function with a json object as argument, converting the json object to nim types + let jsonWrapperName = genSym(nskProc, "jsonWrapper") + let jsonWrapper = createJsonWrapper(fun, fun.getType, jsonWrapperName) + jsonWrapper.addPragma("closure".ident) + return genAst(jsonWrapper, jsonWrapperName, args): + block: + jsonWrapper + jsonWrapperName(args) + +proc vimState(editor: TextEditor): var EditorVimState = + if not editorStates.contains(editor.id): + editorStates[editor.id] = EditorVimState() + return editorStates[editor.id] + +proc shouldRecortImplicitPeriodMacro(editor: TextEditor): bool = + case $editor.getUsage() + of "command-line", "search-bar": + return false + else: + return true + +proc recordCurrentCommandInPeriodMacro(editor: TextEditor) = + if not isReplayingCommands() and editor.shouldRecortImplicitPeriodMacro(): + setRegisterText(ws"", ws".") + editor.recordCurrentCommand(@@[ws"."]) + +proc startRecordingCurrentCommandInPeriodMacro(editor: TextEditor) = + if not isReplayingCommands() and editor.shouldRecortImplicitPeriodMacro(): + startRecordingCommands(ws".-temp") + setRegisterText(ws"", ws".-temp") + editor.recordCurrentCommand(@@[ws".-temp"]) + editor.vimState.revisionBeforeImplicitInsertMacro = editor.getRevision + +type VimTextObjectRange* = enum Inner, Outer, CurrentToEnd + +proc getVimLineMargin*(): float = getSetting("editor.text.vim.line-margin", 5.float) +proc getVimClipboard*(): string = getSetting("editor.text.vim.clipboard", "") +proc getVimDefaultRegister*(): string = + case getVimClipboard(): + of "unnamed": return "*" + of "unnamedplus": return "+" + else: return "\"" + +proc getCurrentMacroRegister*(): string = getSetting("editor.current-macro-register", "") + +proc getEnclosing(line: string, column: int, predicate: proc(c: char): bool): (int, int) = + var startColumn = column + var endColumn = column + while endColumn < line.high and predicate(line[endColumn + 1]): + inc endColumn + while startColumn > 0 and predicate(line[startColumn - 1]): + dec startColumn + return (startColumn, endColumn) + +proc handleSelectWord(editor: TextEditor, cursor: Cursor) {.exposeActive(editorContext).} = + editor.setSelection(cursor.toSelection) + +proc selectLine(editor: TextEditor) {.exposeActive(editorContext).} = + debugf"selectLine" + editor.setSelections stackWitList(editor.selections.mapIt (if it.isBackwards: + Selection(first: Cursor(line: it.first.line, column: editor.lineLength(it.first.line)), last: Cursor(line: it.last.line, column: 0)) + else: + Selection(first: Cursor(line: it.first.line, column: 0), last: Cursor(line: it.last.line, column: editor.lineLength(it.last.line))) + )) + +proc selectLast(editor: TextEditor, move: string, count: int = 1) {.exposeActive(editorContext).} = + # stdout.write(ansiForegroundColorCode(color)) + debugf"selectLast '{move}', count" + let (action, arg) = move.parseAction + for i in 0.. 0: + (it.first, editor.applyMove(it.last, "column", -1).last).toSelection + elif it.first.line + 1 < editor.lineCount: + (editor.applyMove(it.first, "column", 1).last, it.last).toSelection + else: + it + else: + if it.first.line > 0: + (editor.applyMove(it.first, "column", -1).last, it.last).toSelection + elif it.last.line + 1 < editor.lineCount: + (it.first, editor.applyMove(it.last, "column", 1).last).toSelection + else: + it + ) + + return selections.mapIt(it.normalized.first.toSelection) + +# proc vimUpdateSelections(editor: TextEditor, selections: Selections) = +# if editor.vimState.selectLines: +# editor.setSelections selections.mapIt editor.doMoveCursorLine(it.normalized.first, 1).toSelection + +# else: +# editor.setSelections selections +# editor.updateTargetColumn() +# editor.scrollToCursor Last + +# proc vimDeleteSelection(editor: TextEditor, forceInclusiveEnd: bool, oldSelections: Option[Selections] = Selections.none) {.exposeActive(editorContext, "vim-delete-selection").} = +# let newSelections = editor.copySelection(getVimDefaultRegister()) +# let selectionsToDelete = editor.selections +# if oldSelections.isSome: +# editor.setSelections oldSelections.get +# editor.addNextCheckpoint("insert") +# let inclusiveEnd = (not editor.vimState.selectLines) and (editor.vimState.deleteInclusiveEnd or forceInclusiveEnd) +# editor.vimUpdateSelections editor.delete( +# selectionsToDelete, inclusiveEnd=inclusiveEnd) +# editor.vimState.deleteInclusiveEnd = true +# editor.setMode "vim-new.normal" + +# proc vimChangeSelection*(editor: TextEditor, forceInclusiveEnd: bool, oldSelections: Option[Selections] = Selections.none) {.exposeActive(editorContext, "vim-change-selection").} = +# let newSelections = editor.copySelection(getVimDefaultRegister()) +# let selectionsToDelete = editor.selections +# if oldSelections.isSome: +# editor.setSelections oldSelections.get +# editor.addNextCheckpoint("insert") +# # todo: figure out if we should check for selectLines here as well +# editor.vimUpdateSelections editor.delete( +# selectionsToDelete, inclusiveEnd=editor.vimState.deleteInclusiveEnd or forceInclusiveEnd) +# editor.vimState.deleteInclusiveEnd = true +# editor.setMode "vim-new.insert" + +# proc vimYankSelection*(editor: TextEditor) {.exposeActive(editorContext, "vim-yank-selection").} = +# let selections = editor.copySelection(getVimDefaultRegister()) +# editor.setSelections selections +# editor.setMode "vim-new.normal" + +# proc vimYankSelectionClipboard*(editor: TextEditor) {.exposeActive(editorContext, "vim-yank-selection-clipboard").} = +# let selections = editor.copySelection() +# editor.setSelections selections +# editor.setMode "vim-new.normal" + +# proc vimReplace(editor: TextEditor, input: string) {.exposeActive(editorContext, "vim-replace").} = +# let texts = editor.selections.mapIt(block: +# let selection = it +# let text = editor.getText(selection, inclusiveEnd=true) +# var newText = newStringOfCap(text.runeLen.int * input.runeLen.int) +# var lastIndex = 0 +# var index = text.find('\n') +# if index == -1: +# newText.add input.repeat(text.runeLen.int) +# else: +# while index != -1: +# let lineLen = text.toOpenArray(lastIndex, index).runeLen.int - 1 +# newText.add input.repeat(lineLen) +# newText.add "\n" +# lastIndex = index + 1 +# index = text.find('\n', index + 1) + +# let lineLen = text.toOpenArray(lastIndex, text.high).runeLen.int +# newText.add input.repeat(lineLen) + +# newText +# ) + +# # infof"replace {editor.selections} with '{input}' -> {texts}" + +# editor.addNextCheckpoint "insert" +# editor.setSelections editor.edit(editor.selections, texts, inclusiveEnd=true).mapIt(it.first.toSelection) +# editor.setMode "vim-new.normal" + +# proc vimSelectMove(editor: TextEditor, move: string, count: int = 1) {.exposeActive(editorContext, "vim-select-move").} = +# # infof"vimSelectMove '{move}' {count}" +# let (action, arg) = move.parseAction +# for i in 0.. failed to parse key: {target}" + +# let key = parseTarget(target) + +# for _ in 0.. 0: lineLen.dec +# result = (cursor.line, min(cursor.column, lineLen)) + +# proc vimMotionLine*(editor: TextEditor, cursor: Cursor, count: int): Selection = +# var lineLen = editor.lineLength(cursor.line) +# if not editor.vimState.cursorIncludeEol and lineLen > 0: lineLen.dec +# result = ((cursor.line, 0), (cursor.line, lineLen)) + +# proc vimMotionVisualLine*(editor: TextEditor, cursor: Cursor, count: int): Selection = +# var lineLen = editor.lineLength(cursor.line) +# result = editor.getSelectionForMove(cursor, "visual-line", count) +# if not editor.vimState.cursorIncludeEol and result.last.column > result.first.column: +# result.last.column.dec +# elif result.last.column < lineLen: # This is the case if we're not in the last visual sub line +# result.last.column.dec + +# proc vimMotionParagraphInner*(editor: TextEditor, cursor: Cursor, count: int): Selection = +# let isEmpty = editor.lineLength(cursor.line) == 0 + +# result = ((cursor.line, 0), cursor) +# while result.first.line - 1 >= 0 and (editor.lineLength(result.first.line - 1) == 0) == isEmpty: +# dec result.first.line +# while result.last.line + 1 < editor.lineCount and (editor.lineLength(result.last.line + 1) == 0) == isEmpty: +# inc result.last.line + +# result.last.column = editor.lineLength(result.last.line) + +# proc vimMotionParagraphOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = +# result = editor.vimMotionParagraphInner(cursor, count) +# if result.last.line + 1 < editor.lineCount: +# result = result or editor.vimMotionParagraphInner((result.last.line + 1, 0), 1) + +# proc vimMotionWordOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = +# result = vimMotionWord(editor, cursor, count) +# if result.last.column < editor.lineLength(result.last.line) and editor.getChar(result.last) in Whitespace: +# result.last = editor.vimMotionWord(result.last, 1).last + +# proc vimMotionWordBigOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = +# result = vimMotionWordBig(editor, cursor, count) +# if result.last.column < editor.lineLength(result.last.line) and editor.getChar(result.last) in Whitespace: +# result.last = editor.vimMotionWordBig(result.last, 1).last + +# proc charAt*(editor: TextEditor, cursor: Cursor): char = +# let res = editor.getText(cursor.toSelection, inclusiveEnd=true) +# if res.len > 0: +# return res[0] +# else: +# return '\0' + +# proc vimMotionSurround*(editor: TextEditor, cursor: Cursor, count: int, c0: char, c1: char, inside: bool): Selection = +# result = cursor.toSelection +# # infof"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}" +# while true: +# let lastChar = editor.charAt(result.last) +# let (startDepth, endDepth) = if lastChar == c0: +# (1, 0) +# elif lastChar == c1: +# (0, 1) +# else: +# (1, 1) + +# # infof"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find around: {startDepth}, {endDepth}" +# if editor.findSurroundStart(result.first, count, c0, c1, startDepth).getSome(opening) and editor.findSurroundEnd(result.last, count, c0, c1, endDepth).getSome(closing): +# result = (opening, closing) +# # infof"vimMotionSurround: found inside {result}" +# if inside: +# result.first = editor.doMoveCursorColumn(result.first, 1) +# result.last = editor.doMoveCursorColumn(result.last, -1) +# return + +# # infof"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find ahead: {startDepth}, {endDepth}" +# if editor.findSurroundEnd(result.first, count, c0, c1, -1).getSome(opening) and editor.findSurroundEnd(opening, count, c0, c1, 0).getSome(closing): +# result = (opening, closing) +# # infof"vimMotionSurround: found ahead {result}" +# if inside: +# result.first = editor.doMoveCursorColumn(result.first, 1) +# result.last = editor.doMoveCursorColumn(result.last, -1) +# return +# else: +# # infof"vimMotionSurround: found nothing {result}" +# return + +# proc vimMoveToMatching(editor: TextEditor) {.exposeActive(editorContext, "vim-move-to-matching").} = +# # todo: pass as parameter +# let which = if editor.mode == "vim-new.visual" or editor.mode == "vim-new.visual-line": +# SelectionCursor.Last +# else: +# SelectionCursor.Both + +# editor.setSelections editor.selections.mapIt(block: +# let c = editor.charAt(it.last) +# let (open, close, last) = case c +# of '(': ('(', ')', true) +# of '{': ('{', '}', true) +# of '[': ('[', ']', true) +# of '<': ('<', '>', true) +# of ')': ('(', ')', false) +# of '}': ('{', '}', false) +# of ']': ('[', ']', false) +# of '>': ('<', '>', false) +# of '"': ('"', '"', true) +# of '\'': ('\'', '\'', true) +# else: return + +# let selection = editor.vimMotionSurround(it.last, 0, open, close, false) + +# if last: +# selection.last.toSelection(it, which) +# else: +# selection.first.toSelection(it, which) +# ) + +# editor.scrollToCursor Last +# editor.updateTargetColumn() + +# proc vimMotionSurroundBracesInner*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '{', '}', true) +# proc vimMotionSurroundBracesOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '{', '}', false) +# proc vimMotionSurroundParensInner*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '(', ')', true) +# proc vimMotionSurroundParensOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '(', ')', false) +# proc vimMotionSurroundBracketsInner*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '[', ']', true) +# proc vimMotionSurroundBracketsOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '[', ']', false) +# proc vimMotionSurroundAngleInner*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '<', '>', true) +# proc vimMotionSurroundAngleOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '<', '>', false) +# proc vimMotionSurroundDoubleQuotesInner*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '"', '"', true) +# proc vimMotionSurroundDoubleQuotesOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '"', '"', false) +# proc vimMotionSurroundSingleQuotesInner*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '\'', '\'', true) +# proc vimMotionSurroundSingleQuotesOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '\'', '\'', false) + +# # todo +# addCustomTextMove "vim-line", vimMotionLine +# addCustomTextMove "vim-visual-line", vimMotionVisualLine +# addCustomTextMove "vim-word-outer", vimMotionWordOuter +# addCustomTextMove "vim-WORD-outer", vimMotionWordBigOuter +# addCustomTextMove "vim-paragraph-inner", vimMotionParagraphInner +# addCustomTextMove "vim-paragraph-outer", vimMotionParagraphOuter +# addCustomTextMove "vim-surround-{-inner", vimMotionSurroundBracesInner +# addCustomTextMove "vim-surround-{-outer", vimMotionSurroundBracesOuter +# addCustomTextMove "vim-surround-(-inner", vimMotionSurroundParensInner +# addCustomTextMove "vim-surround-(-outer", vimMotionSurroundParensOuter +# addCustomTextMove "vim-surround-[-inner", vimMotionSurroundBracketsInner +# addCustomTextMove "vim-surround-[-outer", vimMotionSurroundBracketsOuter +# addCustomTextMove "vim-surround-angle-inner", vimMotionSurroundAngleInner +# addCustomTextMove "vim-surround-angle-outer", vimMotionSurroundAngleOuter +# addCustomTextMove "vim-surround-\"-inner", vimMotionSurroundDoubleQuotesInner +# addCustomTextMove "vim-surround-\"-outer", vimMotionSurroundDoubleQuotesOuter +# addCustomTextMove "vim-surround-'-inner", vimMotionSurroundSingleQuotesInner +# addCustomTextMove "vim-surround-'-outer", vimMotionSurroundSingleQuotesOuter + +# iterator iterateTextObjects*(editor: TextEditor, cursor: Cursor, move: string, backwards: bool = false): Selection = +# var selection = editor.getSelectionForMove(cursor, move, 0) +# # infof"iterateTextObjects({cursor}, {move}, {backwards}), selection: {selection}" +# yield selection +# while true: +# let lastSelection = selection +# if not backwards and selection.last.column == editor.lineLength(selection.last.line): +# if selection.last.line == editor.lineCount - 1: +# break +# selection = (selection.last.line + 1, 0).toSelection +# elif backwards and selection.first.column == 0: +# if selection.first.line == 0: +# break +# selection = (selection.first.line - 1, editor.lineLength(selection.first.line - 1)).toSelection +# if selection.first.column == 0: +# yield selection +# continue + +# let nextCursor = if backwards: (selection.first.line, selection.first.column - 1) else: (selection.last.line, selection.last.column + 1) +# let newSelection = editor.getSelectionForMove(nextCursor, move, 0) +# # infof"iterateTextObjects({cursor}, {move}, {backwards}) nextCursor: {nextCursor}, newSelection: {newSelection}" +# if newSelection == lastSelection: +# break + +# selection = newSelection +# yield selection + +# iterator enumerateTextObjects*(editor: TextEditor, cursor: Cursor, move: string, backwards: bool = false): (int, Selection) = +# var i = 0 +# for selection in iterateTextObjects(editor, cursor, move, backwards): +# yield (i, selection) +# inc i + +# proc vimSelectTextObject(editor: TextEditor, textObject: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1, textObjectRange: VimTextObjectRange = Inner) {.exposeActive(editorContext, "vim-select-text-object").} = +# # infof"vimSelectTextObject({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count})" + +# editor.setSelections editor.selections.mapIt(block: +# var res = it.last +# var resultSelection = it +# # infof"-> {resultSelection}" + +# for i, selection in enumerateTextObjects(editor, res, textObject, backwards): +# # infof"{i}: {res} -> {selection}" +# resultSelection = resultSelection or selection +# if i == max(count, 1) - 1: +# break + +# # infof"vimSelectTextObject({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count}): {resultSelection}" +# if it.isBackwards: +# resultSelection.reverse +# else: +# resultSelection +# ) + +# editor.scrollToCursor(Last) +# editor.updateTargetColumn() + +# proc vimSelectSurrounding(editor: TextEditor, textObject: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1, textObjectRange: VimTextObjectRange = Inner) {.exposeActive(editorContext, "vim-select-surrounding").} = +# # infof"vimSelectSurrounding({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count})" + +# editor.setSelections editor.selections.mapIt(block: +# let resultSelection = editor.getSelectionForMove(it.last, textObject, count) +# # infof"vimSelectSurrounding({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count}): {resultSelection}" +# if it.isBackwards: +# resultSelection.reverse +# else: +# resultSelection +# ) + +# editor.scrollToCursor(Last) +# editor.updateTargetColumn() + +# proc moveSelectionNext(editor: TextEditor, move: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1) {.exposeActive(editorContext, "move-selection-next").} = +# # infof"moveSelectionNext '{move}' {count} {backwards} {allowEmpty}" +# editor.vimState.deleteInclusiveEnd = false +# let which = getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) +# editor.setSelections editor.selections.mapIt(block: +# var res = it.last +# for k in 0..= editor.lineLength(selection.first.line) or editor.getChar(selection.first) notin Whitespace: +# res = cursor +# break +# # echo res, ", ", it, ", ", which +# res.toSelection(it, which) +# ) + +# editor.scrollToCursor(Last) +# editor.updateTargetColumn() + +# proc applyMove(editor: TextEditor, selections: seq[Selection], move: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1, which: Option[SelectionCursor] = SelectionCursor.none): seq[Selection] = +# ## Applies the given move `count` times and returns the resulting selections +# ## `allowEmpty` If true then the move can stop on empty lines +# ## `backwards` Move backwards +# ## `count` How often to apply the move +# ## `which` How to assemble the final selection from the input and the move. If not set uses `editor.text.cursor.movement` + +# # infof"moveSelectionEnd '{move}' {count} {backwards} {allowEmpty}" +# let which = which.get(getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both)) +# return selections.mapIt(block: +# var res = it.last +# for k in 0.. 1: +# editor.moveCursorLine(count - 1) +# editor.moveLast("vim-line") +# editor.scrollToCursor Last +# editor.updateTargetColumn() + +# proc vimMoveToEndOfVisualLine(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-end-of-visual-line").} = +# # infof"vimMoveToEndOfLine {count}" +# let count = max(1, count) +# if count > 1: +# editor.moveCursorLine(count - 1) +# editor.moveLast("vim-visual-line") +# editor.scrollToCursor Last +# editor.updateTargetColumn() + +# proc vimMoveCursorLineFirstChar(editor: TextEditor, direction: int, count: int = 1) {.exposeActive(editorContext, "vim-move-cursor-line-first-char").} = +# editor.moveCursorLine(direction * max(count, 1)) +# editor.moveFirst "line-no-indent" +# editor.updateTargetColumn() + +# proc vimMoveToStartOfLine(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-start-of-line").} = +# # infof"vimMoveToStartOfLine {count}" +# let count = max(1, count) +# if count > 1: +# editor.moveCursorLine(count - 1) +# editor.moveFirst "line-no-indent" +# editor.scrollToCursor Last +# editor.updateTargetColumn() + +# proc vimPaste(editor: TextEditor, pasteRight: bool = false, inclusiveEnd: bool = false, register: string = "") {.exposeActive(editorContext, "vim-paste").} = +# # infof"vimPaste {register}, lines: {yankedLines}" +# let register = if register == "vim-default-register": +# getVimDefaultRegister() +# else: +# register + +# editor.addNextCheckpoint "insert" + +# let selectionsToDelete = editor.selections +# editor.setSelections editor.delete(selectionsToDelete, inclusiveEnd=false) + +# if yankedLines: +# # todo: pass bool as parameter +# if editor.mode != "vim-new.visual-line": +# editor.moveLast "line", Both +# editor.insertText "\n", autoIndent=false + +# if pasteRight: +# editor.setSelections editor.selections.mapIt(editor.doMoveCursorColumn(it.last, 1, wrap=false).toSelection) + +# editor.setMode "vim-new.normal" +# editor.paste register, inclusiveEnd=inclusiveEnd + +# proc vimToggleCase(editor: TextEditor, moveCursorRight: bool) {.exposeActive(editorContext, "vim-toggle-case").} = +# var editTexts: seq[string] + +# for s in editor.selections: +# let text = editor.getText(s, inclusiveEnd=true) +# var newText = "" +# for r in text.runes: +# if r.isLower: +# newText.add $r.toUpper +# else: +# newText.add $r.toLower +# editTexts.add newText + +# editor.addNextCheckpoint "insert" +# let oldSelections = editor.selections +# discard editor.edit(editor.selections, editTexts, inclusiveEnd=true) +# editor.setSelections oldSelections.mapIt(it.first.toSelection) + +# editor.setMode "vim-new.normal" + +# if moveCursorRight: +# editor.moveCursorColumn(1, Both, wrap=false, +# includeAfter=editor.vimState.cursorIncludeEol) +# editor.updateTargetColumn() + +# proc vimCloseCurrentViewOrQuit() {.exposeActive(editorContext, "vim-close-current-view-or-quit").} = +# let openEditors = getNumVisibleViews() + getNumHiddenViews() +# if openEditors == 1: +# plugin_runtime.quit() +# else: +# closeActiveView() + +# proc vimIndent(editor: TextEditor) {.exposeActive(editorContext, "vim-indent").} = +# editor.addNextCheckpoint "insert" +# editor.indent() + +# proc vimUnindent(editor: TextEditor) {.exposeActive(editorContext, "vim-unindent").} = +# editor.addNextCheckpoint "insert" +# editor.unindent() + +# proc vimAddCursorAbove(editor: TextEditor) {.exposeActive(editorContext, "vim-add-cursor-above").} = +# editor.addCursorAbove() +# editor.scrollToCursor(Last) + +# proc vimAddCursorBelow(editor: TextEditor) {.exposeActive(editorContext, "vim-add-cursor-below").} = +# editor.addCursorBelow() +# editor.scrollToCursor(Last) + +# proc vimEnter(editor: TextEditor) {.exposeActive(editorContext, "vim-enter").} = +# editor.addNextCheckpoint "insert" +# editor.insertText("\n") + +proc normalMode(editor: TextEditor) {.exposeActive(editorContext).} = + ## Exit to normal mode and clear things + log lvlInfo, "[vim] normalMode" + if $editor.mode == "vim-new.normal": + editor.setSelection editor.getSelection.last.toSelection + editor.clearTabStops() + editor.setMode("vim-new.normal") + +# proc vimVisualLineMode(editor: TextEditor) {.exposeActive(editorContext, "vim-visual-line-mode").} = +# editor.setMode "vim-new.visual-line" +# editor.selectLine() + +# proc vimYankLine(editor: TextEditor) {.exposeActive(editorContext, "vim-yank-line").} = +# editor.vimState.selectLines = true +# editor.selectLine() +# editor.vimYankSelection() +# editor.vimState.selectLines = false + +# proc vimDeleteLine(editor: TextEditor) {.exposeActive(editorContext, "vim-delete-line").} = +# editor.vimState.selectLines = true +# let oldSelections = editor.selections +# editor.selectLine() +# editor.vimDeleteSelection(true, oldSelections=oldSelections.some) +# editor.vimState.selectLines = false + +# proc vimChangeLine(editor: TextEditor) {.exposeActive(editorContext, "vim-change-line").} = +# let oldSelections = editor.selections +# editor.selectLine() +# editor.vimChangeSelection(true, oldSelections=oldSelections.some) + +# proc vimDeleteToLineEnd(editor: TextEditor) {.exposeActive(editorContext, "vim-delete-to-line-end").} = +# let oldSelections = editor.selections +# editor.setSelections editor.selections.mapIt (it.last, editor.vimMotionLine(it.last, 0).last) +# editor.vimDeleteSelection(true, oldSelections=oldSelections.some) +# editor.vimState.selectLines = false + +# proc vimChangeToLineEnd(editor: TextEditor) {.exposeActive(editorContext, "vim-change-to-line-end").} = +# let oldSelections = editor.selections +# editor.setSelections editor.selections.mapIt (it.last, editor.vimMotionLine(it.last, 0).last) +# editor.vimChangeSelection(true, oldSelections=oldSelections.some) +# editor.vimState.selectLines = false + +# proc vimYankToLineEnd(editor: TextEditor) {.exposeActive(editorContext, "vim-yank-to-line-end").} = +# editor.setSelections editor.selections.mapIt (it.last, editor.vimMotionLine(it.last, 0).last) +# editor.vimYankSelection() +# editor.vimState.selectLines = false + +# proc vimMoveFileStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-file-start").} = +# let which = getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) +# editor.setSelection (count - 1, 0).toSelection(editor.selection, which) +# editor.moveFirst "line-no-indent" +# editor.scrollToCursor Last +# editor.setNextSnapBehaviour(MinDistanceOffscreen) + +# proc vimMoveFileEnd(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-file-end").} = +# let line = if count == 0: editor.lineCount - 1 else: count - 1 +# let which = getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) +# var newSelection = (line, 0).toSelection(editor.selection, which) +# if newSelection == editor.selection: +# let lineLen = editor.lineLength(line) +# editor.setSelection (line, lineLen).toSelection(editor.selection, which) +# else: +# editor.setSelection newSelection +# editor.moveFirst "line-no-indent" +# editor.scrollToCursor Last +# editor.setNextSnapBehaviour(MinDistanceOffscreen) + +# proc vimMoveToMatchingOrFileOffset(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-matching-or-file-offset").} = +# if count == 0: +# editor.vimMoveToMatching() +# else: +# let line = clamp((count * editor.lineCount) div 100, 0, editor.lineCount - 1) +# let which = getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) +# editor.setSelection (line, 0).toSelection(editor.selection, which) +# editor.moveFirst "line-no-indent" +# editor.scrollToCursor Last +# editor.setNextSnapBehaviour(MinDistanceOffscreen) + +# proc vimScrollLineToTopAndMoveLineStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-scroll-line-to-top-and-move-line-start").} = +# if editor.getCommandCount != 0: +# editor.setSelection (editor.getCommandCount, 0).toSelection +# editor.moveFirst "line-no-indent" +# editor.setCursorScrollOffset getVimLineMargin() * platformTotalLineHeight() + +# proc vimScrollLineToTop(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-scroll-line-to-top").} = +# if editor.getCommandCount != 0: +# editor.setSelection (editor.getCommandCount, editor.selection.last.column).toSelection +# editor.setCursorScrollOffset getVimLineMargin() * platformTotalLineHeight() + +# proc vimCenterLineAndMoveLineStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-center-line-and-move-line-start").} = +# if editor.getCommandCount != 0: +# editor.setSelection (editor.getCommandCount, 0).toSelection +# editor.moveFirst "line-no-indent" +# editor.centerCursor() + +# proc vimCenterLine(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-center-line").} = +# if editor.getCommandCount != 0: +# editor.setSelection (editor.getCommandCount, editor.selection.last.column).toSelection +# editor.centerCursor() + +# proc vimScrollLineToBottomAndMoveLineStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-scroll-line-to-bottom-and-move-line-start").} = +# if editor.getCommandCount != 0: +# editor.setSelection (editor.getCommandCount, 0).toSelection +# editor.moveFirst "line-no-indent" +# editor.setCursorScrollOffset (editor.screenLineCount.float - getVimLineMargin()) * platformTotalLineHeight() + +# proc vimScrollLineToBottom(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-scroll-line-to-bottom").} = +# if editor.getCommandCount != 0: +# editor.setSelection (editor.getCommandCount, editor.selection.last.column).toSelection +# editor.setCursorScrollOffset (editor.screenLineCount.float - getVimLineMargin()) * platformTotalLineHeight() + +proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorContext).} = + debugf"insertMode '{move}'" + case move + of "right": + editor.setSelections editor.selections.mapIt(editor.applyMove(it.last, "column", 1)) + of "line-end": + editor.setSelections editor.selections.mapIt(editor.applyMove(it.last, "line", 1).last.toSelection) + of "line-no-indent": + editor.setSelections editor.selections.mapIt(editor.applyMove(it.last, "line-no-indent", 1).first.toSelection) + of "first": + editor.setSelections editor.selections.mapIt(it.normalized.first.toSelection) + of "line-start": + editor.setSelections editor.selections.mapIt(editor.applyMove(it.last, "line", 1).first.toSelection) + else: + discard + editor.setMode "vim-new.insert" + editor.addNextCheckpoint ws"insert" + +# proc vimInsertLineBelow(editor: TextEditor) {.exposeActive(editorContext, "vim-insert-line-below").} = +# editor.moveLast "line", Both +# editor.addNextCheckpoint "insert" +# editor.insertText "\n" +# editor.setMode "vim-new.insert" + +# proc vimInsertLineAbove(editor: TextEditor, move: string = "") {.exposeActive(editorContext, "vim-insert-line-above").} = +# editor.moveFirst "line", Both +# editor.addNextCheckpoint "insert" +# editor.insertText "\n", autoIndent=false +# editor.vimMoveCursorLine -1 +# editor.setMode "vim-new.insert" + +# proc vimSetSearchQueryFromWord(editor: TextEditor) {.exposeActive(editorContext, "vim-set-search-query-from-word").} = +# editor.setSelection editor.setSearchQueryFromMove("word", prefix=r"\b", suffix=r"\b").first.toSelection + +# proc vimSetSearchQueryFromSelection(editor: TextEditor) {.exposeActive(editorContext, "vim-set-search-query-from-selection").} = +# discard editor.setSearchQuery(editor.getText(editor.selection, inclusiveEnd=true), escapeRegex=true) +# editor.setSelection editor.selection.first.toSelection +# editor.setMode("vim-new.normal") + +# proc vimNextSearchResult(editor: TextEditor) {.exposeActive(editorContext, "vim-next-search-result").} = +# editor.setSelection editor.getNextFindResult(editor.selection.last).first.toSelection +# editor.scrollToCursor Last +# editor.setNextSnapBehaviour(MinDistanceOffscreen) +# editor.updateTargetColumn() + +# proc vimPrevSearchResult(editor: TextEditor) {.exposeActive(editorContext, "vim-prev-search-result").} = +# editor.setSelection editor.getPrevFindResult(editor.selection.last).first.toSelection +# editor.scrollToCursor Last +# editor.setNextSnapBehaviour(MinDistanceOffscreen) +# editor.updateTargetColumn() + +# proc vimOpenSearchBar(editor: TextEditor) {.exposeActive(editorContext, "vim-open-search-bar").} = +# editor.openSearchBar() +# if getActiveEditor().isTextEditor(editor): +# editor.setMode("vim-new.insert") + +# proc vimExitCommandLine() {.expose("vim-exit-command-line").} = +# if getActiveEditor().isTextEditor(editor): +# if editor.mode == "vim-new.normal": +# exitCommandLine() +# return + +# editor.setMode("vim-new.normal") + +# proc vimExitPopup() {.expose("vim-exit-popup").} = +# if getActiveEditor().isTextEditor(editor): +# if editor.mode == "vim-new.normal": +# if getActivePopup().isSelectorPopup(popup): +# popup.cancel() +# return + +# editor.setMode("vim-new.normal") + +# proc vimSelectWordOrAddCursor(editor: TextEditor) {.exposeActive(editorContext, "vim-select-word-or-add-cursor").} = +# if editor.selections.len == 1: +# var selection = editor.setSearchQueryFromMove("word", prefix=r"\b", suffix=r"\b") +# selection.last.column -= 1 +# editor.setSelection selection +# else: +# let next = editor.getNextFindResult(editor.selection.last, includeAfter=false) +# editor.setSelections editor.selections & next +# editor.scrollToCursor Last +# editor.setNextSnapBehaviour(MinDistanceOffscreen) +# editor.updateTargetColumn() + +# editor.setMode("vim-new.visual") + +# proc vimMoveLastSelectionToNextSearchResult(editor: TextEditor) {.exposeActive(editorContext, "vim-move-last-selection-to-next-search-result").} = +# if editor.selections.len == 1: +# var selection = editor.setSearchQueryFromMove("word", prefix=r"\b", suffix=r"\b") +# selection.last.column -= 1 +# editor.setSelection selection +# else: +# let next = editor.getNextFindResult(editor.selection.last, includeAfter=false) +# editor.setSelections editor.selections[0..^2] & next +# editor.scrollToCursor Last +# editor.setNextSnapBehaviour(MinDistanceOffscreen) +# editor.updateTargetColumn() + +# editor.setMode("vim-new.visual") + +# proc vimSetSearchQueryOrAddCursor(editor: TextEditor) {.exposeActive(editorContext, "vim-set-search-query-or-add-cursor").} = +# if editor.selections.len == 1: +# let text = editor.getText(editor.selection, inclusiveEnd=true) +# let textEscaped = text.escapeRegex +# let currentSearchQuery = editor.getSearchQuery() +# # infof"'{text}' -> '{textEscaped}' -> '{currentSearchQuery}'" +# if textEscaped != currentSearchQuery and r"\b" & textEscaped & r"\b" != currentSearchQuery: +# if editor.setSearchQuery(text, escapeRegex=true): +# return + +# let next = editor.getNextFindResult(editor.selection.last, includeAfter=false) +# editor.setSelections editor.selections & next +# editor.scrollToCursor Last +# editor.updateTargetColumn() + +# proc vimSaveState() {.expose("vim-save-state").} = +# try: +# var states = initTable[string, JsonNode]() + +# for id, state in editorStates: +# if id.isTextEditor(editor): +# let filename = editor.getFileName() +# if filename == "": +# continue + +# var marks = initTable[string, seq[Selection]]() +# for name, anchors in editor.vimState.marks: +# let selections = editor.resolveAnchors(anchors) +# marks[name] = selections +# for name, selections in editor.vimState.unresolveMarks: +# marks[name] = selections + +# if marks.len > 0: +# states[filename] = %*{ +# "marks": marks.toJson, +# } + +# setSessionData("vim.states", states) +# except: +# infof"Failed to save vim editor states" + +# proc resolveMarks(editor: TextEditor) = +# let unresolveMarks = editor.vimState.unresolveMarks +# for name, selections in unresolveMarks: +# let anchors = editor.createAnchors(selections) +# if anchors.len > 0: +# editor.vimState.marks[name] = anchors +# editor.vimState.unresolveMarks.del(name) + +# proc vimAddMark(editor: TextEditor, name: string) {.exposeActive(editorContext, "vim-add-mark").} = +# editor.resolveMarks() +# editor.vimState.marks[name] = editor.createAnchors(editor.selections) + +# proc vimGotoMark(editor: TextEditor, name: string) {.exposeActive(editorContext, "vim-goto-mark").} = +# editor.resolveMarks() + +# if name in editor.vimState.marks: +# let newSelections = editor.resolveAnchors(editor.vimState.marks[name]) +# if newSelections.len == 0: +# return + +# case editor.mode +# of "vim-new.visual", "vim-new.visual-line": +# let oldSelections = editor.selections +# if newSelections.len == oldSelections.len: +# editor.setSelections collect: +# for i in 0..newSelections.high: +# oldSelections[i] or newSelections[i] +# else: +# editor.setSelections newSelections +# else: +# editor.setSelections newSelections + +# editor.updateTargetColumn() +# editor.scrollToCursor Last +# editor.setNextSnapBehaviour(MinDistanceOffscreen) + +# proc vimDeleteWordBack(editor: TextEditor) {.exposeActive(editorContext, "vim-delete-word-back").} = +# let selections = editor.applyMove(editor.selections, "vim-word", true, true, 1, which = SelectionCursor.Last.some) +# editor.setSelections editor.delete(selections) +# editor.autoShowCompletions() + +# proc vimDeleteLineBack(editor: TextEditor) {.exposeActive(editorContext, "vim-delete-line-back").} = +# let selections = editor.applyMove(editor.selections, "vim-line", true, true, 1, which = SelectionCursor.Last.some) +# editor.setSelections editor.delete(selections) +# editor.autoShowCompletions() + +# proc vimSurround(editor: TextEditor, text: string) {.exposeActive(editorContext, "vim-surround").} = +# let (left, right) = case text +# of "(", ")": ("(", ")") +# of "{", "}": ("{", "}") +# of "[", "]": ("[", "]") +# of "<", ">": ("<", ">") +# else: +# (text, text) + +# var insertSelections: Selections = @[] +# var insertTexts: seq[string] = @[] +# for s in editor.selections: +# let s = s.normalized +# insertSelections.add s.first.toSelection +# insertSelections.add editor.doMoveCursorColumn(s.last, 1).toSelection +# insertTexts.add left +# insertTexts.add right + +# editor.addNextCheckpoint "insert" +# let newSelections = editor.insertMulti(insertSelections, insertTexts) +# if newSelections.len mod 2 != 0: +# return + +# editor.setSelections collect: +# for i in 0.. {newModes}" + + let oldMode = if oldModes.len > 0: + oldModes[0] + else: + "" + + if newModes.len == 0: + return + + let newMode = newModes[0] + +# if not editor.getCurrentEventHandlers().contains("vim"): +# return + + if newMode == "": + editor.setMode "vim-new.normal" + return + + if not newMode.startsWith("vim"): + return + + let recordModes = [ + "vim-new.visual", + "vim-new.visual-line", + "vim-new.insert", + ].toHashSet + + # infof"vim: handle mode change {oldMode} -> {newMode}" + if newMode == "vim-new.normal": + discard + # if not isReplayingCommands() and isRecordingCommands(".-temp"): + # stopRecordingCommands(".-temp") + + # if editor.getRevision > editor.vimState.revisionBeforeImplicitInsertMacro: + # infof"Record implicit macro because document was modified" + # let text = getRegisterText(".-temp") + # setRegisterText(text, ".") + else: + if oldMode == "vim-new.normal" and newMode in recordModes: + editor.startRecordingCurrentCommandInPeriodMacro() + + # editor.clearCurrentCommandHistory(retainLast=true) + + editor.vimState.selectLines = newMode == "vim-new.visual-line" + editor.vimState.cursorIncludeEol = newMode == "vim-new.insert" + editor.vimState.currentUndoCheckpoint = if newMode == "vim-new.insert": "word" else: "insert" + + # case newMode + # of "vim-new.normal": + # editor.setConfig "text.inclusive-selection", false + # editor.setSelections editor.selections.mapIt(editor.vimClamp(it.last).toSelection) + # editor.saveCurrentCommandHistory() + # editor.hideCompletions() + +# of "vim-new.insert": +# editor.setConfig "text.inclusive-selection", false + +# of "vim-new.visual": +# editor.setConfig "text.inclusive-selection", true + +# of "vim-new.visual-line": +# editor.setConfig "text.inclusive-selection", false + +# else: +# editor.setConfig "text.inclusive-selection", false + +# proc loadVimKeybindings*() {.expose("load-vim-keybindings").} = +# let afterRestoreSessionHandle = addCallback proc(args: JsonNode): JsonNode = +# let states = getSessionData[Table[string, JsonNode]]("vim.states") +# for id in getAllEditors(): +# if id.isTextEditor(editor): +# try: +# let filename = editor.getFileName() +# if states.hasKey(filename): +# let editorState = states[filename] +# if editorState.hasKey("marks"): +# let marks = editorState["marks"].jsonTo(Table[string, seq[Selection]]) +# for name, selections in marks: +# editor.vimState.unresolveMarks[name] = selections +# except: +# infof"Failed to restore marks for {editor}" +# scriptSetCallback("after-restore-session", afterRestoreSessionHandle) + +# let beforeSaveAppStateHandle = addCallback proc(args: JsonNode): JsonNode = +# vimSaveState() +# scriptSetCallback("before-save-app-state", beforeSaveAppStateHandle) diff --git a/plugins/vim/vim.nimble b/plugins/vim/vim.nimble new file mode 100644 index 000000000..8ddc3618b --- /dev/null +++ b/plugins/vim/vim.nimble @@ -0,0 +1,9 @@ +requires "nim >= 2.0.0" +requires "https://github.com/Nimaoth/vmath#661bdaa" +requires "bumpy >= 1.1.2" +requires "chroma >= 0.2.7" +requires "fusion >= 1.2" +requires "regex >= 0.25.0" +requires "results >= 0.5.0" +requires "https://github.com/Nimaoth/nimwasmtime#22cfeef" + diff --git a/scripting/plugin_runtime.nim b/scripting/plugin_runtime.nim index f9167c3b4..6286a4175 100644 --- a/scripting/plugin_runtime.nim +++ b/scripting/plugin_runtime.nim @@ -338,7 +338,7 @@ block: # Custom moves count: int let input = args.jsonTo(Payload) - if input.editor.isTextEditor editor: + if input.editor.EditorId.isTextEditor editor: let selection = handleCustomTextMove(editor, input.move, input.cursor, input.count) if selection.isSome: return selection.toJson diff --git a/src/app.nim b/src/app.nim index 66b2e364d..a28ddcde7 100644 --- a/src/app.nim +++ b/src/app.nim @@ -1350,7 +1350,7 @@ proc toggleStatusBarLocation*(self: App) {.expose("editor").} = proc logs*(self: App, slot: string = "", focus: bool = true, scrollToBottom: bool = false) {.expose("editor").} = let editors = self.editors.getEditorsForDocument(self.logDocument) let editor = if editors.len > 0: - self.layout.showEditor(editors[0].id, slot = slot, focus = focus) + self.layout.showEditor(editors[0].id.EditorId, slot = slot, focus = focus) editors[0] else: self.layout.createAndAddView(self.logDocument).get @@ -2089,7 +2089,7 @@ proc chooseOpen*(self: App, preview: bool = true, scaleX: float = 0.8, scaleY: f let item = popup.getSelectedItem().getOr: return true - let editorId = item.data.parseInt.EditorId.catch: + let editorId = item.data.parseInt.EditorIdNew.catch: log lvlError, fmt"Failed to parse editor id from data '{item}'" return true @@ -3155,13 +3155,13 @@ proc getActiveEditor*(): EditorId {.expose("editor").} = if gEditor.isNil: return EditorId(-1) if gEditor.commands.commandLineMode: - return gEditor.commands.commandLineEditor.id + return gEditor.commands.commandLineEditor.id.EditorId if gEditor.layout.popups.len > 0 and gEditor.layout.popups[gEditor.layout.popups.high].getActiveEditor().getSome(editor): - return editor.id + return editor.id.EditorId if gEditor.layout.tryGetCurrentView().getSome(view) and view.getActiveEditor().getSome(editor): - return editor.id + return editor.id.EditorId return EditorId(-1) @@ -3194,7 +3194,7 @@ proc scriptIsTextEditor*(editorId: EditorId): bool {.expose("editor").} = {.gcsafe.}: if gEditor.isNil: return false - if gEditor.editors.getEditorForId(editorId).getSome(editor): + if gEditor.editors.getEditorForId(editorId.EditorIdNew).getSome(editor): return editor of TextDocumentEditor return false @@ -3203,7 +3203,7 @@ proc scriptIsModelEditor*(editorId: EditorId): bool {.expose("editor").} = if gEditor.isNil: return false when enableAst: - if gEditor.editors.getEditorForId(editorId).getSome(editor): + if gEditor.editors.getEditorForId(editorId.EditorIdNew).getSome(editor): return editor of ModelDocumentEditor return false @@ -3213,9 +3213,9 @@ proc scriptRunActionFor*(editorId: EditorId, action: string, arg: string) {.expo return defer: gEditor.platform.requestRender() - if gEditor.editors.getEditorForId(editorId).getSome(editor): + if gEditor.editors.getEditorForId(editorId.EditorIdNew).getSome(editor): discard editor.handleAction(action, arg, record=false) - elif gEditor.layout.getPopupForId(editorId).getSome(popup): + elif gEditor.layout.getPopupForId(editorId.EditorId).getSome(popup): discard popup.handleAction(action, arg) proc scriptSetCallback*(path: string, id: int) {.expose("editor").} = diff --git a/src/channel.nim b/src/channel.nim index 7cebe3486..4286200fd 100644 --- a/src/channel.nim +++ b/src/channel.nim @@ -1,4 +1,4 @@ -import std/[strformat, hashes, macros, genasts, atomics] +import std/[strformat, hashes, macros, genasts, atomics, locks, tables, options] import misc/[event, custom_async, id, generational_seq] import nimsumtree/arc @@ -29,6 +29,11 @@ type writeChannelPeekable: Atomic[int] channel: ptr Channel[seq[uint8]] + ChannelRegistry* = object + lock*: Lock + readChannels*: Table[string, Arc[BaseChannel]] + writeChannels*: Table[string, Arc[BaseChannel]] + proc `==`*(a, b: ListenId): bool {.borrow.} proc hash*(vr: ListenId): Hash {.borrow.} proc `$`*(vr: ListenId): string {.borrow.} @@ -49,6 +54,7 @@ proc close*(self: Arc[BaseChannel]) {.gcsafe, raises: [].} = self.getMutUnsafe.c proc isOpen*(self: Arc[BaseChannel]): bool {.raises: [].} = self.getMutUnsafe.isOpen() proc peek*(self: Arc[BaseChannel]): int {.raises: [].} = self.getMutUnsafe.peek() proc write*(self: Arc[BaseChannel], data: openArray[uint8]) {.raises: [IOError].} = self.getMutUnsafe.write(data) +proc write*(self: Arc[BaseChannel], data: openArray[char]) {.raises: [IOError].} = self.getMutUnsafe.write(cast[ptr UncheckedArray[uint8]](data[0].addr).toOpenArray(0, data.high)) proc read*(self: Arc[BaseChannel], res: var openArray[uint8]): int {.raises: [IOError].} = self.getMutUnsafe.read(res) proc flushRead*(self: Arc[BaseChannel]): int {.raises: [IOError].} = self.getMutUnsafe.flushRead() proc listen*(self: Arc[BaseChannel], cb: ChannelListener): ListenId {.gcsafe, raises: [].} = self.get.listenImpl(self, cb) @@ -174,3 +180,38 @@ proc newInMemoryChannel*(): Arc[BaseChannel] = listenImpl: proc(self: Arc[BaseChannel], cb: ChannelListener): ListenId {.gcsafe, raises: [].} = listenInMemoryChannel(self.cloneAs(InMemoryChannel), cb), ) return cast[ptr Arc[BaseChannel]](res.addr)[].clone() + +var gChannelRegistry = ChannelRegistry() +gChannelRegistry.lock.initLock() + +proc openGlobalReadChannel*(path: string): Option[Arc[BaseChannel]] {.gcsafe.} = + let channels = ({.gcsafe.}: gChannelRegistry.addr) + withLock(channels.lock): + var chan: Arc[BaseChannel] + if channels.readChannels.take(path, chan): + return chan.some + +proc openGlobalWriteChannel*(path: string): Option[Arc[BaseChannel]] {.gcsafe.} = + let channels = ({.gcsafe.}: gChannelRegistry.addr) + withLock(channels.lock): + var chan: Arc[BaseChannel] + if channels.writeChannels.take(path, chan): + return chan.some + +proc mountGlobalReadChannel*(path: string, chan: Arc[BaseChannel], unique: bool): string {.gcsafe.} = + var path = path + if unique: + path.add "-" & $newId() + let channels = ({.gcsafe.}: gChannelRegistry.addr) + withLock(channels.lock): + channels.readChannels[path] = chan + return path + +proc mountGlobalWriteChannel*(path: string, chan: Arc[BaseChannel], unique: bool): string {.gcsafe.} = + var path = path + if unique: + path.add "-" & $newId() + let channels = ({.gcsafe.}: gChannelRegistry.addr) + withLock(channels.lock): + channels.writeChannels[path] = chan + return path diff --git a/src/command_service.nim b/src/command_service.nim index 6df3cc74d..14ac1138d 100644 --- a/src/command_service.nim +++ b/src/command_service.nim @@ -23,7 +23,7 @@ type parameters*: seq[tuple[name: string, `type`: string]] returnType*: string signature*: string - execute*: proc(args: string): string {.gcsafe.} + execute*: proc(args: string): string {.gcsafe, raises: [CatchableError].} CommandPermissions* = object allowAll*: Option[bool] @@ -51,6 +51,7 @@ type prefixCommandHandlers: seq[tuple[prefix: string, execute: proc(command: string): Option[string] {.gcsafe, raises: [].}]] commandIdCounter: int = 1 commands*: Table[string, Command] + activeCommands*: Table[string, Command] idToCommand*: Table[CommandId, string] proc all*(_: typedesc[CommandPermissions]) = CommandPermissions(allowAll: some(true), disallowAll: some(true)) @@ -115,6 +116,27 @@ proc unregisterCommand*(self: CommandService, id: CommandId) = self.commands.del(self.idToCommand[id]) self.idToCommand.del(id) +proc registerActiveCommand*(self: CommandService, command: sink Command, override: bool = false): CommandId = + if command.name == "": + log lvlError, &"Trying to register command with no name" + return + + if not override and self.activeCommands.contains(command.name): + log lvlError, &"Trying to register command '{command.name}' which already exists" + return + + let id = self.commandIdCounter.CommandId + inc self.commandIdCounter + + self.unregisterCommand(command.name) + + command.id = id + command.signature = "(" & command.parameters.mapIt(it.name & ": " & it.`type`).join(", ") & ") " & command.returnType + self.idToCommand[id] = command.name + self.activeCommands[command.name] = command.ensureMove + + return id + proc registerCommand*(self: CommandService, command: sink Command, override: bool = false): CommandId = if command.name == "": log lvlError, &"Trying to register command with no name" diff --git a/src/document_editor.nim b/src/document_editor.nim index 27b98b235..0553fec47 100644 --- a/src/document_editor.nim +++ b/src/document_editor.nim @@ -5,7 +5,7 @@ import platform/[platform] import scripting/expose import document, events, input, service, platform_service, dispatch_tables, config_provider -from scripting_api import EditorId, newEditorId +from scripting_api import EditorId {.push gcsafe.} {.push raises: [].} @@ -19,8 +19,7 @@ declareSettings EditorSettings, "editor": type EditorIdNew* = distinct uint64 DocumentEditor* = ref object of RootObj - id*: EditorId - idNew*: EditorIdNew + id*: EditorIdNew userId*: Id renderHeader*: bool fillAvailableSpace*: bool @@ -37,8 +36,8 @@ type DocumentEditorService* = ref object of Service platform: Platform - editors*: Table[EditorId, DocumentEditor] - pinnedEditors*: HashSet[EditorId] + editors*: Table[EditorIdNew, DocumentEditor] + pinnedEditors*: HashSet[EditorIdNew] pinnedDocuments*: seq[Document] onEditorRegistered*: Event[DocumentEditor] onEditorDeregistered*: Event[DocumentEditor] @@ -61,7 +60,7 @@ addBuiltinService(DocumentEditorService) method init*(self: DocumentEditorService): Future[Result[void, ref CatchableError]] {.async: (raises: []).} = log lvlInfo, &"DocumentEditorService.init" - self.pinnedEditors = initHashSet[EditorId]() + self.pinnedEditors = initHashSet[EditorIdNew]() return ok() method canOpenFile*(self: DocumentFactory, path: string): bool {.base, gcsafe, raises: [].} = discard @@ -70,10 +69,9 @@ method createDocument*(self: DocumentFactory, services: Services, path: string, method canEditDocument*(self: DocumentEditorFactory, document: Document): bool {.base, gcsafe, raises: [].} = discard method createEditor*(self: DocumentEditorFactory, services: Services, document: Document): DocumentEditor {.base, gcsafe, raises: [].} = discard -func id*(self: DocumentEditor): EditorId = self.id +func id*(self: DocumentEditor): EditorIdNew = self.id proc init*(self: DocumentEditor) = - self.id = newEditorId() self.userId = newId() self.renderHeader = true @@ -166,12 +164,12 @@ proc unregisterDocument*(self: DocumentEditorService, document: Document) = self.documents.del(document.id) proc registerEditor*(self: DocumentEditorService, editor: DocumentEditor): void = - editor.idNew = self.allEditors.add(editor) + editor.id = self.allEditors.add(editor) self.editors[editor.id] = editor self.onEditorRegistered.invoke editor proc unregisterEditor*(self: DocumentEditorService, editor: DocumentEditor): void = - self.allEditors.del(editor.idNew) + self.allEditors.del(editor.id) self.editors.del(editor.id) self.onEditorDeregistered.invoke editor @@ -198,7 +196,7 @@ proc getEditorsForDocument*(self: DocumentEditorService, document: Document): se if editor.getDocument() == document: result.add editor -proc getEditorForId*(self: DocumentEditorService, id: EditorId): Option[DocumentEditor] = +proc getEditorForId*(self: DocumentEditorService, id: EditorIdNew): Option[DocumentEditor] = self.editors.withValue(id, editor): return editor[].some @@ -292,7 +290,7 @@ static: proc getAllEditors*(self: DocumentEditorService): seq[EditorId] {.expose("editors").} = for id in self.editors.keys: - result.add id + result.add id.EditorId proc getExistingEditor*(self: DocumentEditorService, path: string): Option[EditorId] {.expose("editors").} = ## Returns an existing editor for the given file if one exists, @@ -308,7 +306,7 @@ proc getExistingEditor*(self: DocumentEditorService, path: string): Option[Edito continue if editor.getDocument().filename != path: continue - return id.some + return id.EditorId.some return EditorId.none diff --git a/src/finder/open_editor_previewer.nim b/src/finder/open_editor_previewer.nim index d761cc008..b158a0577 100644 --- a/src/finder/open_editor_previewer.nim +++ b/src/finder/open_editor_previewer.nim @@ -38,7 +38,7 @@ method previewItem*(self: OpenEditorPreviewer, item: FinderItem, editor: Documen let editor = editor.TextDocumentEditor - let editorToPreview = self.editors.getEditorForId(editorId).getOr: + let editorToPreview = self.editors.getEditorForId(editorId.EditorIdNew).getOr: return if not (editorToPreview of TextDocumentEditor): diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index 25eeab1a5..a0367d4d3 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -107,41 +107,41 @@ proc collectExports*(funcs: var ExportedFuncs; instance: InstanceT; funcs.mStackAlloc = instance.getExport(context, "mem_stack_alloc") funcs.mStackSave = instance.getExport(context, "mem_stack_save") funcs.mStackRestore = instance.getExport(context, "mem_stack_restore") - let f_8556381821 = instance.getExport(context, "init_plugin") - if f_8556381821.isSome: - assert f_8556381821.get.kind == WASMTIME_EXTERN_FUNC - funcs.initPlugin = f_8556381821.get.of_field.func_field + let f_8556381802 = instance.getExport(context, "init_plugin") + if f_8556381802.isSome: + assert f_8556381802.get.kind == WASMTIME_EXTERN_FUNC + funcs.initPlugin = f_8556381802.get.of_field.func_field else: echo "Failed to find exported function \'", "init_plugin", "\'" - let f_8556381837 = instance.getExport(context, "handle_command") - if f_8556381837.isSome: - assert f_8556381837.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleCommand = f_8556381837.get.of_field.func_field + let f_8556381818 = instance.getExport(context, "handle_command") + if f_8556381818.isSome: + assert f_8556381818.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleCommand = f_8556381818.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_command", "\'" - let f_8556381887 = instance.getExport(context, "handle_mode_changed") - if f_8556381887.isSome: - assert f_8556381887.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleModeChanged = f_8556381887.get.of_field.func_field + let f_8556381868 = instance.getExport(context, "handle_mode_changed") + if f_8556381868.isSome: + assert f_8556381868.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleModeChanged = f_8556381868.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_mode_changed", "\'" - let f_8556381888 = instance.getExport(context, "handle_view_render_callback") - if f_8556381888.isSome: - assert f_8556381888.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleViewRenderCallback = f_8556381888.get.of_field.func_field + let f_8556381869 = instance.getExport(context, "handle_view_render_callback") + if f_8556381869.isSome: + assert f_8556381869.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleViewRenderCallback = f_8556381869.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_view_render_callback", "\'" - let f_8556381912 = instance.getExport(context, "handle_channel_update") - if f_8556381912.isSome: - assert f_8556381912.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleChannelUpdate = f_8556381912.get.of_field.func_field + let f_8556381893 = instance.getExport(context, "handle_channel_update") + if f_8556381893.isSome: + assert f_8556381893.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleChannelUpdate = f_8556381893.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_channel_update", "\'" - let f_8556381913 = instance.getExport(context, "notify_task_complete") - if f_8556381913.isSome: - assert f_8556381913.get.kind == WASMTIME_EXTERN_FUNC - funcs.notifyTaskComplete = f_8556381913.get.of_field.func_field + let f_8556381894 = instance.getExport(context, "notify_task_complete") + if f_8556381894.isSome: + assert f_8556381894.get.kind == WASMTIME_EXTERN_FUNC + funcs.notifyTaskComplete = f_8556381894.get.of_field.func_field else: echo "Failed to find exported function \'", "notify_task_complete", "\'" @@ -369,9 +369,39 @@ proc textEditorAsTextDocument(instance: ptr InstanceData; document: Document): O proc textEditorCommand(instance: ptr InstanceData; editor: TextEditor; name: sink string; arguments: sink string): Result[ string, CommandError] +proc textEditorRecordCurrentCommand(instance: ptr InstanceData; + editor: TextEditor; + registers: sink seq[string]): void +proc textEditorGetUsage(instance: ptr InstanceData; editor: TextEditor): string +proc textEditorGetRevision(instance: ptr InstanceData; editor: TextEditor): int32 +proc textEditorSetMode(instance: ptr InstanceData; editor: TextEditor; + mode: sink string; exclusive: bool): void +proc textEditorMode(instance: ptr InstanceData; editor: TextEditor): string +proc textEditorClearTabStops(instance: ptr InstanceData; editor: TextEditor): void +proc textEditorSelectNextTabStop(instance: ptr InstanceData; editor: TextEditor): void +proc textEditorSelectPrevTabStop(instance: ptr InstanceData; editor: TextEditor): void +proc textEditorUndo(instance: ptr InstanceData; editor: TextEditor; + checkpoint: sink string): void +proc textEditorRedo(instance: ptr InstanceData; editor: TextEditor; + checkpoint: sink string): void +proc textEditorAddNextCheckpoint(instance: ptr InstanceData; editor: TextEditor; + checkpoint: sink string): void +proc textEditorCopy(instance: ptr InstanceData; editor: TextEditor; + register: sink string; inclusiveEnd: bool): void +proc textEditorPaste(instance: ptr InstanceData; editor: TextEditor; + register: sink string; inclusiveEnd: bool): void +proc textEditorApplyMove(instance: ptr InstanceData; editor: TextEditor; + selection: Selection; move: sink string; count: int32; + wrap: bool; includeEol: bool): seq[Selection] proc textEditorSetSelection(instance: ptr InstanceData; editor: TextEditor; s: Selection): void +proc textEditorSetSelections(instance: ptr InstanceData; editor: TextEditor; + s: sink seq[Selection]): void proc textEditorGetSelection(instance: ptr InstanceData; editor: TextEditor): Selection +proc textEditorGetSelections(instance: ptr InstanceData; editor: TextEditor): seq[ + Selection] +proc textEditorLineLength(instance: ptr InstanceData; editor: TextEditor; + line: int32): int32 proc textEditorAddModeChangedHandler(instance: ptr InstanceData; fun: uint32): int32 proc textEditorEdit(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection]; contents: sink seq[string]): seq[ @@ -465,6 +495,11 @@ proc processProcessStart(instance: ptr InstanceData; name: sink string; proc processStderr(instance: ptr InstanceData; self: var ProcessResource): ReadChannelResource proc processStdout(instance: ptr InstanceData; self: var ProcessResource): ReadChannelResource proc processStdin(instance: ptr InstanceData; self: var ProcessResource): WriteChannelResource +proc registersIsReplayingCommands(instance: ptr InstanceData): bool +proc registersSetRegisterText(instance: ptr InstanceData; text: sink string; + register: sink string): void +proc registersStartRecordingCommands(instance: ptr InstanceData; + register: sink string): void proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = block: let e = block: @@ -1171,6 +1206,402 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = cast[ptr int8](memory[retArea + 4].addr)[] = cast[int8](res.error) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", + "record-current-command", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var editor: TextEditor + var registers: seq[string] + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[uint8]](memory[parameters[1].i32].addr) + registers = newSeq[typeof(registers[0])](parameters[2].i32) + for i0 in 0 ..< registers.len: + block: + let p1 = cast[ptr UncheckedArray[char]](memory[ + cast[ptr int32](p0[i0 * 8 + 0].addr)[]].addr) + registers[i0] = newString(cast[ptr int32](p0[i0 * 8 + 4].addr)[]) + for i1 in 0 ..< registers[i0].len: + registers[i0][i1] = p1[i1] + textEditorRecordCurrentCommand(instance, editor, registers) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "get-usage", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + let stackAllocFunc = caller.getExport("mem_stack_alloc").get.of_field.func_field + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + let res = textEditorGetUsage(instance, editor) + let retArea = parameters[^1].i32 + if res.len > 0: + let dataPtrWasm0 = int32(?stackAlloc(stackAllocFunc, store, + (res.len * 1).int32, 4)) + cast[ptr int32](memory[retArea + 0].addr)[] = cast[int32](dataPtrWasm0) + block: + for i0 in 0 ..< res.len: + memory[dataPtrWasm0 + i0] = cast[uint8](res[i0]) + else: + cast[ptr int32](memory[retArea + 0].addr)[] = 0.int32 + cast[ptr int32](memory[retArea + 4].addr)[] = cast[int32](res.len) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64], + [WasmValkind.I32]) + linker.defineFuncUnchecked("nev:plugins/text-editor", "get-revision", ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + let res = textEditorGetRevision(instance, editor) + parameters[0].i32 = cast[int32](res) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], + []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "set-mode", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var editor: TextEditor + var mode: string + var exclusive: bool + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[1].i32].addr) + mode = newString(parameters[2].i32) + for i0 in 0 ..< mode.len: + mode[i0] = p0[i0] + exclusive = parameters[3].i32.bool + textEditorSetMode(instance, editor, mode, exclusive) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "mode", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + let stackAllocFunc = caller.getExport("mem_stack_alloc").get.of_field.func_field + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + let res = textEditorMode(instance, editor) + let retArea = parameters[^1].i32 + if res.len > 0: + let dataPtrWasm0 = int32(?stackAlloc(stackAllocFunc, store, + (res.len * 1).int32, 4)) + cast[ptr int32](memory[retArea + 0].addr)[] = cast[int32](dataPtrWasm0) + block: + for i0 in 0 ..< res.len: + memory[dataPtrWasm0 + i0] = cast[uint8](res[i0]) + else: + cast[ptr int32](memory[retArea + 0].addr)[] = 0.int32 + cast[ptr int32](memory[retArea + 4].addr)[] = cast[int32](res.len) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "clear-tab-stops", + ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + textEditorClearTabStops(instance, editor) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", + "select-next-tab-stop", ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + textEditorSelectNextTabStop(instance, editor) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", + "select-prev-tab-stop", ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + textEditorSelectPrevTabStop(instance, editor) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "undo", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var editor: TextEditor + var checkpoint: string + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[1].i32].addr) + checkpoint = newString(parameters[2].i32) + for i0 in 0 ..< checkpoint.len: + checkpoint[i0] = p0[i0] + textEditorUndo(instance, editor, checkpoint) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "redo", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var editor: TextEditor + var checkpoint: string + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[1].i32].addr) + checkpoint = newString(parameters[2].i32) + for i0 in 0 ..< checkpoint.len: + checkpoint[i0] = p0[i0] + textEditorRedo(instance, editor, checkpoint) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", + "add-next-checkpoint", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var editor: TextEditor + var checkpoint: string + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[1].i32].addr) + checkpoint = newString(parameters[2].i32) + for i0 in 0 ..< checkpoint.len: + checkpoint[i0] = p0[i0] + textEditorAddNextCheckpoint(instance, editor, checkpoint) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], + []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "copy", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var editor: TextEditor + var register: string + var inclusiveEnd: bool + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[1].i32].addr) + register = newString(parameters[2].i32) + for i0 in 0 ..< register.len: + register[i0] = p0[i0] + inclusiveEnd = parameters[3].i32.bool + textEditorCopy(instance, editor, register, inclusiveEnd) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], + []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "paste", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var editor: TextEditor + var register: string + var inclusiveEnd: bool + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[1].i32].addr) + register = newString(parameters[2].i32) + for i0 in 0 ..< register.len: + register[i0] = p0[i0] + inclusiveEnd = parameters[3].i32.bool + textEditorPaste(instance, editor, register, inclusiveEnd) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, + WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "apply-move", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + let stackAllocFunc = caller.getExport("mem_stack_alloc").get.of_field.func_field + var editor: TextEditor + var selection: Selection + var move: string + var count: int32 + var wrap: bool + var includeEol: bool + editor.id = convert(parameters[0].i64, uint64) + selection.first.line = convert(parameters[1].i32, int32) + selection.first.column = convert(parameters[2].i32, int32) + selection.last.line = convert(parameters[3].i32, int32) + selection.last.column = convert(parameters[4].i32, int32) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[5].i32].addr) + move = newString(parameters[6].i32) + for i0 in 0 ..< move.len: + move[i0] = p0[i0] + count = convert(parameters[7].i32, int32) + wrap = parameters[8].i32.bool + includeEol = parameters[9].i32.bool + let res = textEditorApplyMove(instance, editor, selection, move, count, + wrap, includeEol) + let retArea = parameters[^1].i32 + if res.len > 0: + let dataPtrWasm0 = int32(?stackAlloc(stackAllocFunc, store, + (res.len * 16).int32, 4)) + cast[ptr int32](memory[retArea + 0].addr)[] = cast[int32](dataPtrWasm0) + block: + for i0 in 0 ..< res.len: + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 0].addr)[] = res[ + i0].first.line + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 4].addr)[] = res[ + i0].first.column + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 8].addr)[] = res[ + i0].last.line + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 12].addr)[] = res[ + i0].last.column + else: + cast[ptr int32](memory[retArea + 0].addr)[] = 0.int32 + cast[ptr int32](memory[retArea + 4].addr)[] = cast[int32](res.len) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, @@ -1187,6 +1618,42 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = textEditorSetSelection(instance, editor, s) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "set-selections", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var editor: TextEditor + var s: seq[Selection] + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[uint8]](memory[parameters[1].i32].addr) + s = newSeq[typeof(s[0])](parameters[2].i32) + for i0 in 0 ..< s.len: + s[i0].first.line = convert(cast[ptr int32](p0[i0 * 16 + 0].addr)[], + int32) + s[i0].first.column = convert( + cast[ptr int32](p0[i0 * 16 + 4].addr)[], int32) + s[i0].last.line = convert(cast[ptr int32](p0[i0 * 16 + 8].addr)[], + int32) + s[i0].last.column = convert(cast[ptr int32](p0[i0 * 16 + 12].addr)[], + int32) + textEditorSetSelections(instance, editor, s) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype( @@ -1215,6 +1682,62 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = cast[ptr int32](memory[retArea + 12].addr)[] = res.last.column if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "get-selections", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + let stackAllocFunc = caller.getExport("mem_stack_alloc").get.of_field.func_field + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + let res = textEditorGetSelections(instance, editor) + let retArea = parameters[^1].i32 + if res.len > 0: + let dataPtrWasm0 = int32(?stackAlloc(stackAllocFunc, store, + (res.len * 16).int32, 4)) + cast[ptr int32](memory[retArea + 0].addr)[] = cast[int32](dataPtrWasm0) + block: + for i0 in 0 ..< res.len: + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 0].addr)[] = res[ + i0].first.line + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 4].addr)[] = res[ + i0].first.column + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 8].addr)[] = res[ + i0].last.line + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 12].addr)[] = res[ + i0].last.column + else: + cast[ptr int32](memory[retArea + 0].addr)[] = 0.int32 + cast[ptr int32](memory[retArea + 4].addr)[] = cast[int32](res.len) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32], [WasmValkind.I32]) + linker.defineFuncUnchecked("nev:plugins/text-editor", "line-length", ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + var line: int32 + editor.id = convert(parameters[0].i64, uint64) + line = convert(parameters[1].i32, int32) + let res = textEditorLineLength(instance, editor, line) + parameters[0].i32 = cast[int32](res) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I32], @@ -2634,3 +3157,76 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = parameters[0].i32 = ?instance.resources.resourceNew(store, res) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([], [WasmValkind.I32]) + linker.defineFuncUnchecked("nev:plugins/registers", + "is-replaying-commands", ty): + var instance = cast[ptr InstanceData](store.getData()) + let res = registersIsReplayingCommands(instance) + parameters[0].i32 = cast[int32](res) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], + []) + linker.defineFuncUnchecked("nev:plugins/registers", "set-register-text", + ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var text: string + var register: string + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[0].i32].addr) + text = newString(parameters[1].i32) + for i0 in 0 ..< text.len: + text[i0] = p0[i0] + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[2].i32].addr) + register = newString(parameters[3].i32) + for i0 in 0 ..< register.len: + register[i0] = p0[i0] + registersSetRegisterText(instance, text, register) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/registers", + "start-recording-commands", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var register: string + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[0].i32].addr) + register = newString(parameters[1].i32) + for i0 in 0 ..< register.len: + register[i0] = p0[i0] + registersStartRecordingCommands(instance, register) + if e.isErr: + return e diff --git a/src/language_server_command_line.nim b/src/language_server_command_line.nim index 8b6b00b01..5e423fcee 100644 --- a/src/language_server_command_line.nim +++ b/src/language_server_command_line.nim @@ -104,6 +104,24 @@ method getCompletions*(self: LanguageServerCommandLine, filename: string, locati documentation: CompletionItemDocumentationVariant.init(docs).some, ) + for (name, command) in self.commands.activeCommands.pairs: + var docs = "" + if self.events.commandInfos.getInfos(name).getSome(infos): + for i, info in infos: + if i > 0: + docs.add "\n" + docs.add &"[{info.context}] {info.keys} -> {info.command}" + docs.add "\n\n" + + docs.add command.description + + completions.add CompletionItem( + label: name, + kind: CompletionKind.Function, + detail: command.signature.some, + documentation: CompletionItemDocumentationVariant.init(docs).some, + ) + else: {.gcsafe.}: for (name, command) in self.commands.commands.pairs: diff --git a/src/layout.nim b/src/layout.nim index 22c8c8fa5..3081d11cc 100644 --- a/src/layout.nim +++ b/src/layout.nim @@ -629,8 +629,24 @@ proc showView*(self: LayoutService, view: View, slot: string = "", focus: bool = for v in self.layout.visibleLeafViews(): if v == view: return - discard self.layout.removeView(view) - self.addView(view, slot=slot, focus=false, addToHistory=addToHistory) + + if self.layout.contains(view): + var child = view + var p = self.layout.parentLayout(child) + while p != nil: + if not p.isVisible(child): + debugf"{p.desc}, {child.desc} not visible" + let activated = self.layout.tryActivateView proc(v: View): bool = + return v == child + debugf"activated: {activated}" + else: + debugf"{p.desc}, {child.desc} visible" + child = p + p = self.layout.parentLayout(child) + + else: + discard self.layout.removeView(view) + self.addView(view, slot=slot, focus=false, addToHistory=addToHistory) self.platform.requestRender() @@ -644,7 +660,7 @@ proc showView*(self: LayoutService, viewId: int32, slot: string = "", focus: boo proc showEditor*(self: LayoutService, editorId: EditorId, slot: string = "", focus: bool = true) {.expose("layout").} = ## Make the given editor visible - let editor = self.editors.getEditorForId(editorId).getOr: + let editor = self.editors.getEditorForId(editorId.EditorIdNew).getOr: log lvlError, &"No editor with id {editorId} exists" return @@ -672,7 +688,7 @@ proc getOrOpenEditor*(self: LayoutService, path: string): Option[EditorId] {.exp return EditorId.none if self.editors.createEditorForDocument(document).getSome(editor): - return editor.id.some + return editor.id.EditorId.some return EditorId.none @@ -689,7 +705,7 @@ proc tryOpenExisting*(self: LayoutService, path: string, appFile: bool = false, proc tryOpenExisting*(self: LayoutService, editor: EditorId, addToHistory = true, slot: string = ""): Option[DocumentEditor] = # debugf"tryOpenExisting '{editor}'" for i, view in self.allViews: - if view of EditorView and view.EditorView.editor.id == editor: + if view of EditorView and view.EditorView.editor.id == editor.EditorIdNew: log(lvlInfo, fmt"Reusing open editor in view {i}") self.showView(view, slot = slot) return view.EditorView.editor.some @@ -937,15 +953,15 @@ proc openNextView*(self: LayoutService) {.expose("layout").} = break proc openLastView*(self: LayoutService) {.expose("layout").} = - let hiddenViews = self.getHiddenViews() - if hiddenViews.len == 0: - return - - let view = hiddenViews.last - let slot = self.layout.activeLeafSlot() - log lvlInfo, &"openLastView viewId={view.id}, view={view.desc} in '{slot}'" - self.showView(view, slot) - self.platform.requestRender() + var res = self.allViews + for i in countdown(self.allViews.high, 0): + let view = self.allViews[i] + if not self.layout.contains(view): + let slot = self.layout.activeLeafSlot() + log lvlInfo, &"openLastView viewId={view.id}, view={view.desc} in '{slot}'" + self.showView(view, slot) + self.platform.requestRender() + break proc setLayout*(self: LayoutService, layout: string) {.expose("layout").} = if layout in self.layouts: diff --git a/src/layouts.nim b/src/layouts.nim index ccf311b97..18fd8a654 100644 --- a/src/layouts.nim +++ b/src/layouts.nim @@ -181,12 +181,17 @@ proc forEachVisibleView*(self: Layout, cb: proc(view: View): bool {.gcsafe, rais discard self.forEachVisibleViewImpl(cb) proc parentLayout*(self: Layout, view: View): Layout = - var res: Layout = nil - self.forEachView proc(v: View): bool = - if v of Layout and view in v.Layout.children: - res = v.Layout - return true - return res + for i, c in self.children: + if c == nil: + continue + if c == view: + return self + if c of Layout: + let p = c.Layout.parentLayout(view) + if p != nil: + return p + + return nil proc leafViews*(self: Layout): seq[View] = var res = newSeq[View]() @@ -202,6 +207,25 @@ proc visibleLeafViews*(self: Layout): seq[View] = res.add v return res +proc isVisible*(self: Layout, view: View): bool = + var res = false + self.forEachVisibleView proc(v: View): bool = + if v == view: + res = true + return true + return res + +proc contains*(self: Layout, view: View): bool = + for i, c in self.children: + if c == nil: + continue + if c == view: + return true + if c of Layout: + if c.Layout.contains(view): + return true + return false + method desc*(self: View): string {.base.} = "View" method desc*(self: Layout): string = "Layout" method desc*(self: CenterLayout): string = "CenterLayout" diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index ea44817bc..400d8c6b1 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -7,7 +7,7 @@ import text/[text_editor, text_document] import render_view, view import platform/platform, platform_service import config_provider, command_service -import plugin_service, document_editor, vfs, vfs_service, channel +import plugin_service, document_editor, vfs, vfs_service, channel, register, terminal_service import wasmtime, wit_host_module, plugin_api_base, wasi, plugin_thread_pool from scripting_api import nil @@ -38,15 +38,12 @@ type ProcessResource = object stderr: Arc[BaseChannel] type - ChannelRegistry* = object - lock: Lock - readChannels: Table[string, ReadChannelResource] - writeChannels: Table[string, WriteChannelResource] - HostContext* = ref object services: Services platform: Platform commands*: CommandService + registers*: Registers + terminals*: TerminalService editors*: DocumentEditorService layout*: LayoutService plugins*: PluginService @@ -55,7 +52,7 @@ type vfs*: VFS timer*: Timer - InstanceData = object of RootObj + InstanceData = object of InstanceDataWasi resources*: WasmModuleResources isMainThread: bool engine: ptr WasmEngineT @@ -68,7 +65,7 @@ type commands: seq[CommandId] namespace: string args: string - channels: Arc[ChannelRegistry] + # channels: Arc[ChannelRegistry] destroyRequested: bool timer*: Timer @@ -149,13 +146,15 @@ type linkerWasiFull: ptr LinkerT host: HostContext instances: seq[WasmModuleInstanceImpl] - channels: Arc[ChannelRegistry] + # channels: Arc[ChannelRegistry] method init*(self: PluginApi, services: Services, engine: ptr WasmEngineT) = self.host = HostContext() self.host.services = services self.host.platform = services.getService(PlatformService).get.platform self.host.commands = services.getService(CommandService).get + self.host.registers = services.getService(Registers).get + self.host.terminals = services.getService(TerminalService).get self.host.editors = services.getService(DocumentEditorService).get self.host.layout = services.getService(LayoutService).get self.host.plugins = services.getService(PluginService).get @@ -164,8 +163,8 @@ method init*(self: PluginApi, services: Services, engine: ptr WasmEngineT) = self.host.vfs = self.host.vfsService.vfs self.host.timer = startTimer() - self.channels = Arc[ChannelRegistry].new() - self.channels.getMut.lock.initLock() + # self.channels = Arc[ChannelRegistry].new() + # self.channels.getMut.lock.initLock() self.engine = engine self.linkerWasiNone = engine.newLinker() @@ -217,7 +216,10 @@ method createModule*(self: PluginApi, module: ptr ModuleT, plugin: Plugin): Wasm instanceData.getMut.permissions = plugin.permissions instanceData.getMut.namespace = plugin.manifest.id instanceData.getMut.host = self.host - instanceData.getMut.channels = self.channels + instanceData.getMut.stdin = newInMemoryChannel() + instanceData.getMut.stdout = newInMemoryChannel() + instanceData.getMut.stderr = newInMemoryChannel() + # instanceData.getMut.channels = self.channels instanceData.getMut.timer = startTimer() let ctx = instanceData.get.store.context @@ -268,6 +270,11 @@ method createModule*(self: PluginApi, module: ptr ModuleT, plugin: Plugin): Wasm self.instances.removeShift(instance) return + let options = scripting_api.CreateTerminalOptions( + group: plugin.manifest.id, + ) + self.host.layout.registerView(self.host.terminals.createTerminalView(instanceData.get.stdin, instanceData.get.stdout, options)) + return instance method destroyInstance*(self: PluginApi, instance: WasmModuleInstance) = @@ -293,7 +300,7 @@ method cloneInstance*(instance: ptr InstanceData): Arc[InstanceDataImpl] = instanceData.getMut.permissions = instance.permissions instanceData.getMut.namespace = instance.namespace instanceData.getMut.host = instance.host - instanceData.getMut.channels = instance.channels + # instanceData.getMut.channels = instance.channels instanceData.getMut.timer = startTimer() let ctx = instanceData.get.store.context @@ -363,7 +370,7 @@ proc editorActiveEditor(instance: ptr InstanceData): Option[Editor] = if instance.host == nil: return if instance.host.layout.tryGetCurrentEditorView().getSome(view): - return Editor(id: view.editor.DocumentEditor.idNew.uint64).some + return Editor(id: view.editor.DocumentEditor.id.uint64).some return Editor.none proc editorGetDocument(instance: ptr InstanceData; editor: Editor): Option[Document] = @@ -379,7 +386,7 @@ proc textEditorActiveTextEditor(instance: ptr InstanceData): Option[TextEditor] if instance.host == nil: return if instance.host.layout.tryGetCurrentEditorView().getSome(view) and view.editor of TextDocumentEditor: - return TextEditor(id: view.editor.TextDocumentEditor.idNew.uint64).some + return TextEditor(id: view.editor.TextDocumentEditor.id.uint64).some return TextEditor.none proc textEditorGetDocument(instance: ptr InstanceData; editor: TextEditor): Option[TextDocument] = @@ -395,7 +402,7 @@ proc textEditorAsTextEditor(instance: ptr InstanceData; editor: Editor): Option[ if instance.host == nil: return if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: - return TextEditor(id: editor.TextDocumentEditor.idNew.uint64).some + return TextEditor(id: editor.TextDocumentEditor.id.uint64).some return TextEditor.none proc textEditorAsTextDocument(instance: ptr InstanceData; document: Document): Option[TextDocument] = @@ -405,6 +412,67 @@ proc textEditorAsTextDocument(instance: ptr InstanceData; document: Document): O return TextDocument(id: text_document.TextDocument(document).id.uint64).some return TextDocument.none +proc textEditorLineLength(instance: ptr InstanceData; editor: TextEditor; line: int32): int32 = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + return editor.TextDocumentEditor.lineLength(line.int).int32 + +proc textEditorClearTabStops(instance: ptr InstanceData; editor: TextEditor): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.clearTabStops() + +proc textEditorSelectNextTabStop(instance: ptr InstanceData; editor: TextEditor): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.selectNextTabStop() + +proc textEditorSelectPrevTabStop(instance: ptr InstanceData; editor: TextEditor): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.selectPrevTabStop() + +proc textEditorUndo(instance: ptr InstanceData; editor: TextEditor, checkpoint: sink string): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.undo(checkpoint) + +proc textEditorRedo(instance: ptr InstanceData; editor: TextEditor, checkpoint: sink string): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.redo(checkpoint) + +proc textEditorAddNextCheckpoint(instance: ptr InstanceData; editor: TextEditor, checkpoint: sink string): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.addNextCheckpoint(checkpoint) + +proc textEditorCopy(instance: ptr InstanceData; editor: TextEditor, register: sink string, inclusiveEnd: bool): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.copy(register, inclusiveEnd) + +proc textEditorPaste(instance: ptr InstanceData; editor: TextEditor, register: sink string, inclusiveEnd: bool): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.paste(register, inclusiveEnd) + +proc textEditorApplyMove(instance: ptr InstanceData; editor: TextEditor; selection: Selection; move: sink string; count: int32; wrap: bool; includeEol: bool): seq[Selection] = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + let textEditor = editor.TextDocumentEditor + return @[textEditor.getSelectionForMove(selection.last.toInternal, move, count).toWasm] + proc textEditorSetSelection(instance: ptr InstanceData; editor: TextEditor; s: Selection): void = if instance.host == nil: return @@ -412,6 +480,13 @@ proc textEditorSetSelection(instance: ptr InstanceData; editor: TextEditor; s: S let textEditor = editor.TextDocumentEditor textEditor.selection = ((s.first.line.int, s.first.column.int), (s.last.line.int, s.last.column.int)) +proc textEditorSetSelections(instance: ptr InstanceData; editor: TextEditor; s: sink seq[Selection]): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + let textEditor = editor.TextDocumentEditor + textEditor.selections = s.mapIt(it.toInternal) + proc textEditorGetSelection(instance: ptr InstanceData; editor: TextEditor): Selection = if instance.host == nil: return @@ -422,6 +497,14 @@ proc textEditorGetSelection(instance: ptr InstanceData; editor: TextEditor): Sel else: Selection(first: Cursor(line: 1, column: 2), last: Cursor(line: 6, column: 9)) +proc textEditorGetSelections(instance: ptr InstanceData; editor: TextEditor): seq[Selection] = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + let textEditor = editor.TextDocumentEditor + let s = textEditor.selection + return editor.TextDocumentEditor.selections.mapIt(it.toWasm) + proc textEditorEdit(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection]; contents: sink seq[string]): seq[Selection] = if instance.host == nil: return @@ -442,6 +525,18 @@ proc textEditor_addModeChangedHandler(instance: ptr InstanceData, fun: uint32): log lvlError, "Failed to call handleModeChanged: " & res.err.msg return 0 +proc textEditorSetMode(instance: ptr InstanceData; editor: TextEditor, mode: sink string, exclusive: bool): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.setMode(mode, exclusive) + +proc textEditorMode(instance: ptr InstanceData; editor: TextEditor): string = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + return editor.TextDocumentEditor.mode() + proc textEditorCommand(instance: ptr InstanceData; editor: TextEditor, name: sink string, arguments: sink string): Result[string, CommandError] = if instance.host == nil: return @@ -453,6 +548,24 @@ proc textEditorCommand(instance: ptr InstanceData; editor: TextEditor, name: sin return results.ok($res) result.err(CommandError.NotFound) +proc textEditorRecordCurrentCommand(instance: ptr InstanceData; editor: TextEditor; registers: sink seq[string]): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.recordCurrentCommand(registers) + +proc textEditorGetUsage(instance: ptr InstanceData; editor: TextEditor): string = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + return editor.TextDocumentEditor.getUsage() + +proc textEditorGetRevision(instance: ptr InstanceData; editor: TextEditor): int32 = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + return editor.TextDocumentEditor.getRevision().int32 + proc textEditorContent(instance: ptr InstanceData; editor: TextEditor): RopeResource = if instance.host == nil: return @@ -626,6 +739,21 @@ proc coreSpawnBackground(instance: ptr InstanceData, args: sink string, executor proc coreFinishBackground(instance: ptr InstanceData) = instance.destroyRequested = true +proc registersIsReplayingCommands(instance: ptr InstanceData): bool = + if instance.host == nil: + return false + return instance.host.registers.isReplayingCommands() + +proc registersSetRegisterText(instance: ptr InstanceData; text: sink string; register: sink string): void = + if instance.host == nil: + return + instance.host.registers.setRegisterText(text, register) + +proc registersStartRecordingCommands(instance: ptr InstanceData; register: sink string): void = + if instance.host == nil: + return + instance.host.registers.startRecordingCommands(register) + proc commandsDefineCommand(instance: ptr InstanceData, name: sink string, active: bool, docs: sink string, params: sink seq[(string, string)], returnType: sink string, context: sink string; fun: uint32; data: uint32): void = if instance.host == nil: @@ -643,7 +771,10 @@ proc commandsDefineCommand(instance: ptr InstanceData, name: sink string, active return res ), ) - instance.commands.add(instance.host.commands.registerCommand(command, override = true)) + if active: + instance.commands.add(instance.host.commands.registerActiveCommand(command, override = true)) + else: + instance.commands.add(instance.host.commands.registerCommand(command, override = true)) proc commandsRunCommand(instance: ptr InstanceData, name: sink string, arguments: sink string): Result[string, CommandError] = if instance.host == nil: @@ -852,38 +983,46 @@ proc channelWriteBytes(instance: ptr InstanceData; self: var WriteChannelResourc discard proc channelReadChannelOpen(instance: ptr InstanceData; path: sink string): Option[ReadChannelResource] = - let channels = instance.channels.getMutUnsafe.addr - withLock(channels.lock): - var chan: ReadChannelResource - if channels.readChannels.take(path, chan): - return chan.some - return ReadChannelResource.none + let chan = openGlobalReadChannel(path) + if chan.isSome: + return ReadChannelResource(channel: chan.get).some + # let channels = instance.channels.getMutUnsafe.addr + # withLock(channels.lock): + # var chan: ReadChannelResource + # if channels.readChannels.take(path, chan): + # return chan.some + # return ReadChannelResource.none proc channelReadChannelMount(instance: ptr InstanceData; channel: sink ReadChannelResource; path: sink string; unique: bool): string = - var path = path - if unique: - path.add "-" & $newId() - let channels = instance.channels.getMutUnsafe.addr - withLock(channels.lock): - channels.readChannels[path] = channel - return path + mountGlobalReadChannel(path, channel.channel, unique) + # var path = path + # if unique: + # path.add "-" & $newId() + # let channels = instance.channels.getMutUnsafe.addr + # withLock(channels.lock): + # channels.readChannels[path] = channel + # return path proc channelWriteChannelOpen(instance: ptr InstanceData; path: sink string): Option[WriteChannelResource] = - let channels = instance.channels.getMutUnsafe.addr - withLock(channels.lock): - var chan: WriteChannelResource - if channels.writeChannels.take(path, chan): - return chan.some - return WriteChannelResource.none + let chan = openGlobalWriteChannel(path) + if chan.isSome: + return WriteChannelResource(channel: chan.get).some + # let channels = instance.channels.getMutUnsafe.addr + # withLock(channels.lock): + # var chan: WriteChannelResource + # if channels.writeChannels.take(path, chan): + # return chan.some + # return WriteChannelResource.none proc channelWriteChannelMount(instance: ptr InstanceData; channel: sink WriteChannelResource; path: sink string; unique: bool): string = - var path = path - if unique: - path.add "-" & $newId() - let channels = instance.channels.getMutUnsafe.addr - withLock(channels.lock): - channels.writeChannels[path] = channel - return path + mountGlobalWriteChannel(path, channel.channel, unique) + # var path = path + # if unique: + # path.add "-" & $newId() + # let channels = instance.channels.getMutUnsafe.addr + # withLock(channels.lock): + # channels.writeChannels[path] = channel + # return path proc channelNewInMemoryChannel(instance: ptr InstanceData): (ReadChannelResource, WriteChannelResource) = var c = newInMemoryChannel() diff --git a/src/plugin_api/wasi.nim b/src/plugin_api/wasi.nim index 89b603955..cec63e2ce 100644 --- a/src/plugin_api/wasi.nim +++ b/src/plugin_api/wasi.nim @@ -1,5 +1,7 @@ import std/[macros, strutils, os, strformat] import misc/[custom_logger, util, macro_utils] +import nimsumtree/arc +import channel import wasmtime @@ -12,6 +14,13 @@ type GetMemoryImpl* = proc(caller: ptr CallerT, store: ptr ContextT): WasmMemory var stdout {.threadvar.}: string var stderr {.threadvar.}: string +type + InstanceDataWasi* = object of RootObj + stdin*: Arc[BaseChannel] + stdout*: Arc[BaseChannel] + stderr*: Arc[BaseChannel] + + type WasiIovec = object data: WasmPtr @@ -365,6 +374,8 @@ proc definePluginWasi*(linker: ptr LinkerT, getMemory: GetMemoryImpl): WasmtimeR discard linker.defineFuncUnchecked("wasi_snapshot_preview1", "fd_write", newFunctype([WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], [WasmValkind.I32])): # let mem = getMemory(caller, store) + var instance = cast[ptr InstanceDataWasi](store.getData()) + var mainMemory = caller.getExport("memory") let mem = initWasmMemory(store, mainMemory.get.of_field.memory.addr) @@ -392,11 +403,24 @@ proc definePluginWasi*(linker: ptr LinkerT, getMemory: GetMemoryImpl): WasmtimeR case fd of 1: + if file[].len > 0: + try: + instance.stdout.write(file[]) + except IOError: + discard + if file[].endsWith("\n"): file[].setLen(file[].len - 1) if file[].len > 0: log lvlNotice, file[] + of 2: + if file[].len > 0: + try: + instance.stderr.write(file[]) + except IOError: + discard + if file[].endsWith("\n"): file[].setLen(file[].len - 1) if file[].len > 0: @@ -432,3 +456,26 @@ proc definePluginWasi*(linker: ptr LinkerT, getMemory: GetMemoryImpl): WasmtimeR parameters[0].i32 = WasiErrno.Access.int32 + discard linker.defineFuncUnchecked("wasi_snapshot_preview1", "environ_sizes_get", newFunctype([WasmValkind.I32, WasmValkind.I32], [WasmValkind.I32])): + echo "environ_sizes_get" + + discard linker.defineFuncUnchecked("wasi_snapshot_preview1", "environ_get", newFunctype([WasmValkind.I32, WasmValkind.I32], [WasmValkind.I32])): + echo "environ_get" + + discard linker.defineFuncUnchecked("env", "__syscall_chmod", newFunctype([WasmValkind.I32, WasmValkind.I32], [WasmValkind.I32])): + echo "__syscall_chmod" + + discard linker.defineFuncUnchecked("env", "__syscall_fadvise64", newFunctype([WasmValkind.I32, WasmValkind.I64, WasmValkind.I64, WasmValkind.I32], [WasmValkind.I32])): + echo "__syscall_fadvise64" + + discard linker.defineFuncUnchecked("env", "__syscall_readlinkat", newFunctype([WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], [WasmValkind.I32])): + echo "__syscall_readlinkat" + + discard linker.defineFuncUnchecked("env", "__syscall_renameat", newFunctype([WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], [WasmValkind.I32])): + echo "__syscall_renameat" + + discard linker.defineFuncUnchecked("env", "__syscall_symlinkat", newFunctype([WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], [WasmValkind.I32])): + echo "__syscall_symlinkat" + + discard linker.defineFuncUnchecked("env", "__syscall_unlinkat", newFunctype([WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], [WasmValkind.I32])): + echo "__syscall_unlinkat" diff --git a/src/scripting_api.nim b/src/scripting_api.nim index cb9dff133..b1747eaea 100644 --- a/src/scripting_api.nim +++ b/src/scripting_api.nim @@ -11,7 +11,7 @@ else: export vmath type - EditorId* = int + EditorId* = uint64 EditorType* = enum Text, Ast, Model, Other TextDocumentEditor* = object diff --git a/src/terminal_service.nim b/src/terminal_service.nim index ed09c00e2..c9a9dbdf6 100644 --- a/src/terminal_service.nim +++ b/src/terminal_service.nim @@ -1,13 +1,13 @@ import std/[os, streams, strutils, sequtils, strformat, typedthreads, tables, json, colors] import vmath import chroma -import nimsumtree/[rope] +import nimsumtree/[rope, arc] import misc/[custom_logger, util, custom_unicode, custom_async, event, timer, disposable_ref, myjsonutils] import dispatch_tables, config_provider, events, view, layout, service, platform_service, selector_popup, vfs_service, vfs, theme import scripting/expose import platform/[tui, platform] import finder/[finder, previewer] -import vterm, input, input_api, register, command_service +import vterm, input, input_api, register, command_service, channel import scripting_api as api except DocumentEditor, TextDocumentEditor, AstDocumentEditor, ModelDocumentEditor, Popup, SelectorPopup from scripting_api import RunInTerminalOptions, CreateTerminalOptions @@ -204,6 +204,8 @@ type msg: string OsHandles = object + inputWriteEvent2: ThreadSignalPtr + outputReadEvent2: ThreadSignalPtr when defined(windows): hpcon: HPCON inputWriteHandle: HANDLE @@ -219,10 +221,13 @@ type childPid: Pid ThreadState = object - vterm: ptr VTerm - screen: ptr VTermScreen + vtermInternal: pointer + screenInternal: pointer inputChannel: ptr Channel[InputEvent] outputChannel: ptr Channel[OutputEvent] + useChannels: bool + readChannel: Arc[BaseChannel] + writeChannel: Arc[BaseChannel] width: int height: int scrollY: int = 0 @@ -259,6 +264,7 @@ type onRope: Event[ptr Rope] onUpdated: Event[void] + useChannels: bool handles: OsHandles TerminalView* = ref object of View @@ -287,6 +293,9 @@ type proc createTerminalView(self: TerminalService, command: string, options: CreateTerminalOptions, id: Id = idNone()): TerminalView +proc vterm(state: ThreadState): ptr VTerm = cast[ptr VTerm](state.vtermInternal) +proc screen(state: ThreadState): ptr VTermScreen = cast[ptr VTermScreen](state.screenInternal) + proc createRope*(state: var ThreadState, scrollback: bool = true): Rope = var cell: VTermScreenCell var rope = Rope.new() @@ -461,15 +470,22 @@ proc handleOutputChannel(self: TerminalService, terminal: Terminal) {.async.} = proc handleOutput(s: cstring; len: csize_t; user: pointer) {.cdecl.} = if len > 0: let state = cast[ptr ThreadState](user) - when defined(windows): - var bytesWritten: int32 - if WriteFile(state[].handles.inputWriteHandle, s[0].addr, len.cint, bytesWritten.addr, nil) == 0: - echo "Failed to write data to shell: ", newOSError(osLastError()).msg - if bytesWritten.int < len.int: - echo "--------------------------------------------" - echo "failed to write all bytes to shell" + if state.useChannels: + discard + try: + state.writeChannel.write(s.toOpenArray(0, len.int - 1)) + except IOError as e: + echo "Failed to write data to channel: ", e.msg else: - discard write(state.handles.masterFd, s[0].addr, len.int) + when defined(windows): + var bytesWritten: int32 + if WriteFile(state[].handles.inputWriteHandle, s[0].addr, len.cint, bytesWritten.addr, nil) == 0: + echo "Failed to write data to shell: ", newOSError(osLastError()).msg + if bytesWritten.int < len.int: + echo "--------------------------------------------" + echo "failed to write all bytes to shell" + else: + discard write(state.handles.masterFd, s[0].addr, len.int) proc handleInputEvents(state: var ThreadState) = while state.inputChannel[].peek() > 0: @@ -517,11 +533,12 @@ proc handleInputEvents(state: var ThreadState) = state.vterm.setSize(state.height.cint, state.width.cint) state.screen.flushDamage() - when defined(windows): - ResizePseudoConsole(state.handles.hpcon, wincon.COORD(X: state.width.SHORT, Y: state.height.SHORT)) - else: - var winp: IOctl_WinSize = IOctl_WinSize(ws_row: state.height.cushort, ws_col: state.width.cushort, ws_xpixel: 500.cushort, ws_ypixel: 500.cushort) - discard termios.ioctl(state.handles.masterFd, TIOCSWINSZ, winp.addr) + if not state.useChannels: + when defined(windows): + ResizePseudoConsole(state.handles.hpcon, wincon.COORD(X: state.width.SHORT, Y: state.height.SHORT)) + else: + var winp: IOctl_WinSize = IOctl_WinSize(ws_row: state.height.cushort, ws_col: state.width.cushort, ws_xpixel: 500.cushort, ws_ypixel: 500.cushort) + discard termios.ioctl(state.handles.masterFd, TIOCSWINSZ, winp.addr) state.dirty = true @@ -545,69 +562,87 @@ proc handleInputEvents(state: var ThreadState) = echo &"Failed to send input: {getCurrentExceptionMsg()}" proc handleProcessOutput(state: var ThreadState, buffer: var string) {.raises: [OSError].} = - when defined(windows): - template handleData(data: untyped, bytesToWrite: int): untyped = - let written = state.vterm.writeInput(data, bytesToWrite.csize_t).int - if written != bytesToWrite: - echo "fix me: vterm.nim.terminalThread vterm.writeInput" - assert written == bytesToWrite, "fix me: vterm.nim.terminalThread vterm.writeInput" - - state.screen.flushDamage() - state.dirty = true - - var bytesRead: DWORD = 0 - if state.waitingForOvelapped: - if GetOverlappedResult(state.handles.outputReadHandle, state.outputOverlapped.addr, bytesRead.addr, 0) != 0: - state.waitingForOvelapped = false - handleData(buffer.cstring, bytesRead.int) + if state.useChannels: + discard + try: + buffer.setLen(state.readChannel.flushRead()) + if buffer.len > 0: + let read = state.readChannel.read(cast[ptr UncheckedArray[uint8]](buffer[0].addr).toOpenArray(0, buffer.high)) + buffer.setLen(read) - else: - let error = osLastError() - case error.int32 - of ERROR_HANDLE_EOF: - state.waitingForOvelapped = false - return + let written = state.vterm.writeInput(buffer.cstring, read.csize_t).int + if written != read: + echo "fix me: vterm.nim.terminalThread vterm.writeInput" + assert written == read, "fix me: vterm.nim.terminalThread vterm.writeInput" - of ERROR_IO_INCOMPLETE: - state.waitingForOvelapped = true - return + state.screen.flushDamage() + state.dirty = true + except IOError as e: + discard + else: + when defined(windows): + template handleData(data: untyped, bytesToWrite: int): untyped = + let written = state.vterm.writeInput(data, bytesToWrite.csize_t).int + if written != bytesToWrite: + echo "fix me: vterm.nim.terminalThread vterm.writeInput" + assert written == bytesToWrite, "fix me: vterm.nim.terminalThread vterm.writeInput" + + state.screen.flushDamage() + state.dirty = true + + var bytesRead: DWORD = 0 + if state.waitingForOvelapped: + if GetOverlappedResult(state.handles.outputReadHandle, state.outputOverlapped.addr, bytesRead.addr, 0) != 0: + state.waitingForOvelapped = false + handleData(buffer.cstring, bytesRead.int) else: - raiseOSError(error) + let error = osLastError() + case error.int32 + of ERROR_HANDLE_EOF: + state.waitingForOvelapped = false + return - buffer.setLen(bufferSize) - state.outputOverlapped.hEvent = state.handles.outputReadEvent - if ReadFile(state.handles.outputReadHandle, buffer[0].addr, buffer.len.DWORD, bytesRead.addr, state.outputOverlapped.addr) != 0: - handleData(buffer.cstring, bytesRead.int) - state.waitingForOvelapped = false - return + of ERROR_IO_INCOMPLETE: + state.waitingForOvelapped = true + return - let error = osLastError() - case error.int32 - of ERROR_HANDLE_EOF: - state.waitingForOvelapped = false - return + else: + raiseOSError(error) - of ERROR_IO_PENDING: - state.waitingForOvelapped = true - return + buffer.setLen(bufferSize) + state.outputOverlapped.hEvent = state.handles.outputReadEvent + if ReadFile(state.handles.outputReadHandle, buffer[0].addr, buffer.len.DWORD, bytesRead.addr, state.outputOverlapped.addr) != 0: + handleData(buffer.cstring, bytesRead.int) + state.waitingForOvelapped = false + return - else: - raiseOSError(error) + let error = osLastError() + case error.int32 + of ERROR_HANDLE_EOF: + state.waitingForOvelapped = false + return - else: - buffer.setLen(bufferSize) - let n = read(state.handles.masterFd, buffer[0].addr, buffer.len) - if n > 0: - let written = state.vterm.writeInput(buffer.cstring, n.csize_t).int - if written != n: - echo "fix me: vterm.nim.terminalThread vterm.writeInput" - assert written == n, "fix me: vterm.nim.terminalThread vterm.writeInput" - - state.screen.flushDamage() - state.dirty = true - elif n == 0: - state.processTerminated = true + of ERROR_IO_PENDING: + state.waitingForOvelapped = true + return + + else: + raiseOSError(error) + + else: + buffer.setLen(bufferSize) + let n = read(state.handles.masterFd, buffer[0].addr, buffer.len) + if n > 0: + let written = state.vterm.writeInput(buffer.cstring, n.csize_t).int + if written != n: + echo "fix me: vterm.nim.terminalThread vterm.writeInput" + assert written == n, "fix me: vterm.nim.terminalThread vterm.writeInput" + + state.screen.flushDamage() + state.dirty = true + elif n == 0: + state.processTerminated = true proc log*(state: ThreadState, str: string) = if state.enableLog: @@ -713,68 +748,80 @@ proc terminalThread(s: ThreadState) {.thread, nimcall.} = try: while true: - when defined(windows): - var handles = [state.handles.inputWriteEvent, state.handles.outputReadEvent, state.handles.processInfo.hProcess] - let res = WaitForMultipleObjects(handles.len.DWORD, handles[0].addr, FALSE, INFINITE) - if res == WAIT_FAILED: - discard - elif res == WAIT_TIMEOUT: + if state.useChannels: + try: + let waitInput = state.handles.inputWriteEvent2.wait() + let waitOutput = state.readChannel.get.signal.wait() + waitFor(waitInput or waitOutput) + state.handleProcessOutput(buffer) + state.handleInputEvents() + except AsyncError, CancelledError: + # echo "async error ", getCurrentExceptionMsg() discard - else: - if res >= WAIT_ABANDONED_0: - # let index = res - WAIT_ABANDONED_0 - discard - elif res >= WAIT_OBJECT_0: - let index = res - WAIT_OBJECT_0 - assert index >= 0 and index < 3 - - if index == 2: - # Process ended - var exitCodeC: DWORD = 0 - discard GetExitCodeProcess(state.handles.processInfo.hProcess, exitCodeC.addr) - exitCode = exitCodeC.int.some - state.processTerminated = true - break - - state.handleProcessOutput(buffer) - state.handleInputEvents() else: - var fds = [ - TPollfd(fd: state.handles.masterFd, events: POLLIN), - TPollfd(fd: state.handles.inputWriteEventFd, events: POLLIN), - ] - - let res = poll(fds[0].addr, fds.len.Tnfds, -1) - if res < 0: - break - - proc wifexited(status: cint): cint = {.emit: [result, " = WIFEXITED(", status, ");"].} - proc wexitstatus(status: cint): cint = {.emit: [result, " = WEXITSTATUS(", status, ");"].} - - var status: cint - let waitRes = waitpid(state.handles.childPid, status, WNOHANG) - if waitRes == state.handles.childPid: - state.processTerminated = true - var exitCodeC: cint - if wifexited(status) != 0: - exitCodeC = wexitstatus(status) - exitCode = exitCodeC.int.some - break - - if (fds[0].revents and POLLIN) != 0: + when defined(windows): + var handles = [state.handles.inputWriteEvent, state.handles.outputReadEvent, state.handles.processInfo.hProcess] + let res = WaitForMultipleObjects(handles.len.DWORD, handles[0].addr, FALSE, INFINITE) + if res == WAIT_FAILED: + discard + elif res == WAIT_TIMEOUT: + discard + else: + if res >= WAIT_ABANDONED_0: + # let index = res - WAIT_ABANDONED_0 + discard + elif res >= WAIT_OBJECT_0: + let index = res - WAIT_OBJECT_0 + assert index >= 0 and index < 3 + + if index == 2: + # Process ended + var exitCodeC: DWORD = 0 + discard GetExitCodeProcess(state.handles.processInfo.hProcess, exitCodeC.addr) + exitCode = exitCodeC.int.some + state.processTerminated = true + break + state.handleProcessOutput(buffer) - if state.processTerminated: + state.handleInputEvents() + + else: + var fds = [ + TPollfd(fd: state.handles.masterFd, events: POLLIN), + TPollfd(fd: state.handles.inputWriteEventFd, events: POLLIN), + ] + + let res = poll(fds[0].addr, fds.len.Tnfds, -1) + if res < 0: + break + + proc wifexited(status: cint): cint = {.emit: [result, " = WIFEXITED(", status, ");"].} + proc wexitstatus(status: cint): cint = {.emit: [result, " = WEXITSTATUS(", status, ");"].} + + var status: cint + let waitRes = waitpid(state.handles.childPid, status, WNOHANG) + if waitRes == state.handles.childPid: + state.processTerminated = true var exitCodeC: cint if wifexited(status) != 0: exitCodeC = wexitstatus(status) exitCode = exitCodeC.int.some break - if (fds[1].revents and POLLIN) != 0: - var b: uint64 = 0 - discard read(state.handles.inputWriteEventFd, b.addr, sizeof(typeof(b))) - state.handleInputEvents() + if (fds[0].revents and POLLIN) != 0: + state.handleProcessOutput(buffer) + if state.processTerminated: + var exitCodeC: cint + if wifexited(status) != 0: + exitCodeC = wexitstatus(status) + exitCode = exitCodeC.int.some + break + + if (fds[1].revents and POLLIN) != 0: + var b: uint64 = 0 + discard read(state.handles.inputWriteEventFd, b.addr, sizeof(typeof(b))) + state.handleInputEvents() if state.terminateRequested: break @@ -798,6 +845,7 @@ proc sendEvent(self: Terminal, event: InputEvent) = if self.threadTerminated: return self.inputChannel[].send(event) + discard self.handles.inputWriteEvent2.fireSync() when defined(windows): discard SetEvent(self.handles.inputWriteEvent) else: @@ -819,14 +867,17 @@ proc terminate*(self: Terminal) {.async.} = # todo: use async signals await sleepAsync(10.milliseconds) - when defined(windows): - CloseHandle(self.handles.inputWriteEvent) - CloseHandle(self.handles.outputReadEvent) - CloseHandle(self.handles.inputWriteHandle) - CloseHandle(self.handles.outputReadHandle) - HeapFree(GetProcessHeap(), 0, self.handles.startupInfo.lpAttributeList) - else: - discard close(self.handles.inputWriteEventFd) + discard self.handles.inputWriteEvent2.close() + discard self.handles.outputReadEvent2.close() + if not self.useChannels: + when defined(windows): + CloseHandle(self.handles.inputWriteEvent) + CloseHandle(self.handles.outputReadEvent) + CloseHandle(self.handles.inputWriteHandle) + CloseHandle(self.handles.outputReadHandle) + HeapFree(GetProcessHeap(), 0, self.handles.startupInfo.lpAttributeList) + else: + discard close(self.handles.inputWriteEventFd) self.inputChannel[].close() self.inputChannel.deallocShared() @@ -894,6 +945,8 @@ proc createTerminal*(self: TerminalService, width: int, height: int, command: st hpcon: hPC, inputWriteHandle: inputWriteHandle, outputReadHandle: outputReadHandle, + inputWriteEvent2: ThreadSignalPtr.new().value, + outputReadEvent2: ThreadSignalPtr.new().value, inputWriteEvent: inputWriteEvent, outputReadEvent: outputReadEvent, processInfo: pi, @@ -942,6 +995,8 @@ proc createTerminal*(self: TerminalService, width: int, height: int, command: st raise newOSError(osLastError(), "execlp") let handles = OsHandles( + inputWriteEvent2: ThreadSignalPtr.new().value, + outputReadEvent2: ThreadSignalPtr.new().value, masterFd: master_fd, slaveFd: slave_fd, inputWriteEventFd: inputWriteEventFd, @@ -976,8 +1031,8 @@ proc createTerminal*(self: TerminalService, width: int, height: int, command: st asyncSpawn self.handleOutputChannel(result) var threadState = ThreadState( - vterm: vterm, - screen: screen, + vtermInternal: vterm, + screenInternal: screen, inputChannel: result.inputChannel, outputChannel: result.outputChannel, width: width, @@ -989,6 +1044,59 @@ proc createTerminal*(self: TerminalService, width: int, height: int, command: st result.thread.createThread(terminalThread, threadState) +proc createTerminal*(self: TerminalService, width: int, height: int, writeChannel: Arc[BaseChannel], readChannel: Arc[BaseChannel], autoRunCommand: string = ""): Terminal {.raises: [OSError, IOError, ResourceExhaustedError].} = + let id = self.idCounter + inc self.idCounter + + let handles = OsHandles( + inputWriteEvent2: ThreadSignalPtr.new().value, + outputReadEvent2: ThreadSignalPtr.new().value, + ) + + let vterm = VTerm.new(height.cint, width.cint) + if vterm == nil: + raise newException(IOError, "Failed to init VTerm") + + vterm.setUtf8(1) + + let screen = vterm.screen() + screen.reset(1) + screen.setDamageMerge(VTERM_DAMAGE_SCROLL) + + result = Terminal( + id: id, + autoRunCommand: autoRunCommand, + handles: handles, + useChannels: true, + ) + + proc createChannel[T](channel: var ptr[Channel[T]]) = + channel = cast[ptr Channel[T]](allocShared0(sizeof(Channel[T]))) + channel[].open() + + result.inputChannel.createChannel() + result.outputChannel.createChannel() + + result.terminalBuffer.initTerminalBuffer(width, height) + asyncSpawn self.handleOutputChannel(result) + + var threadState = ThreadState( + vtermInternal: vterm, + screenInternal: screen, + inputChannel: result.inputChannel, + outputChannel: result.outputChannel, + width: width, + height: height, + cursor: (0, 0, true, true), + autoRunCommand: autoRunCommand, + handles: handles, + useChannels: true, + writeChannel: writeChannel, + readChannel: readChannel, + ) + + result.thread.createThread(terminalThread, threadState) + proc handleAction(self: TerminalService, view: TerminalView, action: string, arg: string): Option[string] proc handleInput(self: TerminalService, view: TerminalView, text: string) proc handleKey(self: TerminalService, view: TerminalView, input: int64, modifiers: Modifiers) @@ -1052,6 +1160,8 @@ method desc*(self: TerminalView): string = &"TerminalView" method kind*(self: TerminalView): string = "terminal" method display*(self: TerminalView): string = &"term://{self.terminal.command} - {self.terminal.group}" method saveState*(self: TerminalView): JsonNode = + if self.terminal.useChannels: + return nil result = newJObject() result["kind"] = self.kind.toJson result["id"] = self.id.toJson @@ -1216,6 +1326,47 @@ proc createTerminalView(self: TerminalService, command: string, options: CreateT except: log lvlError, &"Failed to create terminal: {getCurrentExceptionMsg()}" +proc createTerminalView*(self: TerminalService, stdin: Arc[BaseChannel], stdout: Arc[BaseChannel], options: CreateTerminalOptions, id: Id = idNone()): TerminalView = + try: + let term = self.createTerminal(80, 50, stdin, stdout, options.autoRunCommand) + term.group = options.group + term.setTheme(self.themes.theme) + + let view = TerminalView( + mId: id, + terminals: self, + terminal: term, + closeOnTerminate: options.closeOnTerminate, + slot: options.slot, + ) + + if options.mode.getSome(mode): + view.mode = mode + else: + view.mode = self.settings.defaultMode.get() + self.updateModeEventHandlers(view) + + discard term.onUpdated.subscribe proc() = + self.services.getService(PlatformService).get.platform.requestRender() + view.markDirty() + + discard term.onTerminated.subscribe proc(exitCode: Option[int]) = + if not view.open: + return + log lvlInfo, &"Terminal process '' terminated with exit code {exitCode}" + view.mode = self.settings.defaultMode.get() + self.updateModeEventHandlers(view) + if view.closeOnTerminate: + self.layout.closeView(view) + view.markDirty() + self.requestRender() + + self.terminals[term.id] = view + + return view + except: + log lvlError, &"Failed to create terminal: {getCurrentExceptionMsg()}" + proc getTerminalService*(): Option[TerminalService] = {.gcsafe.}: if gServices.isNil: return TerminalService.none diff --git a/src/text/language/debugger.nim b/src/text/language/debugger.nim index 0c0c9723c..7c296353d 100644 --- a/src/text/language/debugger.nim +++ b/src/text/language/debugger.nim @@ -1064,7 +1064,7 @@ proc applyBreakpointSignsToEditor(self: Debugger, editor: TextDocumentEditor) = proc toggleBreakpointAt*(self: Debugger, editorId: EditorId, line: int) {.expose("debugger").} = ## Line is 0-based - if self.editors.getEditorForId(editorId).getSome(editor) and editor of TextDocumentEditor: + if self.editors.getEditorForId(editorId.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: let path = editor.TextDocumentEditor.document.filename if not self.breakpoints.contains(path): self.breakpoints[path] = @[] @@ -1085,7 +1085,7 @@ proc toggleBreakpointAt*(self: Debugger, editorId: EditorId, line: int) {.expose proc toggleBreakpoint*(self: Debugger) {.expose("debugger").} = if self.layout.tryGetCurrentEditorView().getSome(view) and view.editor of TextDocumentEditor: - self.toggleBreakpointAt(view.editor.id, view.editor.TextDocumentEditor.selection.last.line) + self.toggleBreakpointAt(view.editor.id.EditorId, view.editor.TextDocumentEditor.selection.last.line) proc removeBreakpoint*(self: Debugger, path: string, line: int) {.expose("debugger").} = ## Line is 1-based diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index 104f54dd1..e5b365f06 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -1181,7 +1181,7 @@ method handleScroll*(self: TextDocumentEditor, scroll: Vec2, mousePosWindow: Vec proc getTextDocumentEditor(wrapper: api.TextDocumentEditor): Option[TextDocumentEditor] = {.gcsafe.}: if gServices.getService(DocumentEditorService).getSome(editors): - if editors.getEditorForId(wrapper.id).getSome(editor): + if editors.getEditorForId(wrapper.id.EditorIdNew).getSome(editor): if editor of TextDocumentEditor: return editor.TextDocumentEditor.some return TextDocumentEditor.none @@ -3122,6 +3122,9 @@ proc getSelectionForMove*(self: TextDocumentEditor, cursor: Cursor, move: string let line = self.document.numLines - 1 result.last = (line, self.document.lineLength(line)) + of "column": + result = self.doMoveCursorColumn(cursor, count).toSelection + of "prev-find-result": result = self.getPrevFindResult(cursor, count) @@ -3173,7 +3176,7 @@ proc getSelectionForMove*(self: TextDocumentEditor, cursor: Cursor, move: string result = cursor.toSelection let cursorJson = self.plugins.invokeAnyCallback("editor.text.custom-move", %*{ - "editor": self.id, + "editor": self.id.EditorId, "move": move, "cursor": cursor.toJson, "count": count, @@ -3410,7 +3413,7 @@ proc openFileAt(self: TextDocumentEditor, filename: string, location: Option[Sel self.updateTargetColumn(Last) self.centerCursor() self.setNextSnapBehaviour(ScrollSnapBehaviour.MinDistanceOffscreen) - self.layout.showEditor(self.id) + self.layout.showEditor(self.id.EditorId) else: let editor = self.layout.openFile(filename) @@ -4706,7 +4709,17 @@ proc handleActionInternal(self: TextDocumentEditor, action: string, args: JsonNo # debugf"[textedit] handleAction {action}, '{args}'" var args = args.copy - args.elems.insert api.TextDocumentEditor(id: self.id).toJson, 0 + args.elems.insert api.TextDocumentEditor(id: self.id.EditorId).toJson, 0 + + if self.commands.activeCommands.contains(action): + try: + let res = self.commands.activeCommands[action].execute(args.elems.mapIt($it).join(" ")) + if res == "": + return newJNull().some + return res.parseJson().some + except CatchableError as e: + log lvlError, &"Failed to execute command '{action} {args}': {e.msg}" + return newJNull().some block: let res = self.plugins.invokeAnyCallback(action, args) diff --git a/wit/v0/api.wit b/wit/v0/api.wit index 5f4008c38..0e31921fe 100644 --- a/wit/v0/api.wit +++ b/wit/v0/api.wit @@ -205,10 +205,38 @@ interface text-editor { /// Run the given command on the given text editor. This requires 'command' permissions. command: func(editor: text-editor, name: string, arguments: string) -> result; + /// todo + record-current-command: func(editor: text-editor, registers: list); + + /// todo + get-usage: func(editor: text-editor) -> string; + /// todo + get-revision: func(editor: text-editor) -> s32; + /// todo + set-mode: func(editor: text-editor, mode: string, exclusive: bool); + /// todo + mode: func(editor: text-editor) -> string; + clear-tab-stops: func(editor: text-editor); + select-next-tab-stop: func(editor: text-editor); + select-prev-tab-stop: func(editor: text-editor); + undo: func(editor: text-editor, checkpoint: string); + redo: func(editor: text-editor, checkpoint: string); + add-next-checkpoint: func(editor: text-editor, checkpoint: string); + copy: func(editor: text-editor, register: string, inclusive-end: bool); + paste: func(editor: text-editor, register: string, inclusive-end: bool); + + apply-move: func(editor: text-editor, selection: selection, move: string, count: s32, wrap: bool, include-eol: bool) -> list; + /// Sets the given selection as the only selection for the given editor. set-selection: func(editor: text-editor, s: selection); + /// Sets the selections for the given editor. + set-selections: func(editor: text-editor, s: list); /// Returns the last selection of the given editor. get-selection: func(editor: text-editor) -> selection; + /// Returns the selections of the given editor. + get-selections: func(editor: text-editor) -> list; + /// Return the length of the given line (0 based) + line-length: func(editor: text-editor, line: s32) -> s32; /// Add a callback which will be called whenever any editor changes mode. add-mode-changed-handler: func(fun: u32) -> s32; @@ -221,6 +249,15 @@ interface text-editor { content: func(editor: text-editor) -> rope; } +interface registers { + /// todo + is-replaying-commands: func() -> bool; + /// todo + set-register-text: func(text: string, register: string); + /// todo + start-recording-commands: func(register: string); +} + interface text-document { use types.{text-document, cursor, selection, rope}; @@ -391,6 +428,7 @@ world plugin { import vfs; import channel; import process; + import registers; export guest; } From 2f97ac780e66188947c4967ced9382ea2181e443 Mon Sep 17 00:00:00 2001 From: Nico Date: Mon, 18 Aug 2025 20:23:30 +0200 Subject: [PATCH 02/47] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 10095a72b..807609d69 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,7 @@ name: Build and test the editor on: # Runs on pushes targeting the default branch push: - branches: ["main", "plugin-threads"] + branches: ["main", "vim-motions"] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: From cfad98ff7976a17925f017d9d275a06ccf6d5f24 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Mon, 18 Aug 2025 20:35:40 +0200 Subject: [PATCH 03/47] More progress --- plugin_api/v0/api.nim | 3 +- plugin_api/v0/registers.nim | 46 ++++++++++++++ plugins/vim/vim.nim | 27 ++++---- src/generated/plugin_api_host.nim | 102 ++++++++++++++++++++++++++++++ src/layout.nim | 4 -- src/plugin_api/plugin_api.nim | 15 +++++ wit/v0/api.wit | 6 ++ 7 files changed, 184 insertions(+), 19 deletions(-) diff --git a/plugin_api/v0/api.nim b/plugin_api/v0/api.nim index 53f3a0c46..8bc7a2d5a 100644 --- a/plugin_api/v0/api.nim +++ b/plugin_api/v0/api.nim @@ -159,7 +159,8 @@ when pluginWorld == "plugin": let bn = b.normalized return Selection(first: min(an.first, bn.first), last: max(an.last, bn.last)) - converter toCursor*(c: tuple[line, column: int]): Cursor = Cursor(line: c.line.int32, column: c.column.int32) + converter toCursor*(c: (int, int)): Cursor = Cursor(line: c[0].int32, column: c[1].int32) + converter toCursor*(c: (int32, int32)): Cursor = Cursor(line: c[0], column: c[1]) converter toSelection*(c: tuple[line, column: int]): Selection = Selection(first: c.toCursor, last: c.toCursor) converter toSelection*(c: tuple[first, last: Cursor]): Selection = Selection(first: c.first, last: c.last) proc toSelection*(c: Cursor): Selection = Selection(first: c, last: c) diff --git a/plugin_api/v0/registers.nim b/plugin_api/v0/registers.nim index 8bc2c40fb..90d500ae1 100644 --- a/plugin_api/v0/registers.nim +++ b/plugin_api/v0/registers.nim @@ -16,6 +16,21 @@ proc isReplayingCommands*(): bool {.nodestroy.} = let res = registersIsReplayingCommandsImported() result = res.bool +proc registersIsRecordingCommandsImported(a0: int32; a1: int32): bool {. + wasmimport("is-recording-commands", "nev:plugins/registers").} +proc isRecordingCommands*(register: WitString): bool {.nodestroy.} = + ## todo + var + arg0: int32 + arg1: int32 + if register.len > 0: + arg0 = cast[int32](register[0].addr) + else: + arg0 = 0.int32 + arg1 = cast[int32](register.len) + let res = registersIsRecordingCommandsImported(arg0, arg1) + result = res.bool + proc registersSetRegisterTextImported(a0: int32; a1: int32; a2: int32; a3: int32): void {. wasmimport("set-register-text", "nev:plugins/registers").} proc setRegisterText*(text: WitString; register: WitString): void {.nodestroy.} = @@ -37,6 +52,23 @@ proc setRegisterText*(text: WitString; register: WitString): void {.nodestroy.} arg3 = cast[int32](register.len) registersSetRegisterTextImported(arg0, arg1, arg2, arg3) +proc registersGetRegisterTextImported(a0: int32; a1: int32; a2: int32): void {. + wasmimport("get-register-text", "nev:plugins/registers").} +proc getRegisterText*(register: WitString): WitString {.nodestroy.} = + ## todo + var + retArea: array[8, uint8] + arg0: int32 + arg1: int32 + if register.len > 0: + arg0 = cast[int32](register[0].addr) + else: + arg0 = 0.int32 + arg1 = cast[int32](register.len) + registersGetRegisterTextImported(arg0, arg1, cast[int32](retArea[0].addr)) + result = ws(cast[ptr char](cast[ptr int32](retArea[0].addr)[]), + cast[ptr int32](retArea[4].addr)[]) + proc registersStartRecordingCommandsImported(a0: int32; a1: int32): void {. wasmimport("start-recording-commands", "nev:plugins/registers").} proc startRecordingCommands*(register: WitString): void {.nodestroy.} = @@ -50,3 +82,17 @@ proc startRecordingCommands*(register: WitString): void {.nodestroy.} = arg0 = 0.int32 arg1 = cast[int32](register.len) registersStartRecordingCommandsImported(arg0, arg1) + +proc registersStopRecordingCommandsImported(a0: int32; a1: int32): void {. + wasmimport("stop-recording-commands", "nev:plugins/registers").} +proc stopRecordingCommands*(register: WitString): void {.nodestroy.} = + ## todo + var + arg0: int32 + arg1: int32 + if register.len > 0: + arg0 = cast[int32](register[0].addr) + else: + arg0 = 0.int32 + arg1 = cast[int32](register.len) + registersStopRecordingCommandsImported(arg0, arg1) diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index ba78ffe6b..f8d310e5c 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -437,10 +437,10 @@ proc copySelection(editor: TextEditor, register: string = ""): seq[Selection] = # editor.moveCursorColumn(-1) # editor.updateTargetColumn() -# proc vimClamp*(editor: TextEditor, cursor: Cursor): Cursor = -# var lineLen = editor.lineLength(cursor.line) -# if not editor.vimState.cursorIncludeEol and lineLen > 0: lineLen.dec -# result = (cursor.line, min(cursor.column, lineLen)) +proc vimClamp*(editor: TextEditor, cursor: Cursor): Cursor = + var lineLen = editor.lineLength(cursor.line) + if not editor.vimState.cursorIncludeEol and lineLen > 0: lineLen.dec + result = (cursor.line, min(cursor.column, lineLen)) # proc vimMotionLine*(editor: TextEditor, cursor: Cursor, count: int): Selection = # var lineLen = editor.lineLength(cursor.line) @@ -1462,14 +1462,13 @@ proc modeChangedHandler(editor: TextEditor, oldModes: seq[string], newModes: seq # infof"vim: handle mode change {oldMode} -> {newMode}" if newMode == "vim-new.normal": - discard - # if not isReplayingCommands() and isRecordingCommands(".-temp"): - # stopRecordingCommands(".-temp") + if not isReplayingCommands() and isRecordingCommands(ws".-temp"): + stopRecordingCommands(ws".-temp") - # if editor.getRevision > editor.vimState.revisionBeforeImplicitInsertMacro: - # infof"Record implicit macro because document was modified" - # let text = getRegisterText(".-temp") - # setRegisterText(text, ".") + if editor.getRevision > editor.vimState.revisionBeforeImplicitInsertMacro: + debugf"Record implicit macro because document was modified" + let text = getRegisterText(ws".-temp") + setRegisterText(text, ws".") else: if oldMode == "vim-new.normal" and newMode in recordModes: editor.startRecordingCurrentCommandInPeriodMacro() @@ -1480,10 +1479,10 @@ proc modeChangedHandler(editor: TextEditor, oldModes: seq[string], newModes: seq editor.vimState.cursorIncludeEol = newMode == "vim-new.insert" editor.vimState.currentUndoCheckpoint = if newMode == "vim-new.insert": "word" else: "insert" - # case newMode - # of "vim-new.normal": + case newMode + of "vim-new.normal": # editor.setConfig "text.inclusive-selection", false - # editor.setSelections editor.selections.mapIt(editor.vimClamp(it.last).toSelection) + editor.setSelections editor.selections.mapIt(editor.vimClamp(it.last).toSelection) # editor.saveCurrentCommandHistory() # editor.hideCompletions() diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index a0367d4d3..7429ce3e5 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -496,10 +496,15 @@ proc processStderr(instance: ptr InstanceData; self: var ProcessResource): ReadC proc processStdout(instance: ptr InstanceData; self: var ProcessResource): ReadChannelResource proc processStdin(instance: ptr InstanceData; self: var ProcessResource): WriteChannelResource proc registersIsReplayingCommands(instance: ptr InstanceData): bool +proc registersIsRecordingCommands(instance: ptr InstanceData; + register: sink string): bool proc registersSetRegisterText(instance: ptr InstanceData; text: sink string; register: sink string): void +proc registersGetRegisterText(instance: ptr InstanceData; register: sink string): string proc registersStartRecordingCommands(instance: ptr InstanceData; register: sink string): void +proc registersStopRecordingCommands(instance: ptr InstanceData; + register: sink string): void proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = block: let e = block: @@ -3167,6 +3172,35 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = parameters[0].i32 = cast[int32](res) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32], [WasmValkind.I32]) + linker.defineFuncUnchecked("nev:plugins/registers", + "is-recording-commands", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var register: string + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[0].i32].addr) + register = newString(parameters[1].i32) + for i0 in 0 ..< register.len: + register[i0] = p0[i0] + let res = registersIsRecordingCommands(instance, register) + parameters[0].i32 = cast[int32](res) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype( @@ -3202,6 +3236,46 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = registersSetRegisterText(instance, text, register) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/registers", "get-register-text", + ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + let stackAllocFunc = caller.getExport("mem_stack_alloc").get.of_field.func_field + var register: string + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[0].i32].addr) + register = newString(parameters[1].i32) + for i0 in 0 ..< register.len: + register[i0] = p0[i0] + let res = registersGetRegisterText(instance, register) + let retArea = parameters[^1].i32 + if res.len > 0: + let dataPtrWasm0 = int32(?stackAlloc(stackAllocFunc, store, + (res.len * 1).int32, 4)) + cast[ptr int32](memory[retArea + 0].addr)[] = cast[int32](dataPtrWasm0) + block: + for i0 in 0 ..< res.len: + memory[dataPtrWasm0 + i0] = cast[uint8](res[i0]) + else: + cast[ptr int32](memory[retArea + 0].addr)[] = 0.int32 + cast[ptr int32](memory[retArea + 4].addr)[] = cast[int32](res.len) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype( @@ -3230,3 +3304,31 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = registersStartRecordingCommands(instance, register) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/registers", + "stop-recording-commands", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var register: string + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[0].i32].addr) + register = newString(parameters[1].i32) + for i0 in 0 ..< register.len: + register[i0] = p0[i0] + registersStopRecordingCommands(instance, register) + if e.isErr: + return e diff --git a/src/layout.nim b/src/layout.nim index 3081d11cc..f95d59eec 100644 --- a/src/layout.nim +++ b/src/layout.nim @@ -635,12 +635,8 @@ proc showView*(self: LayoutService, view: View, slot: string = "", focus: bool = var p = self.layout.parentLayout(child) while p != nil: if not p.isVisible(child): - debugf"{p.desc}, {child.desc} not visible" let activated = self.layout.tryActivateView proc(v: View): bool = return v == child - debugf"activated: {activated}" - else: - debugf"{p.desc}, {child.desc} visible" child = p p = self.layout.parentLayout(child) diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index 400d8c6b1..8e6b8e877 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -744,16 +744,31 @@ proc registersIsReplayingCommands(instance: ptr InstanceData): bool = return false return instance.host.registers.isReplayingCommands() +proc registersIsRecordingCommands(instance: ptr InstanceData; register: sink string): bool = + if instance.host == nil: + return false + return instance.host.registers.isRecordingCommands(register) + proc registersSetRegisterText(instance: ptr InstanceData; text: sink string; register: sink string): void = if instance.host == nil: return instance.host.registers.setRegisterText(text, register) +proc registersGetRegisterText(instance: ptr InstanceData; register: sink string): string = + if instance.host == nil: + return + return instance.host.registers.getRegisterText(register) + proc registersStartRecordingCommands(instance: ptr InstanceData; register: sink string): void = if instance.host == nil: return instance.host.registers.startRecordingCommands(register) +proc registersStopRecordingCommands(instance: ptr InstanceData; register: sink string): void = + if instance.host == nil: + return + instance.host.registers.stopRecordingCommands(register) + proc commandsDefineCommand(instance: ptr InstanceData, name: sink string, active: bool, docs: sink string, params: sink seq[(string, string)], returnType: sink string, context: sink string; fun: uint32; data: uint32): void = if instance.host == nil: diff --git a/wit/v0/api.wit b/wit/v0/api.wit index 0e31921fe..17eff50e5 100644 --- a/wit/v0/api.wit +++ b/wit/v0/api.wit @@ -253,9 +253,15 @@ interface registers { /// todo is-replaying-commands: func() -> bool; /// todo + is-recording-commands: func(register: string) -> bool; + /// todo set-register-text: func(text: string, register: string); /// todo + get-register-text: func(register: string) -> string; + /// todo start-recording-commands: func(register: string); + /// todo + stop-recording-commands: func(register: string); } interface text-document { From b2c71464167eabbd5b9dae92f3c31df5d8783785 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Tue, 19 Aug 2025 21:37:39 +0200 Subject: [PATCH 04/47] More progress on new vim plugin --- plugin_api/v0/api.nim | 31 ++++++ plugin_api/v0/text_editor.nim | 95 ++++++++++++++++ plugins/vim/vim.nim | 38 +++---- scripting/editor_text_api_wasm.nim | 14 +++ src/generated/plugin_api_host.nim | 170 +++++++++++++++++++++++++++++ src/plugin_api/plugin_api.nim | 40 +++++++ src/text/text_editor.nim | 4 + wit/v0/api.wit | 25 +++++ 8 files changed, 398 insertions(+), 19 deletions(-) diff --git a/plugin_api/v0/api.nim b/plugin_api/v0/api.nim index 8bc7a2d5a..cb6f3e947 100644 --- a/plugin_api/v0/api.nim +++ b/plugin_api/v0/api.nim @@ -28,6 +28,13 @@ proc emscripten_stack_init() {.importc.} proc NimMain() {.importc.} +######### todo: move these to wit_types + +proc `==`*(a, b: WitString): bool = + if a.len != b.len: + return false + return a.toOpenArray() == b.toOpenArray() + ############################ exported functions ############################ type CommandHandler = proc(data: uint32, args: WitString): WitString {.cdecl.} @@ -87,6 +94,30 @@ when pluginWorld == "plugin": except: return def + proc getSetting*(editor: TextEditor, name: string, T: typedesc): T = + try: + return getSettingRaw(editor, name).parseJson().jsonTo(T) + except: + return T.default + + proc getSetting*[T](editor: TextEditor, name: string, def: T): T = + try: + return ($getSettingRaw(editor, ws(name))).parseJson().jsonTo(T) + except: + return def + + proc setSetting*[T](name: string, value: T) = + try: + setSettingRaw(name.ws, stackWitString($value.toJson)) + except: + discard + + proc setSetting*[T](editor: TextEditor, name: string, value: T) = + try: + editor.setSettingRaw(name.ws, stackWitString($value.toJson)) + except: + discard + func `$`*(cursor: Cursor): string = return $cursor.line & ":" & $cursor.column diff --git a/plugin_api/v0/text_editor.nim b/plugin_api/v0/text_editor.nim index cb4f82900..9e172b9ae 100644 --- a/plugin_api/v0/text_editor.nim +++ b/plugin_api/v0/text_editor.nim @@ -121,6 +121,34 @@ proc recordCurrentCommand*(editor: TextEditor; registers: WitList[WitString]): v arg2 = cast[int32](registers.len) textEditorRecordCurrentCommandImported(arg0, arg1, arg2) +proc textEditorClearCurrentCommandHistoryImported(a0: uint64; a1: bool): void {. + wasmimport("clear-current-command-history", "nev:plugins/text-editor").} +proc clearCurrentCommandHistory*(editor: TextEditor; retainLast: bool): void {. + nodestroy.} = + ## todo + var + arg0: uint64 + arg1: bool + arg0 = editor.id + arg1 = retainLast + textEditorClearCurrentCommandHistoryImported(arg0, arg1) + +proc textEditorSaveCurrentCommandHistoryImported(a0: uint64): void {. + wasmimport("save-current-command-history", "nev:plugins/text-editor").} +proc saveCurrentCommandHistory*(editor: TextEditor): void {.nodestroy.} = + ## todo + var arg0: uint64 + arg0 = editor.id + textEditorSaveCurrentCommandHistoryImported(arg0) + +proc textEditorHideCompletionsImported(a0: uint64): void {. + wasmimport("hide-completions", "nev:plugins/text-editor").} +proc hideCompletions*(editor: TextEditor): void {.nodestroy.} = + ## todo + var arg0: uint64 + arg0 = editor.id + textEditorHideCompletionsImported(arg0) + proc textEditorGetUsageImported(a0: uint64; a1: int32): void {. wasmimport("get-usage", "nev:plugins/text-editor").} proc getUsage*(editor: TextEditor): WitString {.nodestroy.} = @@ -173,9 +201,22 @@ proc mode*(editor: TextEditor): WitString {.nodestroy.} = result = ws(cast[ptr char](cast[ptr int32](retArea[0].addr)[]), cast[ptr int32](retArea[4].addr)[]) +proc textEditorModesImported(a0: uint64; a1: int32): void {. + wasmimport("modes", "nev:plugins/text-editor").} +proc modes*(editor: TextEditor): WitList[WitString] {.nodestroy.} = + ## todo + var + retArea: array[8, uint8] + arg0: uint64 + arg0 = editor.id + textEditorModesImported(arg0, cast[int32](retArea[0].addr)) + result = wl(cast[ptr typeof(result[0])](cast[ptr int32](retArea[0].addr)[]), + cast[ptr int32](retArea[4].addr)[]) + proc textEditorClearTabStopsImported(a0: uint64): void {. wasmimport("clear-tab-stops", "nev:plugins/text-editor").} proc clearTabStops*(editor: TextEditor): void {.nodestroy.} = + ## todo var arg0: uint64 arg0 = editor.id textEditorClearTabStopsImported(arg0) @@ -183,6 +224,7 @@ proc clearTabStops*(editor: TextEditor): void {.nodestroy.} = proc textEditorSelectNextTabStopImported(a0: uint64): void {. wasmimport("select-next-tab-stop", "nev:plugins/text-editor").} proc selectNextTabStop*(editor: TextEditor): void {.nodestroy.} = + ## todo var arg0: uint64 arg0 = editor.id textEditorSelectNextTabStopImported(arg0) @@ -190,6 +232,7 @@ proc selectNextTabStop*(editor: TextEditor): void {.nodestroy.} = proc textEditorSelectPrevTabStopImported(a0: uint64): void {. wasmimport("select-prev-tab-stop", "nev:plugins/text-editor").} proc selectPrevTabStop*(editor: TextEditor): void {.nodestroy.} = + ## todo var arg0: uint64 arg0 = editor.id textEditorSelectPrevTabStopImported(arg0) @@ -197,6 +240,7 @@ proc selectPrevTabStop*(editor: TextEditor): void {.nodestroy.} = proc textEditorUndoImported(a0: uint64; a1: int32; a2: int32): void {. wasmimport("undo", "nev:plugins/text-editor").} proc undo*(editor: TextEditor; checkpoint: WitString): void {.nodestroy.} = + ## todo var arg0: uint64 arg1: int32 @@ -212,6 +256,7 @@ proc undo*(editor: TextEditor; checkpoint: WitString): void {.nodestroy.} = proc textEditorRedoImported(a0: uint64; a1: int32; a2: int32): void {. wasmimport("redo", "nev:plugins/text-editor").} proc redo*(editor: TextEditor; checkpoint: WitString): void {.nodestroy.} = + ## todo var arg0: uint64 arg1: int32 @@ -228,6 +273,7 @@ proc textEditorAddNextCheckpointImported(a0: uint64; a1: int32; a2: int32): void wasmimport("add-next-checkpoint", "nev:plugins/text-editor").} proc addNextCheckpoint*(editor: TextEditor; checkpoint: WitString): void {. nodestroy.} = + ## todo var arg0: uint64 arg1: int32 @@ -244,6 +290,7 @@ proc textEditorCopyImported(a0: uint64; a1: int32; a2: int32; a3: bool): void {. wasmimport("copy", "nev:plugins/text-editor").} proc copy*(editor: TextEditor; register: WitString; inclusiveEnd: bool): void {. nodestroy.} = + ## todo var arg0: uint64 arg1: int32 @@ -262,6 +309,7 @@ proc textEditorPasteImported(a0: uint64; a1: int32; a2: int32; a3: bool): void { wasmimport("paste", "nev:plugins/text-editor").} proc paste*(editor: TextEditor; register: WitString; inclusiveEnd: bool): void {. nodestroy.} = + ## todo var arg0: uint64 arg1: int32 @@ -283,6 +331,7 @@ proc textEditorApplyMoveImported(a0: uint64; a1: int32; a2: int32; a3: int32; proc applyMove*(editor: TextEditor; selection: Selection; move: WitString; count: int32; wrap: bool; includeEol: bool): WitList[Selection] {. nodestroy.} = + ## todo var retArea: array[48, uint8] arg0: uint64 @@ -394,11 +443,57 @@ proc addModeChangedHandler*(fun: uint32): int32 {.nodestroy.} = let res = textEditorAddModeChangedHandlerImported(arg0) result = convert(res, int32) +proc textEditorGetSettingRawImported(a0: uint64; a1: int32; a2: int32; a3: int32): void {. + wasmimport("get-setting-raw", "nev:plugins/text-editor").} +proc getSettingRaw*(editor: TextEditor; name: WitString): WitString {.nodestroy.} = + ## Returns the value of the setting with the given path, encoded as JSON. + var + retArea: array[16, uint8] + arg0: uint64 + arg1: int32 + arg2: int32 + arg0 = editor.id + if name.len > 0: + arg1 = cast[int32](name[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](name.len) + textEditorGetSettingRawImported(arg0, arg1, arg2, + cast[int32](retArea[0].addr)) + result = ws(cast[ptr char](cast[ptr int32](retArea[0].addr)[]), + cast[ptr int32](retArea[4].addr)[]) + +proc textEditorSetSettingRawImported(a0: uint64; a1: int32; a2: int32; + a3: int32; a4: int32): void {. + wasmimport("set-setting-raw", "nev:plugins/text-editor").} +proc setSettingRaw*(editor: TextEditor; name: WitString; value: WitString): void {. + nodestroy.} = + ## Set the value of the setting with the given path. The value must be encoded as JSON. + var + arg0: uint64 + arg1: int32 + arg2: int32 + arg3: int32 + arg4: int32 + arg0 = editor.id + if name.len > 0: + arg1 = cast[int32](name[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](name.len) + if value.len > 0: + arg3 = cast[int32](value[0].addr) + else: + arg3 = 0.int32 + arg4 = cast[int32](value.len) + textEditorSetSettingRawImported(arg0, arg1, arg2, arg3, arg4) + proc textEditorEditImported(a0: uint64; a1: int32; a2: int32; a3: int32; a4: int32; a5: int32): void {. wasmimport("edit", "nev:plugins/text-editor").} proc edit*(editor: TextEditor; selections: WitList[Selection]; contents: WitList[WitString]): WitList[Selection] {.nodestroy.} = + ## todo var retArea: array[24, uint8] arg0: uint64 diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index f8d310e5c..c00403773 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -933,7 +933,7 @@ proc vimClamp*(editor: TextEditor, cursor: Cursor): Cursor = proc normalMode(editor: TextEditor) {.exposeActive(editorContext).} = ## Exit to normal mode and clear things - log lvlInfo, "[vim] normalMode" + log lvlInfo, "normalMode" if $editor.mode == "vim-new.normal": editor.setSelection editor.getSelection.last.toSelection editor.clearTabStops() @@ -1043,7 +1043,7 @@ proc normalMode(editor: TextEditor) {.exposeActive(editorContext).} = # editor.setCursorScrollOffset (editor.screenLineCount.float - getVimLineMargin()) * platformTotalLineHeight() proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorContext).} = - debugf"insertMode '{move}'" + # debugf"insertMode '{move}'" case move of "right": editor.setSelections editor.selections.mapIt(editor.applyMove(it.last, "column", 1)) @@ -1432,7 +1432,7 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # editor.setMode "vim-new.insert" proc modeChangedHandler(editor: TextEditor, oldModes: seq[string], newModes: seq[string]) {.exposeActive(editorContext).} = - # log lvlInfo, &"[vim] modeChangedHandler {editor}, {oldModes} -> {newModes}" + # log lvlInfo, &"modeChangedHandler {editor}, {oldModes} -> {newModes}" let oldMode = if oldModes.len > 0: oldModes[0] @@ -1444,8 +1444,8 @@ proc modeChangedHandler(editor: TextEditor, oldModes: seq[string], newModes: seq let newMode = newModes[0] -# if not editor.getCurrentEventHandlers().contains("vim"): -# return + if not editor.modes().toOpenArray.contains(ws"vim-new"): + return if newMode == "": editor.setMode "vim-new.normal" @@ -1460,20 +1460,20 @@ proc modeChangedHandler(editor: TextEditor, oldModes: seq[string], newModes: seq "vim-new.insert", ].toHashSet - # infof"vim: handle mode change {oldMode} -> {newMode}" + # debugf"vim: handle mode change {oldMode} -> {newMode}" if newMode == "vim-new.normal": if not isReplayingCommands() and isRecordingCommands(ws".-temp"): stopRecordingCommands(ws".-temp") if editor.getRevision > editor.vimState.revisionBeforeImplicitInsertMacro: - debugf"Record implicit macro because document was modified" + # debugf"Record implicit macro because document was modified" let text = getRegisterText(ws".-temp") setRegisterText(text, ws".") else: if oldMode == "vim-new.normal" and newMode in recordModes: editor.startRecordingCurrentCommandInPeriodMacro() - # editor.clearCurrentCommandHistory(retainLast=true) + editor.clearCurrentCommandHistory(retainLast=true) editor.vimState.selectLines = newMode == "vim-new.visual-line" editor.vimState.cursorIncludeEol = newMode == "vim-new.insert" @@ -1481,22 +1481,22 @@ proc modeChangedHandler(editor: TextEditor, oldModes: seq[string], newModes: seq case newMode of "vim-new.normal": - # editor.setConfig "text.inclusive-selection", false + editor.setSetting "text.inclusive-selection", false editor.setSelections editor.selections.mapIt(editor.vimClamp(it.last).toSelection) - # editor.saveCurrentCommandHistory() - # editor.hideCompletions() + editor.saveCurrentCommandHistory() + editor.hideCompletions() -# of "vim-new.insert": -# editor.setConfig "text.inclusive-selection", false + of "vim-new.insert": + editor.setSetting "text.inclusive-selection", false -# of "vim-new.visual": -# editor.setConfig "text.inclusive-selection", true + of "vim-new.visual": + editor.setSetting "text.inclusive-selection", true -# of "vim-new.visual-line": -# editor.setConfig "text.inclusive-selection", false + of "vim-new.visual-line": + editor.setSetting "text.inclusive-selection", false -# else: -# editor.setConfig "text.inclusive-selection", false + else: + editor.setSetting "text.inclusive-selection", false # proc loadVimKeybindings*() {.expose("load-vim-keybindings").} = # let afterRestoreSessionHandle = addCallback proc(args: JsonNode): JsonNode = diff --git a/scripting/editor_text_api_wasm.nim b/scripting/editor_text_api_wasm.nim index dc8fb985e..a83fa6c23 100644 --- a/scripting/editor_text_api_wasm.nim +++ b/scripting/editor_text_api_wasm.nim @@ -490,6 +490,20 @@ proc mode*(self: TextDocumentEditor): string {.gcsafe, raises: [].} = raiseAssert(getCurrentExceptionMsg()) +proc editor_text_modes_seq_string_TextDocumentEditor_wasm(arg: cstring): cstring {. + importc.} +proc modes*(self: TextDocumentEditor): seq[string] {.gcsafe, raises: [].} = + var argsJson = newJArray() + argsJson.add self.toJson() + let argsJsonString = $argsJson + let res {.used.} = editor_text_modes_seq_string_TextDocumentEditor_wasm( + argsJsonString.cstring) + try: + result = parseJson($res).jsonTo(typeof(result)) + except CatchableError: + raiseAssert(getCurrentExceptionMsg()) + + proc editor_text_getContextWithMode_string_TextDocumentEditor_string_wasm( arg: cstring): cstring {.importc.} proc getContextWithMode*(self: TextDocumentEditor; context: string): string {. diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index 7429ce3e5..782c2fd61 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -372,11 +372,17 @@ proc textEditorCommand(instance: ptr InstanceData; editor: TextEditor; proc textEditorRecordCurrentCommand(instance: ptr InstanceData; editor: TextEditor; registers: sink seq[string]): void +proc textEditorClearCurrentCommandHistory(instance: ptr InstanceData; + editor: TextEditor; retainLast: bool): void +proc textEditorSaveCurrentCommandHistory(instance: ptr InstanceData; + editor: TextEditor): void +proc textEditorHideCompletions(instance: ptr InstanceData; editor: TextEditor): void proc textEditorGetUsage(instance: ptr InstanceData; editor: TextEditor): string proc textEditorGetRevision(instance: ptr InstanceData; editor: TextEditor): int32 proc textEditorSetMode(instance: ptr InstanceData; editor: TextEditor; mode: sink string; exclusive: bool): void proc textEditorMode(instance: ptr InstanceData; editor: TextEditor): string +proc textEditorModes(instance: ptr InstanceData; editor: TextEditor): seq[string] proc textEditorClearTabStops(instance: ptr InstanceData; editor: TextEditor): void proc textEditorSelectNextTabStop(instance: ptr InstanceData; editor: TextEditor): void proc textEditorSelectPrevTabStop(instance: ptr InstanceData; editor: TextEditor): void @@ -403,6 +409,10 @@ proc textEditorGetSelections(instance: ptr InstanceData; editor: TextEditor): se proc textEditorLineLength(instance: ptr InstanceData; editor: TextEditor; line: int32): int32 proc textEditorAddModeChangedHandler(instance: ptr InstanceData; fun: uint32): int32 +proc textEditorGetSettingRaw(instance: ptr InstanceData; editor: TextEditor; + name: sink string): string +proc textEditorSetSettingRaw(instance: ptr InstanceData; editor: TextEditor; + name: sink string; value: sink string): void proc textEditorEdit(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection]; contents: sink seq[string]): seq[ Selection] @@ -1246,6 +1256,42 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = textEditorRecordCurrentCommand(instance, editor, registers) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", + "clear-current-command-history", ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + var retainLast: bool + editor.id = convert(parameters[0].i64, uint64) + retainLast = parameters[1].i32.bool + textEditorClearCurrentCommandHistory(instance, editor, retainLast) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", + "save-current-command-history", ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + textEditorSaveCurrentCommandHistory(instance, editor) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "hide-completions", + ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + textEditorHideCompletions(instance, editor) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype( @@ -1360,6 +1406,51 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = cast[ptr int32](memory[retArea + 4].addr)[] = cast[int32](res.len) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "modes", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + let stackAllocFunc = caller.getExport("mem_stack_alloc").get.of_field.func_field + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + let res = textEditorModes(instance, editor) + let retArea = parameters[^1].i32 + if res.len > 0: + let dataPtrWasm0 = int32(?stackAlloc(stackAllocFunc, store, + (res.len * 8).int32, 4)) + cast[ptr int32](memory[retArea + 0].addr)[] = cast[int32](dataPtrWasm0) + block: + for i0 in 0 ..< res.len: + if res[i0].len > 0: + let dataPtrWasm1 = int32(?stackAlloc(stackAllocFunc, store, + (res[i0].len * 1).int32, 4)) + cast[ptr int32](memory[dataPtrWasm0 + i0 * 8 + 0].addr)[] = cast[int32](dataPtrWasm1) + block: + for i1 in 0 ..< res[i0].len: + memory[dataPtrWasm1 + i1] = cast[uint8](res[i0][i1]) + else: + cast[ptr int32](memory[dataPtrWasm0 + i0 * 8 + 0].addr)[] = 0.int32 + cast[ptr int32](memory[dataPtrWasm0 + i0 * 8 + 4].addr)[] = cast[int32](res[ + i0].len) + else: + cast[ptr int32](memory[retArea + 0].addr)[] = 0.int32 + cast[ptr int32](memory[retArea + 4].addr)[] = cast[int32](res.len) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64], []) @@ -1756,6 +1847,85 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = parameters[0].i32 = cast[int32](res) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], + []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "get-setting-raw", + ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + let stackAllocFunc = caller.getExport("mem_stack_alloc").get.of_field.func_field + var editor: TextEditor + var name: string + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[1].i32].addr) + name = newString(parameters[2].i32) + for i0 in 0 ..< name.len: + name[i0] = p0[i0] + let res = textEditorGetSettingRaw(instance, editor, name) + let retArea = parameters[^1].i32 + if res.len > 0: + let dataPtrWasm0 = int32(?stackAlloc(stackAllocFunc, store, + (res.len * 1).int32, 4)) + cast[ptr int32](memory[retArea + 0].addr)[] = cast[int32](dataPtrWasm0) + block: + for i0 in 0 ..< res.len: + memory[dataPtrWasm0 + i0] = cast[uint8](res[i0]) + else: + cast[ptr int32](memory[retArea + 0].addr)[] = 0.int32 + cast[ptr int32](memory[retArea + 4].addr)[] = cast[int32](res.len) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "set-setting-raw", + ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var editor: TextEditor + var name: string + var value: string + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[1].i32].addr) + name = newString(parameters[2].i32) + for i0 in 0 ..< name.len: + name[i0] = p0[i0] + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[3].i32].addr) + value = newString(parameters[4].i32) + for i0 in 0 ..< value.len: + value[i0] = p0[i0] + textEditorSetSettingRaw(instance, editor, name, value) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index 8e6b8e877..223833de2 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -537,6 +537,30 @@ proc textEditorMode(instance: ptr InstanceData; editor: TextEditor): string = if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: return editor.TextDocumentEditor.mode() +proc textEditorModes(instance: ptr InstanceData; editor: TextEditor): seq[string] = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + return editor.TextDocumentEditor.modes() + +proc textEditorClearCurrentCommandHistory(instance: ptr InstanceData; editor: TextEditor, retainLast: bool) = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.clearCurrentCommandHistory(retainLast) + +proc textEditorSaveCurrentCommandHistory(instance: ptr InstanceData; editor: TextEditor) = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.saveCurrentCommandHistory() + +proc textEditorHideCompletions(instance: ptr InstanceData; editor: TextEditor) = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.hideCompletions() + proc textEditorCommand(instance: ptr InstanceData; editor: TextEditor, name: sink string, arguments: sink string): Result[string, CommandError] = if instance.host == nil: return @@ -583,6 +607,22 @@ proc textDocumentContent(instance: ptr InstanceData; document: TextDocument): Ro return RopeResource(rope: textDocument.rope.clone().slice().suffix(Point())) return RopeResource(rope: createRope("").slice().suffix(Point())) +proc textEditorGetSettingRaw(instance: ptr InstanceData, editor: TextEditor, name: sink string): string = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + return $editor.TextDocumentEditor.config.get(name, newJexNull()) + +proc textEditorSetSettingRaw(instance: ptr InstanceData, editor: TextEditor, name: sink string, value: sink string) = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + try: + # todo: permissions + editor.TextDocumentEditor.config.set(name, parseJsonex(value)) + except CatchableError as e: + log lvlError, "set-setting-raw: Failed to set setting '{name}' to {value}: {e.msg}" + proc typesNewRope(instance: ptr InstanceData, content: sink string): RopeResource = return RopeResource(rope: createRope(content).slice().suffix(Point())) diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index e5b365f06..45afc67a2 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -1648,6 +1648,10 @@ proc mode*(self: TextDocumentEditor): string {.expose("editor.text").} = return modes.last return "" +proc modes*(self: TextDocumentEditor): seq[string] {.expose("editor.text").} = + ## Returns the current modes of the text editor + return self.settings.modes.get() + proc getContextWithMode(self: TextDocumentEditor, context: string): string {.expose("editor.text").} = ## Appends the current mode to context return context & "." & $self.mode diff --git a/wit/v0/api.wit b/wit/v0/api.wit index 17eff50e5..bc0e1e523 100644 --- a/wit/v0/api.wit +++ b/wit/v0/api.wit @@ -207,6 +207,13 @@ interface text-editor { /// todo record-current-command: func(editor: text-editor, registers: list); + /// todo + clear-current-command-history: func(editor: text-editor, retain-last: bool); + /// todo + save-current-command-history: func(editor: text-editor); + + /// todo + hide-completions: func(editor: text-editor); /// todo get-usage: func(editor: text-editor) -> string; @@ -216,15 +223,26 @@ interface text-editor { set-mode: func(editor: text-editor, mode: string, exclusive: bool); /// todo mode: func(editor: text-editor) -> string; + /// todo + modes: func(editor: text-editor) -> list; + /// todo clear-tab-stops: func(editor: text-editor); + /// todo select-next-tab-stop: func(editor: text-editor); + /// todo select-prev-tab-stop: func(editor: text-editor); + /// todo undo: func(editor: text-editor, checkpoint: string); + /// todo redo: func(editor: text-editor, checkpoint: string); + /// todo add-next-checkpoint: func(editor: text-editor, checkpoint: string); + /// todo copy: func(editor: text-editor, register: string, inclusive-end: bool); + /// todo paste: func(editor: text-editor, register: string, inclusive-end: bool); + /// todo apply-move: func(editor: text-editor, selection: selection, move: string, count: s32, wrap: bool, include-eol: bool) -> list; /// Sets the given selection as the only selection for the given editor. @@ -240,6 +258,13 @@ interface text-editor { /// Add a callback which will be called whenever any editor changes mode. add-mode-changed-handler: func(fun: u32) -> s32; + /// Returns the value of the setting with the given path, encoded as JSON. + get-setting-raw: func(editor: text-editor, name: string) -> string; + + /// Set the value of the setting with the given path. The value must be encoded as JSON. + set-setting-raw: func(editor: text-editor, name: string, value: string); + + /// todo edit: func(editor: text-editor, selections: list, contents: list) -> list; /// Returns the rope containing the current content of the text editors document. From 978b2265059b091a7771a13a406540be11f1ec05 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sat, 23 Aug 2025 13:33:13 +0200 Subject: [PATCH 05/47] More progress --- config/keybindings.json | 366 +++++++++++++++-------------- plugin_api/v0/api.nim | 11 + plugin_api/v0/plugin_api_guest.nim | 28 +++ plugin_api/v0/text_editor.nim | 108 ++++++++- plugins/vim/vim.nim | 253 ++++++++++---------- scripting/editor_text_api_wasm.nim | 9 +- src/generated/plugin_api_host.nim | 224 +++++++++++++++--- src/move_database.nim | 40 ++++ src/plugin_api/plugin_api.nim | 56 ++++- src/text/text_editor.nim | 58 ++++- wit/v0/api.wit | 30 ++- 11 files changed, 828 insertions(+), 355 deletions(-) create mode 100644 src/move_database.nim diff --git a/config/keybindings.json b/config/keybindings.json index 1d816383b..4b52eb6a4 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -686,84 +686,92 @@ }, "vim-new#text_object": { - "iw": ["vim-select-text-object", "vim-word-inner", false, true, "<#text_object.count>"], - "aw": ["vim-select-text-object", "vim-word-outer", false, true, "<#text_object.count>"], - "iW": ["vim-select-text-object", "vim-WORD-inner", false, true, "<#text_object.count>"], - "aW": ["vim-select-text-object", "vim-WORD-outer", false, true, "<#text_object.count>"], - "ip": ["vim-select-text-object", "vim-paragraph-inner", false, true, "<#text_object.count>"], - "ap": ["vim-select-text-object", "vim-paragraph-outer", false, true, "<#text_object.count>"], + "iw": ["vim.select-text-object", "vim.word-inner", false, true, "<#text_object.count>"], + "aw": ["vim.select-text-object", "vim.word-outer", false, true, "<#text_object.count>"], + "iW": ["vim.select-text-object", "vim.WORD-inner", false, true, "<#text_object.count>"], + "aW": ["vim.select-text-object", "vim.WORD-outer", false, true, "<#text_object.count>"], + "ip": ["vim.select-text-object", "vim.paragraph-inner", false, true, "<#text_object.count>"], + "ap": ["vim.select-text-object", "vim.paragraph-outer", false, true, "<#text_object.count>"], + + "i{": ["vim.select-surrounding", "vim.surround-{-inner", false, true, "<#text_object.count>"], + "a{": ["vim.select-surrounding", "vim.surround-{-outer", false, true, "<#text_object.count>"], + "i}": ["vim.select-surrounding", "vim.surround-{-inner", false, true, "<#text_object.count>"], + "a}": ["vim.select-surrounding", "vim.surround-{-outer", false, true, "<#text_object.count>"], + "i(": ["vim.select-surrounding", "vim.surround-(-inner", false, true, "<#text_object.count>"], + "a(": ["vim.select-surrounding", "vim.surround-(-outer", false, true, "<#text_object.count>"], + "i)": ["vim.select-surrounding", "vim.surround-(-inner", false, true, "<#text_object.count>"], + "a)": ["vim.select-surrounding", "vim.surround-(-outer", false, true, "<#text_object.count>"], + "i[": ["vim.select-surrounding", "vim.surround-[-inner", false, true, "<#text_object.count>"], + "a[": ["vim.select-surrounding", "vim.surround-[-outer", false, true, "<#text_object.count>"], + "i]": ["vim.select-surrounding", "vim.surround-[-inner", false, true, "<#text_object.count>"], + "a]": ["vim.select-surrounding", "vim.surround-[-outer", false, true, "<#text_object.count>"], + "i\\<": ["vim.select-surrounding", "vim.surround-angle-inner", false, true, "<#text_object.count>"], + "a\\<": ["vim.select-surrounding", "vim.surround-angle-outer", false, true, "<#text_object.count>"], + "i\\>": ["vim.select-surrounding", "vim.surround-angle-inner", false, true, "<#text_object.count>"], + "a\\>": ["vim.select-surrounding", "vim.surround-angle-outer", false, true, "<#text_object.count>"], + "i\"": ["vim.select-surrounding", "vim.surround-\"-inner", false, true, "<#text_object.count>"], + "a\"": ["vim.select-surrounding", "vim.surround-\"-outer", false, true, "<#text_object.count>"], + "i'": ["vim.select-surrounding", "vim.surround-'-inner", false, true, "<#text_object.count>"], + "a'": ["vim.select-surrounding", "vim.surround-'-outer", false, true, "<#text_object.count>"], + }, - "i{": ["vim-select-surrounding", "vim-surround-{-inner", false, true, "<#text_object.count>"], - "a{": ["vim-select-surrounding", "vim-surround-{-outer", false, true, "<#text_object.count>"], - "i}": ["vim-select-surrounding", "vim-surround-{-inner", false, true, "<#text_object.count>"], - "a}": ["vim-select-surrounding", "vim-surround-{-outer", false, true, "<#text_object.count>"], - "i(": ["vim-select-surrounding", "vim-surround-(-inner", false, true, "<#text_object.count>"], - "a(": ["vim-select-surrounding", "vim-surround-(-outer", false, true, "<#text_object.count>"], - "i)": ["vim-select-surrounding", "vim-surround-(-inner", false, true, "<#text_object.count>"], - "a)": ["vim-select-surrounding", "vim-surround-(-outer", false, true, "<#text_object.count>"], - "i[": ["vim-select-surrounding", "vim-surround-[-inner", false, true, "<#text_object.count>"], - "a[": ["vim-select-surrounding", "vim-surround-[-outer", false, true, "<#text_object.count>"], - "i]": ["vim-select-surrounding", "vim-surround-[-inner", false, true, "<#text_object.count>"], - "a]": ["vim-select-surrounding", "vim-surround-[-outer", false, true, "<#text_object.count>"], - "i\\<": ["vim-select-surrounding", "vim-surround-angle-inner", false, true, "<#text_object.count>"], - "a\\<": ["vim-select-surrounding", "vim-surround-angle-outer", false, true, "<#text_object.count>"], - "i\\>": ["vim-select-surrounding", "vim-surround-angle-inner", false, true, "<#text_object.count>"], - "a\\>": ["vim-select-surrounding", "vim-surround-angle-outer", false, true, "<#text_object.count>"], - "i\"": ["vim-select-surrounding", "vim-surround-\"-inner", false, true, "<#text_object.count>"], - "a\"": ["vim-select-surrounding", "vim-surround-\"-outer", false, true, "<#text_object.count>"], - "i'": ["vim-select-surrounding", "vim-surround-'-inner", false, true, "<#text_object.count>"], - "a'": ["vim-select-surrounding", "vim-surround-'-outer", false, true, "<#text_object.count>"], + "vim-new.visual-line#move": { + "": ["vim.move-direction", "line-up", 1, "<#move.count>", false, false], + "": ["vim.move-direction", "line-down", 1, "<#move.count>", false, false], + "k": ["vim.move-direction", "line-up", 1, "<#move.count>", false, false], + "j": ["vim.move-direction", "line-down", 1, "<#move.count>", false, false], }, "vim-new#move": { - "": ["vim-move-cursor-column", -1, "<#move.count>"], - "": ["vim-move-cursor-column", 1, "<#move.count>"], - "": ["vim-move-cursor-visual-line", -1, "<#move.count>"], - "": ["vim-move-cursor-visual-line", 1, "<#move.count>"], - "h": ["vim-move-cursor-column", -1, "<#move.count>"], - "j": ["vim-move-cursor-visual-line", 1, "<#move.count>"], - "k": ["vim-move-cursor-visual-line", -1, "<#move.count>"], - "l": ["vim-move-cursor-column", 1, "<#move.count>"], + "": ["vim.move-direction", "column", -1, "<#move.count>"], + "": ["vim.move-direction", "column", 1, "<#move.count>"], + "": ["vim.move-direction", "visual-line-up", 1, "<#move.count>", false, false], + "": ["vim.move-direction", "visual-line-down", 1, "<#move.count>", false, false], + "h": ["vim.move-direction", "column", -1, "<#move.count>"], + "l": ["vim.move-direction", "column", 1, "<#move.count>"], + "k": ["vim.move-direction", "visual-line-up", 1, "<#move.count>", false, false], + "j": ["vim.move-direction", "visual-line-down", 1, "<#move.count>", false, false], "gm": ["move-cursor-line-center"], "gM": ["move-cursor-center"], - "0": ["vim-move-first", "line"], - "": ["vim-move-first", "visual-line"], - "^": ["vim-move-first", "line-no-indent"], - "$": ["vim-move-to-end-of-line", "<#move.count>"], - "": ["vim-move-to-end-of-visual-line", "<#move.count>"], - "g0": ["vim-move-first", "line"], - "g^": ["vim-move-first", "line-no-indent"], - "g$": ["vim-move-to-end-of-line", "<#move.count>"], - "gg": ["vim-move-file-start", "<#move.count>"], - "G": ["vim-move-file-end", "<#move.count>"], - "%": ["vim-move-to-matching-or-file-offset", "<#move.count>"], - - "w": ["move-selection-next", "vim-word", false, true, "<#move.count>"], - "": ["move-selection-next", "vim-word", false, true, "<#move.count>"], - "W": ["move-selection-next", "vim-WORD", false, true, "<#move.count>"], - "": ["move-selection-next", "vim-WORD", false, true, "<#move.count>"], - "e": ["move-selection-end", "vim-word", false, false, "<#move.count>"], - "E": ["move-selection-end", "vim-WORD", false, false, "<#move.count>"], - "b": ["move-selection-end", "vim-word", true, true, "<#move.count>"], - "": ["move-selection-end", "vim-word", true, true, "<#move.count>"], - "B": ["move-selection-end", "vim-WORD", true, true, "<#move.count>"], - "": ["move-selection-end", "vim-WORD", true, true, "<#move.count>"], - "ge": ["move-selection-next", "vim-word", true, false, "<#move.count>"], - "gE": ["move-selection-next", "vim-WORD", true, false, "<#move.count>"], + "0": ["vim.move-first", "line"], + "": ["vim.move-first", "visual-line"], + "^": ["vim.move-first", "line-no-indent"], + "$": ["vim.move-to-end-of-line", "<#move.count>"], + // "": ["vim.move-to-end-of-visual-line", "<#move.count>"], + "": ["vim.move-last", "visual-line", "<#move.count>"], + "g0": ["vim.move-first", "line"], + "g^": ["vim.move-first", "line-no-indent"], + "g$": ["vim.move-to-end-of-line", "<#move.count>"], + "gg": ["vim.move-file-start", "<#move.count>"], + "G": ["vim.move-file-end", "<#move.count>"], + "%": ["vim.move-to-matching-or-file-offset", "<#move.count>"], + + "w": ["move-selection-next", "vim.word", false, true, "<#move.count>"], + "": ["move-selection-next", "vim.word", false, true, "<#move.count>"], + "W": ["move-selection-next", "vim.WORD", false, true, "<#move.count>"], + "": ["move-selection-next", "vim.WORD", false, true, "<#move.count>"], + "e": ["move-selection-end", "vim.word", false, false, "<#move.count>"], + "E": ["move-selection-end", "vim.WORD", false, false, "<#move.count>"], + "b": ["move-selection-end", "vim.word", true, true, "<#move.count>"], + "": ["move-selection-end", "vim.word", true, true, "<#move.count>"], + "B": ["move-selection-end", "vim.WORD", true, true, "<#move.count>"], + "": ["move-selection-end", "vim.WORD", true, true, "<#move.count>"], + "ge": ["move-selection-next", "vim.word", true, false, "<#move.count>"], + "gE": ["move-selection-next", "vim.WORD", true, false, "<#move.count>"], "}": ["move-paragraph", false, "<#move.count>"], "{": ["move-paragraph", true, "<#move.count>"], - "f": ["vim-move-to ", false, "<#move.count>"], - "t": ["vim-move-to ", true, "<#move.count>"], - - "": ["vim-move-cursor-visual-page", 0.5, "<#move.count>", true], - "": ["vim-move-cursor-visual-page", 1, "<#move.count>", true], - "": ["vim-move-cursor-visual-page", -0.5, "<#move.count>", true], - "": ["vim-move-cursor-visual-page", -1, "<#move.count>", true], - "|": ["vim-move-to-column", "<#move.count>"], - "-": ["vim-move-cursor-line-first-char", -1, "<#move.count>"], - "+": ["vim-move-cursor-line-first-char", 1, "<#move.count>"], - "_": ["vim-move-to-start-of-line", "<#move.count>"], + "f": ["vim.move-to ", false, "<#move.count>"], + "t": ["vim.move-to ", true, "<#move.count>"], + + "": ["vim.move-cursor-visual-page", 0.5, "<#move.count>", true], + "": ["vim.move-cursor-visual-page", 1, "<#move.count>", true], + "": ["vim.move-cursor-visual-page", -0.5, "<#move.count>", true], + "": ["vim.move-cursor-visual-page", -1, "<#move.count>", true], + "|": ["vim.move-to-column", "<#move.count>"], + "-": ["vim.move-cursor-line-first-char", -1, "<#move.count>"], + "+": ["vim.move-cursor-line-first-char", 1, "<#move.count>"], + "_": ["vim.move-to-start-of-line", "<#move.count>"], }, "vim-new.base": { @@ -784,7 +792,7 @@ "w": ["focus-next-view", "**"], "p": ["open-prev-view"], "": ["open-prev-view"], - "q": ["vim-close-current-view-or-quit"], + "q": ["vim.close-current-view-or-quit"], "": ["close-active-view"], "c": ["hide-active-view"], "C": ["close-active-view"], @@ -811,17 +819,17 @@ "vim-new": { "": ["vim.normal-mode"], - "": ["vim-add-cursor-above"], - "": ["vim-add-cursor-below"], + "": ["vim.add-cursor-above"], + "": ["vim.add-cursor-below"], // special - "d": ["vim-delete-move <#count>"], - "c": ["vim-change-move <#count>"], - "y": ["vim-yank-move <#count>"], + "d": ["vim.delete-move <#count>"], + "c": ["vim.change-move <#count>"], + "y": ["vim.yank-move <#count>"], - "d": ["vim-delete-move <#count>"], - "c": ["vim-change-move <#count>"], - "y": ["vim-yank-move <#count>"], + "d": ["vim.delete-move <#count>"], + "c": ["vim.change-move <#count>"], + "y": ["vim.yank-move <#count>"], "": ["scroll-lines", -2], "": ["scroll-text-horizontal", -4], @@ -830,47 +838,47 @@ "": ["scroll-lines", -1], "": ["scroll-lines", 1], - "/": ["vim-open-search-bar"], - "gc": ["vim-toggle-line-comment"], - "*": ["vim-set-search-query-from-word"], - "n": ["vim-next-search-result"], - "N": ["vim-prev-search-result"], + "/": ["vim.open-search-bar"], + "gc": ["vim.toggle-line-comment"], + "*": ["vim.set-search-query-from-word"], + "n": ["vim.next-search-result"], + "N": ["vim.prev-search-result"], ".": ["replay-commands", "."], - "L": ["vim-select-word-or-add-cursor"], - "H": ["vim-move-last-selection-to-next-search-result"], + "L": ["vim.select-word-or-add-cursor"], + "H": ["vim.move-last-selection-to-next-search-result"], - "m": ["vim-add-mark "], - "'": ["vim-goto-mark "], + "m": ["vim.add-mark "], + "'": ["vim.goto-mark "], - "q": ["vim-start-macro "], - "@": ["vim-play-macro "], - "Q": ["vim-stop-macro"], + "q": ["vim.start-macro "], + "@": ["vim.play-macro "], + "Q": ["vim.stop-macro"], - "M": ["enter-choose-cursor-mode", "vim-handle-select-word"], + "M": ["enter-choose-cursor-mode", "vim.handle-select-word"], // movement "": ["select-prev"], "": ["select-next"], "": ["vim.select-last "], - "o": ["vim-reverse-selections"], + "o": ["vim.reverse-selections"], "": ["select-next-tab-stop"], "": ["select-prev-tab-stop"], - "gt": ["vim-goto-next-diagnostic"], - "gn": ["vim-goto-prev-diagnostic"], - "gf": ["vim-goto-next-change"], - "gh": ["vim-goto-prev-change"], + "gt": ["vim.goto-next-diagnostic"], + "gn": ["vim.goto-prev-diagnostic"], + "gf": ["vim.goto-next-change"], + "gh": ["vim.goto-prev-change"], // copy/paste - "p": ["vim-paste", true, false, "vim-default-register"], - "P": ["vim-paste", true, false], + "p": ["vim.paste", true, false, "vim.default-register"], + "P": ["vim.paste", true, false], // editing - "x": ["vim-delete-right"], - "": ["vim-delete-right"], - "": ["vim-delete-left"], - "": ["vim-delete-left"], - "": ["vim-delete-left"], + "x": ["vim.delete-right"], + "": ["vim.delete-right"], + "": ["vim.delete-left"], + "": ["vim.delete-left"], + "": ["vim.delete-left"], "r": ["set-mode", "vim-new.replace"], // undo/redo @@ -889,30 +897,30 @@ "A": ["vim.insert-mode", "line-end"], "I": ["vim.insert-mode", "line-no-indent"], "gI": ["vim.insert-mode", "line-start"], - "o": ["vim-insert-line-below"], - "O": ["vim-insert-line-above"], + "o": ["vim.insert-line-below"], + "O": ["vim.insert-line-above"], "v": ["set-mode", "vim-new.visual"], - "V": ["vim-visual-line-mode"], + "V": ["vim.visual-line-mode"], - "dd": ["vim-delete-line"], - "cc": ["vim-change-line"], - "yy": ["vim-yank-line"], - "D": ["vim-delete-to-line-end"], - "C": ["vim-change-to-line-end"], - "Y": ["vim-yank-to-line-end"], + "dd": ["vim.delete-line"], + "cc": ["vim.change-line"], + "yy": ["vim.yank-line"], + "D": ["vim.delete-to-line-end"], + "C": ["vim.change-to-line-end"], + "Y": ["vim.yank-to-line-end"], // cursor movement - "gxf": ["vim-increment"], - "gxh": ["vim-decrement"], - "gxc": ["vim-increment-by-index"], + "gxf": ["vim.increment"], + "gxh": ["vim.decrement"], + "gxc": ["vim.increment-by-index"], // editing - "~": ["vim-toggle-case", true], - "\\>\\>": ["vim-indent"], - "\\<\\<": ["vim-unindent"], - "s": ["all", [".set-mode", "vim-new.visual"], [".vim-change-selection", true]], - "J": ["vim-join-lines", true], - "gJ": ["vim-join-lines", false], + "~": ["vim.toggle-case", true], + "\\>\\>": ["vim.indent"], + "\\<\\<": ["vim.unindent"], + "s": ["all", [".set-mode", "vim-new.visual"], [".vim.change-selection", true]], + "J": ["vim.join-lines", true], + "gJ": ["vim.join-lines", false], // lsp "gd": ["goto-definition"], @@ -929,12 +937,12 @@ "H": ["show-diagnostics-for-current"], // scroll - "z": ["vim-scroll-line-to-top-and-move-line-start"], - "zt": ["vim-scroll-line-to-top"], - "z.": ["vim-center-line-and-move-line-start"], - "zz": ["vim-center-line"], - "z-": ["vim-scroll-line-to-bottom-and-move-line-start"], - "zb": ["vim-scroll-line-to-bottom"], + "z": ["vim.scroll-line-to-top-and-move-line-start"], + "zt": ["vim.scroll-line-to-top"], + "z.": ["vim.center-line-and-move-line-start"], + "zz": ["vim.center-line"], + "z-": ["vim.scroll-line-to-bottom-and-move-line-start"], + "zb": ["vim.scroll-line-to-bottom"], // vcs "gy": ["stage-selected", false], @@ -944,14 +952,14 @@ "vim-new.insert": { "cf": ["set-mode", "vim-new.normal"], "": ["insert-text", " "], - "": ["vim-enter"], - "": ["vim-delete-word-back"], - "": ["vim-delete-line-back"], + "": ["vim.enter"], + "": ["vim.delete-word-back"], + "": ["vim.delete-line-back"], "": ["get-completions"], "": ["get-completions"], "": ["insert-indent"], - "": ["vim-indent"], - "": ["vim-unindent"], + "": ["vim.indent"], + "": ["vim.unindent"], "": ["set-mode", "vim-new.insert-register"], }, @@ -960,49 +968,49 @@ "A": ["vim.insert-mode", "right"], "I": ["vim.insert-mode", "first"], "": ["vim.select "], - "": ["vim-select-move <#count>"], + "": ["vim.select-move <#count>"], - "y": ["vim-yank-selection"], - "gy": ["vim-yank-selection-clipboard"], - "d": ["vim-delete-selection", true], - "x": ["vim-delete-selection", true], - "X": ["vim-delete-selection", true], - "": ["vim-delete-selection", true], - "": ["vim-delete-selection", true], - "c": ["vim-change-selection", true], - "s": ["vim-change-selection", true], - "*": ["vim-set-search-query-from-selection"], + "y": ["vim.yank-selection"], + "gy": ["vim.yank-selection-clipboard"], + "d": ["vim.delete-selection", true], + "x": ["vim.delete-selection", true], + "X": ["vim.delete-selection", true], + "": ["vim.delete-selection", true], + "": ["vim.delete-selection", true], + "c": ["vim.change-selection", true], + "s": ["vim.change-selection", true], + "*": ["vim.set-search-query-from-selection"], - "L": ["vim-set-search-query-or-add-cursor"], + "L": ["vim.set-search-query-or-add-cursor"], // copy/paste - "p": ["vim-paste", false, true, "vim-default-register"], - "P": ["vim-paste", false, true], - "y": ["vim-yank-selection"], - "gy": ["vim-yank-selection-clipboard"], - "d": ["vim-delete-selection", true], - "c": ["vim-change-selection", true], - "s": ["vim-change-selection", true], + "p": ["vim.paste", false, true, "vim.default-register"], + "P": ["vim.paste", false, true], + "y": ["vim.yank-selection"], + "gy": ["vim.yank-selection-clipboard"], + "d": ["vim.delete-selection", true], + "c": ["vim.change-selection", true], + "s": ["vim.change-selection", true], // cursor movement - "o": ["vim-invert-selections"], + "o": ["vim.invert-selections"], "gp": ["select-parent-current-ts", false], - "gl": ["vim-add-next-same-node-to-selection"], - "gh": ["vim-move-selection-to-next-same-node"], - "gs": ["vim-add-next-sibling-to-selection"], - "gd": ["vim-move-selection-to-next-sibling"], - "gi": ["vim-shrink-selection"], - "gxx": ["vim-evaluate-selection"], - "gxf": ["vim-increment-selection"], - "gxh": ["vim-decrement-selection"], - "gxc": ["vim-increment-selection-by-index"], + "gl": ["vim.add-next-same-node-to-selection"], + "gh": ["vim.move-selection-to-next-same-node"], + "gs": ["vim.add-next-sibling-to-selection"], + "gd": ["vim.move-selection-to-next-sibling"], + "gi": ["vim.shrink-selection"], + "gxx": ["vim.evaluate-selection"], + "gxf": ["vim.increment-selection"], + "gxh": ["vim.decrement-selection"], + "gxc": ["vim.increment-selection-by-index"], "gxg": "cycle-selected-case", // editing - "~": ["vim-toggle-case", false], - "\\>": ["vim-indent"], - "\\<": ["vim-unindent"], - "S": ["vim-surround "], + "~": ["vim.toggle-case", false], + "\\>": ["vim.indent"], + "\\<": ["vim.unindent"], + "S": ["vim.surround "], // vcs "gy": ["stage-selected", true], @@ -1012,24 +1020,24 @@ "vim-new.visual-line": { // special "": ["vim.select "], - "": ["vim-select-move <#count>"], + "": ["vim.select-move <#count>"], // copy/paste - "p": ["vim-paste", false, true, "vim-default-register"], - "P": ["vim-paste", false, true], - "y": ["vim-yank-selection"], - "gy": ["vim-yank-selection-clipboard"], - "d": ["vim-delete-selection", true], - "c": ["vim-change-selection", true], - "s": ["vim-change-selection", true], + "p": ["vim.paste", false, true, "vim.default-register"], + "P": ["vim.paste", false, true], + "y": ["vim.yank-selection"], + "gy": ["vim.yank-selection-clipboard"], + "d": ["vim.delete-selection", true], + "c": ["vim.change-selection", true], + "s": ["vim.change-selection", true], // cursor movement - "o": ["vim-invert-line-selections"], + "o": ["vim.invert-line-selections"], // editing - "~": ["vim-toggle-case", false], - "\\>": ["vim-indent"], - "\\<": ["vim-unindent"], + "~": ["vim.toggle-case", false], + "\\>": ["vim.indent"], + "\\<": ["vim.unindent"], // vcs "gy": ["stage-selected", true], @@ -1037,13 +1045,13 @@ }, "vim-new.replace": { - "": ["vim-replace", " "], + "": ["vim.replace", " "], "": ["set-mode", "vim-new.normal"], }, "vim-new.insert-register": { "": ["set-mode", "vim-new.insert"], - "": ["all", [".vim-paste", false, true, "vim-default-register"], [".set-mode", "vim-new.insert"]], + "": ["all", [".vim.paste", false, true, "vim.default-register"], [".set-mode", "vim-new.insert"]], }, "vim-new.completion": { diff --git a/plugin_api/v0/api.nim b/plugin_api/v0/api.nim index cb6f3e947..2fcbf3ae8 100644 --- a/plugin_api/v0/api.nim +++ b/plugin_api/v0/api.nim @@ -39,6 +39,7 @@ proc `==`*(a, b: WitString): bool = type CommandHandler = proc(data: uint32, args: WitString): WitString {.cdecl.} type ChannelUpdateHandler = proc(data: uint32, closed: bool): ChannelListenResponse {.cdecl, raises: [].} +type MoveHandler = proc(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl, raises: [].} proc initPlugin() = emscripten_stack_init() @@ -77,6 +78,10 @@ proc notifyTasksComplete(tasks: WitList[tuple[task: uint64, canceled: bool]]) = for (task, canceled) in tasks: notifyTaskComplete(task, canceled) +proc handleMove(fun: uint32; data: uint32; text: sink Rope; selections: WitList[Selection]; count: int32; eol: bool): WitList[Selection] = + let fun = cast[MoveHandler](fun) + return stackWitList(fun(data, text.ensureMove, selections.toOpenArray(), count.int, eol)) + ############################ nice wrappers around the raw api ############################ proc wl*[T](): WitList[T] = WitList[T].default() @@ -217,6 +222,12 @@ when pluginWorld == "plugin": proc applyMove*(editor: TextEditor, cursor: Cursor, move: string, count: int = 1, wrap: bool = true, includeEol: bool = true): Selection = return editor.applyMove(cursor.toSelection, move.ws, count, wrap, includeEol)[0] + proc scrollToCursor*(editor: TextEditor) = + editor.scrollToCursor(ScrollBehaviour.none) + + proc addCustomTextMove*(name: string, move: MoveHandler) = + defineMove(name.ws, 0, cast[uint32](move)) + proc asEditor*(editor: TextEditor): Editor = Editor(id: editor.id) proc asDocument*(document: TextDocument): Document = Document(id: document.id) diff --git a/plugin_api/v0/plugin_api_guest.nim b/plugin_api/v0/plugin_api_guest.nim index 451375721..78057bf49 100644 --- a/plugin_api/v0/plugin_api_guest.nim +++ b/plugin_api/v0/plugin_api_guest.nim @@ -156,3 +156,31 @@ proc notifyTaskCompleteExported(a0: uint64; a1: bool): void {. task = convert(a0, uint64) canceled = a1.bool notifyTaskComplete(task, canceled) + +proc handleMove(fun: uint32; data: uint32; text: sink Rope; + selections: WitList[Selection]; count: int32; eol: bool): WitList[ + Selection] +var handleMoveRetArea: array[28, uint8] +proc handleMoveExported(a0: uint32; a1: uint32; a2: int32; a3: int32; a4: int32; + a5: int32; a6: bool): int32 {. + wasmexport("handle-move", "nev:plugins/guest").} = + var + fun: uint32 + data: uint32 + text: Rope + selections: WitList[Selection] + count: int32 + eol: bool + fun = convert(a0, uint32) + data = convert(a1, uint32) + text.handle = a2 + 1 + selections = wl(cast[ptr typeof(selections[0])](a3), a4) + count = convert(a5, int32) + eol = a6.bool + let res = handleMove(fun, data, text, selections, count, eol) + if res.len > 0: + cast[ptr int32](handleMoveRetArea[0].addr)[] = cast[int32](res[0].addr) + else: + cast[ptr int32](handleMoveRetArea[0].addr)[] = 0.int32 + cast[ptr int32](handleMoveRetArea[4].addr)[] = cast[int32](res.len) + cast[int32](handleMoveRetArea[0].addr) diff --git a/plugin_api/v0/text_editor.nim b/plugin_api/v0/text_editor.nim index 9e172b9ae..87969afa6 100644 --- a/plugin_api/v0/text_editor.nim +++ b/plugin_api/v0/text_editor.nim @@ -15,6 +15,15 @@ import import commands +type + ScrollBehaviour* = enum + CenterAlways = "center-always", CenterOffscreen = "center-offscreen", + CenterMargin = "center-margin", ScrollToMargin = "scroll-to-margin", + TopOfScreen = "top-of-screen" + ScrollSnapBehaviour* = enum + Never = "never", Always = "always", + MinDistanceOffscreen = "min-distance-offscreen", + MinDistanceCenter = "min-distance-center" proc textEditorActiveTextEditorImported(a0: int32): void {. wasmimport("active-text-editor", "nev:plugins/text-editor").} proc activeTextEditor*(): Option[TextEditor] {.nodestroy.} = @@ -149,6 +158,41 @@ proc hideCompletions*(editor: TextEditor): void {.nodestroy.} = arg0 = editor.id textEditorHideCompletionsImported(arg0) +proc textEditorScrollToCursorImported(a0: uint64; a1: int8; a2: int8): void {. + wasmimport("scroll-to-cursor", "nev:plugins/text-editor").} +proc scrollToCursor*(editor: TextEditor; behaviour: Option[ScrollBehaviour]): void {. + nodestroy.} = + ## todo + var + arg0: uint64 + arg1: int8 + arg2: int8 + arg0 = editor.id + arg1 = behaviour.isSome.int8 + if behaviour.isSome: + arg2 = cast[int8](behaviour.get) + textEditorScrollToCursorImported(arg0, arg1, arg2) + +proc textEditorSetNextSnapBehaviourImported(a0: uint64; a1: int8): void {. + wasmimport("set-next-snap-behaviour", "nev:plugins/text-editor").} +proc setNextSnapBehaviour*(editor: TextEditor; behaviour: ScrollSnapBehaviour): void {. + nodestroy.} = + ## todo + var + arg0: uint64 + arg1: int8 + arg0 = editor.id + arg1 = cast[int8](behaviour) + textEditorSetNextSnapBehaviourImported(arg0, arg1) + +proc textEditorUpdateTargetColumnImported(a0: uint64): void {. + wasmimport("update-target-column", "nev:plugins/text-editor").} +proc updateTargetColumn*(editor: TextEditor): void {.nodestroy.} = + ## todo + var arg0: uint64 + arg0 = editor.id + textEditorUpdateTargetColumnImported(arg0) + proc textEditorGetUsageImported(a0: uint64; a1: int32): void {. wasmimport("get-usage", "nev:plugins/text-editor").} proc getUsage*(editor: TextEditor): WitString {.nodestroy.} = @@ -362,6 +406,42 @@ proc applyMove*(editor: TextEditor; selection: Selection; move: WitString; result = wl(cast[ptr typeof(result[0])](cast[ptr int32](retArea[0].addr)[]), cast[ptr int32](retArea[4].addr)[]) +proc textEditorMultiMoveImported(a0: uint64; a1: int32; a2: int32; a3: int32; + a4: int32; a5: int32; a6: bool; a7: bool; + a8: int32): void {. + wasmimport("multi-move", "nev:plugins/text-editor").} +proc multiMove*(editor: TextEditor; selections: WitList[Selection]; + move: WitString; count: int32; wrap: bool; includeEol: bool): WitList[ + Selection] {.nodestroy.} = + var + retArea: array[40, uint8] + arg0: uint64 + arg1: int32 + arg2: int32 + arg3: int32 + arg4: int32 + arg5: int32 + arg6: bool + arg7: bool + arg0 = editor.id + if selections.len > 0: + arg1 = cast[int32](selections[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](selections.len) + if move.len > 0: + arg3 = cast[int32](move[0].addr) + else: + arg3 = 0.int32 + arg4 = cast[int32](move.len) + arg5 = count + arg6 = wrap + arg7 = includeEol + textEditorMultiMoveImported(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, + cast[int32](retArea[0].addr)) + result = wl(cast[ptr typeof(result[0])](cast[ptr int32](retArea[0].addr)[]), + cast[ptr int32](retArea[4].addr)[]) + proc textEditorSetSelectionImported(a0: uint64; a1: int32; a2: int32; a3: int32; a4: int32): void {. wasmimport("set-selection", "nev:plugins/text-editor").} @@ -489,18 +569,20 @@ proc setSettingRaw*(editor: TextEditor; name: WitString; value: WitString): void textEditorSetSettingRawImported(arg0, arg1, arg2, arg3, arg4) proc textEditorEditImported(a0: uint64; a1: int32; a2: int32; a3: int32; - a4: int32; a5: int32): void {. + a4: int32; a5: bool; a6: int32): void {. wasmimport("edit", "nev:plugins/text-editor").} proc edit*(editor: TextEditor; selections: WitList[Selection]; - contents: WitList[WitString]): WitList[Selection] {.nodestroy.} = + contents: WitList[WitString]; inclusive: bool): WitList[Selection] {. + nodestroy.} = ## todo var - retArea: array[24, uint8] + retArea: array[32, uint8] arg0: uint64 arg1: int32 arg2: int32 arg3: int32 arg4: int32 + arg5: bool arg0 = editor.id if selections.len > 0: arg1 = cast[int32](selections[0].addr) @@ -512,11 +594,29 @@ proc edit*(editor: TextEditor; selections: WitList[Selection]; else: arg3 = 0.int32 arg4 = cast[int32](contents.len) - textEditorEditImported(arg0, arg1, arg2, arg3, arg4, + arg5 = inclusive + textEditorEditImported(arg0, arg1, arg2, arg3, arg4, arg5, cast[int32](retArea[0].addr)) result = wl(cast[ptr typeof(result[0])](cast[ptr int32](retArea[0].addr)[]), cast[ptr int32](retArea[4].addr)[]) +proc textEditorDefineMoveImported(a0: int32; a1: int32; a2: uint32; a3: uint32): void {. + wasmimport("define-move", "nev:plugins/text-editor").} +proc defineMove*(move: WitString; fun: uint32; data: uint32): void {.nodestroy.} = + var + arg0: int32 + arg1: int32 + arg2: uint32 + arg3: uint32 + if move.len > 0: + arg0 = cast[int32](move[0].addr) + else: + arg0 = 0.int32 + arg1 = cast[int32](move.len) + arg2 = fun + arg3 = data + textEditorDefineMoveImported(arg0, arg1, arg2, arg3) + proc textEditorContentImported(a0: uint64): int32 {. wasmimport("content", "nev:plugins/text-editor").} proc content*(editor: TextEditor): Rope {.nodestroy.} = diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index c00403773..0800fb8fd 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -194,6 +194,10 @@ proc vimState(editor: TextEditor): var EditorVimState = editorStates[editor.id] = EditorVimState() return editorStates[editor.id] +proc getContextWithMode(self: TextEditor, context: string): string = + ## Appends the current mode to context + return context & "." & $self.mode + proc shouldRecortImplicitPeriodMacro(editor: TextEditor): bool = case $editor.getUsage() of "command-line", "search-bar": @@ -238,7 +242,6 @@ proc handleSelectWord(editor: TextEditor, cursor: Cursor) {.exposeActive(editorC editor.setSelection(cursor.toSelection) proc selectLine(editor: TextEditor) {.exposeActive(editorContext).} = - debugf"selectLine" editor.setSelections stackWitList(editor.selections.mapIt (if it.isBackwards: Selection(first: Cursor(line: it.first.line, column: editor.lineLength(it.first.line)), last: Cursor(line: it.last.line, column: 0)) else: @@ -246,8 +249,6 @@ proc selectLine(editor: TextEditor) {.exposeActive(editorContext).} = )) proc selectLast(editor: TextEditor, move: string, count: int = 1) {.exposeActive(editorContext).} = - # stdout.write(ansiForegroundColorCode(color)) - debugf"selectLast '{move}', count" let (action, arg) = move.parseAction for i in 0.. 1: # editor.moveCursorLine(count - 1) # editor.moveLast("vim-line") -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.updateTargetColumn() # proc vimMoveToEndOfVisualLine(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-end-of-visual-line").} = @@ -838,7 +836,7 @@ proc vimClamp*(editor: TextEditor, cursor: Cursor): Cursor = # if count > 1: # editor.moveCursorLine(count - 1) # editor.moveLast("vim-visual-line") -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.updateTargetColumn() # proc vimMoveCursorLineFirstChar(editor: TextEditor, direction: int, count: int = 1) {.exposeActive(editorContext, "vim-move-cursor-line-first-char").} = @@ -852,7 +850,7 @@ proc vimClamp*(editor: TextEditor, cursor: Cursor): Cursor = # if count > 1: # editor.moveCursorLine(count - 1) # editor.moveFirst "line-no-indent" -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.updateTargetColumn() # proc vimPaste(editor: TextEditor, pasteRight: bool = false, inclusiveEnd: bool = false, register: string = "") {.exposeActive(editorContext, "vim-paste").} = @@ -921,11 +919,11 @@ proc vimClamp*(editor: TextEditor, cursor: Cursor): Cursor = # proc vimAddCursorAbove(editor: TextEditor) {.exposeActive(editorContext, "vim-add-cursor-above").} = # editor.addCursorAbove() -# editor.scrollToCursor(Last) +# editor.scrollToCursor() # proc vimAddCursorBelow(editor: TextEditor) {.exposeActive(editorContext, "vim-add-cursor-below").} = # editor.addCursorBelow() -# editor.scrollToCursor(Last) +# editor.scrollToCursor() # proc vimEnter(editor: TextEditor) {.exposeActive(editorContext, "vim-enter").} = # editor.addNextCheckpoint "insert" @@ -933,15 +931,14 @@ proc vimClamp*(editor: TextEditor, cursor: Cursor): Cursor = proc normalMode(editor: TextEditor) {.exposeActive(editorContext).} = ## Exit to normal mode and clear things - log lvlInfo, "normalMode" if $editor.mode == "vim-new.normal": editor.setSelection editor.getSelection.last.toSelection editor.clearTabStops() editor.setMode("vim-new.normal") -# proc vimVisualLineMode(editor: TextEditor) {.exposeActive(editorContext, "vim-visual-line-mode").} = -# editor.setMode "vim-new.visual-line" -# editor.selectLine() +proc visualLineMode(editor: TextEditor) {.exposeActive(editorContext).} = + editor.setMode "vim-new.visual-line" + editor.selectLine() # proc vimYankLine(editor: TextEditor) {.exposeActive(editorContext, "vim-yank-line").} = # editor.vimState.selectLines = true @@ -982,7 +979,7 @@ proc normalMode(editor: TextEditor) {.exposeActive(editorContext).} = # let which = getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) # editor.setSelection (count - 1, 0).toSelection(editor.selection, which) # editor.moveFirst "line-no-indent" -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # proc vimMoveFileEnd(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-file-end").} = @@ -995,7 +992,7 @@ proc normalMode(editor: TextEditor) {.exposeActive(editorContext).} = # else: # editor.setSelection newSelection # editor.moveFirst "line-no-indent" -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # proc vimMoveToMatchingOrFileOffset(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-matching-or-file-offset").} = @@ -1006,7 +1003,7 @@ proc normalMode(editor: TextEditor) {.exposeActive(editorContext).} = # let which = getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) # editor.setSelection (line, 0).toSelection(editor.selection, which) # editor.moveFirst "line-no-indent" -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # proc vimScrollLineToTopAndMoveLineStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-scroll-line-to-top-and-move-line-start").} = @@ -1083,13 +1080,13 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # proc vimNextSearchResult(editor: TextEditor) {.exposeActive(editorContext, "vim-next-search-result").} = # editor.setSelection editor.getNextFindResult(editor.selection.last).first.toSelection -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # editor.updateTargetColumn() # proc vimPrevSearchResult(editor: TextEditor) {.exposeActive(editorContext, "vim-prev-search-result").} = # editor.setSelection editor.getPrevFindResult(editor.selection.last).first.toSelection -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # editor.updateTargetColumn() @@ -1123,7 +1120,7 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # else: # let next = editor.getNextFindResult(editor.selection.last, includeAfter=false) # editor.setSelections editor.selections & next -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # editor.updateTargetColumn() @@ -1137,7 +1134,7 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # else: # let next = editor.getNextFindResult(editor.selection.last, includeAfter=false) # editor.setSelections editor.selections[0..^2] & next -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # editor.updateTargetColumn() @@ -1155,7 +1152,7 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # let next = editor.getNextFindResult(editor.selection.last, includeAfter=false) # editor.setSelections editor.selections & next -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.updateTargetColumn() # proc vimSaveState() {.expose("vim-save-state").} = @@ -1217,7 +1214,7 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # editor.setSelections newSelections # editor.updateTargetColumn() -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # proc vimDeleteWordBack(editor: TextEditor) {.exposeActive(editorContext, "vim-delete-word-back").} = @@ -1239,7 +1236,7 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # else: # (text, text) -# var insertSelections: Selections = @[] +# var insertSelections: seq[Selection] = @[] # var insertTexts: seq[string] = @[] # for s in editor.selections: # let s = s.normalized @@ -1283,19 +1280,19 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # proc vimInvertSelections(editor: TextEditor) {.exposeActive(editorContext, "vim-invert-selections").} = # editor.setSelections editor.selections.mapIt((it.last, it.first)) -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.updateTargetColumn() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # proc vimInvertLineSelections(editor: TextEditor) {.exposeActive(editorContext, "vim-invert-line-selections").} = # editor.setSelections editor.selections.mapIt((it.last, it.first)) -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.updateTargetColumn() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # proc vimReverseSelections(editor: TextEditor) {.exposeActive(editorContext, "vim-reverse-selections").} = # editor.setSelections editor.selections.reversed() -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.updateTargetColumn() # editor.setNextSnapBehaviour(MinDistanceOffscreen) @@ -1322,34 +1319,34 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # proc vimMoveToColumn(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-column").} = # editor.setSelections editor.selections.mapIt((it.last.line, count).toSelection) -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.updateTargetColumn() # proc vimAddNextSameNodeToSelection(editor: TextEditor) {.exposeActive(editorContext, "vim-add-next-same-node-to-selection").} = # if editor.getNextNodeWithSameType(editor.selection, includeAfter=false).getSome(selection): # editor.setSelections editor.selections & selection -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # editor.updateTargetColumn() # proc vimMoveSelectionToNextSameNode(editor: TextEditor) {.exposeActive(editorContext, "vim-move-selection-to-next-same-node").} = # if editor.getNextNodeWithSameType(editor.selection, includeAfter=false).getSome(selection): # editor.setSelections editor.selections[0..^2] & selection -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # editor.updateTargetColumn() # proc vimAddNextSiblingToSelection(editor: TextEditor) {.exposeActive(editorContext, "vim-add-next-sibling-to-selection").} = # if editor.getNextNamedSiblingNodeSelection(editor.selection, includeAfter=false).getSome(selection): # editor.setSelections editor.selections & selection -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # editor.updateTargetColumn() # proc vimMoveSelectionToNextSibling(editor: TextEditor) {.exposeActive(editorContext, "vim-move-selection-to-next-sibling").} = # if editor.getNextNamedSiblingNodeSelection(editor.selection, includeAfter=false).getSome(selection): # editor.setSelections editor.selections[0..^2] & selection -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # editor.updateTargetColumn() @@ -1360,7 +1357,7 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # else: # (editor.doMoveCursorColumn(it.first, 1), editor.doMoveCursorColumn(it.last, -1)) # ) -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # editor.updateTargetColumn() @@ -1401,26 +1398,26 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # proc vimGotoNextDiagnostic(editor: TextEditor) {.exposeActive(editorContext, "vim-goto-next-diagnostic").} = # let severity = getOption("text.jump-diagnostic-severity", 1) # editor.setSelection editor.getNextDiagnostic(editor.selection.last, severity).first.toSelection -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.updateTargetColumn() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # proc vimGotoPrevDiagnostic(editor: TextEditor) {.exposeActive(editorContext, "vim-goto-prev-diagnostic").} = # let severity = getOption("text.jump-diagnostic-severity", 1) # editor.setSelection editor.getPrevDiagnostic(editor.selection.last, severity).first.toSelection -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.updateTargetColumn() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # proc vimGotoNextChange(editor: TextEditor) {.exposeActive(editorContext, "vim-goto-next-change").} = # editor.setSelection editor.getNextChange(editor.selection.last).first.toSelection -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.centerCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # proc vimGotoPrevChange(editor: TextEditor) {.exposeActive(editorContext, "vim-goto-prev-change").} = # editor.setSelection editor.getPrevChange(editor.selection.last).first.toSelection -# editor.scrollToCursor Last +# editor.scrollToCursor() # editor.centerCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) diff --git a/scripting/editor_text_api_wasm.nim b/scripting/editor_text_api_wasm.nim index a83fa6c23..cf187a41a 100644 --- a/scripting/editor_text_api_wasm.nim +++ b/scripting/editor_text_api_wasm.nim @@ -1932,18 +1932,19 @@ proc vimMotionWordBig*(self: TextDocumentEditor; cursor: Cursor; count: int = 0) raiseAssert(getCurrentExceptionMsg()) -proc editor_text_getSelectionForMove_Selection_TextDocumentEditor_Cursor_string_int_wasm( +proc editor_text_getSelectionForMove_Selection_TextDocumentEditor_Cursor_string_int_bool_wasm( arg: cstring): cstring {.importc.} proc getSelectionForMove*(self: TextDocumentEditor; cursor: Cursor; - move: string; count: int = 0): Selection {.gcsafe, - raises: [].} = + move: string; count: int = 0; includeEol: bool = true): Selection {. + gcsafe, raises: [].} = var argsJson = newJArray() argsJson.add self.toJson() argsJson.add cursor.toJson() argsJson.add move.toJson() argsJson.add count.toJson() + argsJson.add includeEol.toJson() let argsJsonString = $argsJson - let res {.used.} = editor_text_getSelectionForMove_Selection_TextDocumentEditor_Cursor_string_int_wasm( + let res {.used.} = editor_text_getSelectionForMove_Selection_TextDocumentEditor_Cursor_string_int_bool_wasm( argsJsonString.cstring) try: result = parseJson($res).jsonTo(typeof(result)) diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index 782c2fd61..46c3a0882 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -62,6 +62,14 @@ type ReadFlag* = enum Binary = "binary" ReadFlags* = set[ReadFlag] + ScrollBehaviour* = enum + CenterAlways = "center-always", CenterOffscreen = "center-offscreen", + CenterMargin = "center-margin", ScrollToMargin = "scroll-to-margin", + TopOfScreen = "top-of-screen" + ScrollSnapBehaviour* = enum + Never = "never", Always = "always", + MinDistanceOffscreen = "min-distance-offscreen", + MinDistanceCenter = "min-distance-center" when not declared(RopeResource): {.error: "Missing resource type definition for " & "RopeResource" & ". Define the type before the importWit statement.".} @@ -107,41 +115,41 @@ proc collectExports*(funcs: var ExportedFuncs; instance: InstanceT; funcs.mStackAlloc = instance.getExport(context, "mem_stack_alloc") funcs.mStackSave = instance.getExport(context, "mem_stack_save") funcs.mStackRestore = instance.getExport(context, "mem_stack_restore") - let f_8556381802 = instance.getExport(context, "init_plugin") - if f_8556381802.isSome: - assert f_8556381802.get.kind == WASMTIME_EXTERN_FUNC - funcs.initPlugin = f_8556381802.get.of_field.func_field + let f_8573159018 = instance.getExport(context, "init_plugin") + if f_8573159018.isSome: + assert f_8573159018.get.kind == WASMTIME_EXTERN_FUNC + funcs.initPlugin = f_8573159018.get.of_field.func_field else: echo "Failed to find exported function \'", "init_plugin", "\'" - let f_8556381818 = instance.getExport(context, "handle_command") - if f_8556381818.isSome: - assert f_8556381818.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleCommand = f_8556381818.get.of_field.func_field + let f_8573159034 = instance.getExport(context, "handle_command") + if f_8573159034.isSome: + assert f_8573159034.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleCommand = f_8573159034.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_command", "\'" - let f_8556381868 = instance.getExport(context, "handle_mode_changed") - if f_8556381868.isSome: - assert f_8556381868.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleModeChanged = f_8556381868.get.of_field.func_field + let f_8573159084 = instance.getExport(context, "handle_mode_changed") + if f_8573159084.isSome: + assert f_8573159084.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleModeChanged = f_8573159084.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_mode_changed", "\'" - let f_8556381869 = instance.getExport(context, "handle_view_render_callback") - if f_8556381869.isSome: - assert f_8556381869.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleViewRenderCallback = f_8556381869.get.of_field.func_field + let f_8573159085 = instance.getExport(context, "handle_view_render_callback") + if f_8573159085.isSome: + assert f_8573159085.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleViewRenderCallback = f_8573159085.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_view_render_callback", "\'" - let f_8556381893 = instance.getExport(context, "handle_channel_update") - if f_8556381893.isSome: - assert f_8556381893.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleChannelUpdate = f_8556381893.get.of_field.func_field + let f_8573159109 = instance.getExport(context, "handle_channel_update") + if f_8573159109.isSome: + assert f_8573159109.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleChannelUpdate = f_8573159109.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_channel_update", "\'" - let f_8556381894 = instance.getExport(context, "notify_task_complete") - if f_8556381894.isSome: - assert f_8556381894.get.kind == WASMTIME_EXTERN_FUNC - funcs.notifyTaskComplete = f_8556381894.get.of_field.func_field + let f_8573159110 = instance.getExport(context, "notify_task_complete") + if f_8573159110.isSome: + assert f_8573159110.get.kind == WASMTIME_EXTERN_FUNC + funcs.notifyTaskComplete = f_8573159110.get.of_field.func_field else: echo "Failed to find exported function \'", "notify_task_complete", "\'" @@ -377,6 +385,12 @@ proc textEditorClearCurrentCommandHistory(instance: ptr InstanceData; proc textEditorSaveCurrentCommandHistory(instance: ptr InstanceData; editor: TextEditor): void proc textEditorHideCompletions(instance: ptr InstanceData; editor: TextEditor): void +proc textEditorScrollToCursor(instance: ptr InstanceData; editor: TextEditor; + behaviour: Option[ScrollBehaviour]): void +proc textEditorSetNextSnapBehaviour(instance: ptr InstanceData; + editor: TextEditor; + behaviour: ScrollSnapBehaviour): void +proc textEditorUpdateTargetColumn(instance: ptr InstanceData; editor: TextEditor): void proc textEditorGetUsage(instance: ptr InstanceData; editor: TextEditor): string proc textEditorGetRevision(instance: ptr InstanceData; editor: TextEditor): int32 proc textEditorSetMode(instance: ptr InstanceData; editor: TextEditor; @@ -399,6 +413,10 @@ proc textEditorPaste(instance: ptr InstanceData; editor: TextEditor; proc textEditorApplyMove(instance: ptr InstanceData; editor: TextEditor; selection: Selection; move: sink string; count: int32; wrap: bool; includeEol: bool): seq[Selection] +proc textEditorMultiMove(instance: ptr InstanceData; editor: TextEditor; + selections: sink seq[Selection]; move: sink string; + count: int32; wrap: bool; includeEol: bool): seq[ + Selection] proc textEditorSetSelection(instance: ptr InstanceData; editor: TextEditor; s: Selection): void proc textEditorSetSelections(instance: ptr InstanceData; editor: TextEditor; @@ -414,8 +432,10 @@ proc textEditorGetSettingRaw(instance: ptr InstanceData; editor: TextEditor; proc textEditorSetSettingRaw(instance: ptr InstanceData; editor: TextEditor; name: sink string; value: sink string): void proc textEditorEdit(instance: ptr InstanceData; editor: TextEditor; - selections: sink seq[Selection]; contents: sink seq[string]): seq[ - Selection] + selections: sink seq[Selection]; contents: sink seq[string]; + inclusive: bool): seq[Selection] +proc textEditorDefineMove(instance: ptr InstanceData; move: sink string; + fun: uint32; data: uint32): void proc textEditorContent(instance: ptr InstanceData; editor: TextEditor): RopeResource proc textDocumentContent(instance: ptr InstanceData; document: TextDocument): RopeResource proc layoutShow(instance: ptr InstanceData; v: View; slot: sink string; @@ -1292,6 +1312,48 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = textEditorHideCompletions(instance, editor) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "scroll-to-cursor", + ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + var behaviour: Option[ScrollBehaviour] + editor.id = convert(parameters[0].i64, uint64) + if parameters[1].i32 != 0: + var temp: ScrollBehaviour + temp = cast[ScrollBehaviour](parameters[2].i32) + behaviour = temp.some + textEditorScrollToCursor(instance, editor, behaviour) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", + "set-next-snap-behaviour", ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + var behaviour: ScrollSnapBehaviour + editor.id = convert(parameters[0].i64, uint64) + behaviour = cast[ScrollSnapBehaviour](parameters[1].i32) + textEditorSetNextSnapBehaviour(instance, editor, behaviour) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", + "update-target-column", ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + textEditorUpdateTargetColumn(instance, editor) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype( @@ -1698,6 +1760,75 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = cast[ptr int32](memory[retArea + 4].addr)[] = cast[int32](res.len) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "multi-move", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + let stackAllocFunc = caller.getExport("mem_stack_alloc").get.of_field.func_field + var editor: TextEditor + var selections: seq[Selection] + var move: string + var count: int32 + var wrap: bool + var includeEol: bool + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[uint8]](memory[parameters[1].i32].addr) + selections = newSeq[typeof(selections[0])](parameters[2].i32) + for i0 in 0 ..< selections.len: + selections[i0].first.line = convert( + cast[ptr int32](p0[i0 * 16 + 0].addr)[], int32) + selections[i0].first.column = convert( + cast[ptr int32](p0[i0 * 16 + 4].addr)[], int32) + selections[i0].last.line = convert( + cast[ptr int32](p0[i0 * 16 + 8].addr)[], int32) + selections[i0].last.column = convert( + cast[ptr int32](p0[i0 * 16 + 12].addr)[], int32) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[3].i32].addr) + move = newString(parameters[4].i32) + for i0 in 0 ..< move.len: + move[i0] = p0[i0] + count = convert(parameters[5].i32, int32) + wrap = parameters[6].i32.bool + includeEol = parameters[7].i32.bool + let res = textEditorMultiMove(instance, editor, selections, move, count, + wrap, includeEol) + let retArea = parameters[^1].i32 + if res.len > 0: + let dataPtrWasm0 = int32(?stackAlloc(stackAllocFunc, store, + (res.len * 16).int32, 4)) + cast[ptr int32](memory[retArea + 0].addr)[] = cast[int32](dataPtrWasm0) + block: + for i0 in 0 ..< res.len: + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 0].addr)[] = res[ + i0].first.line + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 4].addr)[] = res[ + i0].first.column + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 8].addr)[] = res[ + i0].last.line + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 12].addr)[] = res[ + i0].last.column + else: + cast[ptr int32](memory[retArea + 0].addr)[] = 0.int32 + cast[ptr int32](memory[retArea + 4].addr)[] = cast[int32](res.len) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, @@ -1929,8 +2060,8 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, - WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], - []) + WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, + WasmValkind.I32], []) linker.defineFuncUnchecked("nev:plugins/text-editor", "edit", ty): var instance = cast[ptr InstanceData](store.getData()) var mainMemory = caller.getExport("memory") @@ -1949,6 +2080,7 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = var editor: TextEditor var selections: seq[Selection] var contents: seq[string] + var inclusive: bool editor.id = convert(parameters[0].i64, uint64) block: let p0 = cast[ptr UncheckedArray[uint8]](memory[parameters[1].i32].addr) @@ -1972,7 +2104,9 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = contents[i0] = newString(cast[ptr int32](p0[i0 * 8 + 4].addr)[]) for i1 in 0 ..< contents[i0].len: contents[i0][i1] = p1[i1] - let res = textEditorEdit(instance, editor, selections, contents) + inclusive = parameters[5].i32.bool + let res = textEditorEdit(instance, editor, selections, contents, + inclusive) let retArea = parameters[^1].i32 if res.len > 0: let dataPtrWasm0 = int32(?stackAlloc(stackAllocFunc, store, @@ -1993,6 +2127,38 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = cast[ptr int32](memory[retArea + 4].addr)[] = cast[int32](res.len) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], + []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "define-move", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var move: string + var fun: uint32 + var data: uint32 + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[0].i32].addr) + move = newString(parameters[1].i32) + for i0 in 0 ..< move.len: + move[i0] = p0[i0] + fun = convert(parameters[2].i32, uint32) + data = convert(parameters[3].i32, uint32) + textEditorDefineMove(instance, move, fun, data) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64], diff --git a/src/move_database.nim b/src/move_database.nim new file mode 100644 index 000000000..4e089a821 --- /dev/null +++ b/src/move_database.nim @@ -0,0 +1,40 @@ +import std/[strutils, sequtils, sugar, options, json, streams, strformat, tables, + deques, sets, algorithm] +import scripting_api except DocumentEditor, TextDocumentEditor, AstDocumentEditor +from scripting_api as api import nil +import misc/[id, util, rect_utils, event, custom_logger, custom_async, fuzzy_matching, + custom_unicode, delayed_task, myjsonutils, regex, timer, response, rope_utils, rope_regex, jsonex] +import text/custom_treesitter, text/indent +import config_provider, service +import text/[overlay_map, tab_map, wrap_map, diff_map, display_map] +import nimsumtree/[rope] + +{.push gcsafe, raises: [].} + +logCategory "moves" + +type + MoveImpl* = proc(rope: Rope, move: string, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.gcsafe, raises: [].} + MoveDatabase* = ref object of Service + moves: Table[string, MoveImpl] + +func serviceName*(_: typedesc[MoveDatabase]): string = "MoveDatabase" + +addBuiltinService(MoveDatabase) + +method init*(self: MoveDatabase): Future[Result[void, ref CatchableError]] {.async: (raises: []).} = + return ok() + +proc applyMove*(self: MoveDatabase, rope: Rope, move: string, selections: openArray[Selection], count: int = 0, includeEol: bool = true): seq[Selection] = + + if move in self.moves: + let impl = self.moves[move] + return impl(rope, move, selections, count, includeEol) + + case move + else: + log lvlError, &"Unknown move '{move}'" + return @selections + +proc registerMove*(self: MoveDatabase, move: string, impl: MoveImpl) = + self.moves[move] = impl diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index 223833de2..3f14700a4 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -355,6 +355,20 @@ var threadPool = newPluginThreadPool[InstanceDataImpl](10, runInstanceThread) proc toInternal(c: Cursor): scripting_api.Cursor = (c.line.int, c.column.int) proc toInternal(c: Selection): scripting_api.Selection = (c.first.toInternal, c.last.toInternal) +proc toInternal(c: ScrollBehaviour): scripting_api.ScrollBehaviour = + case c + of CenterAlways: scripting_api.CenterAlways + of CenterOffscreen: scripting_api.CenterOffscreen + of CenterMargin: scripting_api.CenterMargin + of ScrollToMargin: scripting_api.ScrollToMargin + of TopOfScreen: scripting_api.TopOfScreen + +proc toInternal(c: ScrollSnapBehaviour): scripting_api.ScrollSnapBehaviour = + case c + of Never: scripting_api.Never + of Always: scripting_api.Always + of MinDistanceOffscreen: scripting_api.MinDistanceOffscreen + of MinDistanceCenter: scripting_api.MinDistanceCenter proc toWasm(c: scripting_api.Cursor): Cursor = Cursor(line: c.line.int32, column: c.column.int32) proc toWasm(c: scripting_api.Selection): Selection = Selection(first: c.first.toWasm, last: c.last.toWasm) @@ -471,7 +485,14 @@ proc textEditorApplyMove(instance: ptr InstanceData; editor: TextEditor; selecti return if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: let textEditor = editor.TextDocumentEditor - return @[textEditor.getSelectionForMove(selection.last.toInternal, move, count).toWasm] + return @[textEditor.getSelectionForMove(selection.last.toInternal, move, count, includeEol).toWasm] + +proc textEditorMultiMove(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection]; move: sink string; count: int32; wrap: bool; includeEol: bool): seq[Selection] = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + let textEditor = editor.TextDocumentEditor + return textEditor.getSelectionsForMove(selections.mapIt(it.toInternal), move, count, includeEol).mapIt(it.toWasm) proc textEditorSetSelection(instance: ptr InstanceData; editor: TextEditor; s: Selection): void = if instance.host == nil: @@ -505,15 +526,26 @@ proc textEditorGetSelections(instance: ptr InstanceData; editor: TextEditor): se let s = textEditor.selection return editor.TextDocumentEditor.selections.mapIt(it.toWasm) -proc textEditorEdit(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection]; contents: sink seq[string]): seq[Selection] = +proc textEditorEdit(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection]; contents: sink seq[string], inclusive: bool): seq[Selection] = if instance.host == nil: return if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: let selections = selections.mapIt(it.toInternal) - let res = editor.TextDocumentEditor.edit(selections, contents) + let res = editor.TextDocumentEditor.edit(selections, contents, inclusiveEnd = inclusive) return res.mapIt(it.toWasm) return selections +proc textEditorDefineMove(instance: ptr InstanceData; move: sink string; fun: uint32; data: uint32): void = + if instance.host == nil: + return + + proc moveImpl(rope: Rope, move: string, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.gcsafe, raises: [].} = + let res = instance.funcs.handleMove(fun, $args.removed, $args.added) + if res.isErr: + log lvlError, "Failed to call handleModeChanged: " & res.err.msg + + instance.host.moves.registerMove(move, moveImpl) + proc textEditor_addModeChangedHandler(instance: ptr InstanceData, fun: uint32): int32 = if instance.host == nil: return @@ -561,6 +593,24 @@ proc textEditorHideCompletions(instance: ptr InstanceData; editor: TextEditor) = if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: editor.TextDocumentEditor.hideCompletions() +proc textEditorScrollToCursor(instance: ptr InstanceData; editor: TextEditor; behaviour: Option[ScrollBehaviour]): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.scrollToCursor(scripting_api.SelectionCursor.Last, scrollBehaviour = behaviour.mapIt(it.toInternal)) + +proc textEditorSetNextSnapBehaviour(instance: ptr InstanceData; editor: TextEditor; behaviour: ScrollSnapBehaviour): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.setNextSnapBehaviour(behaviour.toInternal) + +proc textEditorUpdateTargetColumn(instance: ptr InstanceData; editor: TextEditor): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.updateTargetColumn() + proc textEditorCommand(instance: ptr InstanceData; editor: TextEditor, name: sink string, arguments: sink string): Result[string, CommandError] = if instance.host == nil: return diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index 45afc67a2..98cafcbd2 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -13,7 +13,7 @@ import document, document_editor, events, vmath, bumpy, input, custom_treesitter import completion, completion_provider_document, completion_provider_lsp, completion_provider_snippet, selector_popup_builder, dispatch_tables, register import config_provider, service, layout, platform_service, vfs, vfs_service, command_service, toast -import diff, plugin_service +import diff, plugin_service, move_database import workspaces/workspace import finder/[previewer, finder] import vcs/vcs @@ -231,6 +231,7 @@ type TextDocumentEditor* = ref object of DocumentEditor plugins: PluginService registers: Registers workspace: Workspace + moveDatabase: MoveDatabase vfsService: VFSService vfs*: VFS commands*: CommandService @@ -441,7 +442,7 @@ proc handleInput(self: TextDocumentEditor, input: string, record: bool): EventRe proc showCompletionWindow(self: TextDocumentEditor) proc updateCompletionsFromEngine(self: TextDocumentEditor) proc hideCompletions*(self: TextDocumentEditor) -proc getSelectionForMove*(self: TextDocumentEditor, cursor: Cursor, move: string, count: int = 0): Selection +proc getSelectionForMove*(self: TextDocumentEditor, cursor: Cursor, move: string, count: int = 0, includeEol: bool = true): Selection proc extendSelectionWithMove*(self: TextDocumentEditor, selection: Selection, move: string, count: int = 0): Selection proc updateTargetColumn*(self: TextDocumentEditor, cursor: SelectionCursor = Last) proc updateInlayHints*(self: TextDocumentEditor) @@ -1697,8 +1698,7 @@ proc delete*(self: TextDocumentEditor, selections: seq[Selection], notify: bool proc edit*(self: TextDocumentEditor, selections: seq[Selection], texts: seq[string], notify: bool = true, record: bool = true, inclusiveEnd: bool = false): seq[Selection] {. expose("editor.text").} = - return self.document.edit(selections, self.selections, texts, notify, record, - inclusiveEnd=inclusiveEnd) + return self.document.edit(selections, self.selections, texts, notify, record, inclusiveEnd=inclusiveEnd) proc deleteLines(self: TextDocumentEditor, slice: Slice[int], oldSelections: Selections) {. expose("editor.text").} = @@ -3003,8 +3003,41 @@ proc vimMotionWordBig*(self: TextDocumentEditor, cursor: Cursor, count: int = 0) let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c notin Whitespace) return ((cursor.line, startColumn), (cursor.line, endColumn)) +proc getSelectionsForMove*(self: TextDocumentEditor, selections: openArray[Selection], move: string, + count: int = 0, includeEol: bool = true): seq[Selection] = + + let cursorSelector = self.config.get(self.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) + + case move + of "visual-line-up", "visual-line-down": + var minLine = int.high + var maxLine = int.low + for s in self.selections: + minLine = min(minLine, s.last.line) + maxLine = max(maxLine, s.last.line) + + let direction = if move == "visual-line-down": 1 else: -1 + + proc doMoveCursor(self: TextDocumentEditor, cursor: Cursor, offset: int, includeAfter: bool): Cursor = + let targetColumn = if maxLine - minLine + 1 < self.selections.len: + self.displayMap.toDisplayPoint(cursor.toPoint).column.int + else: + self.targetColumn + self.doMoveCursorVisualLine(cursor, offset, false, includeAfter, targetColumn.some) + return selections.mapIt(doMoveCursor(self, it.last, direction * max(count, 1), includeEol).toSelection(it, cursorSelector)) + + of "line-up", "line-down": + let direction = if move == "line-down": 1 else: -1 + return selections.mapIt(self.doMoveCursorLine(it.last, direction * max(count, 1), false, includeEol).toSelection(it, cursorSelector)) + + of "column": + result = selections.mapIt(self.doMoveCursorColumn(it.last, count, includeAfter = includeEol).toSelection(it, cursorSelector)) + + else: + return self.moveDatabase.applyMove(self.document.rope, move, selections, count, includeEol) + proc getSelectionForMove*(self: TextDocumentEditor, cursor: Cursor, move: string, - count: int = 0): Selection {.expose("editor.text").} = + count: int = 0, includeEol: bool = true): Selection {.expose("editor.text").} = case move of "word": result = self.findWordBoundary(cursor) @@ -3084,9 +3117,13 @@ proc getSelectionForMove*(self: TextDocumentEditor, cursor: Cursor, move: string result = (first, (cursor.line, self.document.lineLength(cursor.line))) of "line": - result = ((cursor.line, 0), (cursor.line, self.document.lineLength(cursor.line))) + let lineLen = self.document.lineLength(cursor.line) + result = ((cursor.line, 0), (cursor.line, lineLen)) + if not includeEol and result.last.column == lineLen: + result.last = self.doMoveCursorColumn(result.last, -1, wrap = false) of "visual-line": + let lineLen = self.document.lineLength(cursor.line) let wrapPoint = self.displayMap.toWrapPoint(cursor.toPoint) let displayLineStart = wrapPoint(wrapPoint.row) let displayLineEnd = wrapPoint(wrapPoint.row + 1) @@ -3096,6 +3133,12 @@ proc getSelectionForMove*(self: TextDocumentEditor, cursor: Cursor, move: string result[1].line -= 1 result[1].column = self.document.lineLength(result[1].line) + if not includeEol: + if result.last.column == lineLen: + result.last = self.doMoveCursorColumn(result.last, -1, wrap = false) + elif result.last.column < self.document.lineLength(cursor.line): # This is the case if we're not in the last visual sub line + result.last = self.doMoveCursorColumn(result.last, -1, wrap = false) + of "line-next": result = ((cursor.line, 0), (cursor.line, self.document.lineLength(cursor.line))) if result.last.line + 1 < self.document.numLines: @@ -3127,7 +3170,7 @@ proc getSelectionForMove*(self: TextDocumentEditor, cursor: Cursor, move: string result.last = (line, self.document.lineLength(line)) of "column": - result = self.doMoveCursorColumn(cursor, count).toSelection + result = self.doMoveCursorColumn(cursor, count, includeAfter = includeEol).toSelection of "prev-find-result": result = self.getPrevFindResult(cursor, count) @@ -5127,6 +5170,7 @@ proc newTextEditor*(document: TextDocument, services: Services): TextDocumentEdi self.plugins = self.services.getService(PluginService).get self.registers = self.services.getService(Registers).get self.workspace = self.services.getService(Workspace).get + self.moveDatabase = self.services.getService(MoveDatabase).get self.vfs = self.services.getService(VFSService).get.vfs self.commands = self.services.getService(CommandService).get self.displayMap = DisplayMap.new() diff --git a/wit/v0/api.wit b/wit/v0/api.wit index bc0e1e523..99ca3535a 100644 --- a/wit/v0/api.wit +++ b/wit/v0/api.wit @@ -193,6 +193,21 @@ interface text-editor { use types.{editor, document, text-editor, text-document, cursor, selection, rope}; use commands.{command-error}; + enum scroll-behaviour { + center-always, + center-offscreen, + center-margin, + scroll-to-margin, + top-of-screen, + } + + enum scroll-snap-behaviour { + never, + always, + min-distance-offscreen, + min-distance-center, + } + /// Returns a handle for the currently active text editor. active-text-editor: func() -> option; /// Returns the text document the given editor is currently editing. @@ -215,6 +230,14 @@ interface text-editor { /// todo hide-completions: func(editor: text-editor); + /// todo + scroll-to-cursor: func(editor: text-editor, behaviour: option); + /// todo + set-next-snap-behaviour: func(editor: text-editor, behaviour: scroll-snap-behaviour); + + /// todo + update-target-column: func(editor: text-editor); + /// todo get-usage: func(editor: text-editor) -> string; /// todo @@ -244,6 +267,7 @@ interface text-editor { /// todo apply-move: func(editor: text-editor, selection: selection, move: string, count: s32, wrap: bool, include-eol: bool) -> list; + multi-move: func(editor: text-editor, selections: list, move: string, count: s32, wrap: bool, include-eol: bool) -> list; /// Sets the given selection as the only selection for the given editor. set-selection: func(editor: text-editor, s: selection); @@ -265,7 +289,9 @@ interface text-editor { set-setting-raw: func(editor: text-editor, name: string, value: string); /// todo - edit: func(editor: text-editor, selections: list, contents: list) -> list; + edit: func(editor: text-editor, selections: list, contents: list, inclusive: bool) -> list; + + define-move: func(move: string, fun: u32, data: u32); /// Returns the rope containing the current content of the text editors document. /// This rope is not automatically kept up to date when the document changes, but @@ -427,12 +453,14 @@ interface settings { interface guest { use render.{view}; use channel.{channel-listen-response}; + use types.{rope, selection}; init-plugin: func(); handle-command: func(fun: u32, data: u32, arguments: string) -> string; handle-mode-changed: func(fun: u32, old: string, new: string); handle-view-render-callback: func(id: s32, fun: u32, data: u32); handle-channel-update: func(fun: u32, data: u32, closed: bool) -> channel-listen-response; notify-task-complete: func(task: u64, canceled: bool); + handle-move: func(fun: u32, data: u32, text: rope, selections: list, count: s32, eol: bool) -> list; } /// APIs which can safely be used on a background thread From 7694df8128e7b4d243c474b4f3ca9dd6a0ce8f56 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 24 Aug 2025 15:26:06 +0200 Subject: [PATCH 06/47] More progress --- config/keybindings.json | 24 +- plugin_api/v0/api.nim | 54 +++- plugin_api/v0/plugin_api_guest.nim | 10 +- plugin_api/v0/types.nim | 29 +++ plugins/vim/vim.nim | 388 ++++++++++++++--------------- src/generated/plugin_api_host.nim | 154 ++++++++++-- src/misc/rope_utils.nim | 22 ++ src/move_database.nim | 235 ++++++++++++++++- src/plugin_api/plugin_api.nim | 69 +++-- src/text/text_editor.nim | 15 -- wit/v0/api.wit | 4 +- 11 files changed, 694 insertions(+), 310 deletions(-) diff --git a/config/keybindings.json b/config/keybindings.json index 4b52eb6a4..e8e4330db 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -747,18 +747,18 @@ "G": ["vim.move-file-end", "<#move.count>"], "%": ["vim.move-to-matching-or-file-offset", "<#move.count>"], - "w": ["move-selection-next", "vim.word", false, true, "<#move.count>"], - "": ["move-selection-next", "vim.word", false, true, "<#move.count>"], - "W": ["move-selection-next", "vim.WORD", false, true, "<#move.count>"], - "": ["move-selection-next", "vim.WORD", false, true, "<#move.count>"], - "e": ["move-selection-end", "vim.word", false, false, "<#move.count>"], - "E": ["move-selection-end", "vim.WORD", false, false, "<#move.count>"], - "b": ["move-selection-end", "vim.word", true, true, "<#move.count>"], - "": ["move-selection-end", "vim.word", true, true, "<#move.count>"], - "B": ["move-selection-end", "vim.WORD", true, true, "<#move.count>"], - "": ["move-selection-end", "vim.WORD", true, true, "<#move.count>"], - "ge": ["move-selection-next", "vim.word", true, false, "<#move.count>"], - "gE": ["move-selection-next", "vim.WORD", true, false, "<#move.count>"], + "w": ["vim.move-selection-next", "vim.word", false, true, "<#move.count>"], + "": ["vim.move-selection-next", "vim.word", false, true, "<#move.count>"], + "W": ["vim.move-selection-next", "vim.WORD", false, true, "<#move.count>"], + "": ["vim.move-selection-next", "vim.WORD", false, true, "<#move.count>"], + "e": ["vim.move-selection-end", "vim.word", false, false, "<#move.count>"], + "E": ["vim.move-selection-end", "vim.WORD", false, false, "<#move.count>"], + "b": ["vim.move-selection-end", "vim.word", true, true, "<#move.count>"], + "": ["vim.move-selection-end", "vim.word", true, true, "<#move.count>"], + "B": ["vim.move-selection-end", "vim.WORD", true, true, "<#move.count>"], + "": ["vim.move-selection-end", "vim.WORD", true, true, "<#move.count>"], + "ge": ["vim.move-selection-next", "vim.word", true, false, "<#move.count>"], + "gE": ["vim.move-selection-next", "vim.WORD", true, false, "<#move.count>"], "}": ["move-paragraph", false, "<#move.count>"], "{": ["move-paragraph", true, "<#move.count>"], "f": ["vim.move-to ", false, "<#move.count>"], diff --git a/plugin_api/v0/api.nim b/plugin_api/v0/api.nim index 2fcbf3ae8..983303314 100644 --- a/plugin_api/v0/api.nim +++ b/plugin_api/v0/api.nim @@ -1,9 +1,12 @@ -import std/[strformat, json, jsonutils, strutils, sequtils, sugar] +import std/[strformat, json, jsonutils, strutils, sequtils, sugar, os, terminal, colors] import wit_guest, wit_types, wit_runtime, generational_seq, event, util export wit_types, wit_runtime import async export async +# todo: remove this eventually +from "../../src/scripting_api.nim" as sca import nil + const pluginWorld {.strdefine.} = "plugin" when defined(witRebuild): @@ -78,7 +81,9 @@ proc notifyTasksComplete(tasks: WitList[tuple[task: uint64, canceled: bool]]) = for (task, canceled) in tasks: notifyTaskComplete(task, canceled) -proc handleMove(fun: uint32; data: uint32; text: sink Rope; selections: WitList[Selection]; count: int32; eol: bool): WitList[Selection] = +proc handleMove(fun: uint32; data: uint32; text: uint32; selections: WitList[Selection]; count: int32; eol: bool): WitList[Selection] = + echo "handleMove " + var text = Rope(handle: text.int32) let fun = cast[MoveHandler](fun) return stackWitList(fun(data, text.ensureMove, selections.toOpenArray(), count.int, eol)) @@ -141,17 +146,6 @@ when pluginWorld == "plugin": func `<=`*(a: Cursor, b: Cursor): bool = return a == b or a < b - func `>`*(a: Cursor, b: Cursor): bool = - if a.line > b.line: - return true - elif a.line == b.line and a.column > b.column: - return true - else: - return false - - func `>=`*(a: Cursor, b: Cursor): bool = - return a == b or a >= b - func min*(a: Cursor, b: Cursor): Cursor = if a < b: return a @@ -228,6 +222,16 @@ when pluginWorld == "plugin": proc addCustomTextMove*(name: string, move: MoveHandler) = defineMove(name.ws, 0, cast[uint32](move)) +func toSelection*(cursor: Cursor, default: Selection, which: sca.SelectionCursor): Selection = + case which + of sca.SelectionCursor.Config: return default + of sca.SelectionCursor.Both: return (cursor, cursor).toSelection + of sca.SelectionCursor.First: return (cursor, default.last).toSelection + of sca.SelectionCursor.Last: return (default.first, cursor).toSelection + of sca.SelectionCursor.LastToFirst: return (default.last, cursor).toSelection + +proc charAt*(rope: Rope, cursor: Cursor): char = rope.byteAt(cursor).char + proc asEditor*(editor: TextEditor): Editor = Editor(id: editor.id) proc asDocument*(document: TextDocument): Document = Document(id: document.id) @@ -357,3 +361,27 @@ proc runInBackground*(executor: BackgroundExecutor, p: proc(task: BackgroundTask let args = &"{cast[int](p)}\n{readerPath}\n{writerPath}" spawnBackground(stackWitString(args), executor) +############################# logging ############################ + +type LogLevel* = enum lvlInfo, lvlNotice, lvlDebug, lvlWarn, lvlError + +proc log*(level: LogLevel, str: string) = + let color = case level + of lvlDebug: rgb(100, 100, 200) + of lvlInfo: rgb(200, 200, 200) + of lvlNotice: rgb(200, 255, 255) + of lvlWarn: rgb(200, 200, 100) + of lvlError: rgb(255, 150, 150) + # of lvlFatal: rgb(255, 0, 0) + else: rgb(255, 255, 255) + try: + {.gcsafe.}: + stdout.write(ansiForegroundColorCode(color)) + stdout.write("[vim] ") + stdout.write(str) + stdout.write("\r\n") + except IOError: + discard + +template debugf*(x: static string) = + log lvlDebug, fmt(x) diff --git a/plugin_api/v0/plugin_api_guest.nim b/plugin_api/v0/plugin_api_guest.nim index 78057bf49..207aae7d1 100644 --- a/plugin_api/v0/plugin_api_guest.nim +++ b/plugin_api/v0/plugin_api_guest.nim @@ -157,23 +157,23 @@ proc notifyTaskCompleteExported(a0: uint64; a1: bool): void {. canceled = a1.bool notifyTaskComplete(task, canceled) -proc handleMove(fun: uint32; data: uint32; text: sink Rope; +proc handleMove(fun: uint32; data: uint32; text: uint32; selections: WitList[Selection]; count: int32; eol: bool): WitList[ Selection] var handleMoveRetArea: array[28, uint8] -proc handleMoveExported(a0: uint32; a1: uint32; a2: int32; a3: int32; a4: int32; - a5: int32; a6: bool): int32 {. +proc handleMoveExported(a0: uint32; a1: uint32; a2: uint32; a3: int32; + a4: int32; a5: int32; a6: bool): int32 {. wasmexport("handle-move", "nev:plugins/guest").} = var fun: uint32 data: uint32 - text: Rope + text: uint32 selections: WitList[Selection] count: int32 eol: bool fun = convert(a0, uint32) data = convert(a1, uint32) - text.handle = a2 + 1 + text = convert(a2, uint32) selections = wl(cast[ptr typeof(selections[0])](a3), a4) count = convert(a5, int32) eol = a6.bool diff --git a/plugin_api/v0/types.nim b/plugin_api/v0/types.nim index 0cd98e7ad..e7bcb47e0 100644 --- a/plugin_api/v0/types.nim +++ b/plugin_api/v0/types.nim @@ -147,3 +147,32 @@ proc slicePoints*(self: Rope; a: Cursor; b: Cursor): Rope {.nodestroy.} = arg3 = b.column let res = typesSlicePointsImported(arg0, arg1, arg2, arg3, arg4) result.handle = res + 1 + +proc typesRuneAtImported(a0: int32; a1: int32; a2: int32): Rune {. + wasmimport("[method]rope.rune-at", "nev:plugins/types").} +proc runeAt*(self: Rope; a: Cursor): Rune {.nodestroy.} = + ## /// Returns a slice of the rope from line 'a' to 'b'. 'b' is inclusive. + ## /// This operation is cheap because it doesn't create a copy of the text. + ## slice-lines: func(a: s64, b: s64) -> rope; + var + arg0: int32 + arg1: int32 + arg2: int32 + arg0 = cast[int32](self.handle - 1) + arg1 = a.line + arg2 = a.column + let res = typesRuneAtImported(arg0, arg1, arg2) + result = res.Rune + +proc typesByteAtImported(a0: int32; a1: int32; a2: int32): uint8 {. + wasmimport("[method]rope.byte-at", "nev:plugins/types").} +proc byteAt*(self: Rope; a: Cursor): uint8 {.nodestroy.} = + var + arg0: int32 + arg1: int32 + arg2: int32 + arg0 = cast[int32](self.handle - 1) + arg1 = a.line + arg2 = a.column + let res = typesByteAtImported(arg0, arg1, arg2) + result = convert(res, uint8) diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 0800fb8fd..e791e77c8 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -7,29 +7,6 @@ import api from "../../src/scripting_api.nim" as sca import nil import "../../src/input_api.nim" -type LogLevel = enum lvlInfo, lvlNotice, lvlDebug, lvlWarn, lvlError - -proc log(level: LogLevel, str: string) = - let color = case level - of lvlDebug: rgb(100, 100, 200) - of lvlInfo: rgb(200, 200, 200) - of lvlNotice: rgb(200, 255, 255) - of lvlWarn: rgb(200, 200, 100) - of lvlError: rgb(255, 150, 150) - # of lvlFatal: rgb(255, 0, 0) - else: rgb(255, 255, 255) - try: - {.gcsafe.}: - stdout.write(ansiForegroundColorCode(color)) - stdout.write("[vim] ") - stdout.write(str) - stdout.write("\r\n") - except IOError: - discard - -template debugf*(x: static string) = - log lvlDebug, fmt(x) - var yankedLines: bool = false ## Whether the last thing we yanked was in a line mode type EditorVimState = object @@ -367,46 +344,45 @@ proc yankSelectionClipboard*(editor: TextEditor) {.exposeActive(editorContext).} # newText # ) -# # infof"replace {editor.selections} with '{input}' -> {texts}" +# # debugf"replace {editor.selections} with '{input}' -> {texts}" # editor.addNextCheckpoint "insert" # editor.setSelections editor.edit(editor.selections, texts, inclusiveEnd=true).mapIt(it.first.toSelection) # editor.setMode "vim-new.normal" -# proc vimSelectMove(editor: TextEditor, move: string, count: int = 1) {.exposeActive(editorContext, "vim-select-move").} = -# # infof"vimSelectMove '{move}' {count}" -# let (action, arg) = move.parseAction -# for i in 0.. failed to parse key: {target}" +# debugf" -> failed to parse key: {target}" # let key = parseTarget(target) @@ -462,11 +438,11 @@ proc vimClamp*(editor: TextEditor, cursor: Cursor): Cursor = # if result.last.line + 1 < editor.lineCount: # result = result or editor.vimMotionParagraphInner((result.last.line + 1, 0), 1) -proc vimMotionWordOuter(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl, raises: [].} = -# proc vimMotionWordOuter*(rope: sink Rope, cursor: Cursor, count: int): Selection = - result = vimMotionWord(editor, cursor, count) - if result.last.column < editor.lineLength(result.last.line) and editor.getChar(result.last) in Whitespace: - result.last = editor.vimMotionWord(result.last, 1).last +# proc vimMotionWordOuter(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl, raises: [].} = +# # proc vimMotionWordOuter*(rope: sink Rope, cursor: Cursor, count: int): Selection = +# result = vimMotionWord(editor, cursor, count) +# if result.last.column < editor.lineLength(result.last.line) and editor.getChar(result.last) in Whitespace: +# result.last = editor.vimMotionWord(result.last, 1).last # proc vimMotionWordBigOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = # result = vimMotionWordBig(editor, cursor, count) @@ -482,7 +458,7 @@ proc vimMotionWordOuter(data: uint32, text: sink Rope, selections: openArray[Sel # proc vimMotionSurround*(editor: TextEditor, cursor: Cursor, count: int, c0: char, c1: char, inside: bool): Selection = # result = cursor.toSelection -# # infof"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}" +# # debugf"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}" # while true: # let lastChar = editor.charAt(result.last) # let (startDepth, endDepth) = if lastChar == c0: @@ -492,25 +468,25 @@ proc vimMotionWordOuter(data: uint32, text: sink Rope, selections: openArray[Sel # else: # (1, 1) -# # infof"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find around: {startDepth}, {endDepth}" +# # debugf"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find around: {startDepth}, {endDepth}" # if editor.findSurroundStart(result.first, count, c0, c1, startDepth).getSome(opening) and editor.findSurroundEnd(result.last, count, c0, c1, endDepth).getSome(closing): # result = (opening, closing) -# # infof"vimMotionSurround: found inside {result}" +# # debugf"vimMotionSurround: found inside {result}" # if inside: # result.first = editor.doMoveCursorColumn(result.first, 1) # result.last = editor.doMoveCursorColumn(result.last, -1) # return -# # infof"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find ahead: {startDepth}, {endDepth}" +# # debugf"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find ahead: {startDepth}, {endDepth}" # if editor.findSurroundEnd(result.first, count, c0, c1, -1).getSome(opening) and editor.findSurroundEnd(opening, count, c0, c1, 0).getSome(closing): # result = (opening, closing) -# # infof"vimMotionSurround: found ahead {result}" +# # debugf"vimMotionSurround: found ahead {result}" # if inside: # result.first = editor.doMoveCursorColumn(result.first, 1) # result.last = editor.doMoveCursorColumn(result.last, -1) # return # else: -# # infof"vimMotionSurround: found nothing {result}" +# # debugf"vimMotionSurround: found nothing {result}" # return # proc vimMoveToMatching(editor: TextEditor) {.exposeActive(editorContext, "vim-move-to-matching").} = @@ -562,7 +538,7 @@ proc vimMotionWordOuter(data: uint32, text: sink Rope, selections: openArray[Sel # # todo # addCustomTextMove "vim-line", vimMotionLine # addCustomTextMove "vim-visual-line", vimMotionVisualLine -addCustomTextMove "vim-word-outer", vimMotionWordOuter +# addCustomTextMove "vim-word-outer", vimMotionWordOuter # addCustomTextMove "vim-WORD-outer", vimMotionWordBigOuter # addCustomTextMove "vim-paragraph-inner", vimMotionParagraphInner # addCustomTextMove "vim-paragraph-outer", vimMotionParagraphOuter @@ -579,69 +555,69 @@ addCustomTextMove "vim-word-outer", vimMotionWordOuter # addCustomTextMove "vim-surround-'-inner", vimMotionSurroundSingleQuotesInner # addCustomTextMove "vim-surround-'-outer", vimMotionSurroundSingleQuotesOuter -# iterator iterateTextObjects*(editor: TextEditor, cursor: Cursor, move: string, backwards: bool = false): Selection = -# var selection = editor.getSelectionForMove(cursor, move, 0) -# # infof"iterateTextObjects({cursor}, {move}, {backwards}), selection: {selection}" -# yield selection -# while true: -# let lastSelection = selection -# if not backwards and selection.last.column == editor.lineLength(selection.last.line): -# if selection.last.line == editor.lineCount - 1: -# break -# selection = (selection.last.line + 1, 0).toSelection -# elif backwards and selection.first.column == 0: -# if selection.first.line == 0: -# break -# selection = (selection.first.line - 1, editor.lineLength(selection.first.line - 1)).toSelection -# if selection.first.column == 0: -# yield selection -# continue - -# let nextCursor = if backwards: (selection.first.line, selection.first.column - 1) else: (selection.last.line, selection.last.column + 1) -# let newSelection = editor.getSelectionForMove(nextCursor, move, 0) -# # infof"iterateTextObjects({cursor}, {move}, {backwards}) nextCursor: {nextCursor}, newSelection: {newSelection}" -# if newSelection == lastSelection: -# break - -# selection = newSelection -# yield selection - -# iterator enumerateTextObjects*(editor: TextEditor, cursor: Cursor, move: string, backwards: bool = false): (int, Selection) = -# var i = 0 -# for selection in iterateTextObjects(editor, cursor, move, backwards): -# yield (i, selection) -# inc i - -# proc vimSelectTextObject(editor: TextEditor, textObject: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1, textObjectRange: VimTextObjectRange = Inner) {.exposeActive(editorContext, "vim-select-text-object").} = -# # infof"vimSelectTextObject({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count})" - -# editor.setSelections editor.selections.mapIt(block: -# var res = it.last -# var resultSelection = it -# # infof"-> {resultSelection}" - -# for i, selection in enumerateTextObjects(editor, res, textObject, backwards): -# # infof"{i}: {res} -> {selection}" -# resultSelection = resultSelection or selection -# if i == max(count, 1) - 1: -# break - -# # infof"vimSelectTextObject({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count}): {resultSelection}" -# if it.isBackwards: -# resultSelection.reverse -# else: -# resultSelection -# ) +iterator iterateTextObjects*(editor: TextEditor, cursor: Cursor, move: string, backwards: bool = false): Selection = + var selection = editor.applyMove(cursor, move, 0) + # debugf"iterateTextObjects({cursor}, {move}, {backwards}), selection: {selection}" + yield selection + while true: + let lastSelection = selection + if not backwards and selection.last.column == editor.lineLength(selection.last.line): + if selection.last.line == editor.lineCount - 1: + break + selection = (selection.last.line.int + 1, 0).toSelection + elif backwards and selection.first.column == 0: + if selection.first.line == 0: + break + selection = (selection.first.line - 1, editor.lineLength(selection.first.line - 1)).toSelection + if selection.first.column == 0: + yield selection + continue + + let nextCursor = if backwards: (selection.first.line, selection.first.column - 1) else: (selection.last.line, selection.last.column + 1) + let newSelection = editor.applyMove(nextCursor, move, 0) + # debugf"iterateTextObjects({cursor}, {move}, {backwards}) nextCursor: {nextCursor}, newSelection: {newSelection}" + if newSelection == lastSelection: + break + + selection = newSelection + yield selection + +iterator enumerateTextObjects*(editor: TextEditor, cursor: Cursor, move: string, backwards: bool = false): (int, Selection) = + var i = 0 + for selection in iterateTextObjects(editor, cursor, move, backwards): + yield (i, selection) + inc i + +proc selectTextObject(editor: TextEditor, textObject: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1, textObjectRange: VimTextObjectRange = Inner) {.exposeActive(editorContext).} = + debugf"selectTextObject({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count})" + + editor.setSelections editor.selections.mapIt(block: + var res = it.last + var resultSelection = it + # debugf"-> {resultSelection}" + + for i, selection in enumerateTextObjects(editor, res, textObject, backwards): + debugf"{i}: {res} -> {selection}" + resultSelection = resultSelection or selection + if i == max(count, 1) - 1: + break + + # debugf"selectTextObject({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count}): {resultSelection}" + if it.isBackwards: + resultSelection.reverse + else: + resultSelection + ) -# editor.scrollToCursor() -# editor.updateTargetColumn() + editor.scrollToCursor() + editor.updateTargetColumn() # proc vimSelectSurrounding(editor: TextEditor, textObject: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1, textObjectRange: VimTextObjectRange = Inner) {.exposeActive(editorContext, "vim-select-surrounding").} = -# # infof"vimSelectSurrounding({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count})" +# # debugf"vimSelectSurrounding({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count})" # editor.setSelections editor.selections.mapIt(block: # let resultSelection = editor.getSelectionForMove(it.last, textObject, count) -# # infof"vimSelectSurrounding({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count}): {resultSelection}" +# # debugf"vimSelectSurrounding({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count}): {resultSelection}" # if it.isBackwards: # resultSelection.reverse # else: @@ -651,76 +627,78 @@ addCustomTextMove "vim-word-outer", vimMotionWordOuter # editor.scrollToCursor() # editor.updateTargetColumn() -# proc moveSelectionNext(editor: TextEditor, move: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1) {.exposeActive(editorContext, "move-selection-next").} = -# # infof"moveSelectionNext '{move}' {count} {backwards} {allowEmpty}" -# editor.vimState.deleteInclusiveEnd = false -# let which = getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) -# editor.setSelections editor.selections.mapIt(block: -# var res = it.last -# for k in 0..= editor.lineLength(selection.first.line) or editor.getChar(selection.first) notin Whitespace: -# res = cursor -# break -# # echo res, ", ", it, ", ", which -# res.toSelection(it, which) -# ) - -# editor.scrollToCursor() -# editor.updateTargetColumn() +proc moveSelectionNext(editor: TextEditor, move: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1) {.exposeActive(editorContext).} = + # debugf"moveSelectionNext '{move}' {count} {backwards} {allowEmpty}" + let text = editor.content + editor.vimState.deleteInclusiveEnd = false + let which = getSetting[sca.SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), sca.SelectionCursor.Both) + editor.setSelections editor.selections.mapIt(block: + var res = it.last + for k in 0..= editor.lineLength(selection.first.line) or text.charAt(selection.first) notin Whitespace: + res = cursor + break + # echo res, ", ", it, ", ", which + res.toSelection(it, which) + ) -# proc applyMove(editor: TextEditor, selections: seq[Selection], move: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1, which: Option[SelectionCursor] = SelectionCursor.none): seq[Selection] = -# ## Applies the given move `count` times and returns the resulting selections -# ## `allowEmpty` If true then the move can stop on empty lines -# ## `backwards` Move backwards -# ## `count` How often to apply the move -# ## `which` How to assemble the final selection from the input and the move. If not set uses `editor.text.cursor.movement` + editor.scrollToCursor() + editor.updateTargetColumn() -# # infof"moveSelectionEnd '{move}' {count} {backwards} {allowEmpty}" -# let which = which.get(getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both)) -# return selections.mapIt(block: -# var res = it.last -# for k in 0.. 1: # editor.moveCursorLine(count - 1) @@ -831,7 +809,7 @@ proc moveDirection(editor: TextEditor, move: string, direction: int, count: int # editor.updateTargetColumn() # proc vimMoveToEndOfVisualLine(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-end-of-visual-line").} = -# # infof"vimMoveToEndOfLine {count}" +# # debugf"vimMoveToEndOfLine {count}" # let count = max(1, count) # if count > 1: # editor.moveCursorLine(count - 1) @@ -845,7 +823,7 @@ proc moveDirection(editor: TextEditor, move: string, direction: int, count: int # editor.updateTargetColumn() # proc vimMoveToStartOfLine(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-start-of-line").} = -# # infof"vimMoveToStartOfLine {count}" +# # debugf"vimMoveToStartOfLine {count}" # let count = max(1, count) # if count > 1: # editor.moveCursorLine(count - 1) @@ -854,7 +832,7 @@ proc moveDirection(editor: TextEditor, move: string, direction: int, count: int # editor.updateTargetColumn() # proc vimPaste(editor: TextEditor, pasteRight: bool = false, inclusiveEnd: bool = false, register: string = "") {.exposeActive(editorContext, "vim-paste").} = -# # infof"vimPaste {register}, lines: {yankedLines}" +# # debugf"vimPaste {register}, lines: {yankedLines}" # let register = if register == "vim-default-register": # getVimDefaultRegister() # else: @@ -943,40 +921,40 @@ proc visualLineMode(editor: TextEditor) {.exposeActive(editorContext).} = # proc vimYankLine(editor: TextEditor) {.exposeActive(editorContext, "vim-yank-line").} = # editor.vimState.selectLines = true # editor.selectLine() -# editor.vimYankSelection() +# editor.yankSelection() # editor.vimState.selectLines = false # proc vimDeleteLine(editor: TextEditor) {.exposeActive(editorContext, "vim-delete-line").} = # editor.vimState.selectLines = true # let oldSelections = editor.selections # editor.selectLine() -# editor.vimDeleteSelection(true, oldSelections=oldSelections.some) +# editor.deleteSelection(true, oldSelections=oldSelections.some) # editor.vimState.selectLines = false # proc vimChangeLine(editor: TextEditor) {.exposeActive(editorContext, "vim-change-line").} = # let oldSelections = editor.selections # editor.selectLine() -# editor.vimChangeSelection(true, oldSelections=oldSelections.some) +# editor.changeSelection(true, oldSelections=oldSelections.some) # proc vimDeleteToLineEnd(editor: TextEditor) {.exposeActive(editorContext, "vim-delete-to-line-end").} = # let oldSelections = editor.selections # editor.setSelections editor.selections.mapIt (it.last, editor.vimMotionLine(it.last, 0).last) -# editor.vimDeleteSelection(true, oldSelections=oldSelections.some) +# editor.deleteSelection(true, oldSelections=oldSelections.some) # editor.vimState.selectLines = false # proc vimChangeToLineEnd(editor: TextEditor) {.exposeActive(editorContext, "vim-change-to-line-end").} = # let oldSelections = editor.selections # editor.setSelections editor.selections.mapIt (it.last, editor.vimMotionLine(it.last, 0).last) -# editor.vimChangeSelection(true, oldSelections=oldSelections.some) +# editor.changeSelection(true, oldSelections=oldSelections.some) # editor.vimState.selectLines = false # proc vimYankToLineEnd(editor: TextEditor) {.exposeActive(editorContext, "vim-yank-to-line-end").} = # editor.setSelections editor.selections.mapIt (it.last, editor.vimMotionLine(it.last, 0).last) -# editor.vimYankSelection() +# editor.yankSelection() # editor.vimState.selectLines = false # proc vimMoveFileStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-file-start").} = -# let which = getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) +# let which = getSetting[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) # editor.setSelection (count - 1, 0).toSelection(editor.selection, which) # editor.moveFirst "line-no-indent" # editor.scrollToCursor() @@ -984,7 +962,7 @@ proc visualLineMode(editor: TextEditor) {.exposeActive(editorContext).} = # proc vimMoveFileEnd(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-file-end").} = # let line = if count == 0: editor.lineCount - 1 else: count - 1 -# let which = getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) +# let which = getSetting[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) # var newSelection = (line, 0).toSelection(editor.selection, which) # if newSelection == editor.selection: # let lineLen = editor.lineLength(line) @@ -1000,7 +978,7 @@ proc visualLineMode(editor: TextEditor) {.exposeActive(editorContext).} = # editor.vimMoveToMatching() # else: # let line = clamp((count * editor.lineCount) div 100, 0, editor.lineCount - 1) -# let which = getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) +# let which = getSetting[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) # editor.setSelection (line, 0).toSelection(editor.selection, which) # editor.moveFirst "line-no-indent" # editor.scrollToCursor() @@ -1145,7 +1123,7 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # let text = editor.getText(editor.selection, inclusiveEnd=true) # let textEscaped = text.escapeRegex # let currentSearchQuery = editor.getSearchQuery() -# # infof"'{text}' -> '{textEscaped}' -> '{currentSearchQuery}'" +# # debugf"'{text}' -> '{textEscaped}' -> '{currentSearchQuery}'" # if textEscaped != currentSearchQuery and r"\b" & textEscaped & r"\b" != currentSearchQuery: # if editor.setSearchQuery(text, escapeRegex=true): # return @@ -1179,7 +1157,7 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # setSessionData("vim.states", states) # except: -# infof"Failed to save vim editor states" +# debugf"Failed to save vim editor states" # proc resolveMarks(editor: TextEditor) = # let unresolveMarks = editor.vimState.unresolveMarks @@ -1396,14 +1374,14 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # editor.setSelections editor.selections.mapIt(editor.doMoveCursorColumn(it.last, -1).toSelection) # proc vimGotoNextDiagnostic(editor: TextEditor) {.exposeActive(editorContext, "vim-goto-next-diagnostic").} = -# let severity = getOption("text.jump-diagnostic-severity", 1) +# let severity = getSetting("text.jump-diagnostic-severity", 1) # editor.setSelection editor.getNextDiagnostic(editor.selection.last, severity).first.toSelection # editor.scrollToCursor() # editor.updateTargetColumn() # editor.setNextSnapBehaviour(MinDistanceOffscreen) # proc vimGotoPrevDiagnostic(editor: TextEditor) {.exposeActive(editorContext, "vim-goto-prev-diagnostic").} = -# let severity = getOption("text.jump-diagnostic-severity", 1) +# let severity = getSetting("text.jump-diagnostic-severity", 1) # editor.setSelection editor.getPrevDiagnostic(editor.selection.last, severity).first.toSelection # editor.scrollToCursor() # editor.updateTargetColumn() @@ -1509,7 +1487,7 @@ proc modeChangedHandler(editor: TextEditor, oldModes: seq[string], newModes: seq # for name, selections in marks: # editor.vimState.unresolveMarks[name] = selections # except: -# infof"Failed to restore marks for {editor}" +# debugf"Failed to restore marks for {editor}" # scriptSetCallback("after-restore-session", afterRestoreSessionHandle) # let beforeSaveAppStateHandle = addCallback proc(args: JsonNode): JsonNode = diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index 46c3a0882..a22f6c117 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -100,6 +100,7 @@ type handleViewRenderCallback*: FuncT handleChannelUpdate*: FuncT notifyTaskComplete*: FuncT + handleMove*: FuncT proc mem(funcs: ExportedFuncs): WasmMemory = if funcs.mMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: return initWasmMemory(funcs.mMemory.get.of_field.sharedmemory) @@ -115,43 +116,49 @@ proc collectExports*(funcs: var ExportedFuncs; instance: InstanceT; funcs.mStackAlloc = instance.getExport(context, "mem_stack_alloc") funcs.mStackSave = instance.getExport(context, "mem_stack_save") funcs.mStackRestore = instance.getExport(context, "mem_stack_restore") - let f_8573159018 = instance.getExport(context, "init_plugin") - if f_8573159018.isSome: - assert f_8573159018.get.kind == WASMTIME_EXTERN_FUNC - funcs.initPlugin = f_8573159018.get.of_field.func_field + let f_8573159021 = instance.getExport(context, "init_plugin") + if f_8573159021.isSome: + assert f_8573159021.get.kind == WASMTIME_EXTERN_FUNC + funcs.initPlugin = f_8573159021.get.of_field.func_field else: echo "Failed to find exported function \'", "init_plugin", "\'" - let f_8573159034 = instance.getExport(context, "handle_command") - if f_8573159034.isSome: - assert f_8573159034.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleCommand = f_8573159034.get.of_field.func_field + let f_8573159037 = instance.getExport(context, "handle_command") + if f_8573159037.isSome: + assert f_8573159037.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleCommand = f_8573159037.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_command", "\'" - let f_8573159084 = instance.getExport(context, "handle_mode_changed") - if f_8573159084.isSome: - assert f_8573159084.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleModeChanged = f_8573159084.get.of_field.func_field + let f_8573159087 = instance.getExport(context, "handle_mode_changed") + if f_8573159087.isSome: + assert f_8573159087.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleModeChanged = f_8573159087.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_mode_changed", "\'" - let f_8573159085 = instance.getExport(context, "handle_view_render_callback") - if f_8573159085.isSome: - assert f_8573159085.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleViewRenderCallback = f_8573159085.get.of_field.func_field + let f_8573159088 = instance.getExport(context, "handle_view_render_callback") + if f_8573159088.isSome: + assert f_8573159088.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleViewRenderCallback = f_8573159088.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_view_render_callback", "\'" - let f_8573159109 = instance.getExport(context, "handle_channel_update") - if f_8573159109.isSome: - assert f_8573159109.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleChannelUpdate = f_8573159109.get.of_field.func_field + let f_8573159112 = instance.getExport(context, "handle_channel_update") + if f_8573159112.isSome: + assert f_8573159112.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleChannelUpdate = f_8573159112.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_channel_update", "\'" - let f_8573159110 = instance.getExport(context, "notify_task_complete") - if f_8573159110.isSome: - assert f_8573159110.get.kind == WASMTIME_EXTERN_FUNC - funcs.notifyTaskComplete = f_8573159110.get.of_field.func_field + let f_8573159113 = instance.getExport(context, "notify_task_complete") + if f_8573159113.isSome: + assert f_8573159113.get.kind == WASMTIME_EXTERN_FUNC + funcs.notifyTaskComplete = f_8573159113.get.of_field.func_field else: echo "Failed to find exported function \'", "notify_task_complete", "\'" + let f_8573159114 = instance.getExport(context, "handle_move") + if f_8573159114.isSome: + assert f_8573159114.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleMove = f_8573159114.get.of_field.func_field + else: + echo "Failed to find exported function \'", "handle_move", "\'" proc initPlugin*(funcs: ExportedFuncs): WasmtimeResult[void] = var args: array[max(1, 0), ValT] @@ -336,6 +343,71 @@ proc notifyTaskComplete*(funcs: ExportedFuncs; task: uint64; canceled: bool): Wa if res.isErr: return res.toResult(void) +proc handleMove*(funcs: ExportedFuncs; fun: uint32; data: uint32; text: uint32; + selections: seq[Selection]; count: int32; eol: bool): WasmtimeResult[ + seq[Selection]] = + var args: array[max(1, 7), ValT] + var results: array[max(1, 1), ValT] + var trap: ptr WasmTrapT = nil + var memory = funcs.mem + let savePoint = stackSave(funcs.mStackSave.get.of_field.func_field, + funcs.mContext) + defer: + discard stackRestore(funcs.mStackRestore.get.of_field.func_field, + funcs.mContext, savePoint.val) + var dataPtrWasm0: WasmPtr + args[0] = toWasmVal(fun) + args[1] = toWasmVal(data) + args[2] = toWasmVal(text) + if selections.len > 0: + dataPtrWasm0 = block: + let temp = stackAlloc(funcs.mStackAlloc.get.of_field.func_field, + funcs.mContext, (selections.len * 16).int32, 4) + if temp.isErr: + return temp.toResult(seq[Selection]) + temp.val + args[3] = toWasmVal(cast[int32](dataPtrWasm0)) + block: + for i0 in 0 ..< selections.len: + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 0].addr)[] = selections[ + i0].first.line + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 4].addr)[] = selections[ + i0].first.column + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 8].addr)[] = selections[ + i0].last.line + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 12].addr)[] = selections[ + i0].last.column + else: + args[3] = toWasmVal(0.int32) + args[4] = toWasmVal(cast[int32](selections.len)) + args[5] = toWasmVal(count) + args[6] = toWasmVal(eol) + let res = funcs.handleMove.addr.call(funcs.mContext, + args.toOpenArray(0, 7 - 1), + results.toOpenArray(0, 1 - 1), trap.addr).toResult( + seq[Selection]) + if trap != nil: + return trap.toResult(seq[Selection]) + if res.isErr: + return res.toResult(seq[Selection]) + var retVal: seq[Selection] + let retArea: ptr UncheckedArray[uint8] = memory.getRawPtr( + results[0].to(WasmPtr)) + block: + let p0 = cast[ptr UncheckedArray[uint8]](memory.getRawPtr( + cast[ptr int32](retArea[0].addr)[].WasmPtr)) + retVal = newSeq[typeof(retVal[0])](cast[ptr int32](retArea[4].addr)[]) + for i0 in 0 ..< retVal.len: + retVal[i0].first.line = convert(cast[ptr int32](p0[i0 * 16 + 0].addr)[], + int32) + retVal[i0].first.column = convert(cast[ptr int32](p0[i0 * 16 + 4].addr)[], + int32) + retVal[i0].last.line = convert(cast[ptr int32](p0[i0 * 16 + 8].addr)[], + int32) + retVal[i0].last.column = convert(cast[ptr int32](p0[i0 * 16 + 12].addr)[], + int32) + return wasmtime.ok(retVal) + proc coreApiVersion(instance: ptr InstanceData): int32 proc coreGetTime(instance: ptr InstanceData): float64 proc coreGetPlatform(instance: ptr InstanceData): Platform @@ -364,6 +436,8 @@ proc typesSlice(instance: ptr InstanceData; self: var RopeResource; a: int64; b: int64): RopeResource proc typesSlicePoints(instance: ptr InstanceData; self: var RopeResource; a: Cursor; b: Cursor): RopeResource +proc typesRuneAt(instance: ptr InstanceData; self: var RopeResource; a: Cursor): Rune +proc typesByteAt(instance: ptr InstanceData; self: var RopeResource; a: Cursor): uint8 proc editorActiveEditor(instance: ptr InstanceData): Option[Editor] proc editorGetDocument(instance: ptr InstanceData; editor: Editor): Option[ Document] @@ -1031,6 +1105,38 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = parameters[0].i32 = ?instance.resources.resourceNew(store, res) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], [WasmValkind.I32]) + linker.defineFuncUnchecked("nev:plugins/types", "[method]rope.rune-at", ty): + var instance = cast[ptr InstanceData](store.getData()) + var self: ptr RopeResource + var a: Cursor + self = ?instance.resources.resourceHostData(parameters[0].i32, + RopeResource) + a.line = convert(parameters[1].i32, int32) + a.column = convert(parameters[2].i32, int32) + let res = typesRuneAt(instance, self[], a) + parameters[0].i32 = cast[int32](res) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], [WasmValkind.I32]) + linker.defineFuncUnchecked("nev:plugins/types", "[method]rope.byte-at", ty): + var instance = cast[ptr InstanceData](store.getData()) + var self: ptr RopeResource + var a: Cursor + self = ?instance.resources.resourceHostData(parameters[0].i32, + RopeResource) + a.line = convert(parameters[1].i32, int32) + a.column = convert(parameters[2].i32, int32) + let res = typesByteAt(instance, self[], a) + parameters[0].i32 = cast[int32](res) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I32], []) diff --git a/src/misc/rope_utils.nim b/src/misc/rope_utils.nim index 912ba280a..a0621df3c 100644 --- a/src/misc/rope_utils.nim +++ b/src/misc/rope_utils.nim @@ -169,6 +169,13 @@ func lineRange*[D](self: RopeSlice[D], line: int, includeLineEnd: bool = true): return 0...0 +func getLine*(self: Rope, line: int, D: typedesc = int): RopeSlice[D] = + if line notin 0..= self.lines: return D.default...D.default @@ -248,6 +255,21 @@ func runeIndex*(self: RopeSlice, offset: int): RuneIndex = ## Convert byte offset to rune index return self.convert(offset, Count).RuneIndex +proc getEnclosing*(text: RopeSlice, column: int, predicate: proc(c: char): bool {.gcsafe, raises: [].}): (int, int) = + var cf = text.cursor(column) + var cb = cf.clone() + while cf.offset < text.len - 1: + cf.seekNextRune() + if not predicate(cf.currentChar()): + cf.seekPrevRune() + break + while cb.offset > 0: + cb.seekPrevRune() + if not predicate(cb.currentChar()): + cb.seekNextRune() + break + return (cb.offset, cf.offset) + proc lineStartsWith*(self: Rope, line: int, text: string, ignoreWhitespace: bool): bool = let lineRange = if ignoreWhitespace: self.indentedRange(line, int) diff --git a/src/move_database.nim b/src/move_database.nim index 4e089a821..b30cb6250 100644 --- a/src/move_database.nim +++ b/src/move_database.nim @@ -1,12 +1,10 @@ -import std/[strutils, sequtils, sugar, options, json, streams, strformat, tables, - deques, sets, algorithm] +import std/[strutils, sequtils, sugar, options, json, strformat, tables, algorithm] import scripting_api except DocumentEditor, TextDocumentEditor, AstDocumentEditor -from scripting_api as api import nil -import misc/[id, util, rect_utils, event, custom_logger, custom_async, fuzzy_matching, - custom_unicode, delayed_task, myjsonutils, regex, timer, response, rope_utils, rope_regex, jsonex] +from scripting_api as sca import nil +import misc/[util, custom_logger, custom_unicode, myjsonutils, regex, rope_utils, rope_regex, custom_async] import text/custom_treesitter, text/indent import config_provider, service -import text/[overlay_map, tab_map, wrap_map, diff_map, display_map] +# import text/[overlay_map, tab_map, wrap_map, diff_map, display_map] import nimsumtree/[rope] {.push gcsafe, raises: [].} @@ -25,16 +23,237 @@ addBuiltinService(MoveDatabase) method init*(self: MoveDatabase): Future[Result[void, ref CatchableError]] {.async: (raises: []).} = return ok() +proc vimMotionWord*(text: Rope, cursor: Cursor): Selection = + const AlphaNumeric = {'A'..'Z', 'a'..'z', '0'..'9', '_'} + + var line = text.getLine(cursor.line) + if line.len == 0: + return (cursor.line, 0).toSelection + + let c = line.charAt(cursor.column.clamp(0, line.len - 1)) + if c in Whitespace: + let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c in Whitespace) + return ((cursor.line, startColumn), (cursor.line, endColumn)) + + elif c in AlphaNumeric: + let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c in AlphaNumeric) + return ((cursor.line, startColumn), (cursor.line, endColumn)) + + else: + let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c notin Whitespace and c notin AlphaNumeric) + return ((cursor.line, startColumn), (cursor.line, endColumn)) + +proc vimMotionWordBig*(text: Rope, cursor: Cursor): Selection = + var line = text.getLine(cursor.line) + if line.len == 0: + return (cursor.line, 0).toSelection + + let c = line.charAt(cursor.column.clamp(0, line.len - 1)) + if c in Whitespace: + let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c in Whitespace) + return ((cursor.line, startColumn), (cursor.line, endColumn)) + + else: + let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c notin Whitespace) + return ((cursor.line, startColumn), (cursor.line, endColumn)) + proc applyMove*(self: MoveDatabase, rope: Rope, move: string, selections: openArray[Selection], count: int = 0, includeEol: bool = true): seq[Selection] = + debugf"applyMove '{move}', {selections}, {count}, {includeEol}" if move in self.moves: let impl = self.moves[move] return impl(rope, move, selections, count, includeEol) case move + # of "word": + # result = self.findWordBoundary(cursor) + # for _ in 1.. 0: + # result.first = (cursor.line - 1, self.document.lineLength(cursor.line - 1)) + # if cursor.column == line.len and cursor.line < self.document.numLines - 1: + # result.last = (cursor.line + 1, 0) + + # for _ in 1.. 0: + # result.first = (result.first.line - 1, self.document.lineLength(result.first.line - 1)) + # if result.last.column == line.len and result.last.line < self.document.numLines - 1: + # result.last = (result.last.line + 1, 0) + + # of "word-back": + # return self.getSelectionForMove((cursor.line, max(0, cursor.column - 1)), "word", count).reverse + + # of "word-line-back": + # return self.getSelectionForMove((cursor.line, max(0, cursor.column - 1)), "word-line", count).reverse + + # of "number": + # var r = cursor.toPoint...cursor.toPoint + # var c = self.document.rope.cursorT(cursor.toPoint) + # while c.currentChar in {'0'..'9'}: + # c.seekNextRune() + # r.b = c.position + + # if r.a.column > 0: + # c = self.document.rope.cursorT(cursor.toPoint) + # while c.position.column > 0: + # c.seekPrevRune() + # if c.currentChar == '-': + # r.a = c.position + # break + + # if c.currentChar notin {'0'..'9'}: + # c.seekNextRune() + # break + # r.a = c.position + + # return r.toSelection + + # of "line-back": + # let first = if cursor.line > 0 and cursor.column == 0: + # (cursor.line - 1, self.document.lineLength(cursor.line - 1)) + # else: + # (cursor.line, 0) + # result = (first, (cursor.line, self.document.lineLength(cursor.line))) + + # of "line": + # let lineLen = self.document.lineLength(cursor.line) + # result = ((cursor.line, 0), (cursor.line, lineLen)) + # if not includeEol and result.last.column == lineLen: + # result.last = self.doMoveCursorColumn(result.last, -1, wrap = false) + + # of "visual-line": + # let lineLen = self.document.lineLength(cursor.line) + # let wrapPoint = self.displayMap.toWrapPoint(cursor.toPoint) + # let displayLineStart = wrapPoint(wrapPoint.row) + # let displayLineEnd = wrapPoint(wrapPoint.row + 1) + # result[0] = self.displayMap.toPoint(displayLineStart, Right).toCursor + # result[1] = self.displayMap.toPoint(displayLineEnd, Right).toCursor + # if result[1].column == 0: + # result[1].line -= 1 + # result[1].column = self.document.lineLength(result[1].line) + + # if not includeEol: + # if result.last.column == lineLen: + # result.last = self.doMoveCursorColumn(result.last, -1, wrap = false) + # elif result.last.column < self.document.lineLength(cursor.line): # This is the case if we're not in the last visual sub line + # result.last = self.doMoveCursorColumn(result.last, -1, wrap = false) + + # of "line-next": + # result = ((cursor.line, 0), (cursor.line, self.document.lineLength(cursor.line))) + # if result.last.line + 1 < self.document.numLines: + # result.last = (result.last.line + 1, 0) + # for _ in 1.. 0: + # result.first = (result.first.line - 1, self.document.lineLength(result.first.line - 1)) + # for _ in 1.. 0: + # result.first = (result.first.line - 1, self.document.lineLength(result.first.line - 1)) + + # of "line-no-indent": + # let indent = self.document.rope.indentBytes(cursor.line) + # result = ((cursor.line, indent), (cursor.line, self.document.lineLength(cursor.line))) + + # of "file": + # result.first = (0, 0) + # let line = self.document.numLines - 1 + # result.last = (line, self.document.lineLength(line)) + + # of "column": + # result = self.doMoveCursorColumn(cursor, count, includeAfter = includeEol).toSelection + + # of "prev-find-result": + # result = self.getPrevFindResult(cursor, count) + + # of "next-find-result": + # result = self.getNextFindResult(cursor, count) + + # of "\"": + # result = self.getSelectionInPair(cursor, '"') + + # of "'": + # result = self.getSelectionInPair(cursor, '\'') + + # of "(", ")": + # result = self.getSelectionInPairNested(cursor, '(', ')') + + # of "{", "}": + # result = self.getSelectionInPairNested(cursor, '{', '}') + + # of "[", "]": + # result = self.getSelectionInPairNested(cursor, '[', ']') + else: - log lvlError, &"Unknown move '{move}'" - return @selections + if move.startsWith("move-to "): + discard + # # todo: use RopeCursor + # let str = move[8..^1] + # let line = self.document.getLine cursor.line + # result = cursor.toSelection + # let index = line.suffix(cursor.column).find(str) + # if index >= 0: + # result.last = (cursor.line, index + 1 + cursor.column) + # for _ in 1..= 0: + # result.last = (result.last.line, index + 1 + result.last.column) + + # elif move.startsWith("move-before "): + # # todo: use RopeCursor + # let str = move[12..^1] + # let line = self.document.getLine cursor.line + # result = cursor.toSelection + # let index = line.suffix(cursor.column + 1).find(str) + # if index >= 0: + # result.last = (cursor.line, index + cursor.column + 1) + # for _ in 1..= 0: + # result.last = (result.last.line, index + result.last.column + 1) + + else: + log lvlError, &"Unknown move '{move}'" + return @selections proc registerMove*(self: MoveDatabase, move: string, impl: MoveImpl) = self.moves[move] = impl diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index 3f14700a4..0b5b4c5ab 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -1,5 +1,5 @@ import std/[macros, strutils, os, strformat, sequtils, json, sets, pathnorm, locks, tables] -import misc/[custom_logger, custom_async, util, event, jsonex, timer, myjsonutils, render_command, binary_encoder, async_process] +import misc/[custom_logger, custom_async, util, event, jsonex, timer, myjsonutils, render_command, binary_encoder, async_process, rope_utils] import nimsumtree/[rope, sumtree, arc] import service import layout @@ -7,9 +7,9 @@ import text/[text_editor, text_document] import render_view, view import platform/platform, platform_service import config_provider, command_service -import plugin_service, document_editor, vfs, vfs_service, channel, register, terminal_service +import plugin_service, document_editor, vfs, vfs_service, channel, register, terminal_service, move_database import wasmtime, wit_host_module, plugin_api_base, wasi, plugin_thread_pool -from scripting_api import nil +from scripting_api as sca import nil {.push gcsafe, raises: [].} @@ -50,6 +50,7 @@ type settings*: ConfigStore vfsService*: VFSService vfs*: VFS + moves*: MoveDatabase timer*: Timer InstanceData = object of InstanceDataWasi @@ -161,6 +162,7 @@ method init*(self: PluginApi, services: Services, engine: ptr WasmEngineT) = self.host.settings = services.getService(ConfigService).get.runtime self.host.vfsService = services.getService(VFSService).get self.host.vfs = self.host.vfsService.vfs + self.host.moves = services.getService(MoveDatabase).get self.host.timer = startTimer() # self.channels = Arc[ChannelRegistry].new() @@ -270,7 +272,7 @@ method createModule*(self: PluginApi, module: ptr ModuleT, plugin: Plugin): Wasm self.instances.removeShift(instance) return - let options = scripting_api.CreateTerminalOptions( + let options = sca.CreateTerminalOptions( group: plugin.manifest.id, ) self.host.layout.registerView(self.host.terminals.createTerminalView(instanceData.get.stdin, instanceData.get.stdout, options)) @@ -353,25 +355,25 @@ var threadPool = newPluginThreadPool[InstanceDataImpl](10, runInstanceThread) ###################################### Conversion functions ##################################### -proc toInternal(c: Cursor): scripting_api.Cursor = (c.line.int, c.column.int) -proc toInternal(c: Selection): scripting_api.Selection = (c.first.toInternal, c.last.toInternal) -proc toInternal(c: ScrollBehaviour): scripting_api.ScrollBehaviour = +proc toInternal(c: Cursor): sca.Cursor = (c.line.int, c.column.int) +proc toInternal(c: Selection): sca.Selection = (c.first.toInternal, c.last.toInternal) +proc toInternal(c: ScrollBehaviour): sca.ScrollBehaviour = case c - of CenterAlways: scripting_api.CenterAlways - of CenterOffscreen: scripting_api.CenterOffscreen - of CenterMargin: scripting_api.CenterMargin - of ScrollToMargin: scripting_api.ScrollToMargin - of TopOfScreen: scripting_api.TopOfScreen + of CenterAlways: sca.CenterAlways + of CenterOffscreen: sca.CenterOffscreen + of CenterMargin: sca.CenterMargin + of ScrollToMargin: sca.ScrollToMargin + of TopOfScreen: sca.TopOfScreen -proc toInternal(c: ScrollSnapBehaviour): scripting_api.ScrollSnapBehaviour = +proc toInternal(c: ScrollSnapBehaviour): sca.ScrollSnapBehaviour = case c - of Never: scripting_api.Never - of Always: scripting_api.Always - of MinDistanceOffscreen: scripting_api.MinDistanceOffscreen - of MinDistanceCenter: scripting_api.MinDistanceCenter + of Never: sca.Never + of Always: sca.Always + of MinDistanceOffscreen: sca.MinDistanceOffscreen + of MinDistanceCenter: sca.MinDistanceCenter -proc toWasm(c: scripting_api.Cursor): Cursor = Cursor(line: c.line.int32, column: c.column.int32) -proc toWasm(c: scripting_api.Selection): Selection = Selection(first: c.first.toWasm, last: c.last.toWasm) +proc toWasm(c: sca.Cursor): Cursor = Cursor(line: c.line.int32, column: c.column.int32) +proc toWasm(c: sca.Selection): Selection = Selection(first: c.first.toWasm, last: c.last.toWasm) proc toInternal(flags: ReadFlags): set[vfs.ReadFlag] = result = {} @@ -485,7 +487,7 @@ proc textEditorApplyMove(instance: ptr InstanceData; editor: TextEditor; selecti return if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: let textEditor = editor.TextDocumentEditor - return @[textEditor.getSelectionForMove(selection.last.toInternal, move, count, includeEol).toWasm] + return textEditor.getSelectionsForMove(@[selection.toInternal], move, count, includeEol).mapIt(it.toWasm) proc textEditorMultiMove(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection]; move: sink string; count: int32; wrap: bool; includeEol: bool): seq[Selection] = if instance.host == nil: @@ -539,10 +541,17 @@ proc textEditorDefineMove(instance: ptr InstanceData; move: sink string; fun: ui if instance.host == nil: return - proc moveImpl(rope: Rope, move: string, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.gcsafe, raises: [].} = - let res = instance.funcs.handleMove(fun, $args.removed, $args.added) - if res.isErr: - log lvlError, "Failed to call handleModeChanged: " & res.err.msg + proc moveImpl(rope: Rope, move: string, selections: openArray[sca.Selection], count: int, includeEol: bool): seq[sca.Selection] {.gcsafe, raises: [].} = + var self: ptr ProcessResource + var ropeRes = RopeResource(rope: rope.slice().suffix(Point())) + let index = instance.resources.resourceNew(instance.store.context, ropeRes) + if index.isOk: + let res = instance.funcs.handleMove(fun, data, index.val.uint32, selections.mapIt(it.toWasm), count.int32, includeEol) + if res.isErr: + log lvlError, "Failed to call handleMove: " & res.err.msg + else: + return res.val.mapIt(it.toInternal) + return @[] instance.host.moves.registerMove(move, moveImpl) @@ -597,7 +606,7 @@ proc textEditorScrollToCursor(instance: ptr InstanceData; editor: TextEditor; be if instance.host == nil: return if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: - editor.TextDocumentEditor.scrollToCursor(scripting_api.SelectionCursor.Last, scrollBehaviour = behaviour.mapIt(it.toInternal)) + editor.TextDocumentEditor.scrollToCursor(sca.SelectionCursor.Last, scrollBehaviour = behaviour.mapIt(it.toInternal)) proc textEditorSetNextSnapBehaviour(instance: ptr InstanceData; editor: TextEditor; behaviour: ScrollSnapBehaviour): void = if instance.host == nil: @@ -700,6 +709,12 @@ proc typesSlicePoints(instance: ptr InstanceData, self: var RopeResource, a: Cur let range = Point(row: a.line.uint32, column: a.column.uint32)...Point(row: a.line.uint32, column: a.column.uint32) return RopeResource(rope: self.rope[range]) +proc typesRuneAt(instance: ptr InstanceData; self: var RopeResource; a: Cursor): Rune = + return self.rope.runeAt(a.toInternal.toPoint) + +proc typesByteAt(instance: ptr InstanceData; self: var RopeResource; a: Cursor): uint8 = + return self.rope.charAt(a.toInternal.toPoint).uint8 + proc isAllowed*(permissions: FilesystemPermissions, path: string, vfs: VFS): bool = if permissions.disallowAll.get(false): return false @@ -785,8 +800,8 @@ proc coreGetPlatform(instance: ptr InstanceData): Platform = if instance.host == nil: return case instance.host.platform.backend - of scripting_api.Backend.Gui: return Platform.Gui - of scripting_api.Backend.Terminal: return Platform.Tui + of sca.Backend.Gui: return Platform.Gui + of sca.Backend.Terminal: return Platform.Tui proc coreApiVersion(instance: ptr InstanceData): int32 = return apiVersion diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index 98cafcbd2..36c158089 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -2954,21 +2954,6 @@ proc extendSelectionWithMove*(self: TextDocumentEditor, selection: Selection, mo if selection.isBackwards: result = result.reverse -proc getEnclosing(text: RopeSlice, column: int, predicate: proc(c: char): bool {.gcsafe, raises: [].}): (int, int) = - var cf = text.cursor(column) - var cb = cf.clone() - while cf.offset < text.len - 1: - cf.seekNextRune() - if not predicate(cf.currentChar()): - cf.seekPrevRune() - break - while cb.offset > 0: - cb.seekPrevRune() - if not predicate(cb.currentChar()): - cb.seekNextRune() - break - return (cb.offset, cf.offset) - proc vimMotionWord*(self: TextDocumentEditor, cursor: Cursor, count: int = 0): Selection {.expose("editor.text").} = const AlphaNumeric = {'A'..'Z', 'a'..'z', '0'..'9', '_'} diff --git a/wit/v0/api.wit b/wit/v0/api.wit index 99ca3535a..f5ec6a8f5 100644 --- a/wit/v0/api.wit +++ b/wit/v0/api.wit @@ -48,6 +48,8 @@ interface types { // /// Returns a slice of the rope from line 'a' to 'b'. 'b' is inclusive. // /// This operation is cheap because it doesn't create a copy of the text. // slice-lines: func(a: s64, b: s64) -> rope; + rune-at: func(a: cursor) -> char; + byte-at: func(a: cursor) -> u8; } /// Non-owning handle to an editor. @@ -460,7 +462,7 @@ interface guest { handle-view-render-callback: func(id: s32, fun: u32, data: u32); handle-channel-update: func(fun: u32, data: u32, closed: bool) -> channel-listen-response; notify-task-complete: func(task: u64, canceled: bool); - handle-move: func(fun: u32, data: u32, text: rope, selections: list, count: s32, eol: bool) -> list; + handle-move: func(fun: u32, data: u32, text: u32, selections: list, count: s32, eol: bool) -> list; } /// APIs which can safely be used on a background thread From 8e4c46fa51235b3c0fea50ce083dfb8d822c8236 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 24 Aug 2025 15:31:58 +0200 Subject: [PATCH 07/47] Update nimwasmtime version --- nev.nimble | 2 +- nimble.lock | 4 ++-- plugins/vim/vim.nim | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nev.nimble b/nev.nimble index 471f31ea3..c9d800ee9 100644 --- a/nev.nimble +++ b/nev.nimble @@ -38,7 +38,7 @@ requires "https://github.com/Nimaoth/wasm3 >= 0.1.17" requires "https://github.com/Nimaoth/lrucache.nim >= 1.1.4" requires "https://github.com/Nimaoth/boxy >= 0.4.4" requires "https://github.com/Nimaoth/nimtreesitter-api >= 0.1.21" -requires "https://github.com/Nimaoth/nimwasmtime#22cfeef" +requires "https://github.com/Nimaoth/nimwasmtime#858ff37" requires "https://github.com/Nimaoth/nimsumtree#cc1362f" requires "https://github.com/Nimaoth/zippy >= 0.10.17" diff --git a/nimble.lock b/nimble.lock index 0084c06e8..8abea2815 100644 --- a/nimble.lock +++ b/nimble.lock @@ -358,14 +358,14 @@ }, "nimwasmtime": { "version": "0.2.1", - "vcsRevision": "22cfeef719757063ba951079374bee67a917c0bb", + "vcsRevision": "858ff37988b9698d73221eb5c0b468be3fe44b6b", "url": "https://github.com/Nimaoth/nimwasmtime", "downloadMethod": "git", "dependencies": [ "nimgen" ], "checksums": { - "sha1": "b583e2c45336f1d6ec6a720f3fd5638bc0337b5f" + "sha1": "0eb59f2a6073c3dcf6a95fedebf65a613bc97056" } }, "npeg": { diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index e791e77c8..2b7290cd9 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -359,7 +359,7 @@ proc selectMove(editor: TextEditor, move: string, count: int = 1) {.exposeActive proc deleteMove(editor: TextEditor, move: string, count: int = 1) {.exposeActive(editorContext).} = debugf"vimDeleteMove '{move}' {count}" - let oldSelections = @(editor.selections.toOpenArray) + let oldSelections = @(editor.selections) let (action, arg) = move.parseAction for i in 0.. Date: Sun, 24 Aug 2025 15:38:04 +0200 Subject: [PATCH 08/47] Update nimwasmtime for plugins --- plugins/test_plugin/nimble.lock | 4 +-- plugins/test_plugin/test_plugin.nim | 3 +- plugins/test_plugin/test_plugin.nimble | 2 +- plugins/vim/nimble.lock | 46 ++++++-------------------- plugins/vim/vim.nimble | 2 +- 5 files changed, 15 insertions(+), 42 deletions(-) diff --git a/plugins/test_plugin/nimble.lock b/plugins/test_plugin/nimble.lock index 4f1ea58e1..6e6e3ffc3 100644 --- a/plugins/test_plugin/nimble.lock +++ b/plugins/test_plugin/nimble.lock @@ -45,12 +45,12 @@ }, "nimwasmtime": { "version": "0.2.1", - "vcsRevision": "22cfeef719757063ba951079374bee67a917c0bb", + "vcsRevision": "858ff37988b9698d73221eb5c0b468be3fe44b6b", "url": "https://github.com/Nimaoth/nimwasmtime", "downloadMethod": "git", "dependencies": [], "checksums": { - "sha1": "b583e2c45336f1d6ec6a720f3fd5638bc0337b5f" + "sha1": "0eb59f2a6073c3dcf6a95fedebf65a613bc97056" } }, "unicodedb": { diff --git a/plugins/test_plugin/test_plugin.nim b/plugins/test_plugin/test_plugin.nim index 15c24796e..3abee32e7 100644 --- a/plugins/test_plugin/test_plugin.nim +++ b/plugins/test_plugin/test_plugin.nim @@ -199,13 +199,12 @@ defineCommand(ws"test-start-process", if stdout.atEnd: if buffer.endsWith("\0"): buffer.setLen(buffer.len - 1) - let selections = editor.edit(@@[editor.getSelection], @@[ws(buffer.replace("\r", ""))]) + let selections = editor.edit(@@[editor.getSelection], @@[ws(buffer.replace("\r", ""))], false) if selections.len > 0: editor.setSelection(selections[0].last.toSelection) return Stop return Continue - else: echo "Not in a text editor" diff --git a/plugins/test_plugin/test_plugin.nimble b/plugins/test_plugin/test_plugin.nimble index 8ddc3618b..0a5f36f3c 100644 --- a/plugins/test_plugin/test_plugin.nimble +++ b/plugins/test_plugin/test_plugin.nimble @@ -5,5 +5,5 @@ requires "chroma >= 0.2.7" requires "fusion >= 1.2" requires "regex >= 0.25.0" requires "results >= 0.5.0" -requires "https://github.com/Nimaoth/nimwasmtime#22cfeef" +requires "https://github.com/Nimaoth/nimwasmtime#858ff37" diff --git a/plugins/vim/nimble.lock b/plugins/vim/nimble.lock index cd8864e89..6e6e3ffc3 100644 --- a/plugins/vim/nimble.lock +++ b/plugins/vim/nimble.lock @@ -23,16 +23,6 @@ "sha1": "acc789c4e31b3ee969a13dd0c238d808f39b19c7" } }, - "c2nim": { - "version": "0.9.19", - "vcsRevision": "5753fa7658ed50432ea6c2df84f0a45e36bcb2ea", - "url": "https://github.com/nim-lang/c2nim", - "downloadMethod": "git", - "dependencies": [], - "checksums": { - "sha1": "1954eb89cc353cf2a4e54804d38d5a1e72f3c98e" - } - }, "chroma": { "version": "0.2.7", "vcsRevision": "b2e71179174e040884ebf6a16cbac711c84620b9", @@ -53,6 +43,16 @@ "sha1": "408bde8679bce63dfb6f424e107eb37c5eb31510" } }, + "nimwasmtime": { + "version": "0.2.1", + "vcsRevision": "858ff37988b9698d73221eb5c0b468be3fe44b6b", + "url": "https://github.com/Nimaoth/nimwasmtime", + "downloadMethod": "git", + "dependencies": [], + "checksums": { + "sha1": "0eb59f2a6073c3dcf6a95fedebf65a613bc97056" + } + }, "unicodedb": { "version": "0.12.0", "vcsRevision": "b055310c08db8f879057b4fec15c8301ee93bb2a", @@ -75,32 +75,6 @@ "sha1": "cb2e24397c051e55ad5b72b3c0d4f270a90084df" } }, - "nimgen": { - "version": "0.5.4", - "vcsRevision": "e6697aabbe36e42e4dd261cfd0b8a8be16f6f342", - "url": "https://github.com/Nimaoth/nimgen", - "downloadMethod": "git", - "dependencies": [ - "c2nim", - "regex", - "unicodedb" - ], - "checksums": { - "sha1": "d563cc29937e4d2d66ebeb3eaacef95c6284fb4c" - } - }, - "nimwasmtime": { - "version": "0.2.1", - "vcsRevision": "22cfeef719757063ba951079374bee67a917c0bb", - "url": "https://github.com/Nimaoth/nimwasmtime", - "downloadMethod": "git", - "dependencies": [ - "nimgen" - ], - "checksums": { - "sha1": "b583e2c45336f1d6ec6a720f3fd5638bc0337b5f" - } - }, "results": { "version": "0.5.0", "vcsRevision": "57b2392ad69849e3e2d02b74a8a0feee2c3d9570", diff --git a/plugins/vim/vim.nimble b/plugins/vim/vim.nimble index 8ddc3618b..0a5f36f3c 100644 --- a/plugins/vim/vim.nimble +++ b/plugins/vim/vim.nimble @@ -5,5 +5,5 @@ requires "chroma >= 0.2.7" requires "fusion >= 1.2" requires "regex >= 0.25.0" requires "results >= 0.5.0" -requires "https://github.com/Nimaoth/nimwasmtime#22cfeef" +requires "https://github.com/Nimaoth/nimwasmtime#858ff37" From 44d7fad4088bd3fabe7977924b9c98d58ff593d9 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 24 Aug 2025 15:40:34 +0200 Subject: [PATCH 09/47] Update generated file --- src/generated/plugin_api_host.nim | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index a22f6c117..13d581836 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -1156,7 +1156,7 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = assert false let res = editorActiveEditor(instance) let retArea = parameters[^1].i32 - cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int32 + cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int64 if res.isSome: cast[ptr uint64](memory[retArea + 8].addr)[] = res.get.id if e.isErr: @@ -1183,7 +1183,7 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = editor.id = convert(parameters[0].i64, uint64) let res = editorGetDocument(instance, editor) let retArea = parameters[^1].i32 - cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int32 + cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int64 if res.isSome: cast[ptr uint64](memory[retArea + 8].addr)[] = res.get.id if e.isErr: @@ -1208,7 +1208,7 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = assert false let res = textEditorActiveTextEditor(instance) let retArea = parameters[^1].i32 - cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int32 + cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int64 if res.isSome: cast[ptr uint64](memory[retArea + 8].addr)[] = res.get.id if e.isErr: @@ -1235,7 +1235,7 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = editor.id = convert(parameters[0].i64, uint64) let res = textEditorGetDocument(instance, editor) let retArea = parameters[^1].i32 - cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int32 + cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int64 if res.isSome: cast[ptr uint64](memory[retArea + 8].addr)[] = res.get.id if e.isErr: @@ -1262,7 +1262,7 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = editor.id = convert(parameters[0].i64, uint64) let res = textEditorAsTextEditor(instance, editor) let retArea = parameters[^1].i32 - cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int32 + cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int64 if res.isSome: cast[ptr uint64](memory[retArea + 8].addr)[] = res.get.id if e.isErr: @@ -1290,7 +1290,7 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = document.id = convert(parameters[0].i64, uint64) let res = textEditorAsTextDocument(instance, document) let retArea = parameters[^1].i32 - cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int32 + cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int64 if res.isSome: cast[ptr uint64](memory[retArea + 8].addr)[] = res.get.id if e.isErr: From a640b2dc5af9b87b363fe7e23c85a7a920aaa2c2 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sat, 6 Sep 2025 17:39:41 +0200 Subject: [PATCH 10/47] Progress --- config/keybindings.json | 5 +- config/keybindings_vim.nim | 3 +- config/wasm/keybindings_plugin.wasm | Bin 1478400 -> 1617060 bytes nev.nimble | 4 +- nimble.lock | 8 +- plugin_api/v0/api.nim | 8 +- plugin_api/v0/core.nim | 2 +- plugin_api/v0/render.nim | 36 +++ plugin_api/v0/text_editor.nim | 89 ++++++ plugin_api/v0/types.nim | 66 ++++- plugins/test_plugin/nimble.lock | 4 +- plugins/test_plugin/test_plugin.nimble | 2 +- plugins/vim/nimble.lock | 46 +++- plugins/vim/vim.nim | 314 +++++++++++---------- plugins/vim/vim.nimble | 3 +- plugins/wasm.config.nims | 2 +- scripting/editor_text_api_wasm.nim | 18 +- src/generated/plugin_api_host.nim | 367 ++++++++++++++++++++++--- src/misc/custom_regex.nim | 2 + src/misc/render_command.nim | 7 + src/misc/rope_utils.nim | 4 + src/move_database.nim | 137 +++++++-- src/plugin_api/plugin_api.nim | 72 ++++- src/render_view.nim | 23 +- src/text/text_editor.nim | 46 +++- src/ui/widget_builders.nim | 5 +- wit/v0/api.wit | 28 +- 27 files changed, 1029 insertions(+), 272 deletions(-) create mode 100644 src/misc/custom_regex.nim diff --git a/config/keybindings.json b/config/keybindings.json index e8e4330db..42c2073b1 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -738,7 +738,6 @@ "": ["vim.move-first", "visual-line"], "^": ["vim.move-first", "line-no-indent"], "$": ["vim.move-to-end-of-line", "<#move.count>"], - // "": ["vim.move-to-end-of-visual-line", "<#move.count>"], "": ["vim.move-last", "visual-line", "<#move.count>"], "g0": ["vim.move-first", "line"], "g^": ["vim.move-first", "line-no-indent"], @@ -759,8 +758,8 @@ "": ["vim.move-selection-end", "vim.WORD", true, true, "<#move.count>"], "ge": ["vim.move-selection-next", "vim.word", true, false, "<#move.count>"], "gE": ["vim.move-selection-next", "vim.WORD", true, false, "<#move.count>"], - "}": ["move-paragraph", false, "<#move.count>"], - "{": ["move-paragraph", true, "<#move.count>"], + "}": ["vim.move-paragraph", false, "<#move.count>"], + "{": ["vim.move-paragraph", true, "<#move.count>"], "f": ["vim.move-to ", false, "<#move.count>"], "t": ["vim.move-to ", true, "<#move.count>"], diff --git a/config/keybindings_vim.nim b/config/keybindings_vim.nim index cd6aeeba6..c323c3250 100644 --- a/config/keybindings_vim.nim +++ b/config/keybindings_vim.nim @@ -770,7 +770,8 @@ proc vimEnter(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-ent proc vimNormalMode(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-normal-mode").} = ## Exit to normal mode and clear things if editor.mode == "vim.normal": - editor.selection = editor.selection.last.toSelection + # editor.selection = editor.selection.last.toSelection + editor.setSelection(editor.selection.last.toSelection, addToHistory = true.some) editor.clearTabStops() editor.setMode("vim.normal") diff --git a/config/wasm/keybindings_plugin.wasm b/config/wasm/keybindings_plugin.wasm index 588194d08718a04ca9a3c07da5b4c2487c481a44..1d67cb836f5fc44f2efd81de1749efb6f2f638f6 100644 GIT binary patch literal 1617060 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

  • #U?119=S!5i>hkH$y7-v5_UC{8(klC_erK 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 l!>MxQUX;t=+U`*;$>%8nB1jh!9c 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 EUuC~s5&!@I literal 1478400 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 *dbgKq@ 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�O?4)6ZtKc(tcm#H*dvU}g}n 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

    5Ht#8$;XJ}Uq6C$Pv$3#+GHX3 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 E0aMQldjJ3c diff --git a/nev.nimble b/nev.nimble index c9d800ee9..c52a4c5fb 100644 --- a/nev.nimble +++ b/nev.nimble @@ -38,8 +38,8 @@ requires "https://github.com/Nimaoth/wasm3 >= 0.1.17" requires "https://github.com/Nimaoth/lrucache.nim >= 1.1.4" requires "https://github.com/Nimaoth/boxy >= 0.4.4" requires "https://github.com/Nimaoth/nimtreesitter-api >= 0.1.21" -requires "https://github.com/Nimaoth/nimwasmtime#858ff37" -requires "https://github.com/Nimaoth/nimsumtree#cc1362f" +requires "https://github.com/Nimaoth/nimwasmtime#415a252" +requires "https://github.com/Nimaoth/nimsumtree#47f7208" requires "https://github.com/Nimaoth/zippy >= 0.10.17" # Use this to include all treesitter languages (takes longer to download) diff --git a/nimble.lock b/nimble.lock index 8abea2815..ec75442de 100644 --- a/nimble.lock +++ b/nimble.lock @@ -338,12 +338,12 @@ }, "nimsumtree": { "version": "0.5.6", - "vcsRevision": "cc1362f102da4522ddf9b26bd66547288ea0b094", + "vcsRevision": "47f7208faeeb8ee6a9c6913af7be40ba494857df", "url": "https://github.com/Nimaoth/nimsumtree", "downloadMethod": "git", "dependencies": [], "checksums": { - "sha1": "c05edebaa853e4b0b31b6fda3af9fee7a07ca6e7" + "sha1": "83cdadd2e88aabc7e7edbc26dd0d9b7b370c612d" } }, "nimtreesitter": { @@ -358,14 +358,14 @@ }, "nimwasmtime": { "version": "0.2.1", - "vcsRevision": "858ff37988b9698d73221eb5c0b468be3fe44b6b", + "vcsRevision": "415a252eb4cada08d7ced468d42c92b1b0d4ae72", "url": "https://github.com/Nimaoth/nimwasmtime", "downloadMethod": "git", "dependencies": [ "nimgen" ], "checksums": { - "sha1": "0eb59f2a6073c3dcf6a95fedebf65a613bc97056" + "sha1": "da3c2261fac93db812ac62808b606d45b4d25e0d" } }, "npeg": { diff --git a/plugin_api/v0/api.nim b/plugin_api/v0/api.nim index 983303314..1ac6aea07 100644 --- a/plugin_api/v0/api.nim +++ b/plugin_api/v0/api.nim @@ -1,4 +1,4 @@ -import std/[strformat, json, jsonutils, strutils, sequtils, sugar, os, terminal, colors] +import std/[strformat, json, jsonutils, strutils, sequtils, sugar, os, terminal, colors, unicode] import wit_guest, wit_types, wit_runtime, generational_seq, event, util export wit_types, wit_runtime import async @@ -82,8 +82,7 @@ proc notifyTasksComplete(tasks: WitList[tuple[task: uint64, canceled: bool]]) = notifyTaskComplete(task, canceled) proc handleMove(fun: uint32; data: uint32; text: uint32; selections: WitList[Selection]; count: int32; eol: bool): WitList[Selection] = - echo "handleMove " - var text = Rope(handle: text.int32) + var text = Rope(handle: text.int32 + 1) let fun = cast[MoveHandler](fun) return stackWitList(fun(data, text.ensureMove, selections.toOpenArray(), count.int, eol)) @@ -220,7 +219,7 @@ when pluginWorld == "plugin": editor.scrollToCursor(ScrollBehaviour.none) proc addCustomTextMove*(name: string, move: MoveHandler) = - defineMove(name.ws, 0, cast[uint32](move)) + defineMove(name.ws, cast[uint32](move), 0) func toSelection*(cursor: Cursor, default: Selection, which: sca.SelectionCursor): Selection = case which @@ -231,6 +230,7 @@ func toSelection*(cursor: Cursor, default: Selection, which: sca.SelectionCursor of sca.SelectionCursor.LastToFirst: return (default.last, cursor).toSelection proc charAt*(rope: Rope, cursor: Cursor): char = rope.byteAt(cursor).char +proc slice*(rope: Rope, a: Natural, b: Natural): Rope = rope.slice(a.int64, b.int64, inclusive = false) proc asEditor*(editor: TextEditor): Editor = Editor(id: editor.id) proc asDocument*(document: TextDocument): Document = Document(id: document.id) diff --git a/plugin_api/v0/core.nim b/plugin_api/v0/core.nim index 36a6fa898..34a113aaa 100644 --- a/plugin_api/v0/core.nim +++ b/plugin_api/v0/core.nim @@ -24,7 +24,7 @@ proc apiVersion*(): int32 {.nodestroy.} = proc coreGetTimeImported(): float64 {.wasmimport("get-time", "nev:plugins/core").} proc getTime*(): float64 {.nodestroy.} = - ## Returns the time since the plugin was loaded. Returns 0 if the plugin has no 'time' permission. + ## Returns the time in milliseconds since the plugin was loaded. Returns 0 if the plugin has no 'time' permission. let res = coreGetTimeImported() result = convert(res, float64) diff --git a/plugin_api/v0/render.nim b/plugin_api/v0/render.nim index 10442546a..417248e5a 100644 --- a/plugin_api/v0/render.nim +++ b/plugin_api/v0/render.nim @@ -108,6 +108,42 @@ proc keyDown*(self: RenderView; key: int64): bool {.nodestroy.} = let res = renderKeyDownImported(arg0, arg1) result = res.bool +proc renderMousePosImported(a0: int32; a1: int32): void {. + wasmimport("[method]render-view.mouse-pos", "nev:plugins/render").} +proc mousePos*(self: RenderView): Vec2f {.nodestroy.} = + ## Returns the mouse position relative to the position of the view. (0, 0) is in the top left corner. + var + retArea: array[8, uint8] + arg0: int32 + arg0 = cast[int32](self.handle - 1) + renderMousePosImported(arg0, cast[int32](retArea[0].addr)) + result.x = convert(cast[ptr float32](retArea[0].addr)[], float32) + result.y = convert(cast[ptr float32](retArea[4].addr)[], float32) + +proc renderMouseDownImported(a0: int32; a1: int64): bool {. + wasmimport("[method]render-view.mouse-down", "nev:plugins/render").} +proc mouseDown*(self: RenderView; button: int64): bool {.nodestroy.} = + ## Returns whether the given mouse button is pressed. + var + arg0: int32 + arg1: int64 + arg0 = cast[int32](self.handle - 1) + arg1 = button + let res = renderMouseDownImported(arg0, arg1) + result = res.bool + +proc renderScrollDeltaImported(a0: int32; a1: int32): void {. + wasmimport("[method]render-view.scroll-delta", "nev:plugins/render").} +proc scrollDelta*(self: RenderView): Vec2f {.nodestroy.} = + ## todo + var + retArea: array[8, uint8] + arg0: int32 + arg0 = cast[int32](self.handle - 1) + renderScrollDeltaImported(arg0, cast[int32](retArea[0].addr)) + result.x = convert(cast[ptr float32](retArea[0].addr)[], float32) + result.y = convert(cast[ptr float32](retArea[4].addr)[], float32) + proc renderSetRenderIntervalImported(a0: int32; a1: int32): void {. wasmimport("[method]render-view.set-render-interval", "nev:plugins/render").} proc setRenderInterval*(self: RenderView; ms: int32): void {.nodestroy.} = diff --git a/plugin_api/v0/text_editor.nim b/plugin_api/v0/text_editor.nim index 87969afa6..be77cdda3 100644 --- a/plugin_api/v0/text_editor.nim +++ b/plugin_api/v0/text_editor.nim @@ -368,6 +368,95 @@ proc paste*(editor: TextEditor; register: WitString; inclusiveEnd: bool): void { arg3 = inclusiveEnd textEditorPasteImported(arg0, arg1, arg2, arg3) +proc textEditorSetSearchQueryFromMoveImported(a0: uint64; a1: int32; a2: int32; + a3: int32; a4: int32; a5: int32; a6: int32; a7: int32; a8: int32): void {. + wasmimport("set-search-query-from-move", "nev:plugins/text-editor").} +proc setSearchQueryFromMove*(editor: TextEditor; move: WitString; count: int32; + prefix: WitString; suffix: WitString): Selection {. + nodestroy.} = + ## todo + var + retArea: array[40, uint8] + arg0: uint64 + arg1: int32 + arg2: int32 + arg3: int32 + arg4: int32 + arg5: int32 + arg6: int32 + arg7: int32 + arg0 = editor.id + if move.len > 0: + arg1 = cast[int32](move[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](move.len) + arg3 = count + if prefix.len > 0: + arg4 = cast[int32](prefix[0].addr) + else: + arg4 = 0.int32 + arg5 = cast[int32](prefix.len) + if suffix.len > 0: + arg6 = cast[int32](suffix[0].addr) + else: + arg6 = 0.int32 + arg7 = cast[int32](suffix.len) + textEditorSetSearchQueryFromMoveImported(arg0, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, cast[int32](retArea[0].addr)) + result.first.line = convert(cast[ptr int32](retArea[0].addr)[], int32) + result.first.column = convert(cast[ptr int32](retArea[4].addr)[], int32) + result.last.line = convert(cast[ptr int32](retArea[8].addr)[], int32) + result.last.column = convert(cast[ptr int32](retArea[12].addr)[], int32) + +proc textEditorSetSearchQueryImported(a0: uint64; a1: int32; a2: int32; + a3: bool; a4: int32; a5: int32; a6: int32; + a7: int32): bool {. + wasmimport("set-search-query", "nev:plugins/text-editor").} +proc setSearchQuery*(editor: TextEditor; query: WitString; escapeRegex: bool; + prefix: WitString; suffix: WitString): bool {.nodestroy.} = + ## todo + var + arg0: uint64 + arg1: int32 + arg2: int32 + arg3: bool + arg4: int32 + arg5: int32 + arg6: int32 + arg7: int32 + arg0 = editor.id + if query.len > 0: + arg1 = cast[int32](query[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](query.len) + arg3 = escapeRegex + if prefix.len > 0: + arg4 = cast[int32](prefix[0].addr) + else: + arg4 = 0.int32 + arg5 = cast[int32](prefix.len) + if suffix.len > 0: + arg6 = cast[int32](suffix[0].addr) + else: + arg6 = 0.int32 + arg7 = cast[int32](suffix.len) + let res = textEditorSetSearchQueryImported(arg0, arg1, arg2, arg3, arg4, arg5, + arg6, arg7) + result = res.bool + +proc textEditorGetSearchQueryImported(a0: uint64; a1: int32): void {. + wasmimport("get-search-query", "nev:plugins/text-editor").} +proc getSearchQuery*(editor: TextEditor): WitString {.nodestroy.} = + var + retArea: array[8, uint8] + arg0: uint64 + arg0 = editor.id + textEditorGetSearchQueryImported(arg0, cast[int32](retArea[0].addr)) + result = ws(cast[ptr char](cast[ptr int32](retArea[0].addr)[]), + cast[ptr int32](retArea[4].addr)[]) + proc textEditorApplyMoveImported(a0: uint64; a1: int32; a2: int32; a3: int32; a4: int32; a5: int32; a6: int32; a7: int32; a8: bool; a9: bool; a10: int32): void {. diff --git a/plugin_api/v0/types.nim b/plugin_api/v0/types.nim index e7bcb47e0..02a8ad685 100644 --- a/plugin_api/v0/types.nim +++ b/plugin_api/v0/types.nim @@ -113,21 +113,66 @@ proc text*(self: Rope): WitString {.nodestroy.} = result = ws(cast[ptr char](cast[ptr int32](retArea[0].addr)[]), cast[ptr int32](retArea[4].addr)[]) -proc typesSliceImported(a0: int32; a1: int64; a2: int64): int32 {. +proc typesSliceImported(a0: int32; a1: int64; a2: int64; a3: bool): int32 {. wasmimport("[method]rope.slice", "nev:plugins/types").} -proc slice*(self: Rope; a: int64; b: int64): Rope {.nodestroy.} = +proc slice*(self: Rope; a: int64; b: int64; inclusive: bool): Rope {.nodestroy.} = ## Returns a slice of the rope from 'a' to 'b'. 'a' and 'b' are byte indices, 'b' is exclusive. ## This operation is cheap because it doesn't create a copy of the text. var arg0: int32 arg1: int64 arg2: int64 + arg3: bool arg0 = cast[int32](self.handle - 1) arg1 = a arg2 = b - let res = typesSliceImported(arg0, arg1, arg2) + arg3 = inclusive + let res = typesSliceImported(arg0, arg1, arg2, arg3) result.handle = res + 1 +proc typesSliceSelectionImported(a0: int32; a1: int32; a2: int32; a3: int32; + a4: int32; a5: bool): int32 {. + wasmimport("[method]rope.slice-selection", "nev:plugins/types").} +proc sliceSelection*(self: Rope; s: Selection; inclusive: bool): Rope {. + nodestroy.} = + var + arg0: int32 + arg1: int32 + arg2: int32 + arg3: int32 + arg4: int32 + arg5: bool + arg0 = cast[int32](self.handle - 1) + arg1 = s.first.line + arg2 = s.first.column + arg3 = s.last.line + arg4 = s.last.column + arg5 = inclusive + let res = typesSliceSelectionImported(arg0, arg1, arg2, arg3, arg4, arg5) + result.handle = res + 1 + +proc typesFindImported(a0: int32; a1: int32; a2: int32; a3: int64; a4: int32): void {. + wasmimport("[method]rope.find", "nev:plugins/types").} +proc find*(self: Rope; sub: WitString; start: int64): Option[int64] {.nodestroy.} = + var + retArea: array[24, uint8] + arg0: int32 + arg1: int32 + arg2: int32 + arg3: int64 + arg0 = cast[int32](self.handle - 1) + if sub.len > 0: + arg1 = cast[int32](sub[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](sub.len) + arg3 = start + typesFindImported(arg0, arg1, arg2, arg3, cast[int32](retArea[0].addr)) + if cast[ptr int64](retArea[0].addr)[] != 0: + var temp: int64 + temp = convert(cast[ptr int64](retArea[8].addr)[], int64) + result = temp.some + proc typesSlicePointsImported(a0: int32; a1: int32; a2: int32; a3: int32; a4: int32): int32 {. wasmimport("[method]rope.slice-points", "nev:plugins/types").} @@ -143,11 +188,22 @@ proc slicePoints*(self: Rope; a: Cursor; b: Cursor): Rope {.nodestroy.} = arg0 = cast[int32](self.handle - 1) arg1 = a.line arg2 = a.column - arg2 = b.line - arg3 = b.column + arg3 = b.line + arg4 = b.column let res = typesSlicePointsImported(arg0, arg1, arg2, arg3, arg4) result.handle = res + 1 +proc typesLineLengthImported(a0: int32; a1: int64): int64 {. + wasmimport("[method]rope.line-length", "nev:plugins/types").} +proc lineLength*(self: Rope; line: int64): int64 {.nodestroy.} = + var + arg0: int32 + arg1: int64 + arg0 = cast[int32](self.handle - 1) + arg1 = line + let res = typesLineLengthImported(arg0, arg1) + result = convert(res, int64) + proc typesRuneAtImported(a0: int32; a1: int32; a2: int32): Rune {. wasmimport("[method]rope.rune-at", "nev:plugins/types").} proc runeAt*(self: Rope; a: Cursor): Rune {.nodestroy.} = diff --git a/plugins/test_plugin/nimble.lock b/plugins/test_plugin/nimble.lock index 6e6e3ffc3..ea02f10c4 100644 --- a/plugins/test_plugin/nimble.lock +++ b/plugins/test_plugin/nimble.lock @@ -45,12 +45,12 @@ }, "nimwasmtime": { "version": "0.2.1", - "vcsRevision": "858ff37988b9698d73221eb5c0b468be3fe44b6b", + "vcsRevision": "415a252eb4cada08d7ced468d42c92b1b0d4ae72", "url": "https://github.com/Nimaoth/nimwasmtime", "downloadMethod": "git", "dependencies": [], "checksums": { - "sha1": "0eb59f2a6073c3dcf6a95fedebf65a613bc97056" + "sha1": "da3c2261fac93db812ac62808b606d45b4d25e0d" } }, "unicodedb": { diff --git a/plugins/test_plugin/test_plugin.nimble b/plugins/test_plugin/test_plugin.nimble index 0a5f36f3c..a34096fda 100644 --- a/plugins/test_plugin/test_plugin.nimble +++ b/plugins/test_plugin/test_plugin.nimble @@ -5,5 +5,5 @@ requires "chroma >= 0.2.7" requires "fusion >= 1.2" requires "regex >= 0.25.0" requires "results >= 0.5.0" -requires "https://github.com/Nimaoth/nimwasmtime#858ff37" +requires "https://github.com/Nimaoth/nimwasmtime#415a252" diff --git a/plugins/vim/nimble.lock b/plugins/vim/nimble.lock index 6e6e3ffc3..6ae9dd253 100644 --- a/plugins/vim/nimble.lock +++ b/plugins/vim/nimble.lock @@ -23,6 +23,16 @@ "sha1": "acc789c4e31b3ee969a13dd0c238d808f39b19c7" } }, + "c2nim": { + "version": "0.9.19", + "vcsRevision": "5753fa7658ed50432ea6c2df84f0a45e36bcb2ea", + "url": "https://github.com/nim-lang/c2nim", + "downloadMethod": "git", + "dependencies": [], + "checksums": { + "sha1": "1954eb89cc353cf2a4e54804d38d5a1e72f3c98e" + } + }, "chroma": { "version": "0.2.7", "vcsRevision": "b2e71179174e040884ebf6a16cbac711c84620b9", @@ -43,14 +53,14 @@ "sha1": "408bde8679bce63dfb6f424e107eb37c5eb31510" } }, - "nimwasmtime": { - "version": "0.2.1", - "vcsRevision": "858ff37988b9698d73221eb5c0b468be3fe44b6b", - "url": "https://github.com/Nimaoth/nimwasmtime", + "glob": { + "version": "0.11.2", + "vcsRevision": "64f71af2fa4572c2bdf8987a56a427c1d88fc34f", + "url": "https://github.com/haltcase/glob", "downloadMethod": "git", "dependencies": [], "checksums": { - "sha1": "0eb59f2a6073c3dcf6a95fedebf65a613bc97056" + "sha1": "58fadfae8cb82cab1061dded8066c03b0460ad4d" } }, "unicodedb": { @@ -75,6 +85,32 @@ "sha1": "cb2e24397c051e55ad5b72b3c0d4f270a90084df" } }, + "nimgen": { + "version": "0.5.4", + "vcsRevision": "e6697aabbe36e42e4dd261cfd0b8a8be16f6f342", + "url": "https://github.com/Nimaoth/nimgen", + "downloadMethod": "git", + "dependencies": [ + "c2nim", + "regex", + "unicodedb" + ], + "checksums": { + "sha1": "d563cc29937e4d2d66ebeb3eaacef95c6284fb4c" + } + }, + "nimwasmtime": { + "version": "0.2.1", + "vcsRevision": "415a252eb4cada08d7ced468d42c92b1b0d4ae72", + "url": "https://github.com/Nimaoth/nimwasmtime", + "downloadMethod": "git", + "dependencies": [ + "nimgen" + ], + "checksums": { + "sha1": "da3c2261fac93db812ac62808b606d45b4d25e0d" + } + }, "results": { "version": "0.5.0", "vcsRevision": "57b2392ad69849e3e2d02b74a8a0feee2c3d9570", diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 2b7290cd9..64eef5974 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -1,7 +1,7 @@ # import std/[strutils, macros, genasts, sequtils, sets, algorithm, jsonutils] import std/[strformat, json, jsonutils, strutils, tables, macros, genasts, streams, sequtils, sets, os, terminal, colors] import results -import util, custom_unicode, myjsonutils, id, regex, wrap, sugar +import util, custom_unicode, myjsonutils, id, wrap, sugar, custom_regex # import input_api import api from "../../src/scripting_api.nim" as sca import nil @@ -225,6 +225,17 @@ proc selectLine(editor: TextEditor) {.exposeActive(editorContext).} = Selection(first: Cursor(line: it.first.line, column: 0), last: Cursor(line: it.last.line, column: editor.lineLength(it.last.line))) )) +proc normalMode(editor: TextEditor) {.exposeActive(editorContext).} = + ## Exit to normal mode and clear things + if $editor.mode == "vim-new.normal": + editor.setSelection editor.getSelection.last.toSelection + editor.clearTabStops() + editor.setMode("vim-new.normal") + +proc visualLineMode(editor: TextEditor) {.exposeActive(editorContext).} = + editor.setMode "vim-new.visual-line" + editor.selectLine() + proc selectLast(editor: TextEditor, move: string, count: int = 1) {.exposeActive(editorContext).} = let (action, arg) = move.parseAction for i in 0.. {texts}" + debugf"replace {editor.selections} with '{input}' -> {texts}" -# editor.addNextCheckpoint "insert" -# editor.setSelections editor.edit(editor.selections, texts, inclusiveEnd=true).mapIt(it.first.toSelection) -# editor.setMode "vim-new.normal" + editor.addNextCheckpoint ws"insert" + editor.setSelections editor.edit(editor.selections, @@texts, inclusive=true).mapIt(it.first.toSelection) + editor.normalMode() proc selectMove(editor: TextEditor, move: string, count: int = 1) {.exposeActive(editorContext).} = debugf"selectMove '{move}' {count}" @@ -381,62 +412,56 @@ proc yankMove(editor: TextEditor, move: string, count: int = 1) {.exposeActive(e discard editor.command(action.ws, arg.ws) editor.yankSelection() -# proc vimMoveTo*(editor: TextEditor, target: string, before: bool, count: int = 1) {.exposeActive(editorContext, "vim-move-to").} = -# # debugf"vimMoveTo '{target}' {before}" +proc moveTo*(editor: TextEditor, target: string, before: bool, count: int = 1) {.exposeActive(editorContext).} = + debugf"vimMoveTo '{target}' {before} {count}" -# proc parseTarget(target: string): string = -# if target.len == 1: -# return target + proc parseTarget(target: string): string = + if target.len == 1: + return target -# if target.parseFirstInput().getSome(res): -# if res.inputCode.a == INPUT_SPACE: -# return " " -# elif res.inputCode.a <= int32.high: -# return $Rune(res.inputCode.a) -# else: -# debugf" -> failed to parse key: {target}" + if target.parseFirstInput().getSome(res): + if res.inputCode.a == INPUT_SPACE: + return " " + elif res.inputCode.a <= int32.high: + return $Rune(res.inputCode.a) + else: + log lvlError, &" -> failed to parse key: {target}" -# let key = parseTarget(target) + let key = parseTarget(target) + var s = editor.getSelections -# for _ in 0.. 0: lineLen.dec result = (cursor.line, min(cursor.column, lineLen)) -# proc vimMotionLine*(editor: TextEditor, cursor: Cursor, count: int): Selection = -# var lineLen = editor.lineLength(cursor.line) -# if not editor.vimState.cursorIncludeEol and lineLen > 0: lineLen.dec -# result = ((cursor.line, 0), (cursor.line, lineLen)) +proc vimMotionParagraphInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = + debugf"vimMotionParagraphInner {data}, {selections}, {count}, {includeEol}" + selections.mapIt: + let isEmpty = text.lineLength(it.last.line) == 0 -# proc vimMotionVisualLine*(editor: TextEditor, cursor: Cursor, count: int): Selection = -# var lineLen = editor.lineLength(cursor.line) -# result = editor.getSelectionForMove(cursor, "visual-line", count) -# if not editor.vimState.cursorIncludeEol and result.last.column > result.first.column: -# result.last.column.dec -# elif result.last.column < lineLen: # This is the case if we're not in the last visual sub line -# result.last.column.dec + var res = ((it.last.line.int, 0).toCursor, it.last).toSelection + while res.first.line - 1 >= 0 and (text.lineLength(res.first.line - 1) == 0) == isEmpty: + dec res.first.line + while res.last.line + 1 < text.lines and (text.lineLength(res.last.line + 1) == 0) == isEmpty: + inc res.last.line -# proc vimMotionParagraphInner*(editor: TextEditor, cursor: Cursor, count: int): Selection = -# let isEmpty = editor.lineLength(cursor.line) == 0 + res.last.column = text.lineLength(res.last.line).int32 + res -# result = ((cursor.line, 0), cursor) -# while result.first.line - 1 >= 0 and (editor.lineLength(result.first.line - 1) == 0) == isEmpty: -# dec result.first.line -# while result.last.line + 1 < editor.lineCount and (editor.lineLength(result.last.line + 1) == 0) == isEmpty: -# inc result.last.line - -# result.last.column = editor.lineLength(result.last.line) - -# proc vimMotionParagraphOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = -# result = editor.vimMotionParagraphInner(cursor, count) -# if result.last.line + 1 < editor.lineCount: -# result = result or editor.vimMotionParagraphInner((result.last.line + 1, 0), 1) +proc vimMotionParagraphOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = + result = vimMotionParagraphInner(data, text, selections, count, includeEol) + # todo + # if result.last.line + 1 < editor.lineCount: + # result = result or vimMotionParagraphInner(data, text, (result.last.line.int + 1, 0).toCursor, 1, includeEol) # proc vimMotionWordOuter(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl, raises: [].} = # # proc vimMotionWordOuter*(rope: sink Rope, cursor: Cursor, count: int): Selection = @@ -536,12 +561,10 @@ proc vimClamp*(editor: TextEditor, cursor: Cursor): Cursor = # proc vimMotionSurroundSingleQuotesOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '\'', '\'', false) # # todo -# addCustomTextMove "vim-line", vimMotionLine -# addCustomTextMove "vim-visual-line", vimMotionVisualLine # addCustomTextMove "vim-word-outer", vimMotionWordOuter # addCustomTextMove "vim-WORD-outer", vimMotionWordBigOuter -# addCustomTextMove "vim-paragraph-inner", vimMotionParagraphInner -# addCustomTextMove "vim-paragraph-outer", vimMotionParagraphOuter +addCustomTextMove "vim-paragraph-inner", vimMotionParagraphInner +addCustomTextMove "vim-paragraph-outer", vimMotionParagraphOuter # addCustomTextMove "vim-surround-{-inner", vimMotionSurroundBracesInner # addCustomTextMove "vim-surround-{-outer", vimMotionSurroundBracesOuter # addCustomTextMove "vim-surround-(-inner", vimMotionSurroundParensInner @@ -697,26 +720,26 @@ proc moveSelectionEnd(editor: TextEditor, move: string, backwards: bool = false, editor.scrollToCursor() editor.updateTargetColumn() -# proc moveParagraph(editor: TextEditor, backwards: bool, count: int = 1) {.exposeActive(editorContext, "move-paragraph").} = -# let which = getSetting[sca.SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), sca.SelectionCursor.Both) -# editor.setSelections editor.selections.mapIt(block: -# var res = it.last -# for k in 0.. 1: -# editor.moveCursorLine(count - 1) -# editor.moveLast("vim-visual-line") -# editor.scrollToCursor() -# editor.updateTargetColumn() - # proc vimMoveCursorLineFirstChar(editor: TextEditor, direction: int, count: int = 1) {.exposeActive(editorContext, "vim-move-cursor-line-first-char").} = # editor.moveCursorLine(direction * max(count, 1)) # editor.moveFirst "line-no-indent" @@ -907,17 +921,6 @@ proc moveDirection(editor: TextEditor, move: string, direction: int, count: int # editor.addNextCheckpoint "insert" # editor.insertText("\n") -proc normalMode(editor: TextEditor) {.exposeActive(editorContext).} = - ## Exit to normal mode and clear things - if $editor.mode == "vim-new.normal": - editor.setSelection editor.getSelection.last.toSelection - editor.clearTabStops() - editor.setMode("vim-new.normal") - -proc visualLineMode(editor: TextEditor) {.exposeActive(editorContext).} = - editor.setMode "vim-new.visual-line" - editor.selectLine() - # proc vimYankLine(editor: TextEditor) {.exposeActive(editorContext, "vim-yank-line").} = # editor.vimState.selectLines = true # editor.selectLine() @@ -1090,48 +1093,59 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # editor.setMode("vim-new.normal") -# proc vimSelectWordOrAddCursor(editor: TextEditor) {.exposeActive(editorContext, "vim-select-word-or-add-cursor").} = -# if editor.selections.len == 1: -# var selection = editor.setSearchQueryFromMove("word", prefix=r"\b", suffix=r"\b") -# selection.last.column -= 1 -# editor.setSelection selection -# else: -# let next = editor.getNextFindResult(editor.selection.last, includeAfter=false) -# editor.setSelections editor.selections & next -# editor.scrollToCursor() -# editor.setNextSnapBehaviour(MinDistanceOffscreen) -# editor.updateTargetColumn() +proc last*[T](list: WitList[T]): lent T = + assert list.len > 0 + return list[list.len - 1] -# editor.setMode("vim-new.visual") +proc selectWordOrAddCursor(editor: TextEditor) {.exposeActive(editorContext).} = + let selections = editor.selections + if selections.len == 1: + var selection = editor.setSearchQueryFromMove(ws"word", 1, prefix=ws(r"\b"), suffix=ws(r"\b")) + selection.last.column -= 1 + editor.setSelection selection + else: + let next = editor.multiMove(@@[selections.last], ws"next-search-result", 0, wrap = true, includeEol=false).last + let newSelections = @selections & next + editor.setSelections @@newSelections + editor.scrollToCursor() + editor.setNextSnapBehaviour(MinDistanceOffscreen) + editor.updateTargetColumn() -# proc vimMoveLastSelectionToNextSearchResult(editor: TextEditor) {.exposeActive(editorContext, "vim-move-last-selection-to-next-search-result").} = -# if editor.selections.len == 1: -# var selection = editor.setSearchQueryFromMove("word", prefix=r"\b", suffix=r"\b") -# selection.last.column -= 1 -# editor.setSelection selection -# else: -# let next = editor.getNextFindResult(editor.selection.last, includeAfter=false) -# editor.setSelections editor.selections[0..^2] & next -# editor.scrollToCursor() -# editor.setNextSnapBehaviour(MinDistanceOffscreen) -# editor.updateTargetColumn() + editor.setMode("vim-new.visual") -# editor.setMode("vim-new.visual") +proc moveLastSelectionToNextSearchResult(editor: TextEditor) {.exposeActive(editorContext).} = + let selections = editor.selections + if selections.len == 1: + var selection = editor.setSearchQueryFromMove(ws"word", 1, prefix=ws(r"\b"), suffix=ws(r"\b")) + selection.last.column -= 1 + editor.setSelection selection + else: + let next = editor.multiMove(@@[selections.last], ws"next-search-result", 0, wrap = true, includeEol=false).last + let newSelections = selections.toOpenArray[0..^2] & next + editor.setSelections @@newSelections + editor.scrollToCursor() + editor.setNextSnapBehaviour(MinDistanceOffscreen) + editor.updateTargetColumn() -# proc vimSetSearchQueryOrAddCursor(editor: TextEditor) {.exposeActive(editorContext, "vim-set-search-query-or-add-cursor").} = -# if editor.selections.len == 1: -# let text = editor.getText(editor.selection, inclusiveEnd=true) -# let textEscaped = text.escapeRegex -# let currentSearchQuery = editor.getSearchQuery() -# # debugf"'{text}' -> '{textEscaped}' -> '{currentSearchQuery}'" -# if textEscaped != currentSearchQuery and r"\b" & textEscaped & r"\b" != currentSearchQuery: -# if editor.setSearchQuery(text, escapeRegex=true): -# return + editor.setMode("vim-new.visual") -# let next = editor.getNextFindResult(editor.selection.last, includeAfter=false) -# editor.setSelections editor.selections & next -# editor.scrollToCursor() -# editor.updateTargetColumn() +proc setSearchQueryOrAddCursor(editor: TextEditor) {.exposeActive(editorContext).} = + let selections = editor.selections + debugf"setSearchQueryOrAddCursor {selections}, {selections.last}" + if selections.len == 1: + let selectedText = editor.content.sliceSelection(selections.last, inclusive=true) + let textEscaped = ($selectedText.text).escapeRegex + let currentSearchQuery = $editor.getSearchQuery() + debugf"{selectedText.bytes}, '{selectedText.text}' -> '{textEscaped}' -> '{currentSearchQuery}'" + if textEscaped != currentSearchQuery and r"\b" & textEscaped & r"\b" != currentSearchQuery: + if editor.setSearchQuery(selectedText.text, escapeRegex=true, prefix=ws"", suffix=ws""): + return + + let next = editor.multiMove(@@[selections.last], ws"next-search-result", 0, wrap = true, includeEol=false).last + let newSelections = @selections & next + editor.setSelections @@newSelections + editor.scrollToCursor() + editor.updateTargetColumn() # proc vimSaveState() {.expose("vim-save-state").} = # try: @@ -1399,8 +1413,8 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # editor.centerCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) -# proc vimReplaceInputHandler(editor: TextEditor, input: string) {.exposeActive(editorContext, "vim-replace-input-handler").} = -# editor.vimReplace(input) +proc replaceInputHandler(editor: TextEditor, input: string) {.exposeActive(editorContext).} = + editor.vimReplace(input) # proc vimInsertRegisterInputHandler(editor: TextEditor, input: string) {.exposeActive(editorContext, "vim-insert-register-input-handler").} = # editor.vimPaste register=input, inclusiveEnd=true diff --git a/plugins/vim/vim.nimble b/plugins/vim/vim.nimble index 0a5f36f3c..934766a7f 100644 --- a/plugins/vim/vim.nimble +++ b/plugins/vim/vim.nimble @@ -5,5 +5,6 @@ requires "chroma >= 0.2.7" requires "fusion >= 1.2" requires "regex >= 0.25.0" requires "results >= 0.5.0" -requires "https://github.com/Nimaoth/nimwasmtime#858ff37" +requires "https://github.com/Nimaoth/nimwasmtime#415a252" +requires "glob#64f71af" # "glob >= 0.11.2" # the newest version of glob doesn't have a version but is required for Nim 2.0 diff --git a/plugins/wasm.config.nims b/plugins/wasm.config.nims index e27b91c16..c53c25e81 100644 --- a/plugins/wasm.config.nims +++ b/plugins/wasm.config.nims @@ -13,7 +13,7 @@ else: --clang.linkerexe:emcc # Replace C linker --clang.cpp.exe:emcc # Replace C++ --clang.cpp.linkerexe:emcc # Replace C++ linker. ---listCmd # List what commands we are running so that we can debug them. +# --listCmd # List what commands we are running so that we can debug them. --gc:arc # GC:arc is friendlier with crazy platforms. --exceptions:goto # Goto exceptions are friendlier with crazy platforms. diff --git a/scripting/editor_text_api_wasm.nim b/scripting/editor_text_api_wasm.nim index cf187a41a..9d74028a5 100644 --- a/scripting/editor_text_api_wasm.nim +++ b/scripting/editor_text_api_wasm.nim @@ -2591,27 +2591,29 @@ proc getSelections*(self: TextDocumentEditor): Selections {.gcsafe, raises: [].} raiseAssert(getCurrentExceptionMsg()) -proc editor_text_setSelection_void_TextDocumentEditor_Selection_wasm( +proc editor_text_setSelection_void_TextDocumentEditor_Selection_Option_bool_wasm( arg: cstring): cstring {.importc.} -proc setSelection*(self: TextDocumentEditor; selection: Selection) {.gcsafe, - raises: [].} = +proc setSelection*(self: TextDocumentEditor; selection: Selection; + addToHistory: Option[bool] = bool.none) {.gcsafe, raises: [].} = var argsJson = newJArray() argsJson.add self.toJson() argsJson.add selection.toJson() + argsJson.add addToHistory.toJson() let argsJsonString = $argsJson - let res {.used.} = editor_text_setSelection_void_TextDocumentEditor_Selection_wasm( + let res {.used.} = editor_text_setSelection_void_TextDocumentEditor_Selection_Option_bool_wasm( argsJsonString.cstring) -proc editor_text_setSelections_void_TextDocumentEditor_Selections_wasm( +proc editor_text_setSelections_void_TextDocumentEditor_Selections_Option_bool_wasm( arg: cstring): cstring {.importc.} -proc setSelections*(self: TextDocumentEditor; selections: Selections) {.gcsafe, - raises: [].} = +proc setSelections*(self: TextDocumentEditor; selections: Selections; + addToHistory: Option[bool] = bool.none) {.gcsafe, raises: [].} = var argsJson = newJArray() argsJson.add self.toJson() argsJson.add selections.toJson() + argsJson.add addToHistory.toJson() let argsJsonString = $argsJson - let res {.used.} = editor_text_setSelections_void_TextDocumentEditor_Selections_wasm( + let res {.used.} = editor_text_setSelections_void_TextDocumentEditor_Selections_Option_bool_wasm( argsJsonString.cstring) diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index 13d581836..381a24e95 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -116,47 +116,47 @@ proc collectExports*(funcs: var ExportedFuncs; instance: InstanceT; funcs.mStackAlloc = instance.getExport(context, "mem_stack_alloc") funcs.mStackSave = instance.getExport(context, "mem_stack_save") funcs.mStackRestore = instance.getExport(context, "mem_stack_restore") - let f_8573159021 = instance.getExport(context, "init_plugin") - if f_8573159021.isSome: - assert f_8573159021.get.kind == WASMTIME_EXTERN_FUNC - funcs.initPlugin = f_8573159021.get.of_field.func_field + let f_8556381805 = instance.getExport(context, "init_plugin") + if f_8556381805.isSome: + assert f_8556381805.get.kind == WASMTIME_EXTERN_FUNC + funcs.initPlugin = f_8556381805.get.of_field.func_field else: echo "Failed to find exported function \'", "init_plugin", "\'" - let f_8573159037 = instance.getExport(context, "handle_command") - if f_8573159037.isSome: - assert f_8573159037.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleCommand = f_8573159037.get.of_field.func_field + let f_8556381821 = instance.getExport(context, "handle_command") + if f_8556381821.isSome: + assert f_8556381821.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleCommand = f_8556381821.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_command", "\'" - let f_8573159087 = instance.getExport(context, "handle_mode_changed") - if f_8573159087.isSome: - assert f_8573159087.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleModeChanged = f_8573159087.get.of_field.func_field + let f_8556381871 = instance.getExport(context, "handle_mode_changed") + if f_8556381871.isSome: + assert f_8556381871.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleModeChanged = f_8556381871.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_mode_changed", "\'" - let f_8573159088 = instance.getExport(context, "handle_view_render_callback") - if f_8573159088.isSome: - assert f_8573159088.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleViewRenderCallback = f_8573159088.get.of_field.func_field + let f_8556381872 = instance.getExport(context, "handle_view_render_callback") + if f_8556381872.isSome: + assert f_8556381872.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleViewRenderCallback = f_8556381872.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_view_render_callback", "\'" - let f_8573159112 = instance.getExport(context, "handle_channel_update") - if f_8573159112.isSome: - assert f_8573159112.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleChannelUpdate = f_8573159112.get.of_field.func_field + let f_8556381896 = instance.getExport(context, "handle_channel_update") + if f_8556381896.isSome: + assert f_8556381896.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleChannelUpdate = f_8556381896.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_channel_update", "\'" - let f_8573159113 = instance.getExport(context, "notify_task_complete") - if f_8573159113.isSome: - assert f_8573159113.get.kind == WASMTIME_EXTERN_FUNC - funcs.notifyTaskComplete = f_8573159113.get.of_field.func_field + let f_8556381897 = instance.getExport(context, "notify_task_complete") + if f_8556381897.isSome: + assert f_8556381897.get.kind == WASMTIME_EXTERN_FUNC + funcs.notifyTaskComplete = f_8556381897.get.of_field.func_field else: echo "Failed to find exported function \'", "notify_task_complete", "\'" - let f_8573159114 = instance.getExport(context, "handle_move") - if f_8573159114.isSome: - assert f_8573159114.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleMove = f_8573159114.get.of_field.func_field + let f_8556381898 = instance.getExport(context, "handle_move") + if f_8556381898.isSome: + assert f_8556381898.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleMove = f_8556381898.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_move", "\'" @@ -433,9 +433,15 @@ proc typesRunes(instance: ptr InstanceData; self: var RopeResource): int64 proc typesLines(instance: ptr InstanceData; self: var RopeResource): int64 proc typesText(instance: ptr InstanceData; self: var RopeResource): string proc typesSlice(instance: ptr InstanceData; self: var RopeResource; a: int64; - b: int64): RopeResource + b: int64; inclusive: bool): RopeResource +proc typesSliceSelection(instance: ptr InstanceData; self: var RopeResource; + s: Selection; inclusive: bool): RopeResource +proc typesFind(instance: ptr InstanceData; self: var RopeResource; + sub: sink string; start: int64): Option[int64] proc typesSlicePoints(instance: ptr InstanceData; self: var RopeResource; a: Cursor; b: Cursor): RopeResource +proc typesLineLength(instance: ptr InstanceData; self: var RopeResource; + line: int64): int64 proc typesRuneAt(instance: ptr InstanceData; self: var RopeResource; a: Cursor): Rune proc typesByteAt(instance: ptr InstanceData; self: var RopeResource; a: Cursor): uint8 proc editorActiveEditor(instance: ptr InstanceData): Option[Editor] @@ -484,6 +490,14 @@ proc textEditorCopy(instance: ptr InstanceData; editor: TextEditor; register: sink string; inclusiveEnd: bool): void proc textEditorPaste(instance: ptr InstanceData; editor: TextEditor; register: sink string; inclusiveEnd: bool): void +proc textEditorSetSearchQueryFromMove(instance: ptr InstanceData; + editor: TextEditor; move: sink string; + count: int32; prefix: sink string; + suffix: sink string): Selection +proc textEditorSetSearchQuery(instance: ptr InstanceData; editor: TextEditor; + query: sink string; escapeRegex: bool; + prefix: sink string; suffix: sink string): bool +proc textEditorGetSearchQuery(instance: ptr InstanceData; editor: TextEditor): string proc textEditorApplyMove(instance: ptr InstanceData; editor: TextEditor; selection: Selection; move: sink string; count: int32; wrap: bool; includeEol: bool): seq[Selection] @@ -527,6 +541,10 @@ proc renderId(instance: ptr InstanceData; self: var RenderViewResource): int32 proc renderSize(instance: ptr InstanceData; self: var RenderViewResource): Vec2f proc renderKeyDown(instance: ptr InstanceData; self: var RenderViewResource; key: int64): bool +proc renderMousePos(instance: ptr InstanceData; self: var RenderViewResource): Vec2f +proc renderMouseDown(instance: ptr InstanceData; self: var RenderViewResource; + button: int64): bool +proc renderScrollDelta(instance: ptr InstanceData; self: var RenderViewResource): Vec2f proc renderSetRenderInterval(instance: ptr InstanceData; self: var RenderViewResource; ms: int32): void proc renderSetRenderCommandsRaw(instance: ptr InstanceData; @@ -1071,20 +1089,81 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = block: let e = block: var ty: ptr WasmFunctypeT = newFunctype( - [WasmValkind.I32, WasmValkind.I64, WasmValkind.I64], [WasmValkind.I32]) + [WasmValkind.I32, WasmValkind.I64, WasmValkind.I64, WasmValkind.I32], + [WasmValkind.I32]) linker.defineFuncUnchecked("nev:plugins/types", "[method]rope.slice", ty): var instance = cast[ptr InstanceData](store.getData()) var self: ptr RopeResource var a: int64 var b: int64 + var inclusive: bool self = ?instance.resources.resourceHostData(parameters[0].i32, RopeResource) a = convert(parameters[1].i64, int64) b = convert(parameters[2].i64, int64) - let res = typesSlice(instance, self[], a, b) + inclusive = parameters[3].i32.bool + let res = typesSlice(instance, self[], a, b, inclusive) + parameters[0].i32 = ?instance.resources.resourceNew(store, res) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I32, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], + [WasmValkind.I32]) + linker.defineFuncUnchecked("nev:plugins/types", + "[method]rope.slice-selection", ty): + var instance = cast[ptr InstanceData](store.getData()) + var self: ptr RopeResource + var s: Selection + var inclusive: bool + self = ?instance.resources.resourceHostData(parameters[0].i32, + RopeResource) + s.first.line = convert(parameters[1].i32, int32) + s.first.column = convert(parameters[2].i32, int32) + s.last.line = convert(parameters[3].i32, int32) + s.last.column = convert(parameters[4].i32, int32) + inclusive = parameters[5].i32.bool + let res = typesSliceSelection(instance, self[], s, inclusive) parameters[0].i32 = ?instance.resources.resourceNew(store, res) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I32, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I64, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/types", "[method]rope.find", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var self: ptr RopeResource + var sub: string + var start: int64 + self = ?instance.resources.resourceHostData(parameters[0].i32, + RopeResource) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[1].i32].addr) + sub = newString(parameters[2].i32) + for i0 in 0 ..< sub.len: + sub[i0] = p0[i0] + start = convert(parameters[3].i64, int64) + let res = typesFind(instance, self[], sub, start) + let retArea = parameters[^1].i32 + cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int64 + if res.isSome: + cast[ptr int64](memory[retArea + 8].addr)[] = res.get + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I32, WasmValkind.I32, @@ -1099,12 +1178,28 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = RopeResource) a.line = convert(parameters[1].i32, int32) a.column = convert(parameters[2].i32, int32) - b.line = convert(parameters[2].i32, int32) - b.column = convert(parameters[3].i32, int32) + b.line = convert(parameters[3].i32, int32) + b.column = convert(parameters[4].i32, int32) let res = typesSlicePoints(instance, self[], a, b) parameters[0].i32 = ?instance.resources.resourceNew(store, res) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I64], [WasmValkind.I64]) + linker.defineFuncUnchecked("nev:plugins/types", + "[method]rope.line-length", ty): + var instance = cast[ptr InstanceData](store.getData()) + var self: ptr RopeResource + var line: int64 + self = ?instance.resources.resourceHostData(parameters[0].i32, + RopeResource) + line = convert(parameters[1].i64, int64) + let res = typesLineLength(instance, self[], line) + parameters[0].i64 = cast[int64](res) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype( @@ -1804,6 +1899,140 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = textEditorPaste(instance, editor, register, inclusiveEnd) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", + "set-search-query-from-move", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var editor: TextEditor + var move: string + var count: int32 + var prefix: string + var suffix: string + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[1].i32].addr) + move = newString(parameters[2].i32) + for i0 in 0 ..< move.len: + move[i0] = p0[i0] + count = convert(parameters[3].i32, int32) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[4].i32].addr) + prefix = newString(parameters[5].i32) + for i0 in 0 ..< prefix.len: + prefix[i0] = p0[i0] + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[6].i32].addr) + suffix = newString(parameters[7].i32) + for i0 in 0 ..< suffix.len: + suffix[i0] = p0[i0] + let res = textEditorSetSearchQueryFromMove(instance, editor, move, + count, prefix, suffix) + let retArea = parameters[^1].i32 + cast[ptr int32](memory[retArea + 0].addr)[] = res.first.line + cast[ptr int32](memory[retArea + 4].addr)[] = res.first.column + cast[ptr int32](memory[retArea + 8].addr)[] = res.last.line + cast[ptr int32](memory[retArea + 12].addr)[] = res.last.column + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I32], [WasmValkind.I32]) + linker.defineFuncUnchecked("nev:plugins/text-editor", "set-search-query", + ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var editor: TextEditor + var query: string + var escapeRegex: bool + var prefix: string + var suffix: string + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[1].i32].addr) + query = newString(parameters[2].i32) + for i0 in 0 ..< query.len: + query[i0] = p0[i0] + escapeRegex = parameters[3].i32.bool + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[4].i32].addr) + prefix = newString(parameters[5].i32) + for i0 in 0 ..< prefix.len: + prefix[i0] = p0[i0] + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[6].i32].addr) + suffix = newString(parameters[7].i32) + for i0 in 0 ..< suffix.len: + suffix[i0] = p0[i0] + let res = textEditorSetSearchQuery(instance, editor, query, escapeRegex, + prefix, suffix) + parameters[0].i32 = cast[int32](res) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "get-search-query", + ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + let stackAllocFunc = caller.getExport("mem_stack_alloc").get.of_field.func_field + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + let res = textEditorGetSearchQuery(instance, editor) + let retArea = parameters[^1].i32 + if res.len > 0: + let dataPtrWasm0 = int32(?stackAlloc(stackAllocFunc, store, + (res.len * 1).int32, 4)) + cast[ptr int32](memory[retArea + 0].addr)[] = cast[int32](dataPtrWasm0) + block: + for i0 in 0 ..< res.len: + memory[dataPtrWasm0 + i0] = cast[uint8](res[i0]) + else: + cast[ptr int32](memory[retArea + 0].addr)[] = 0.int32 + cast[ptr int32](memory[retArea + 4].addr)[] = cast[int32](res.len) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, @@ -2508,6 +2737,78 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = parameters[0].i32 = cast[int32](res) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/render", + "[method]render-view.mouse-pos", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var self: ptr RenderViewResource + self = ?instance.resources.resourceHostData(parameters[0].i32, + RenderViewResource) + let res = renderMousePos(instance, self[]) + let retArea = parameters[^1].i32 + cast[ptr float32](memory[retArea + 0].addr)[] = res.x + cast[ptr float32](memory[retArea + 4].addr)[] = res.y + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I64], [WasmValkind.I32]) + linker.defineFuncUnchecked("nev:plugins/render", + "[method]render-view.mouse-down", ty): + var instance = cast[ptr InstanceData](store.getData()) + var self: ptr RenderViewResource + var button: int64 + self = ?instance.resources.resourceHostData(parameters[0].i32, + RenderViewResource) + button = convert(parameters[1].i64, int64) + let res = renderMouseDown(instance, self[], button) + parameters[0].i32 = cast[int32](res) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/render", + "[method]render-view.scroll-delta", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var self: ptr RenderViewResource + self = ?instance.resources.resourceHostData(parameters[0].i32, + RenderViewResource) + let res = renderScrollDelta(instance, self[]) + let retArea = parameters[^1].i32 + cast[ptr float32](memory[retArea + 0].addr)[] = res.x + cast[ptr float32](memory[retArea + 4].addr)[] = res.y + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype( diff --git a/src/misc/custom_regex.nim b/src/misc/custom_regex.nim new file mode 100644 index 000000000..efb56aae7 --- /dev/null +++ b/src/misc/custom_regex.nim @@ -0,0 +1,2 @@ +import "./regex" +export regex diff --git a/src/misc/render_command.nim b/src/misc/render_command.nim index 6f3d7312d..10efd5ac8 100644 --- a/src/misc/render_command.nim +++ b/src/misc/render_command.nim @@ -285,6 +285,7 @@ template buildCommands*(self: var BinaryEncoder, body: untyped) = self.write(RenderCommandKind.Rect.uint8 + 1.uint8) self.write(inBounds) self.write(inColor) + self.write(0.UINodeFlags) template fillRect(inBounds: Rect, inColor: Color): untyped {.used.} = self.write(RenderCommandKind.FilledRect.uint8 + 1.uint8) self.write(inBounds) @@ -302,6 +303,12 @@ template buildCommands*(self: var BinaryEncoder, body: untyped) = self.write(inColor) self.write(inFlags) self.write(txt.toOpenArray(0, txt.high)) + template drawText(inText: openArray[char], inBounds: Rect, inColor: Color, inFlags: UINodeFlags): untyped {.used.} = + self.write(RenderCommandKind.TextRaw.uint8 + 1.uint8) + self.write(inBounds) + self.write(inColor) + self.write(inFlags) + self.write(inText) # template drawText(inText: openArray[char], inBounds: Rect, inColor: Color, inFlags: UINodeFlags): untyped {.used.} = # let offset = renderCommands.strings.len.uint32 # for c in inText: diff --git a/src/misc/rope_utils.nim b/src/misc/rope_utils.nim index a0621df3c..e9bb78da4 100644 --- a/src/misc/rope_utils.nim +++ b/src/misc/rope_utils.nim @@ -153,6 +153,10 @@ func lineRange*(self: Rope, line: int, includeLineEnd: bool = true): Range[int] return 0...0 +func validateOffset*[D](self: RopeSlice[D], offset: int, bias: Bias): int = + let startOffset = self.rope.toOffset(self.range.a) + return self.rope.validateOffset(startOffset + offset, bias) - startOffset + func lineRange*[D](self: RopeSlice[D], line: int, includeLineEnd: bool = true): Range[int] = ## Returns a the range of the given line. If `includeLineEnd` is true then the end of the range will ## be the index of the newline character (used for a selection with an _exclusive_ end cursor), diff --git a/src/move_database.nim b/src/move_database.nim index b30cb6250..87f2edad8 100644 --- a/src/move_database.nim +++ b/src/move_database.nim @@ -4,7 +4,7 @@ from scripting_api as sca import nil import misc/[util, custom_logger, custom_unicode, myjsonutils, regex, rope_utils, rope_regex, custom_async] import text/custom_treesitter, text/indent import config_provider, service -# import text/[overlay_map, tab_map, wrap_map, diff_map, display_map] +import text/[overlay_map, tab_map, wrap_map, diff_map, display_map] import nimsumtree/[rope] {.push gcsafe, raises: [].} @@ -57,9 +57,78 @@ proc vimMotionWordBig*(text: Rope, cursor: Cursor): Selection = let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c notin Whitespace) return ((cursor.line, startColumn), (cursor.line, endColumn)) -proc applyMove*(self: MoveDatabase, rope: Rope, move: string, selections: openArray[Selection], count: int = 0, includeEol: bool = true): seq[Selection] = +proc findNext*(text: Rope, cursor: Cursor, target: Rune): Cursor = + var c = text.cursorT(cursor.toPoint) + c.seekNextRune() + while not c.atEnd and c.currentRune != target: + c.seekNextRune() + return c.position.toCursor + +proc clampCursor*(text: Rope, cursor: Cursor, includeAfter: bool = true): Cursor = + var cursor = cursor + cursor.line = clamp(cursor.line, 0, text.lines - 1) + + var res = text.clipPoint(cursor.toPoint, Bias.Left).toCursor + var c = text.cursorT(res.toPoint) + if not includeAfter and c.currentRune == '\n'.Rune and res.column > 0: + c.seekPrevRune() + res = c.position.toCursor + return res + +proc moveCursorColumn(text: Rope, cursor: Cursor, offset: int, wrap: bool = true, includeEol: bool = true): Cursor = + var cursor = cursor + + if cursor.line notin 0.. 0: + for i in 0..= lastIndex: + if not wrap: + break + if cursor.line < text.lines - 1: + cursor.line = cursor.line + 1 + cursor.column = 0 + lastIndex = text.lastValidIndex(cursor.line, includeEol) + c.seekForward(point(cursor.line, 0)) + continue + else: + cursor.column = lastIndex + break + + c.seekNextRune() + cursor = c.position.toCursor + + elif offset < 0: + for i in 0..<(-offset): + if cursor.column == 0: + if not wrap: + break + if cursor.line > 0: + cursor.line = cursor.line - 1 + lastIndex = text.lastValidIndex(cursor.line, includeEol) + c.seekPrevRune() + if not includeEol: + c.seekPrevRune() + cursor.column = lastIndex + continue + else: + cursor.column = 0 + break + + c.seekPrevRune() + cursor = c.position.toCursor + + return text.clampCursor(cursor, includeEol) + +proc applyMove*(self: MoveDatabase, displayMap: DisplayMap, move: string, selections: openArray[Selection], count: int = 0, includeEol: bool = true): seq[Selection] = debugf"applyMove '{move}', {selections}, {count}, {includeEol}" + let rope {.cursor.} = displayMap.buffer.visibleText + if move in self.moves: let impl = self.moves[move] return impl(rope, move, selections, count, includeEol) @@ -146,28 +215,37 @@ proc applyMove*(self: MoveDatabase, rope: Rope, move: string, selections: openAr # (cursor.line, 0) # result = (first, (cursor.line, self.document.lineLength(cursor.line))) - # of "line": - # let lineLen = self.document.lineLength(cursor.line) - # result = ((cursor.line, 0), (cursor.line, lineLen)) - # if not includeEol and result.last.column == lineLen: - # result.last = self.doMoveCursorColumn(result.last, -1, wrap = false) - - # of "visual-line": - # let lineLen = self.document.lineLength(cursor.line) - # let wrapPoint = self.displayMap.toWrapPoint(cursor.toPoint) - # let displayLineStart = wrapPoint(wrapPoint.row) - # let displayLineEnd = wrapPoint(wrapPoint.row + 1) - # result[0] = self.displayMap.toPoint(displayLineStart, Right).toCursor - # result[1] = self.displayMap.toPoint(displayLineEnd, Right).toCursor - # if result[1].column == 0: - # result[1].line -= 1 - # result[1].column = self.document.lineLength(result[1].line) - - # if not includeEol: - # if result.last.column == lineLen: - # result.last = self.doMoveCursorColumn(result.last, -1, wrap = false) - # elif result.last.column < self.document.lineLength(cursor.line): # This is the case if we're not in the last visual sub line - # result.last = self.doMoveCursorColumn(result.last, -1, wrap = false) + of "line": + return selections.mapIt(block: + let lineLen = rope.lineLen(it.last.line) + var res: Selection = ((it.last.line, 0), (it.last.line, lineLen)) + if not includeEol and res.last.column == lineLen: + res.last = rope.moveCursorColumn(res.last, -1, wrap = false) + res + ) + + of "visual-line": + return selections.mapIt(block: + let lineLen = rope.lineLen(it.last.line) + let wrapPoint = displayMap.toWrapPoint(it.last.toPoint) + let displayLineStart = wrapPoint(wrapPoint.row) + let displayLineEnd = wrapPoint(wrapPoint.row + 1) + var res: Selection = ( + displayMap.toPoint(displayLineStart, Right).toCursor, + displayMap.toPoint(displayLineEnd, Right).toCursor, + ) + if res[1].column == 0: + res[1].line -= 1 + res[1].column = rope.lineLen(res[1].line) + + if not includeEol: + if res.last.column == lineLen: + res.last = rope.moveCursorColumn(res.last, -1, wrap = false) + elif res.last.column < rope.lineLen(it.last.line): # This is the case if we're not in the last visual sub line + res.last = rope.moveCursorColumn(res.last, -1, wrap = false) + + res + ) # of "line-next": # result = ((cursor.line, 0), (cursor.line, self.document.lineLength(cursor.line))) @@ -199,8 +277,8 @@ proc applyMove*(self: MoveDatabase, rope: Rope, move: string, selections: openAr # let line = self.document.numLines - 1 # result.last = (line, self.document.lineLength(line)) - # of "column": - # result = self.doMoveCursorColumn(cursor, count, includeAfter = includeEol).toSelection + of "column": + return selections.mapIt((it.first, rope.moveCursorColumn(it.last, count, includeEol))) # of "prev-find-result": # result = self.getPrevFindResult(cursor, count) @@ -225,8 +303,11 @@ proc applyMove*(self: MoveDatabase, rope: Rope, move: string, selections: openAr else: if move.startsWith("move-to "): - discard - # # todo: use RopeCursor + let c = move["move-to ".len..^1] + if c.len > 0: + let r = c.runeAt(0) + return selections.mapIt((it.first, rope.findNext(it.last, r))) + # let str = move[8..^1] # let line = self.document.getLine cursor.line # result = cursor.toSelection diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index 0b5b4c5ab..47a534df7 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -355,9 +355,10 @@ var threadPool = newPluginThreadPool[InstanceDataImpl](10, runInstanceThread) ###################################### Conversion functions ##################################### -proc toInternal(c: Cursor): sca.Cursor = (c.line.int, c.column.int) -proc toInternal(c: Selection): sca.Selection = (c.first.toInternal, c.last.toInternal) -proc toInternal(c: ScrollBehaviour): sca.ScrollBehaviour = +converter toPoint(c: Cursor): Point = point(c.line.int, c.column.int) +converter toInternal(c: Cursor): sca.Cursor = (c.line.int, c.column.int) +converter toInternal(c: Selection): sca.Selection = (c.first.toInternal, c.last.toInternal) +converter toInternal(c: ScrollBehaviour): sca.ScrollBehaviour = case c of CenterAlways: sca.CenterAlways of CenterOffscreen: sca.CenterOffscreen @@ -365,17 +366,17 @@ proc toInternal(c: ScrollBehaviour): sca.ScrollBehaviour = of ScrollToMargin: sca.ScrollToMargin of TopOfScreen: sca.TopOfScreen -proc toInternal(c: ScrollSnapBehaviour): sca.ScrollSnapBehaviour = +converter toInternal(c: ScrollSnapBehaviour): sca.ScrollSnapBehaviour = case c of Never: sca.Never of Always: sca.Always of MinDistanceOffscreen: sca.MinDistanceOffscreen of MinDistanceCenter: sca.MinDistanceCenter -proc toWasm(c: sca.Cursor): Cursor = Cursor(line: c.line.int32, column: c.column.int32) -proc toWasm(c: sca.Selection): Selection = Selection(first: c.first.toWasm, last: c.last.toWasm) +converter toWasm(c: sca.Cursor): Cursor = Cursor(line: c.line.int32, column: c.column.int32) +converter toWasm(c: sca.Selection): Selection = Selection(first: c.first.toWasm, last: c.last.toWasm) -proc toInternal(flags: ReadFlags): set[vfs.ReadFlag] = +converter toInternal(flags: ReadFlags): set[vfs.ReadFlag] = result = {} if ReadFlag.Binary in flags: result.incl vfs.ReadFlag.Binary @@ -508,7 +509,8 @@ proc textEditorSetSelections(instance: ptr InstanceData; editor: TextEditor; s: return if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: let textEditor = editor.TextDocumentEditor - textEditor.selections = s.mapIt(it.toInternal) + if s.len > 0: + textEditor.selections = s.mapIt(it.toInternal) proc textEditorGetSelection(instance: ptr InstanceData; editor: TextEditor): Selection = if instance.host == nil: @@ -682,6 +684,25 @@ proc textEditorSetSettingRaw(instance: ptr InstanceData, editor: TextEditor, nam except CatchableError as e: log lvlError, "set-setting-raw: Failed to set setting '{name}' to {value}: {e.msg}" +proc textEditorSetSearchQueryFromMove(instance: ptr InstanceData, editor: TextEditor, move: sink string, count: int32, prefix: sink string, suffix: sink string): Selection = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + return editor.TextDocumentEditor.setSearchQueryFromMove(move, count, prefix, suffix) + +proc textEditorSetSearchQuery(instance: ptr InstanceData, editor: TextEditor, query: sink string, escapeRegex: bool, prefix: sink string, suffix: sink string): bool = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + return editor.TextDocumentEditor.setSearchQuery(query, escapeRegex, prefix, suffix) + +proc textEditorGetSearchQuery(instance: ptr InstanceData, editor: TextEditor): string = + if instance.host == nil: + return "" + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + return editor.TextDocumentEditor.getSearchQuery() + return "" + proc typesNewRope(instance: ptr InstanceData, content: sink string): RopeResource = return RopeResource(rope: createRope(content).slice().suffix(Point())) @@ -700,15 +721,33 @@ proc typesRunes(instance: ptr InstanceData, self: var RopeResource): int64 = proc typesLines(instance: ptr InstanceData, self: var RopeResource): int64 = return self.rope.lines.int64 -proc typesSlice(instance: ptr InstanceData, self: var RopeResource, a: int64, b: int64): RopeResource = - let a = min(a, b).clamp(0, self.rope.len) - let b = max(a, b).clamp(0, self.rope.len) +proc typesSlice(instance: ptr InstanceData, self: var RopeResource, a: int64, b: int64, inclusive: bool): RopeResource = + let a = min(a.int, b.int).clamp(0, self.rope.len) + var b = max(a.int, b.int).clamp(0, self.rope.len) + if inclusive: + b = self.rope.clip(b + 1, Bias.Right) return RopeResource(rope: self.rope[a.int...b.int].suffix(Point())) +proc typesSliceSelection(instance: ptr InstanceData, self: var RopeResource, s: Selection, inclusive: bool): RopeResource = + let a = min(s.first.toPoint, s.last.toPoint).clamp(point(0, 0), self.rope.summary.lines) + var b = max(s.first.toPoint, s.last.toPoint).clamp(point(0, 0), self.rope.summary.lines) + if inclusive: + b = self.rope.clip(b + point(0, 1), Bias.Right) + return RopeResource(rope: self.rope[a...b].suffix(Point())) + proc typesSlicePoints(instance: ptr InstanceData, self: var RopeResource, a: Cursor, b: Cursor): RopeResource = let range = Point(row: a.line.uint32, column: a.column.uint32)...Point(row: a.line.uint32, column: a.column.uint32) return RopeResource(rope: self.rope[range]) +proc typesLineLength(instance: ptr InstanceData, self: var RopeResource, line: int64): int64 = + return self.rope.lineRange(line).len + +proc typesFind(instance: ptr InstanceData, self: var RopeResource, sub: sink string, start: int64): Option[int64] = + let i = self.rope.find(sub, start).int64 + if i >= 0: + return i.some + return int64.none + proc typesRuneAt(instance: ptr InstanceData; self: var RopeResource; a: Cursor): Rune = return self.rope.runeAt(a.toInternal.toPoint) @@ -953,11 +992,20 @@ proc renderId(instance: ptr InstanceData; self: var RenderViewResource): int32 = return self.view.id2 proc renderSize(instance: ptr InstanceData; self: var RenderViewResource): Vec2f = - return Vec2f(x: self.view.size.x, y: self.view.size.y) + return Vec2f(x: self.view.bounds.w, y: self.view.bounds.h) proc renderKeyDown(instance: ptr InstanceData; self: var RenderViewResource, key: int64): bool = return key in self.view.keyStates +proc renderMousePos(instance: ptr InstanceData; self: var RenderViewResource): Vec2f = + return Vec2f(x: self.view.mousePos.x, y: self.view.mousePos.y) + +proc renderMouseDown(instance: ptr InstanceData; self: var RenderViewResource; button: int64): bool = + return button in self.view.mouseStates + +proc renderScrollDelta(instance: ptr InstanceData; self: var RenderViewResource): Vec2f = + return Vec2f(x: self.view.scrollDelta.x, y: self.view.scrollDelta.y) + proc renderSetRenderWhenInactive(instance: ptr InstanceData; self: var RenderViewResource; enabled: bool): void = self.view.setRenderWhenInactive(enabled) diff --git a/src/render_view.nim b/src/render_view.nim index 4f73029ec..c5cbfd59a 100644 --- a/src/render_view.nim +++ b/src/render_view.nim @@ -19,7 +19,7 @@ type commands*: RenderCommands userId*: string - size*: Vec2 + bounds*: Rect interval: int = -1 onRender*: proc(view: RenderView) {.gcsafe, raises: [].} renderTask: DelayedTask @@ -31,6 +31,9 @@ type modes*: seq[string] keyStates*: HashSet[int64] + mouseStates*: HashSet[int64] + mousePos*: Vec2 + scrollDelta*: Vec2 proc handleAction(self: RenderView, action: string, arg: string): Option[string] proc handleInput(self: RenderView, text: string) @@ -44,10 +47,26 @@ proc handleKeyRelease*(self: RenderView, input: int64, modifiers: Modifiers) = proc handleRune*(self: RenderView, input: int64, modifiers: Modifiers) = self.keyStates.incl(input) +proc handleMousePress(self: RenderView, button: MouseButton, modifiers: Modifiers, pos: Vec2) = + self.mouseStates.incl(button.int64) + +proc handleMouseRelease(self: RenderView, button: MouseButton, modifiers: Modifiers, pos: Vec2) = + self.mouseStates.excl(button.int64) + +proc handleMouseMove(self: RenderView, pos: Vec2, delta: Vec2, modifiers: Modifiers, buttons: set[MouseButton]) = + self.mousePos = pos - self.bounds.xy + +proc handleScroll(self: RenderView, pos: Vec2, scroll: Vec2, modifiers: Modifiers) = + self.scrollDelta = scroll + proc bindPlatformEvents(self: RenderView) = discard self.platform.onKeyPress.subscribe proc(event: auto): void {.gcsafe, raises: [].} = self.handleKeyPress(event.input, event.modifiers) discard self.platform.onKeyRelease.subscribe proc(event: auto): void {.gcsafe, raises: [].} = self.handleKeyRelease(event.input, event.modifiers) discard self.platform.onRune.subscribe proc(event: auto): void {.gcsafe, raises: [].} = self.handleRune(event.input, event.modifiers) + discard self.platform.onMousePress.subscribe proc(event: tuple[button: MouseButton, modifiers: Modifiers, pos: Vec2]): void {.gcsafe, raises: [].} = self.handleMousePress(event.button, event.modifiers, event.pos) + discard self.platform.onMouseRelease.subscribe proc(event: tuple[button: MouseButton, modifiers: Modifiers, pos: Vec2]): void {.gcsafe, raises: [].} = self.handleMouseRelease(event.button, event.modifiers, event.pos) + discard self.platform.onMouseMove.subscribe proc(event: tuple[pos: Vec2, delta: Vec2, modifiers: Modifiers, buttons: set[MouseButton]]): void {.gcsafe, raises: [].} = self.handleMouseMove(event.pos, event.delta, event.modifiers, event.buttons) + discard self.platform.onScroll.subscribe proc(event: tuple[pos: Vec2, scroll: Vec2, modifiers: Modifiers]): void {.gcsafe, raises: [].} = self.handleScroll(event.pos, event.scroll, event.modifiers) proc newRenderView*(services: Services): RenderView = result = RenderView(services: services) @@ -82,6 +101,8 @@ proc render*(self: RenderView) = except Exception: discard + self.scrollDelta = vec2(0, 0) + method activate*(self: RenderView) = self.active = true self.setRenderInterval(self.interval) diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index 36c158089..8fac0d606 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -2993,6 +2993,30 @@ proc getSelectionsForMove*(self: TextDocumentEditor, selections: openArray[Selec let cursorSelector = self.config.get(self.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) + var move = move + var args: JsonNode = nil + + let argsStart = move.find(" ") + if argsStart > 0: + try: + args = newJArray() + for a in newStringStream(move[(argsStart + 1)..^1]).parseJsonFragments(): + args.add a + except CatchableError as e: + log lvlError, &"getSelectionsForMove: '{move}', Failed to parse args: {e.msg}" + + move = move[0.. s64; /// Returns the text of the rope as a string. This is expensive for large ropes. text: func() -> string; - /// Returns a slice of the rope from 'a' to 'b'. 'a' and 'b' are byte indices, 'b' is exclusive. + /// Returns a slice of the rope from 'a' to 'b'. 'a' and 'b' are byte indices, 'b' is inclusive if `inclusive` is true. /// This operation is cheap because it doesn't create a copy of the text. - slice: func(a: s64, b: s64) -> rope; + slice: func(a: s64, b: s64, inclusive: bool) -> rope; + /// Returns a slice of the rope from the given selection. 's.last' is inclusive if `inclusive` is true. + /// This operation is cheap because it doesn't create a copy of the text. + slice-selection: func(s: selection, inclusive: bool) -> rope; + /// Find the byte index of the sub string 'sub', starting the search at 'start'. The returned index is relative to the start of the rope, not 'start'. + find: func(sub: string, start: s64) -> option; /// Returns a slice of the rope from 'a' to 'b'. The column of 'b' is exclusive. Columns are in bytes. /// This operation is cheap because it doesn't create a copy of the text. slice-points: func(a: cursor, b: cursor) -> rope; + /// Return the length in bytes of the given line (0 based). + line-length: func(line: s64) -> s64; // /// Returns a slice of the rope from line 'a' to 'b'. 'b' is inclusive. // /// This operation is cheap because it doesn't create a copy of the text. // slice-lines: func(a: s64, b: s64) -> rope; @@ -267,6 +274,15 @@ interface text-editor { /// todo paste: func(editor: text-editor, register: string, inclusive-end: bool); + /// Set the search query by applying the given move to the current selection. + /// 'prefix' and 'suffix' are appended to the string after escaping. + set-search-query-from-move: func(editor: text-editor, move: string, count: s32, prefix: string, suffix: string) -> selection; + /// Set the search query to 'query'. If 'escape-regex' is true the the 'query' string is escaped so special characters + /// are searched for literally. 'prefix' and 'suffix' are appended to the string after escaping. + set-search-query: func(editor: text-editor, query: string, escape-regex: bool, prefix: string, suffix: string) -> bool; + /// Returns the current search query. + get-search-query: func(editor: text-editor) -> string; + /// todo apply-move: func(editor: text-editor, selection: selection, move: string, count: s32, wrap: bool, include-eol: bool) -> list; multi-move: func(editor: text-editor, selections: list, move: string, count: s32, wrap: bool, include-eol: bool) -> list; @@ -363,6 +379,12 @@ interface render { size: func() -> vec2f; /// Returns whether the given key is currently pressed. key-down: func(key: s64) -> bool; + /// Returns the mouse position relative to the position of the view. (0, 0) is in the top left corner. + mouse-pos: func() -> vec2f; + /// Returns whether the given mouse button is pressed. + mouse-down: func(button: s64) -> bool; + /// Returns how much the mouse was scrolled this frame. + scroll-delta: func() -> vec2f; /// Specify how often the view should render. -1 means don't render in a timer, 0 means render every frame, /// a number bigger than 0 specifies the interval in milliseconds. set-render-interval: func(ms: s32); @@ -411,7 +433,7 @@ interface core { /// 0 means the latest version, 1 or bigger means a specific version. api-version: func() -> s32; - /// Returns the time since the plugin was loaded. Returns 0 if the plugin has no 'time' permission. + /// Returns the time in milliseconds since the plugin was loaded. Returns 0 if the plugin has no 'time' permission. get-time: func() -> f64; /// Returns what kind platform the app is running on. E.g. 'terminal' or 'gui' From 3bcd36925a44824718d598dc3892e886da23f007 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Tue, 9 Sep 2025 13:13:46 +0200 Subject: [PATCH 11/47] Ssh terminals and sixel support --- nev.nimble | 2 + nimble.lock | 22 + plugin_api/v0/channel.nim | 19 + plugin_api/v0/render.nim | 2 +- plugin_api/v0/text_editor.nim | 7 +- plugin_api/v0/types.nim | 6 +- src/compilation_config.nim | 1 + src/generated/plugin_api_host.nim | 101 +++-- src/layout.nim | 31 ++ src/libvterm/state.c | 6 +- src/misc/async_process.nim | 10 +- src/misc/render_command.nim | 24 ++ src/platform/gui_platform.nim | 13 + src/platform/platform.nim | 4 +- src/platform/terminal_platform.nim | 4 + src/plugin_api/plugin_api.nim | 13 +- src/scripting_api.nim | 13 + src/selector_popup.nim | 2 + src/selector_popup_builder.nim | 2 + src/selector_popup_builder_impl.nim | 1 + src/ssh.nim | 204 ++++++++++ src/terminal_service.nim | 602 ++++++++++++++++++++++++++-- src/ui/widget_builder_terminal.nim | 26 ++ wit/v0/api.wit | 3 + 24 files changed, 1047 insertions(+), 71 deletions(-) create mode 100644 src/ssh.nim diff --git a/nev.nimble b/nev.nimble index c52a4c5fb..b356a0027 100644 --- a/nev.nimble +++ b/nev.nimble @@ -41,6 +41,8 @@ requires "https://github.com/Nimaoth/nimtreesitter-api >= 0.1.21" requires "https://github.com/Nimaoth/nimwasmtime#415a252" requires "https://github.com/Nimaoth/nimsumtree#47f7208" requires "https://github.com/Nimaoth/zippy >= 0.10.17" +requires "libssh2 >= 0.1.9" +requires "ssh2 >= 0.1.9" # Use this to include all treesitter languages (takes longer to download) requires "https://github.com/Nimaoth/nimtreesitter >= 0.1.11" diff --git a/nimble.lock b/nimble.lock index ec75442de..fb901be6a 100644 --- a/nimble.lock +++ b/nimble.lock @@ -290,6 +290,16 @@ "sha1": "58fadfae8cb82cab1061dded8066c03b0460ad4d" } }, + "libssh2": { + "version": "0.1.9", + "vcsRevision": "a5cf5180cddf864b1bddbb0d35b3b5c499160176", + "url": "https://github.com/ba0f3/libssh2.nim", + "downloadMethod": "git", + "dependencies": [], + "checksums": { + "sha1": "424cbdaea8bc2662d8d7601dabe458bbc1d15c1f" + } + }, "lrucache": { "version": "1.1.4", "vcsRevision": "479d4cf03a961d00fb2204e4773c394fe62ac44b", @@ -388,6 +398,18 @@ "sha1": "6bc181924709c8a2d06b1459a31e73eb42cf1283" } }, + "ssh2": { + "version": "0.1.9", + "vcsRevision": "b45447fa3892024eb5ceb5d1516beba57bd68d05", + "url": "https://github.com/ba0f3/ssh2.nim", + "downloadMethod": "git", + "dependencies": [ + "libssh2" + ], + "checksums": { + "sha1": "32cb3446c5f2f7d4f01dabb577aa8c136b4ae1d9" + } + }, "treesitter": { "version": "0.1.21", "vcsRevision": "8625f8ac129aa7625ec20ee6fee15b9aca8d1085", diff --git a/plugin_api/v0/channel.nim b/plugin_api/v0/channel.nim index a42b1b1cc..2a4db2592 100644 --- a/plugin_api/v0/channel.nim +++ b/plugin_api/v0/channel.nim @@ -288,3 +288,22 @@ proc newInMemoryChannel*(): (ReadChannel, WriteChannel) {.nodestroy.} = channelNewInMemoryChannelImported(cast[int32](retArea[0].addr)) result[0].handle = cast[ptr int32](retArea[0].addr)[] + 1 result[1].handle = cast[ptr int32](retArea[4].addr)[] + 1 + +proc channelCreateTerminalImported(a0: int32; a1: int32; a2: int32; a3: int32): void {. + wasmimport("create-terminal", "nev:plugins/channel").} +proc createTerminal*(stdin: sink WriteChannel; stdout: sink ReadChannel; + group: WitString): void {.nodestroy.} = + ## Creates a new channel which buffers data in memory and returns the read and write end. + var + arg0: int32 + arg1: int32 + arg2: int32 + arg3: int32 + arg0 = cast[int32](stdin.handle - 1) + arg1 = cast[int32](stdout.handle - 1) + if group.len > 0: + arg2 = cast[int32](group[0].addr) + else: + arg2 = 0.int32 + arg3 = cast[int32](group.len) + channelCreateTerminalImported(arg0, arg1, arg2, arg3) diff --git a/plugin_api/v0/render.nim b/plugin_api/v0/render.nim index 417248e5a..61d510a36 100644 --- a/plugin_api/v0/render.nim +++ b/plugin_api/v0/render.nim @@ -135,7 +135,7 @@ proc mouseDown*(self: RenderView; button: int64): bool {.nodestroy.} = proc renderScrollDeltaImported(a0: int32; a1: int32): void {. wasmimport("[method]render-view.scroll-delta", "nev:plugins/render").} proc scrollDelta*(self: RenderView): Vec2f {.nodestroy.} = - ## todo + ## Returns how much the mouse was scrolled this frame. var retArea: array[8, uint8] arg0: int32 diff --git a/plugin_api/v0/text_editor.nim b/plugin_api/v0/text_editor.nim index be77cdda3..e18c2c768 100644 --- a/plugin_api/v0/text_editor.nim +++ b/plugin_api/v0/text_editor.nim @@ -374,7 +374,8 @@ proc textEditorSetSearchQueryFromMoveImported(a0: uint64; a1: int32; a2: int32; proc setSearchQueryFromMove*(editor: TextEditor; move: WitString; count: int32; prefix: WitString; suffix: WitString): Selection {. nodestroy.} = - ## todo + ## Set the search query by applying the given move to the current selection. + ## 'prefix' and 'suffix' are appended to the string after escaping. var retArea: array[40, uint8] arg0: uint64 @@ -415,7 +416,8 @@ proc textEditorSetSearchQueryImported(a0: uint64; a1: int32; a2: int32; wasmimport("set-search-query", "nev:plugins/text-editor").} proc setSearchQuery*(editor: TextEditor; query: WitString; escapeRegex: bool; prefix: WitString; suffix: WitString): bool {.nodestroy.} = - ## todo + ## Set the search query to 'query'. If 'escape-regex' is true the the 'query' string is escaped so special characters + ## are searched for literally. 'prefix' and 'suffix' are appended to the string after escaping. var arg0: uint64 arg1: int32 @@ -449,6 +451,7 @@ proc setSearchQuery*(editor: TextEditor; query: WitString; escapeRegex: bool; proc textEditorGetSearchQueryImported(a0: uint64; a1: int32): void {. wasmimport("get-search-query", "nev:plugins/text-editor").} proc getSearchQuery*(editor: TextEditor): WitString {.nodestroy.} = + ## Returns the current search query. var retArea: array[8, uint8] arg0: uint64 diff --git a/plugin_api/v0/types.nim b/plugin_api/v0/types.nim index 02a8ad685..3a9ecd4fc 100644 --- a/plugin_api/v0/types.nim +++ b/plugin_api/v0/types.nim @@ -116,7 +116,7 @@ proc text*(self: Rope): WitString {.nodestroy.} = proc typesSliceImported(a0: int32; a1: int64; a2: int64; a3: bool): int32 {. wasmimport("[method]rope.slice", "nev:plugins/types").} proc slice*(self: Rope; a: int64; b: int64; inclusive: bool): Rope {.nodestroy.} = - ## Returns a slice of the rope from 'a' to 'b'. 'a' and 'b' are byte indices, 'b' is exclusive. + ## Returns a slice of the rope from 'a' to 'b'. 'a' and 'b' are byte indices, 'b' is inclusive if `inclusive` is true. ## This operation is cheap because it doesn't create a copy of the text. var arg0: int32 @@ -135,6 +135,8 @@ proc typesSliceSelectionImported(a0: int32; a1: int32; a2: int32; a3: int32; wasmimport("[method]rope.slice-selection", "nev:plugins/types").} proc sliceSelection*(self: Rope; s: Selection; inclusive: bool): Rope {. nodestroy.} = + ## Returns a slice of the rope from the given selection. 's.last' is inclusive if `inclusive` is true. + ## This operation is cheap because it doesn't create a copy of the text. var arg0: int32 arg1: int32 @@ -154,6 +156,7 @@ proc sliceSelection*(self: Rope; s: Selection; inclusive: bool): Rope {. proc typesFindImported(a0: int32; a1: int32; a2: int32; a3: int64; a4: int32): void {. wasmimport("[method]rope.find", "nev:plugins/types").} proc find*(self: Rope; sub: WitString; start: int64): Option[int64] {.nodestroy.} = + ## Find the byte index of the sub string 'sub', starting the search at 'start'. The returned index is relative to the start of the rope, not 'start'. var retArea: array[24, uint8] arg0: int32 @@ -196,6 +199,7 @@ proc slicePoints*(self: Rope; a: Cursor; b: Cursor): Rope {.nodestroy.} = proc typesLineLengthImported(a0: int32; a1: int64): int64 {. wasmimport("[method]rope.line-length", "nev:plugins/types").} proc lineLength*(self: Rope; line: int64): int64 {.nodestroy.} = + ## Return the length in bytes of the given line (0 based). var arg0: int32 arg1: int64 diff --git a/src/compilation_config.nim b/src/compilation_config.nim index cebd395f3..fc26a8747 100644 --- a/src/compilation_config.nim +++ b/src/compilation_config.nim @@ -7,6 +7,7 @@ const enableTableIdCacheChecking* {.booldefine.}: bool = false const enableSystemClipboard* {.booldefine.}: bool = true const enableAst* {.booldefine.}: bool = false const enableOldPluginVersions* {.booldefine.}: bool = false +const enableLibssh* {.booldefine.}: bool = false const copyWasmtimeDll* {.booldefine.}: bool = true const appName* {.strdefine.}: string = "nev" diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index 381a24e95..226c94d40 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -116,47 +116,47 @@ proc collectExports*(funcs: var ExportedFuncs; instance: InstanceT; funcs.mStackAlloc = instance.getExport(context, "mem_stack_alloc") funcs.mStackSave = instance.getExport(context, "mem_stack_save") funcs.mStackRestore = instance.getExport(context, "mem_stack_restore") - let f_8556381805 = instance.getExport(context, "init_plugin") - if f_8556381805.isSome: - assert f_8556381805.get.kind == WASMTIME_EXTERN_FUNC - funcs.initPlugin = f_8556381805.get.of_field.func_field + let f_9663678061 = instance.getExport(context, "init_plugin") + if f_9663678061.isSome: + assert f_9663678061.get.kind == WASMTIME_EXTERN_FUNC + funcs.initPlugin = f_9663678061.get.of_field.func_field else: echo "Failed to find exported function \'", "init_plugin", "\'" - let f_8556381821 = instance.getExport(context, "handle_command") - if f_8556381821.isSome: - assert f_8556381821.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleCommand = f_8556381821.get.of_field.func_field + let f_9663678077 = instance.getExport(context, "handle_command") + if f_9663678077.isSome: + assert f_9663678077.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleCommand = f_9663678077.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_command", "\'" - let f_8556381871 = instance.getExport(context, "handle_mode_changed") - if f_8556381871.isSome: - assert f_8556381871.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleModeChanged = f_8556381871.get.of_field.func_field + let f_9663678127 = instance.getExport(context, "handle_mode_changed") + if f_9663678127.isSome: + assert f_9663678127.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleModeChanged = f_9663678127.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_mode_changed", "\'" - let f_8556381872 = instance.getExport(context, "handle_view_render_callback") - if f_8556381872.isSome: - assert f_8556381872.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleViewRenderCallback = f_8556381872.get.of_field.func_field + let f_9663678128 = instance.getExport(context, "handle_view_render_callback") + if f_9663678128.isSome: + assert f_9663678128.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleViewRenderCallback = f_9663678128.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_view_render_callback", "\'" - let f_8556381896 = instance.getExport(context, "handle_channel_update") - if f_8556381896.isSome: - assert f_8556381896.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleChannelUpdate = f_8556381896.get.of_field.func_field + let f_9663678152 = instance.getExport(context, "handle_channel_update") + if f_9663678152.isSome: + assert f_9663678152.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleChannelUpdate = f_9663678152.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_channel_update", "\'" - let f_8556381897 = instance.getExport(context, "notify_task_complete") - if f_8556381897.isSome: - assert f_8556381897.get.kind == WASMTIME_EXTERN_FUNC - funcs.notifyTaskComplete = f_8556381897.get.of_field.func_field + let f_9663678153 = instance.getExport(context, "notify_task_complete") + if f_9663678153.isSome: + assert f_9663678153.get.kind == WASMTIME_EXTERN_FUNC + funcs.notifyTaskComplete = f_9663678153.get.of_field.func_field else: echo "Failed to find exported function \'", "notify_task_complete", "\'" - let f_8556381898 = instance.getExport(context, "handle_move") - if f_8556381898.isSome: - assert f_8556381898.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleMove = f_8556381898.get.of_field.func_field + let f_9663678154 = instance.getExport(context, "handle_move") + if f_9663678154.isSome: + assert f_9663678154.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleMove = f_9663678154.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_move", "\'" @@ -612,6 +612,9 @@ proc channelWriteChannelMount(instance: ptr InstanceData; path: sink string; unique: bool): string proc channelNewInMemoryChannel(instance: ptr InstanceData): ( ReadChannelResource, WriteChannelResource) +proc channelCreateTerminal(instance: ptr InstanceData; + stdin: sink WriteChannelResource; + stdout: sink ReadChannelResource; group: sink string): void proc processProcessStart(instance: ptr InstanceData; name: sink string; args: sink seq[string]): ProcessResource proc processStderr(instance: ptr InstanceData; self: var ProcessResource): ReadChannelResource @@ -3822,6 +3825,48 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = store, res[1]) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], + []) + linker.defineFuncUnchecked("nev:plugins/channel", "create-terminal", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var stdin: WriteChannelResource + var stdout: ReadChannelResource + var group: string + block: + let resPtr = ?instance.resources.resourceHostData( + parameters[0].i32, WriteChannelResource) + copyMem(stdin.addr, resPtr, sizeof(typeof(stdin))) + ?instance.resources.resourceDrop(parameters[0].i32, + callDestroy = false) + block: + let resPtr = ?instance.resources.resourceHostData( + parameters[1].i32, ReadChannelResource) + copyMem(stdout.addr, resPtr, sizeof(typeof(stdout))) + ?instance.resources.resourceDrop(parameters[1].i32, + callDestroy = false) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[2].i32].addr) + group = newString(parameters[3].i32) + for i0 in 0 ..< group.len: + group[i0] = p0[i0] + channelCreateTerminal(instance, stdin, stdout, group) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype( diff --git a/src/layout.nim b/src/layout.nim index f95d59eec..0eff592e8 100644 --- a/src/layout.nim +++ b/src/layout.nim @@ -547,6 +547,37 @@ proc popPopup*(self: LayoutService, popup: Popup = nil) = proc pushSelectorPopup*(self: LayoutService, builder: SelectorPopupBuilder): ISelectorPopup = self.pushSelectorPopupImpl(self, builder) +proc promptString*(self: LayoutService, title: string = ""): Future[Option[string]] = + defer: + self.platform.requestRender() + + var fut = newFuture[Option[string]]("App.prompt") + + var builder = SelectorPopupBuilder() + builder.scope = "prompt".some + builder.title = title + builder.scaleX = 0.5 + builder.scaleY = 0.5 + + var res = newSeq[FinderItem]() + let finder = newFinder(newStaticDataSource(res), filterAndSort=true) + builder.finder = finder.some + + builder.handleCanceled = proc(popup: ISelectorPopup) = + fut.complete(string.none) + + builder.customActions["accept"] = proc(popup: ISelectorPopup, args: JsonNode): bool {.gcsafe, raises: [].} = + fut.complete(popup.getSearchString().some) + popup.pop() + true + + builder.handleItemConfirmed = proc(popup: ISelectorPopup, item: FinderItem): bool = + fut.complete(item.displayName.some) + return true + + discard self.pushSelectorPopup(builder) + return fut + iterator visibleEditors*(self: LayoutService): DocumentEditor = ## Returns a list of all editors which are currently shown for view in self.layout.visibleLeafViews(): diff --git a/src/libvterm/state.c b/src/libvterm/state.c index 5036bc20f..68dff11d4 100644 --- a/src/libvterm/state.c +++ b/src/libvterm/state.c @@ -1261,7 +1261,7 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha val = CSI_ARG_OR(args[0], 0); if(val == 0) // DEC VT100 response - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c"); + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?63;1;2;4;22;29c"); // Include some flags for graphics support break; case LEADER('>', 0x63): // DEC secondary Device Attributes @@ -1547,10 +1547,6 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha break; - case 't': - DEBUG_LOG("csi t (leader '%s', intermed '%s', command '%c', args %d)\n", leader, intermed, command, argcount); - break; - default: if(state->fallbacks && state->fallbacks->csi) if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) diff --git a/src/misc/async_process.nim b/src/misc/async_process.nim index 6dee559b5..def57383d 100644 --- a/src/misc/async_process.nim +++ b/src/misc/async_process.nim @@ -42,6 +42,7 @@ type AsyncProcess* = ref object writerFlowVar: FlowVarBase killOnExit: bool = false eval: bool = false + errToOut: bool = false proc isAlive*(process: AsyncProcess): bool = return process.process.isNotNil and process.process.running @@ -421,6 +422,8 @@ proc start*(process: AsyncProcess): bool = var options: set[ProcessOption] = {poUsePath, poDaemon} if process.eval: options.incl poEvalCommand + if process.errToOut: + options.incl poStdErrToStdOut process.process = startProcess(process.name, args=process.args, options=options) except CatchableError as e: log(lvlError, fmt"Failed to start {process.name}: {e.msg}") @@ -469,7 +472,7 @@ proc restartServer(process: AsyncProcess) {.async, gcsafe.} = if not process.onRestarted.isNil: process.onRestarted().await -proc startAsyncProcess*(name: string, args: seq[string] = @[], autoRestart = true, autoStart = true, killOnExit = false, eval: bool = false): AsyncProcess {.gcsafe.} = +proc startAsyncProcess*(name: string, args: seq[string] = @[], autoRestart = true, autoStart = true, killOnExit = false, eval: bool = false, errToOut: bool = false): AsyncProcess {.gcsafe.} = let process = AsyncProcess() process.name = name process.args = @args @@ -479,6 +482,7 @@ proc startAsyncProcess*(name: string, args: seq[string] = @[], autoRestart = tru process.output = newAsyncChannel[Option[string]]() process.killOnExit = killOnExit process.eval = eval + process.errToOut = errToOut process.inputStreamChannel = Arc[Channel[Option[ProcessObj]]].new() process.inputStreamChannel.getMutUnsafe.open() @@ -684,9 +688,11 @@ proc listen*(self: Arc[ProcessOutputChannel], cb: ChannelListener): ListenId {.g asyncSpawn self.listenPoll() proc newProcessOutputChannel*(process: AsyncProcess): Arc[BaseChannel] = + let signal = ThreadSignalPtr.new() var res = Arc[ProcessOutputChannel].new() res.getMut() = ProcessOutputChannel( process: process, + signal: signal.value, destroyImpl: destroyChannelImpl(ProcessOutputChannel), closeImpl: (proc(self: ptr BaseChannel) {.gcsafe, raises: [].} = close(cast[ptr ProcessOutputChannel](self))), isOpenImpl: proc(self: ptr BaseChannel): bool {.gcsafe, raises: [].} = isOpen(cast[ptr ProcessOutputChannel](self)), @@ -707,9 +713,11 @@ proc write*(self: ptr ProcessInputChannel, data: openArray[uint8]) {.raises: [IO self.process.sendSync(str.ensureMove) proc newProcessInputChannel*(process: AsyncProcess): Arc[BaseChannel] = + let signal = ThreadSignalPtr.new() var res = Arc[ProcessInputChannel].new() res.getMut() = ProcessInputChannel( process: process, + signal: signal.value, destroyImpl: destroyChannelImpl(ProcessInputChannel), closeImpl: (proc(self: ptr BaseChannel) {.gcsafe, raises: [].} = close(cast[ptr ProcessInputChannel](self))), isOpenImpl: proc(self: ptr BaseChannel): bool {.gcsafe, raises: [].} = isOpen(cast[ptr ProcessInputChannel](self)), diff --git a/src/misc/render_command.nim b/src/misc/render_command.nim index 10efd5ac8..46cc3ab8a 100644 --- a/src/misc/render_command.nim +++ b/src/misc/render_command.nim @@ -50,11 +50,14 @@ type RenderCommandKind* {.pure.} = enum Rect FilledRect + Image Text TextRaw ScissorStart ScissorEnd + TextureId* = distinct uint64 + RenderCommand* = object bounds*: Rect # 16 color*: Color # 16 @@ -68,6 +71,8 @@ type of RenderCommandKind.TextRaw: data*: ptr UncheckedArray[char] len*: int + of RenderCommandKind.Image: + textureId*: TextureId else: discard @@ -107,6 +112,12 @@ proc write*(self: var BinaryEncoder, flags: UINodeFlags) = proc read*(self: var BinaryDecoder, _: typedesc[UINodeFlags]): UINodeFlags = return self.readLEB128(uint64).UINodeFlags +proc write*(self: var BinaryEncoder, textureId: TextureId) = + self.writeLEB128(uint64, textureId.uint64) + +proc read*(self: var BinaryDecoder, _: typedesc[TextureId]): TextureId = + return self.readLEB128(uint64).TextureId + proc write*(self: var BinaryEncoder, bounds: Rect) = self.write(bounds.x) self.write(bounds.y) @@ -136,6 +147,11 @@ proc write*(self: var BinaryEncoder, command: RenderCommand) = self.write(command.bounds) self.write(command.color) self.write(command.flags) + of RenderCommandKind.Image: + self.write(command.bounds) + self.write(command.color) + self.write(command.textureId) + self.write(command.flags) of RenderCommandKind.TextRaw: self.write(command.bounds) self.write(command.color) @@ -165,6 +181,12 @@ iterator decodeRenderCommands*(self: var BinaryDecoder): RenderCommand = let color = self.read(Color) let flags = self.read(UINodeFlags) yield RenderCommand(kind: RenderCommandKind.FilledRect, bounds: bounds, color: color, flags: flags) + of RenderCommandKind.Image: + let bounds = self.read(Rect) + let color = self.read(Color) + let textureId = self.read(TextureId) + let flags = self.read(UINodeFlags) + yield RenderCommand(kind: RenderCommandKind.Image, bounds: bounds, color: color, flags: flags, textureId: textureId) of RenderCommandKind.TextRaw: let bounds = self.read(Rect) let color = self.read(Color) @@ -255,6 +277,8 @@ template buildCommands*(renderCommands: var RenderCommands, body: untyped) = renderCommands.commands.add(RenderCommand(kind: RenderCommandKind.FilledRect, bounds: inBounds, color: inColor)) template fillRect(inBounds: Rect, inColor: Color, inFlags: UINodeFlags): untyped {.used.} = renderCommands.commands.add(RenderCommand(kind: RenderCommandKind.FilledRect, bounds: inBounds, color: inColor, flags: inFlags)) + template drawImage(inBounds: Rect, inTextureId: TextureId): untyped {.used.} = + renderCommands.commands.add(RenderCommand(kind: RenderCommandKind.Image, bounds: inBounds, color: color(1, 1, 1), textureId: inTextureId)) template drawText(inText: string, inBounds: Rect, inColor: Color, inFlags: UINodeFlags): untyped {.used.} = let txt = inText let offset = renderCommands.strings.len.uint32 diff --git a/src/platform/gui_platform.nim b/src/platform/gui_platform.nim index 1f4537c9d..c91d54a55 100644 --- a/src/platform/gui_platform.nim +++ b/src/platform/gui_platform.nim @@ -42,6 +42,8 @@ type framebufferId: GLuint framebuffer: Texture + textures: Table[string, Texture] + typefaces: Table[string, Typeface] glyphCache: LruCache[(Rune, UINodeFlags), string] asciiGlyphCache: array[128, string] @@ -731,10 +733,21 @@ proc drawText(platform: GuiPlatform, renderCommands: var RenderCommands, text: s except GLerror, Exception: discard +method createTexture*(self: GuiPlatform, image: Image): TextureId {.gcsafe, raises: [].} = + try: + let tex = newTexture(image) + return tex.textureId.TextureId + except GLerror as e: + return 0.TextureId + proc handleRenderCommand(node: UINode, platform: GuiPlatform, command: RenderCommand, nodePos: Vec2, maskBounds: var seq[Rect]) {.inline, raises: [Exception].} = case command.kind of RenderCommandKind.Rect: platform.boxy.strokeRect(command.bounds + nodePos, command.color) + of RenderCommandKind.Image: + let textureId = command.textureId.GLuint + platform.boxy.drawUvRect(nodePos + command.bounds.xy, nodePos + command.bounds.xy + command.bounds.wh, vec2(0), vec2(1), command.color, textureId) + of RenderCommandKind.FilledRect: platform.boxy.drawRect(command.bounds + nodePos, command.color) of RenderCommandKind.TextRaw: diff --git a/src/platform/platform.nim b/src/platform/platform.nim index c6ab02c51..d8b64a94f 100644 --- a/src/platform/platform.nim +++ b/src/platform/platform.nim @@ -1,7 +1,7 @@ import vmath import ui/node import misc/[event, timer] -import input, vfs, app_options, scripting_api +import input, vfs, app_options, scripting_api, pixie export input, event @@ -30,7 +30,6 @@ type layoutOptions*: WLayoutOptions logNextFrameTime*: bool lastEventTime*: Timer - vfs*: VFS backend*: Backend @@ -56,6 +55,7 @@ method getStatisticsString*(self: Platform): string {.base, gcsafe, raises: [].} method layoutText*(self: Platform, text: string): seq[Rect] {.base, gcsafe, raises: [].} = discard method setVsync*(self: Platform, enabled: bool) {.base, gcsafe, raises: [].} = discard method moveToMonitor*(self: Platform, index: int) {.base, gcsafe, raises: [].} = discard +method createTexture*(self: Platform, image: Image): TextureId {.base, gcsafe, raises: [].} = discard func totalLineHeight*(self: Platform): float = self.lineHeight + self.lineDistance diff --git a/src/platform/terminal_platform.nim b/src/platform/terminal_platform.nim index 7b2bfaed9..8954c32ad 100644 --- a/src/platform/terminal_platform.nim +++ b/src/platform/terminal_platform.nim @@ -676,6 +676,8 @@ proc drawNode(builder: UINodeBuilder, platform: TerminalPlatform, node: UINode, platform.cursor.row = pos.y.int else: platform.fillRect(command.bounds + nodePos, command.color) + of RenderCommandKind.Image: + discard of RenderCommandKind.TextRaw: var text = newStringOfCap(command.len) text.setLen(command.len) @@ -704,6 +706,8 @@ proc drawNode(builder: UINodeBuilder, platform: TerminalPlatform, node: UINode, platform.cursor.row = pos.y.int else: platform.fillRect(command.bounds + nodePos, command.color) + of RenderCommandKind.Image: + discard of RenderCommandKind.TextRaw: var text = newStringOfCap(command.len) text.setLen(command.len) diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index 47a534df7..d48d62853 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -275,7 +275,8 @@ method createModule*(self: PluginApi, module: ptr ModuleT, plugin: Plugin): Wasm let options = sca.CreateTerminalOptions( group: plugin.manifest.id, ) - self.host.layout.registerView(self.host.terminals.createTerminalView(instanceData.get.stdin, instanceData.get.stdout, options)) + let view = self.host.terminals.createTerminalView(instanceData.get.stdin, instanceData.get.stdout, options) + self.host.layout.registerView(view) return instance @@ -1052,6 +1053,16 @@ proc renderRemoveMode(instance: ptr InstanceData; self: var RenderViewResource; ###################### Channel +proc channelCreateTerminal(instance: ptr InstanceData, stdin: sink WriteChannelResource, stdout: sink ReadChannelResource, group: sink string) = + if instance.host == nil: + return + let options = sca.CreateTerminalOptions( + group: group, + ) + let view = instance.host.terminals.createTerminalView(stdin.channel, stdout.channel, options) + instance.host.layout.registerView(view) + instance.host.layout.showView(view.id, "#build-run-terminal", true, true) + proc channelCanRead(instance: ptr InstanceData; self: var ReadChannelResource): bool = return self.channel.isOpen diff --git a/src/scripting_api.nim b/src/scripting_api.nim index b1747eaea..ab0839eab 100644 --- a/src/scripting_api.nim +++ b/src/scripting_api.nim @@ -261,15 +261,26 @@ proc getChangedSelection*(selection: Selection, text: string): Selection = return (selection.last, newLast).normalized +type SshOptions* = object + address*: Option[string] + port*: Option[int] + username*: string + publicKeyPath*: string + privateKeyPath*: string + type CreateTerminalOptions* = object + args*: seq[string] group*: string = "" autoRunCommand*: string = "" mode*: Option[string] closeOnTerminate*: bool = true slot*: string = "" focus*: bool = true + createPty*: bool = true + ssh*: Option[SshOptions] type RunInTerminalOptions* = object + args*: seq[string] group*: string = "" autoRunCommand*: string = "" mode*: Option[string] @@ -277,6 +288,8 @@ type RunInTerminalOptions* = object reuseExisting*: bool = true slot*: string = "" focus*: bool = true + createPty*: bool = true + ssh*: Option[SshOptions] when defined(wasm): # todo: this should use the types from the nimsumtree library so it doesn't go out of sync diff --git a/src/selector_popup.nim b/src/selector_popup.nim index 89d88e196..1f789446f 100644 --- a/src/selector_popup.nim +++ b/src/selector_popup.nim @@ -66,11 +66,13 @@ proc getSearchString*(self: SelectorPopup): string {.gcsafe, raises: [].} proc closed*(self: SelectorPopup): bool {.gcsafe, raises: [].} proc getSelectedItem*(self: SelectorPopup): Option[FinderItem] {.gcsafe, raises: [].} proc handleItemsUpdated*(self: SelectorPopup) {.gcsafe, raises: [].} +proc pop*(self: SelectorPopup) {.gcsafe, raises: [].} implTrait ISelectorPopup, SelectorPopup: getSearchString(string, SelectorPopup) closed(bool, SelectorPopup) getSelectedItem(Option[FinderItem], SelectorPopup) + pop(void, SelectorPopup) proc closed*(self: SelectorPopup): bool = return self.textEditor.isNil diff --git a/src/selector_popup_builder.nim b/src/selector_popup_builder.nim index adb14c6bd..575440308 100644 --- a/src/selector_popup_builder.nim +++ b/src/selector_popup_builder.nim @@ -6,10 +6,12 @@ traitRef ISelectorPopup: method getSearchString*(self: ISelectorPopup): string {.base, gcsafe, raises: [].} method closed*(self: ISelectorPopup): bool {.base, gcsafe, raises: [].} method getSelectedItem*(self: ISelectorPopup): Option[FinderItem] {.base, gcsafe, raises: [].} + method pop*(self: ISelectorPopup) {.base, gcsafe, raises: [].} type SelectorPopupBuilder* = object scope*: Option[string] + title*: string scaleX*: float = 0.5 scaleY*: float = 0.5 previewScale*: float = 0.5 diff --git a/src/selector_popup_builder_impl.nim b/src/selector_popup_builder_impl.nim index 25f08a57f..2fcc57f90 100644 --- a/src/selector_popup_builder_impl.nim +++ b/src/selector_popup_builder_impl.nim @@ -10,6 +10,7 @@ import finder/[finder, previewer] proc pushSelectorPopupImpl(self: LayoutService, builder: SelectorPopupBuilder): ISelectorPopup = var popup = newSelectorPopup(self.services, builder.scope, builder.finder, builder.previewer.toDisposableRef) + popup.title = builder.title popup.scale.x = builder.scaleX popup.scale.y = builder.scaleY popup.previewScale = builder.previewScale diff --git a/src/ssh.nim b/src/ssh.nim new file mode 100644 index 000000000..bd1006208 --- /dev/null +++ b/src/ssh.nim @@ -0,0 +1,204 @@ +import std/[strformat] +import libssh2 + +type + SSHClient* = ref object of RootObj + # socket*: AsyncSocket + session*: Session + + SCPClient* = object + session*: Session + # socket*: AsyncSocket + + SFTPClient* = object + session*: Session + # socket*: AsyncSocket + sftp_session*: Sftp + + SSHChannel* = object + impl*: libssh2.Channel + client*: SSHClient + + SSHException* = object of IOError + AuthenticationException* = object of SSHException + FileNotFoundException* = object of SSHException + +proc newSSHClient*(): SSHClient = + ## Creates a new SSH client and initializes the underlying libssh2 library. + ## Raises SSHException if initialization fails. + ## + ## Returns: + ## A new SSHClient instance ready for connections + # if init(0) != 0: + # raise newException(SSHException, "libssh2 initialization failed") + result = new SSHClient + +# proc disconnect*(ssh: SSHClient) = +# ## Cleanly disconnects the SSH session and frees resources. +# ## Should be called when the client is no longer needed. +# ## +# ## It's recommended to use this in a `finally` block or with defer: +# ## ```nim +# ## let client = newSSHClient() +# ## defer: client.disconnect() +# ## ``` +# if ssh.session != nil: +# ssh.session.close_session() +# ssh.session = nil + +# ssh.socket.close() +# libssh2.exit() + +# proc connect*(s: SSHClient, hostname: string, username: string, port = Port(22), password = "", privKey = "", pubKey = "", useAgent = false) {.async.} = +# ## Establishes an SSH connection to a remote host with the specified authentication method. +# ## +# ## Parameters: +# ## hostname: The remote host to connect to +# ## username: The username for authentication +# ## port: The SSH port (default: 22) +# ## password: Optional password for password auth or private key passphrase +# ## privKey: Path to private key file for public key authentication +# ## pubKey: Path to public key file (optional with private key) +# ## useAgent: Whether to attempt authentication using SSH agent +# ## +# ## Authentication Methods: +# ## * Password: Set `password` parameter +# ## * Public Key: Set `privKey` and optionally `pubKey` +# ## * SSH Agent: Set `useAgent` to true +# ## +# ## Raises: +# ## SSHException: On connection or handshake failure +# ## AuthenticationException: On authentication failure +# s.socket = newAsyncSocket() +# s.socket.setSockOpt(OptNoDelay, true, level = 6) +# await s.socket.connect(hostname, port) +# s.session = initSession() +# s.session.setBlocking(false) +# s.session.handshake(s.socket.getFd()) + +# if useAgent: +# let agent = initAgent(s.session) +# agent.connect() +# agent.listIdentities() + +# for identity in agent.identities: +# if agent.authenticate(identity, username): +# break +# agent.close_agent() +# else: +# if privKey.len != 0: +# discard s.session.authPublicKey(username, privKey, pubKey, password) + # else: + # discard s.session.authPassword(username, password) + +proc getLastError*(session: Session): (string, int) = + var + errmsg: cstring + errlen: cint + let errcode = session.session_last_error(addr errmsg, addr errlen, 0) + result = ($errmsg, errcode.int) + +proc getLastErrorMessage*(session: Session): string = + let (msg, code) = getLastError(session) + result = &"{msg} ({code})" + +proc initChannel*(ssh: SSHClient): SSHChannel = + ## Establish a generic session channel + result.client = ssh + while true: + result.impl = ssh.session.channel_open_session() + if result.impl == nil and ssh.session.session_last_errno() == LIBSSH2_ERROR_EAGAIN: + discard + # discard ssh.waitsocket() + else: + break + if result.impl == nil: + raise newException(SSHException, ssh.session.getLastErrorMessage()) + +proc setEnv*(channel: SSHChannel, name, value: string): bool {.inline.} = + ## Set an environment variable on the channel + return channel.impl.channel_set_env(name, value) != -1 + +proc exec*(channel: SSHChannel, command: string): bool = + var rc: cint + while true: + rc = channel.impl.channel_exec(command) + if rc != LIBSSH2_ERROR_EAGAIN: + break + return rc == 0 + +# proc read*(channel: SSHChannel): string = +# var +# buffer: array[0..1024, char] +# rc: cint +# stream = newStringStream() + +# while true: +# rc = channel.impl.channel_read(addr buffer, buffer.len) +# if rc > 0: +# stream.writeData(addr buffer, rc) +# elif rc == LIBSSH2_ERROR_EAGAIN: +# # discard waitsocket(channel.client) +# discard +# else: +# break +# stream.setPosition(0) +# result = stream.readAll() + +# proc readError*(channel: SSHChannel): string = +# var +# buffer: array[0..1024, char] +# rc: cint +# stream = newStringStream() + +# while true: +# rc = channel.impl.channel_read_stderr(addr buffer, buffer.len) +# if rc > 0: +# stream.writeData(addr buffer, rc) +# elif rc == LIBSSH2_ERROR_EAGAIN: +# # discard waitsocket(channel.client) +# discard +# else: +# break +# stream.setPosition(0) +# result = stream.readAll() + +proc close*(channel: SSHChannel): bool = + var rc: cint + while true: + rc = channel.impl.channel_close() + if rc == LIBSSH2_ERROR_EAGAIN: + # discard waitsocket(channel.client) + discard + else: + break + return rc == 0 + +proc getExitStatus*(channel: SSHChannel): int {.inline.} = + channel.impl.channel_get_exit_status() + +#proc getExitSignal*(channel: SSHChannel): int {.inline.} = +# channel.impl.channel_get_exit_signal() + +proc free*(channel: SSHChannel) = + discard channel.impl.channel_free() + +template sshWait*(msg: string, body: untyped): untyped = + var rc: int + while true: + rc = body + if rc != LIBSSH2_ERROR_EAGAIN: + break + if rc != 0: + echo "Failed to ", msg, ": ", rc + +template sshAsyncWait*(msg: string, body: untyped): untyped = + var rc: int + while true: + rc = body + if rc != LIBSSH2_ERROR_EAGAIN: + break + catch sleepAsync(10.milliseconds).await: + discard + if rc != 0: + echo "Failed to ", msg, ": ", rc diff --git a/src/terminal_service.nim b/src/terminal_service.nim index c9a9dbdf6..cd7c7014d 100644 --- a/src/terminal_service.nim +++ b/src/terminal_service.nim @@ -1,14 +1,18 @@ -import std/[os, streams, strutils, sequtils, strformat, typedthreads, tables, json, colors] +import std/[os, streams, strutils, sequtils, strformat, typedthreads, tables, json, colors, hashes] import vmath import chroma import nimsumtree/[rope, arc] -import misc/[custom_logger, util, custom_unicode, custom_async, event, timer, disposable_ref, myjsonutils] +import misc/[custom_logger, util, custom_unicode, custom_async, event, timer, disposable_ref, myjsonutils, render_command, embed_source, async_process] import dispatch_tables, config_provider, events, view, layout, service, platform_service, selector_popup, vfs_service, vfs, theme import scripting/expose import platform/[tui, platform] import finder/[finder, previewer] import vterm, input, input_api, register, command_service, channel import scripting_api as api except DocumentEditor, TextDocumentEditor, AstDocumentEditor, ModelDocumentEditor, Popup, SelectorPopup +import compilation_config + +when defined(enableLibssh): + import libssh2, ssh from scripting_api import RunInTerminalOptions, CreateTerminalOptions @@ -174,12 +178,22 @@ type of InputEventKind.EnableLog: enableLog: bool + Sixel* = object + colors*: seq[chroma.Color] + width*: int + height*: int + px*: int + py*: int + pos*: tuple[line, column: int] + contentHash*: Hash + CursorShape* {.pure.} = enum Block, Underline, BarLeft OutputEventKind {.pure.} = enum TerminalBuffer, Size, Cursor, CursorVisible, CursorShape, CursorBlink, Terminated, Rope, Scroll, Log OutputEvent = object case kind: OutputEventKind of OutputEventKind.TerminalBuffer: buffer: TerminalBuffer + sixels: Table[(int, int), Sixel] of OutputEventKind.Size: width: int height: int @@ -220,11 +234,28 @@ type inputWriteEventFd: cint childPid: Pid + SixelState = object + active: bool + colors: seq[chroma.Color] + palette: array[256, chroma.Color] = default(array[256, chroma.Color]) + i: int = 0 + x: int = 0 + y: int = 0 + width: int = 0 + height: int = 0 + sizeFixed: bool = false + px: int = 0 + py: int = 0 + currentColor: int = 1 + iter: iterator(s: ptr ThreadState): int {.gcsafe, raises: [].} + frag: VTermStringFragment + pos*: tuple[line, column: int] + ThreadState = object vtermInternal: pointer screenInternal: pointer - inputChannel: ptr Channel[InputEvent] - outputChannel: ptr Channel[OutputEvent] + inputChannel: ptr system.Channel[InputEvent] + outputChannel: ptr system.Channel[OutputEvent] useChannels: bool readChannel: Arc[BaseChannel] writeChannel: Arc[BaseChannel] @@ -238,6 +269,8 @@ type processTerminated: bool = false autoRunCommand: string enableLog: bool = false + sixel: SixelState + sixels: Table[(int, int), Sixel] handles: OsHandles when defined(windows): @@ -249,13 +282,15 @@ type group*: string command*: string thread: Thread[ThreadState] - inputChannel: ptr Channel[InputEvent] - outputChannel: ptr Channel[OutputEvent] + inputChannel: ptr system.Channel[InputEvent] + outputChannel: ptr system.Channel[OutputEvent] terminalBuffer*: TerminalBuffer + sixels*: Table[Hash, Sixel] cursor*: tuple[row, col: int, visible: bool, shape: CursorShape, blink: bool] = (0, 0, true, CursorShape.Block, true) scrollY: int exitCode*: Option[int] autoRunCommand: string + createPty: bool = false threadTerminated: bool = false # Events @@ -267,6 +302,11 @@ type useChannels: bool handles: OsHandles + ssh: Option[SshOptions] + when defined(enableLibssh): + sshChannel: SSHChannel + sshClient: SSHClient + TerminalView* = ref object of View terminals*: TerminalService eventHandlers: Table[string, EventHandler] @@ -277,6 +317,7 @@ type closeOnTerminate: bool slot: string open: bool = true + sixelTextures*: Table[Hash, TextureId] TerminalService* = ref object of Service events: EventHandlerService @@ -430,12 +471,23 @@ proc handleOutputChannel(self: TerminalService, terminal: Terminal) {.async.} = while not terminal.threadTerminated: var updated = false while terminal.outputChannel[].peek() > 0: - let event = terminal.outputChannel[].recv() + var event = terminal.outputChannel[].recv() case event.kind of OutputEventKind.TerminalBuffer: - terminal.terminalBuffer = event.buffer + # Swap to avoid expensive copies, and because ensureMove doesn't work for fields + swap terminal.terminalBuffer, event.buffer + terminal.sixels.clear() + for s in event.sixels.mvalues: + let h = s.contentHash + terminal.sixels[h] = Sixel() + swap s, terminal.sixels[h] of OutputEventKind.Size: - discard + when defined(enableLibssh): + if terminal.ssh.isSome: + sshAsyncWait "update terminal size": + echo &"update ssh terminal size {event.width}x{event.height}" + # todo: pixel size + terminal.sshChannel.impl.channel_request_pty_size_ex(event.width, event.height, event.width * 10, event.height * 20) of OutputEventKind.Cursor: terminal.cursor.row = event.row terminal.cursor.col = event.col @@ -470,6 +522,7 @@ proc handleOutputChannel(self: TerminalService, terminal: Terminal) {.async.} = proc handleOutput(s: cstring; len: csize_t; user: pointer) {.cdecl.} = if len > 0: let state = cast[ptr ThreadState](user) + # echo "send input ", s.toOpenArray(0, len.int - 1) if state.useChannels: discard try: @@ -533,12 +586,16 @@ proc handleInputEvents(state: var ThreadState) = state.vterm.setSize(state.height.cint, state.width.cint) state.screen.flushDamage() + echo &"resize {state.width}x{state.height}" if not state.useChannels: when defined(windows): ResizePseudoConsole(state.handles.hpcon, wincon.COORD(X: state.width.SHORT, Y: state.height.SHORT)) else: var winp: IOctl_WinSize = IOctl_WinSize(ws_row: state.height.cushort, ws_col: state.width.cushort, ws_xpixel: 500.cushort, ws_ypixel: 500.cushort) discard termios.ioctl(state.handles.masterFd, TIOCSWINSZ, winp.addr) + else: + let msg = "" + handleOutput(msg.cstring, msg.len.csize_t, state.addr) state.dirty = true @@ -563,13 +620,13 @@ proc handleInputEvents(state: var ThreadState) = proc handleProcessOutput(state: var ThreadState, buffer: var string) {.raises: [OSError].} = if state.useChannels: - discard try: buffer.setLen(state.readChannel.flushRead()) if buffer.len > 0: let read = state.readChannel.read(cast[ptr UncheckedArray[uint8]](buffer[0].addr).toOpenArray(0, buffer.high)) buffer.setLen(read) + # echo buffer.toOpenArray(0, read.int - 1) let written = state.vterm.writeInput(buffer.cstring, read.csize_t).int if written != read: echo "fix me: vterm.nim.terminalThread vterm.writeInput" @@ -582,6 +639,7 @@ proc handleProcessOutput(state: var ThreadState, buffer: var string) {.raises: [ else: when defined(windows): template handleData(data: untyped, bytesToWrite: int): untyped = + # echo data.toOpenArray(0, bytesToWrite - 1) let written = state.vterm.writeInput(data, bytesToWrite.csize_t).int if written != bytesToWrite: echo "fix me: vterm.nim.terminalThread vterm.writeInput" @@ -634,6 +692,7 @@ proc handleProcessOutput(state: var ThreadState, buffer: var string) {.raises: [ buffer.setLen(bufferSize) let n = read(state.handles.masterFd, buffer[0].addr, buffer.len) if n > 0: + # echo buffer.toOpenArray(0, n.int - 1) let written = state.vterm.writeInput(buffer.cstring, n.csize_t).int if written != n: echo "fix me: vterm.nim.terminalThread vterm.writeInput" @@ -652,6 +711,215 @@ proc log*(state: ptr ThreadState, str: string) = if state.enableLog: state[].outputChannel[].send OutputEvent(kind: OutputEventKind.Log, level: lvlDebug, msg: str) +iterator chars(state: ptr ThreadState): char {.closure, gcsafe, raises: [].} = + while true: + while state.sixel.i < state.sixel.frag.len.int: + yield state.sixel.frag.str[state.sixel.i] + inc state.sixel.i + +proc resizeSixel(s: ptr ThreadState, w: int, h: int) = + if s.sixel.sizeFixed: + return + + assert w >= s.sixel.width and h >= s.sixel.height + assert w > s.sixel.width or h > s.sixel.height + + if w == s.sixel.width: + # echo "quick resize, only s.sixel.height changed" + s.sixel.colors.setLen(w * h) + else: + # echo "slow resize, s.sixel.width changed" + var colorsNew = newSeq[chroma.Color](w * h) + for y in 0.. 0: + # echo s.sixel.frag + # echo "=============" + + template resetState() = + s.sixel.x = 0 + s.sixel.y = 0 + s.sixel.px = 0 + s.sixel.py = 0 + s.sixel.width = 0 + s.sixel.height = 0 + s.sixel.sizeFixed = false + s.sixel.currentColor = 1 + s.sixel.colors = newSeq[chroma.Color](0) + s.sixel.pos = (s.cursor.row, s.cursor.col) + for i in 0..s.sixel.palette.high: + s.sixel.palette[i] = color(0, 0, 0, 1) + s.sixel.palette[1] = color(1, 1, 1, 1) + + resetState() + s.sixel.active = true + + template waitForData(): untyped = + if s.sixel.i >= s.sixel.frag.len.int and s.sixel.frag.final: + break + while s.sixel.i >= s.sixel.frag.len.int: + # echo &"wait for more data {s.sixel.i} at {s.sixel.x}, {s.sixel.y}, {currentSourceLocation(-2)}" + yield 5 + # echo "=============" + # if s.sixel.frag.len.int > 0: + # echo s.sixel.frag + # echo "=============" + + try: + var buffer = newString(0) + + s.sixel.i = 0 + while s.sixel.i < s.sixel.frag.len.int: + template parseInt(): int = + waitForData() + while s.sixel.i < s.sixel.frag.len.int and s.sixel.frag.str[s.sixel.i] in {'0'..'9'}: + buffer.add s.sixel.frag.str[s.sixel.i] + inc s.sixel.i + waitForData() + let num = if buffer.len == 0: + 0 + else: + buffer.parseInt() + buffer.setLen(0) + num + + let c = s.sixel.frag.str[s.sixel.i] + case c + of '"': + inc s.sixel.i + s.sixel.px = parseInt() + inc s.sixel.i + s.sixel.py = parseInt() + inc s.sixel.i + s.sixel.width = parseInt() + inc s.sixel.i + s.sixel.height = parseInt() + s.sixel.colors.setLen(s.sixel.width * s.sixel.height) + s.sixel.sizeFixed = true + + of '#': + inc s.sixel.i + s.sixel.currentColor = parseInt() + if s.sixel.i < s.sixel.frag.len.int and s.sixel.frag.str[s.sixel.i] == ';': + inc s.sixel.i + let format = parseInt() + inc s.sixel.i + let x = parseInt() + inc s.sixel.i + let y = parseInt() + inc s.sixel.i + let z = parseInt() + # echo &"# {s.sixel.currentColor}, {format}, ({x} {y} {z})" + + if format == 1: + # todo: HLS + discard + elif format == 2: + # RGB + s.sixel.palette[s.sixel.currentColor].r = x / 100 + s.sixel.palette[s.sixel.currentColor].g = y / 100 + s.sixel.palette[s.sixel.currentColor].b = z / 100 + else: + echo "Invalid format ", format + + of '$': + s.sixel.x = 0 + inc s.sixel.i + + of '-': + s.sixel.x = 0 + s.sixel.y += 6 + if s.sixel.y >= s.sixel.height: + s.resizeSixel(s.sixel.width, s.sixel.y + 1) + inc s.sixel.i + + if s.sixel.y >= s.sixel.height: + break + + of '!': + inc s.sixel.i + let num = parseInt() + waitForData() + if s.sixel.i < s.sixel.frag.len.int: + let ch = s.sixel.frag.str[s.sixel.i] + + let code = ch.int - 63 + if code in 0..63: + if s.sixel.x + num > s.sixel.width: + s.resizeSixel(s.sixel.x + num, s.sixel.height) + for k in 0..= s.sixel.width: + s.resizeSixel(s.sixel.x + 1, s.sixel.height) + if s.sixel.x < s.sixel.width: + for bit in 0..5: + if (code and (1 shl bit)) != 0: + let yy = s.sixel.y + bit + if yy < s.sixel.height: + s.sixel.colors[s.sixel.x + yy * s.sixel.width] = s.sixel.palette[s.sixel.currentColor] + inc s.sixel.x + else: + echo "invalid char '", c, "' ", code + + inc s.sixel.i + + waitForData() + + s.sixels[s.sixel.pos] = Sixel( + colors: s.sixel.colors, + width: s.sixel.width, + height: s.sixel.height, + px: s.sixel.px, + py: s.sixel.py, + pos: s.sixel.pos, + contentHash: s.sixel.colors.hash(), + ) + echo &"sixel {s.sixel.width}x{s.sixel.height}, {s.sixel.px}:{s.sixel.py}, {s.cursor.row}:{s.cursor.col}, {s.sixel.colors.hash}" + + except CatchableError as e: + echo &"Failed to parse sixel data at {s.sixel.i}: {e.msg}" + + s.sixel.active = false + +proc handleSixelData(s: ptr ThreadState, frag: VTermStringFragment) = + if s.sixel.iter == nil or not s.sixel.active: + s.sixel.iter = handleSixel + s.sixel.i = 0 + s.sixel.frag = frag + discard s.sixel.iter(s) + if finished(s.sixel.iter): + s.sixel.iter = nil + proc terminalThread(s: ThreadState) {.thread, nimcall.} = var state = s @@ -660,7 +928,23 @@ proc terminalThread(s: ThreadState) {.thread, nimcall.} = state.outputChannel[].send OutputEvent(kind: OutputEventKind.Log, level: lvlDebug, msg: str) var callbacks = VTermScreenCallbacks( - # damage: (proc(rect: VTermRect; user: pointer): cint {.cdecl.} = discard), + damage: (proc(rect: VTermRect; user: pointer): cint {.cdecl.} = + # echo &"damage: {rect}" + let state = cast[ptr ThreadState](user) + var keysToRemove = newSeq[(int, int)]() + for key in state.sixels.keys: + if key[0] in rect.start_row..rect.end_row and key[1] in rect.start_col..rect.end_col: + keysToRemove.add key + # for key in keysToRemove: + # state.sixels.del key + if keysToRemove.len > 0: + # echo &"remove sixels {keysToRemove}" + state.outputChannel[].send OutputEvent( + kind: OutputEventKind.TerminalBuffer, + buffer: state[].createTerminalBuffer(), + sixels: state.sixels, + ) + ), # moverect: (proc(dest: VTermRect; src: VTermRect; user: pointer): cint {.cdecl.} = discard), movecursor: (proc(pos: VTermPos; oldpos: VTermPos; visible: cint; user: pointer): cint {.cdecl.} = let state = cast[ptr ThreadState](user) @@ -695,6 +979,8 @@ proc terminalThread(s: ThreadState) {.thread, nimcall.} = # bell: (proc(user: pointer): cint {.cdecl.} = discard), resize: (proc(rows: cint; cols: cint; user: pointer): cint {.cdecl.} = let state = cast[ptr ThreadState](user) + echo &"resize: clear sixels" + # state.sixels.clear() state.outputChannel[].send OutputEvent(kind: OutputEventKind.Size, width: cols.int, height: rows.int) ), sb_pushline: (proc(cols: cint; cells: ptr UncheckedArray[VTermScreenCell]; user: pointer): cint {.cdecl.} = @@ -716,6 +1002,8 @@ proc terminalThread(s: ThreadState) {.thread, nimcall.} = ), sb_clear: (proc(user: pointer): cint {.cdecl.} = let state = cast[ptr ThreadState](user) + echo &"sb_clear: clear sixels" + state.sixels.clear() state[].scrollbackBuffer.setLen(0) state.scrollY = 0 state.outputChannel[].send OutputEvent(kind: OutputEventKind.Scroll, scrollY: state.scrollY) @@ -734,6 +1022,73 @@ proc terminalThread(s: ThreadState) {.thread, nimcall.} = ), ) + var parserCallbacks = VTermStateFallbacks( + control: (proc(control: char; user: pointer): cint {.cdecl.} = + # echo &"control '{control}'" + return 1 + ), + csi: (proc(leader: cstring; args: ptr clong; argcount: cint; intermed: cstring; command: char; user: pointer): cint {.cdecl.} = + let state = cast[ptr ThreadState](user) + let args = cast[ptr UncheckedArray[clong]](args) + echo &"csi '{intermed}', '{command}', {args.toOpenArray(0, argcount.int - 1)}" + case command + of 't': + if argcount > 0: + echo "send size" + case args[0] + of 14: + let width = state.width * 10 # todo: pixel size + let height = state.height * 10 # todo: pixel size + let msg = &"\x1b[4;{height};{width}t" + echo msg.toOpenArray + handleOutput(msg.cstring, msg.len.csize_t, user) + of 15: + let width = 10 # todo: pixel size + let height = 10 # todo: pixel size + let msg = &"\x1b[5;{height};{width}t" + echo msg.toOpenArray + handleOutput(msg.cstring, msg.len.csize_t, user) + of 16: + let width = state.width * 10 # todo: pixel size + let height = state.height * 10 # todo: pixel size + let msg = &"\x1b[6;{height};{width}t" + echo msg.toOpenArray + handleOutput(msg.cstring, msg.len.csize_t, user) + of 18: + let msg = &"\x1b[8;{state.height};{state.width}t" + echo msg.toOpenArray + handleOutput(msg.cstring, msg.len.csize_t, user) + else: + discard + else: + discard + return 1 + ), + osc: (proc(command: cint; frag: VTermStringFragment; user: pointer): cint {.cdecl.} = + return 1 + ), + dcs: (proc(command: cstring; commandlen: csize_t; frag: VTermStringFragment; user: pointer): cint {.cdecl.} = + let state = cast[ptr ThreadState](user) + let commandStr = newString(commandlen.int) + copyMem(commandStr[0].addr, command[0].addr, commandlen.int) + # echo &"dcs '{commandStr}', {frag}" + if commandlen > 0 and command[commandlen - 1] == 'q': + handleSixelData(state, frag) + return 1 + ), + apc: (proc(frag: VTermStringFragment; user: pointer): cint {.cdecl.} = + return 1 + ), + pm: (proc(frag: VTermStringFragment; user: pointer): cint {.cdecl.} = + return 1 + ), + sos: (proc(frag: VTermStringFragment; user: pointer): cint {.cdecl.} = + return 1 + ), + ) + + state.vterm.screen.setUnrecognisedFallbacks(parserCallbacks.addr, state.addr) + state.vterm.setOutputCallback(handleOutput, state.addr) state.vterm.screen.setCallbacks(callbacks.addr, state.addr) state.vterm.state.setSelectionCallbacks(selectionCallbacks.addr, state.addr, nil, 1024) @@ -746,15 +1101,28 @@ proc terminalThread(s: ThreadState) {.thread, nimcall.} = var buffer = "" var exitCode = int.none + proc handleInput(state: ptr ThreadState) {.async.} = + while not state.terminateRequested: + await state.handles.inputWriteEvent2.wait() + try: + state[].handleInputEvents() + except CatchableError as e: + echo "async error handle input events ", e.msg + discard + + if state.useChannels: + asyncSpawn handleInput(state.addr) + try: while true: if state.useChannels: try: - let waitInput = state.handles.inputWriteEvent2.wait() - let waitOutput = state.readChannel.get.signal.wait() - waitFor(waitInput or waitOutput) - state.handleProcessOutput(buffer) - state.handleInputEvents() + waitFor state.readChannel.get.signal.wait() + try: + state.handleProcessOutput(buffer) + except CatchableError as e: + echo "async error handle process output ", e.msg + discard except AsyncError, CancelledError: # echo "async error ", getCurrentExceptionMsg() discard @@ -830,7 +1198,9 @@ proc terminalThread(s: ThreadState) {.thread, nimcall.} = state.dirty = false state.outputChannel[].send OutputEvent( kind: OutputEventKind.TerminalBuffer, - buffer: state.createTerminalBuffer()) + buffer: state.createTerminalBuffer(), + sixels: state.sixels, + ) except OSError as e: log(&"terminal thread raised error: {e.msg}") @@ -858,10 +1228,11 @@ proc terminate*(self: Terminal) {.async.} = if not self.threadTerminated: self.sendEvent(InputEvent(kind: InputEventKind.Terminate)) - when defined(windows): - ClosePseudoConsole(self.handles.hpcon) - else: - discard close(self.handles.masterFd) + if not self.useChannels: + when defined(windows): + ClosePseudoConsole(self.handles.hpcon) + else: + discard close(self.handles.masterFd) while not self.threadTerminated: # todo: use async signals @@ -885,7 +1256,164 @@ proc terminate*(self: Terminal) {.async.} = self.outputChannel[].close() self.outputChannel.deallocShared() -proc createTerminal*(self: TerminalService, width: int, height: int, command: string, autoRunCommand: string = ""): Terminal {.raises: [OSError, IOError, ResourceExhaustedError].} = +proc createTerminal*(self: TerminalService, width: int, height: int, writeChannel: Arc[BaseChannel], readChannel: Arc[BaseChannel], autoRunCommand: string = ""): Terminal {.raises: [OSError, IOError, ResourceExhaustedError].} + +proc logErrors(process: AsyncProcess): Future[void] {.async.} = + while true: + let line = await process.recvErrorLine() + log lvlError, "[stderr] ", line + +when defined(enableLibssh): + when defined(windows): + from winlean import nil + import chronos/transports/stream + + proc authPublicKey*(session: Session; username, privKey: string, pubKey = "", passphrase = ""): bool {.raises: [IOError].} = + let privKey = expandTilde(privKey) + var pubKey = pubKey + if pubKey.len > 0: + pubKey = expandTilde(pubKey) + + while true: + let rc = session.userauth_publickey_from_file(username, pubKey.cstring, privKey.cstring, passphrase) + if rc == LIBSSH2_ERROR_EAGAIN: + discard + elif rc < 0: + raise newException(IOError, &"Authentication with privateKey {privKey} failed!") + else: + break + result = true + + proc createSshSession(self: TerminalService, terminal: Terminal, stdin: Arc[BaseChannel], stdout: Arc[BaseChannel], command: string, args: seq[string] = @[], options: SshOptions): Future[void] {.async: (raises: []).} = + try: + log lvlInfo, &"Create ssh session {options}" + discard libssh2.init(0) + let ipAddress = options.address.get("127.0.0.1") + let port = options.port.get(22) + let addressess = resolveTAddress(ipAddress & ":" & $port) + if addressess.len == 0: + raise newException(IOError, &"Failed to resolve address '{ipAddress}:{port}'") + + let address = addressess[0] + let transp = await connect(address, bufferSize = 1024 * 1024) + + # defer: + # libssh2.exit() + + let session = session_init() + # defer: + # discard session_free(session) + + let socket: winlean.SocketHandle = winlean.SocketHandle(transp.fd) + session_set_blocking(session, 0) + var rc: int + while true: + rc = session_handshake(session, socket) + if rc != LIBSSH2_ERROR_EAGAIN: + break + if rc != 0: + raise newException(IOError, "SSH session handshake failed: " & $rc) + + # Authenticate + let vfs = self.services.getService(VFSService).get.vfs + let privateKeyPath = vfs.localize(options.privateKeyPath) + let publicKeyPath = vfs.localize(options.publicKeyPath) + let passphrase = await self.layout.promptString("Passphrase for " & options.username) + if passphrase.isNone: + log lvlInfo, &"Cancel authentication for ssh session '{options.username}@{address}' ({privateKeyPath})" + return + + log lvlInfo, &"Authenticate ssh session '{options.username}@{address}' ({privateKeyPath})" + let authResult = session.authPublicKey(options.username, privateKeyPath, publicKeyPath, passphrase.get("")) + if not authResult: + raise newException(IOError, &"Failed to authenticate ssh session '{options.username}@{address}'") + log lvlInfo, &"Authentication successfull for ssh session '{options.username}@{address}' ({privateKeyPath})" + + let client = newSSHClient() + client.session = session + let sshChannel = client.initChannel() + + sshAsyncWait "request pty": + sshChannel.impl.channel_request_pty("xterm-256color") + sshAsyncWait "start shell": + sshChannel.impl.channel_shell() + + proc forwardSshChannelToStdOutChannel(sshChannel: SSHChannel, chan: Arc[BaseChannel]) {.async: (raises: []).} = + try: + var buffer = newString(1024) + while true: + let rc = sshChannel.impl.channel_read(buffer[0].addr, buffer.len) + if rc > 0: + chan.write(buffer.toOpenArrayByte(0, rc - 1)) + discard chan.getMutUnsafe.signal.fireSync() + elif rc == LIBSSH2_ERROR_EAGAIN: + catch sleepAsync(1.milliseconds).await: + discard + else: + break + except CatchableError as e: + echo "Failed to read output: ", e.msg + + echo "=================== forwardSshChannelToStdOutChannel end" + + asyncSpawn forwardSshChannelToStdOutChannel(sshChannel, stdout) + + discard stdin.listen(proc(channel: var BaseChannel, closed: bool): ChannelListenResponse {.gcsafe, raises: [].} = + try: + let num = channel.peek + if num > 0: + var buff = newString(num) + let read = channel.read(buff.toOpenArrayByte(0, buff.high)) + buff.setLen(read) + + var rc: int + while true: + rc = sshChannel.impl.channel_write(buff.cstring, buff.len) + if rc != LIBSSH2_ERROR_EAGAIN: + break + if rc < 0: + echo "Failed to write to ssh channel: ", rc + rc = sshChannel.impl.channel_flush() + except IOError as e: + echo "Failed to read stdin: ", e.msg + + discard + ) + + # Clean up + # libssh2.exit() + terminal.ssh = options.some + terminal.sshChannel = sshChannel + terminal.sshClient = client + except CatchableError as e: + log lvlError, &"Failed to create ssh connection: {e.msg}" + +proc createTerminal*(self: TerminalService, width: int, height: int, command: string, args: seq[string] = @[], autoRunCommand: string = "", createPty: bool = true, ssh: Option[SshOptions]): Terminal {.raises: [OSError, IOError, ResourceExhaustedError, TransportError, CancelledError].} = + if ssh.isSome: + var stdin = newInMemoryChannel() + var stdout = newInMemoryChannel() + result = self.createTerminal(width, height, stdin, stdout, autoRunCommand) + when defined(enableLibssh): + asyncSpawn self.createSshSession(result, stdin, stdout, command, args, ssh.get) + else: + log lvlError, &"Direct ssh terminals not supported in this build. Use 'ssh' as a shell instead." + return + + if not createPty: + try: + echo &"start process '{command}' {args}" + var process = startAsyncProcess(command, args, killOnExit = true, autoStart = false, errToOut = false) + asyncSpawn process.logErrors() + discard process.start() + let stdout = newProcessOutputChannel(process) + discard stdout.listen(proc(channel: var BaseChannel, closed: bool): ChannelListenResponse {.gcsafe, raises: [].} = + discard channel.signal.fireSync() + ) + let stdin = newProcessInputChannel(process) + return self.createTerminal(width, height, stdin, stdout, autoRunCommand) + except ValueError as e: + log lvlError, &"Failed to start process {command}: {e.msg}" + let id = self.idCounter inc self.idCounter @@ -924,11 +1452,15 @@ proc createTerminal*(self: TerminalService, width: int, height: int, command: st if CreatePseudoConsole(wincon.COORD(X: width.SHORT, Y: height.SHORT), inputReadHandle, outputWriteHandle, 0, addr(hPC)) != S_OK: raiseOSError(osLastError(), "Failed to create pseude console") - var siEx: STARTUPINFOEX = prepareStartupInformation(hPC) var pi: PROCESS_INFORMATION ZeroMemory(addr(pi), sizeof((pi))) let cmd = newWideCString(command) + var siEx: STARTUPINFOEX + ZeroMemory(addr(siEx), sizeof((siEx))) + siEx.StartupInfo.cb = sizeof((STARTUPINFOEX)).DWORD + siEx = prepareStartupInformation(hPC) + if CreateProcessW(nil, cmd, nil, nil, FALSE, EXTENDED_STARTUPINFO_PRESENT, nil, nil, siEx.StartupInfo.addr, pi.addr) == 0: raiseOSError(osLastError(), "Failed to start sub process") @@ -1018,10 +1550,11 @@ proc createTerminal*(self: TerminalService, width: int, height: int, command: st command: command, autoRunCommand: autoRunCommand, handles: handles, + createPty: createPty, ) - proc createChannel[T](channel: var ptr[Channel[T]]) = - channel = cast[ptr Channel[T]](allocShared0(sizeof(Channel[T]))) + proc createChannel[T](channel: var ptr[system.Channel[T]]) = + channel = cast[ptr system.Channel[T]](allocShared0(sizeof(system.Channel[T]))) channel[].open() result.inputChannel.createChannel() @@ -1070,8 +1603,8 @@ proc createTerminal*(self: TerminalService, width: int, height: int, writeChanne useChannels: true, ) - proc createChannel[T](channel: var ptr[Channel[T]]) = - channel = cast[ptr Channel[T]](allocShared0(sizeof(Channel[T]))) + proc createChannel[T](channel: var ptr[system.Channel[T]]) = + channel = cast[ptr system.Channel[T]](allocShared0(sizeof(system.Channel[T]))) channel[].open() result.inputChannel.createChannel() @@ -1160,7 +1693,7 @@ method desc*(self: TerminalView): string = &"TerminalView" method kind*(self: TerminalView): string = "terminal" method display*(self: TerminalView): string = &"term://{self.terminal.command} - {self.terminal.group}" method saveState*(self: TerminalView): JsonNode = - if self.terminal.useChannels: + if self.terminal.useChannels and not self.terminal.ssh.isSome: return nil result = newJObject() result["kind"] = self.kind.toJson @@ -1173,6 +1706,8 @@ method saveState*(self: TerminalView): JsonNode = closeOnTerminate: self.closeOnTerminate, slot: self.slot, focus: false, + createPty: self.terminal.createPty, + ssh: self.terminal.ssh, ).toJson proc setTheme(self: Terminal, theme: Theme) = @@ -1287,7 +1822,7 @@ proc setSize*(self: TerminalView, width: int, height: int) = proc createTerminalView(self: TerminalService, command: string, options: CreateTerminalOptions, id: Id = idNone()): TerminalView = try: - let term = self.createTerminal(80, 50, command, options.autoRunCommand) + let term = self.createTerminal(80, 50, command, options.args, options.autoRunCommand, options.createPty, options.ssh) term.group = options.group term.setTheme(self.themes.theme) @@ -1423,7 +1958,9 @@ proc createTerminal*(self: TerminalService, command: string = "", options: Creat ## `options.slot` Where to open the terminal view. Uses `default` slot if not specified. ## `options.focus` Whether to focus the terminal view. `true` by default. log lvlInfo, &"createTerminal '{command}', {options}" - self.layout.addView(self.createTerminalView(command, options), slot=options.slot, focus=options.focus) + let view = self.createTerminalView(command, options) + if view != nil: + self.layout.addView(view, slot=options.slot, focus=options.focus) proc isIdle(self: TerminalService, terminal: Terminal): bool = if not terminal.cursor.visible or terminal.cursor.col == 0: @@ -1476,7 +2013,12 @@ proc runInTerminal*(self: TerminalService, shell: string, command: string, optio closeOnTerminate: options.closeOnTerminate, slot: options.slot, focus: options.focus, + createPty: options.createPty, + ssh: options.ssh, + args: options.args, )) + if view == nil: + return if command.len > 0: self.handleInput(view, command) self.handleKey(view, INPUT_ENTER, {}) diff --git a/src/ui/widget_builder_terminal.nim b/src/ui/widget_builder_terminal.nim index cca634e37..f2d7a7c4a 100644 --- a/src/ui/widget_builder_terminal.nim +++ b/src/ui/widget_builder_terminal.nim @@ -6,6 +6,7 @@ import platform/[platform, tui] import ui/[widget_builders_base] import app, theme, view import config_provider, terminal_service, terminal_previewer, layout +import pixie from std/colors as colors import nil @@ -16,6 +17,13 @@ import ui/node logCategory "widget_builder_terminal" +proc toRgbaFast*(c: Color): ColorRGBA {.inline.} = + ## Convert Color to ColorRGBA + result.r = (c.r * 255 + 0.5).uint8 + result.g = (c.g * 255 + 0.5).uint8 + result.b = (c.b * 255 + 0.5).uint8 + result.a = (c.a * 255 + 0.5).uint8 + method createUI*(self: TerminalPreviewer, builder: UINodeBuilder, app: App): seq[OverlayFunction] = if self.view != nil: result.add self.view.createUI(builder, app) @@ -279,6 +287,24 @@ method createUI*(self: TerminalView, builder: UINodeBuilder, app: App): seq[Over # Flush last part of the line flush() + # echo &"draw {self.terminal.sixels.len} sixels" + for s in self.terminal.sixels.values: + let offset = vec2(s.pos.column.float * builder.charWidth, s.pos.line.float * builder.textHeight) + if s.contentHash notin self.sixelTextures: + try: + echo &"cache image {s.contentHash} {s.width}x{s.height}" + let image = newImage(s.width, s.height) + for y in 0.. tuple; + + /// Creates a new channel which buffers data in memory and returns the read and write end. + create-terminal: func(stdin: write-channel, stdout: read-channel, group: string); } /// Thread safe From f89f296c6f8a9d60e30b87d5994f393330377d05 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Tue, 9 Sep 2025 13:15:36 +0200 Subject: [PATCH 12/47] Update boxy, vert and frag shader syntax highlighting, clay UI in test plugin --- config/settings.json | 35 ++++++ nev.nimble | 2 +- nimble.lock | 4 +- plugins/test_plugin/manifest.json | 1 + plugins/test_plugin/test_plugin.nim | 180 ++++++++++++++++++++++++---- 5 files changed, 196 insertions(+), 26 deletions(-) diff --git a/config/settings.json b/config/settings.json index 2a325a9b7..16022e787 100644 --- a/config/settings.json +++ b/config/settings.json @@ -17,6 +17,7 @@ "+language-mappings": { "\\..(agda|lagda)$": "agda", "\\..(c|cc|h|inc)$": "c", + "\\..(vert|frag)$": "glsl", "\\..sh$": "bash", "\\..cs$": "c_sharp", "\\..(cpp|tcc|hpp)$": "cpp", @@ -380,6 +381,40 @@ ] } }, + "glsl": { + "+text": { + "treesitter.repository": "tree-sitter/tree-sitter-c", + "treesitter.language": "c", + "tab-width": 2, + "indent": "spaces", + "indent-after": [ + ":", + "=", + "(", + "{", + "[" + ], + "line-comment": "//", + "block-comment": [ + "/*", + "*/" + ], + "+search-regexes": { + "goto-definition": [ + "^\\s*(\\w+[\\*\\s]+)[[0]]\\s*\\([^()]*\\)\\s*(const)?\\s*(\\{)?$", + "^\\s*(typedef\\s+)?(struct|enum|union)\\s+[[0]]" + ], + "symbols": [ + "^\\s*(\\w+[\\*\\s]+)\\w+\\s*\\([^()]*\\)\\s*(const)?\\s*(\\{)?$", + "^\\s*(typedef\\s+)?(struct|enum|union)\\s+\\w+" + ], + "workspace-symbols-by-kind": { + "Function": "^\\s*(\\w+[\\*\\s]+)\\w+\\s*\\([^()]*\\)\\s*(const)?\\s*(\\{)?$", + "Class": "^\\s*(typedef\\s+)?(struct|enum|union)\\s+\\w+" + } + } + } + }, "c": { "+text": { "treesitter.repository": "tree-sitter/tree-sitter-c", diff --git a/nev.nimble b/nev.nimble index b356a0027..cf27899c5 100644 --- a/nev.nimble +++ b/nev.nimble @@ -36,7 +36,7 @@ requires "https://github.com/Nimaoth/ws >= 0.5.0" requires "https://github.com/Nimaoth/windy >= 0.0.5" requires "https://github.com/Nimaoth/wasm3 >= 0.1.17" requires "https://github.com/Nimaoth/lrucache.nim >= 1.1.4" -requires "https://github.com/Nimaoth/boxy >= 0.4.4" +requires "https://github.com/Nimaoth/boxy#bfc8665" requires "https://github.com/Nimaoth/nimtreesitter-api >= 0.1.21" requires "https://github.com/Nimaoth/nimwasmtime#415a252" requires "https://github.com/Nimaoth/nimsumtree#47f7208" diff --git a/nimble.lock b/nimble.lock index fb901be6a..b44d08573 100644 --- a/nimble.lock +++ b/nimble.lock @@ -160,7 +160,7 @@ }, "boxy": { "version": "0.4.4", - "vcsRevision": "371c40a0b7cf8eced67a31797b019570154c3906", + "vcsRevision": "bfc8665443fe9af264eaa80769a6699679f5f273", "url": "https://github.com/Nimaoth/boxy", "downloadMethod": "git", "dependencies": [ @@ -170,7 +170,7 @@ "bitty" ], "checksums": { - "sha1": "364d0f9d48141eb4bcee96bb0ff33372b476230f" + "sha1": "81becde73d830307dd6dc5a77967e9b1ebe8953a" } }, "c2nim": { diff --git a/plugins/test_plugin/manifest.json b/plugins/test_plugin/manifest.json index 988bb51e1..3712d1906 100644 --- a/plugins/test_plugin/manifest.json +++ b/plugins/test_plugin/manifest.json @@ -31,6 +31,7 @@ "test-in-memory-channel": {}, "test-background-thread": {}, "test-thread-pool": {}, + "test-sixel": {}, }, "permissions": { "filesystem": { diff --git a/plugins/test_plugin/test_plugin.nim b/plugins/test_plugin/test_plugin.nim index 3abee32e7..c993ccdd5 100644 --- a/plugins/test_plugin/test_plugin.nim +++ b/plugins/test_plugin/test_plugin.nim @@ -2,6 +2,7 @@ import std/[strformat, json, jsonutils, strutils] import results import util, render_command, binary_encoder import api +import clay var views: seq[RenderView] = @[] var renderCommandEncoder: BinaryEncoder @@ -17,13 +18,29 @@ proc fib(n: int64): int64 = return 1 return fib(n - 1) + fib(n - 2) +proc measureClayText(text: ClayStringSlice; config: ptr ClayTextElementConfig; userData: pointer): ClayDimensions {.cdecl.} = + return ClayDimensions(width: text.length.float * 10, height: 20) + +let totalMemorySize = clay.minMemorySize() +var memory = ClayArena(capacity: totalMemorySize, memory: cast[ptr UncheckedArray[uint8]](allocShared0(totalMemorySize))) +var clayErrorHandler = ClayErrorHandler( + errorHandlerFunction: proc (error: ClayErrorData) = + log lvlError, &"[clay] {error.errorType}: {error.errorText}" +) +var clayContext* = clay.initialize(memory, ClayDimensions(width: 1024, height: 768), clayErrorHandler) +clay.setMeasureTextFunction(measureClayText, nil) +clay.setDebugModeEnabled(true) + +proc toggleClayDebugMode() = + clay.setDebugModeEnabled(not clay.isDebugModeEnabled()) + proc openCustomView(show: bool) = var renderView = renderViewFromUserId(ws"test_plugin_view") if renderView.isNone: - echo "[guest] Create new RenderView" + log lvlInfo, "[guest] Create new RenderView" renderView = newRenderView().some else: - echo "[guest] Reusing existing RenderView" + log lvlInfo, "[guest] Reusing existing RenderView" renderView.get.setUserId(ws"test_plugin_view") renderView.get.setRenderWhenInactive(true) renderView.get.setPreventThrottling(true) @@ -34,10 +51,57 @@ proc openCustomView(show: bool) = show(renderView.get.view, ws"#new-tab", true, true) views.add(renderView.take) +converter toRect(c: ClayBoundingBox): bumpy.Rect = + rect(c.x, c.y, c.width, c.height) + +converter toColor(c: ClayColor): Color = + color(c.r / 255, c.g / 255, c.b / 255, c.a / 255) + +converter toClayVec(c: Vec2f): ClayVector2 = + ClayVector2(x: c.x, y: c.y) + +converter toClayVec(c: Vec2): ClayVector2 = + ClayVector2(x: c.x, y: c.y) + +converter toVec(c: Vec2f): Vec2 = + vec2(c.x, c.y) + +proc encodeClayRenderCommands(renderCommandEncoder: var BinaryEncoder, clayRenderCommands: ClayRenderCommandArray) = + buildCommands(renderCommandEncoder): + for c in clayRenderCommands: + case c.commandType + of None: + discard + of Rectangle: + let color = c.renderData.rectangle.backgroundColor.toColor + let bounds = c.boundingBox.toRect + fillRect(bounds, color) + of Border: + let color = c.renderData.border.color.toColor + let bounds = c.boundingBox.toRect + # let width = c.renderData.border.width + # todo: width > 1 + drawRect(bounds, color) + of Text: + let color = c.renderData.text.textColor.toColor + let bounds = c.boundingBox.toRect + drawText(c.renderData.text.stringContents.toOpenArray(), bounds, color, 0.UINodeFlags) + of Image: + log lvlError, &"Not implemented: {c.commandType}" + of ScissorStart: + startScissor(c.boundingBox.toRect) + of ScissorEnd: + endScissor() + of Custom: + log lvlError, &"Not implemented: {c.commandType}" + +var lastTime = 0.0 +var lastRenderTime = 0.0 +var lastRenderTimeStr = "" proc handleViewRender(id: int32, data: uint32) {.cdecl.} = let index = data.int if index notin 0..views.high: - echo "handleViewRender: index out of bounds {index} notin 0..<{views.len}" + log lvlError, "handleViewRender: index out of bounds {index} notin 0..<{views.len}" return let view {.cursor.} = views[index] @@ -49,31 +113,47 @@ proc handleViewRender(id: int32, data: uint32) {.cdecl.} = num = 1 num = target + let start = getTime() + let deltaTime = start / 1000 - lastTime + lastTime = start / 1000 proc vec2(v: Vec2f): Vec2 = vec2(v.x, v.y) let size = vec2(view.size) - # echo &"[guest] size: {size}" - let s = if size.x > 500: - vec2(20, 20) - else: - vec2(1, 1) - renderCommandEncoder.buffer.setLen(0) - buildCommands(renderCommandEncoder): - for y in 0.. Date: Tue, 9 Sep 2025 13:16:18 +0200 Subject: [PATCH 13/47] Progress on vim motions --- config/keybindings.json | 40 ++++++------- plugins/vim/vim.nim | 122 +++++++++++++++++++++------------------ src/text/text_editor.nim | 36 +++++++++++- 3 files changed, 121 insertions(+), 77 deletions(-) diff --git a/config/keybindings.json b/config/keybindings.json index 42c2073b1..183920bbb 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -693,26 +693,26 @@ "ip": ["vim.select-text-object", "vim.paragraph-inner", false, true, "<#text_object.count>"], "ap": ["vim.select-text-object", "vim.paragraph-outer", false, true, "<#text_object.count>"], - "i{": ["vim.select-surrounding", "vim.surround-{-inner", false, true, "<#text_object.count>"], - "a{": ["vim.select-surrounding", "vim.surround-{-outer", false, true, "<#text_object.count>"], - "i}": ["vim.select-surrounding", "vim.surround-{-inner", false, true, "<#text_object.count>"], - "a}": ["vim.select-surrounding", "vim.surround-{-outer", false, true, "<#text_object.count>"], - "i(": ["vim.select-surrounding", "vim.surround-(-inner", false, true, "<#text_object.count>"], - "a(": ["vim.select-surrounding", "vim.surround-(-outer", false, true, "<#text_object.count>"], - "i)": ["vim.select-surrounding", "vim.surround-(-inner", false, true, "<#text_object.count>"], - "a)": ["vim.select-surrounding", "vim.surround-(-outer", false, true, "<#text_object.count>"], - "i[": ["vim.select-surrounding", "vim.surround-[-inner", false, true, "<#text_object.count>"], - "a[": ["vim.select-surrounding", "vim.surround-[-outer", false, true, "<#text_object.count>"], - "i]": ["vim.select-surrounding", "vim.surround-[-inner", false, true, "<#text_object.count>"], - "a]": ["vim.select-surrounding", "vim.surround-[-outer", false, true, "<#text_object.count>"], - "i\\<": ["vim.select-surrounding", "vim.surround-angle-inner", false, true, "<#text_object.count>"], - "a\\<": ["vim.select-surrounding", "vim.surround-angle-outer", false, true, "<#text_object.count>"], - "i\\>": ["vim.select-surrounding", "vim.surround-angle-inner", false, true, "<#text_object.count>"], - "a\\>": ["vim.select-surrounding", "vim.surround-angle-outer", false, true, "<#text_object.count>"], - "i\"": ["vim.select-surrounding", "vim.surround-\"-inner", false, true, "<#text_object.count>"], - "a\"": ["vim.select-surrounding", "vim.surround-\"-outer", false, true, "<#text_object.count>"], - "i'": ["vim.select-surrounding", "vim.surround-'-inner", false, true, "<#text_object.count>"], - "a'": ["vim.select-surrounding", "vim.surround-'-outer", false, true, "<#text_object.count>"], + "i{": ["vim.select-surrounding", "surround \"{\" \"}\" true", false, true, "<#text_object.count>"], + "a{": ["vim.select-surrounding", "surround \"{\" \"}\" false", false, true, "<#text_object.count>"], + "i}": ["vim.select-surrounding", "surround \"{\" \"}\" true", false, true, "<#text_object.count>"], + "a}": ["vim.select-surrounding", "surround \"{\" \"}\" false", false, true, "<#text_object.count>"], + "i(": ["vim.select-surrounding", "surround \"(\" \")\" true", false, true, "<#text_object.count>"], + "a(": ["vim.select-surrounding", "surround \"(\" \")\" false", false, true, "<#text_object.count>"], + "i)": ["vim.select-surrounding", "surround \"(\" \")\" true", false, true, "<#text_object.count>"], + "a)": ["vim.select-surrounding", "surround \"(\" \")\" false", false, true, "<#text_object.count>"], + "i[": ["vim.select-surrounding", "surround \"[\" \"]\" true", false, true, "<#text_object.count>"], + "a[": ["vim.select-surrounding", "surround \"[\" \"]\" false", false, true, "<#text_object.count>"], + "i]": ["vim.select-surrounding", "surround \"[\" \"]\" true", false, true, "<#text_object.count>"], + "a]": ["vim.select-surrounding", "surround \"[\" \"]\" false", false, true, "<#text_object.count>"], + "i\\<": ["vim.select-surrounding", "surround \"<\" \">\" true", false, true, "<#text_object.count>"], + "a\\<": ["vim.select-surrounding", "surround \"<\" \">\" false", false, true, "<#text_object.count>"], + "i\\>": ["vim.select-surrounding", "surround \"<\" \">\" true", false, true, "<#text_object.count>"], + "a\\>": ["vim.select-surrounding", "surround \"<\" \">\" false", false, true, "<#text_object.count>"], + "i\"": ["vim.select-surrounding", "surround \"\\\"\" \"\\\"\" true", false, true, "<#text_object.count>"], + "a\"": ["vim.select-surrounding", "surround \"\\\"\" \"\\\"\" false", false, true, "<#text_object.count>"], + "i'": ["vim.select-surrounding", "surround \"'\" \"'\" true", false, true, "<#text_object.count>"], + "a'": ["vim.select-surrounding", "surround \"'\" \"'\" false", false, true, "<#text_object.count>"], }, "vim-new.visual-line#move": { diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 64eef5974..37c19a213 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -481,38 +481,42 @@ proc vimMotionParagraphOuter*(data: uint32, text: sink Rope, selections: openArr # else: # return '\0' -# proc vimMotionSurround*(editor: TextEditor, cursor: Cursor, count: int, c0: char, c1: char, inside: bool): Selection = -# result = cursor.toSelection -# # debugf"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}" -# while true: -# let lastChar = editor.charAt(result.last) -# let (startDepth, endDepth) = if lastChar == c0: -# (1, 0) -# elif lastChar == c1: -# (0, 1) -# else: -# (1, 1) - -# # debugf"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find around: {startDepth}, {endDepth}" -# if editor.findSurroundStart(result.first, count, c0, c1, startDepth).getSome(opening) and editor.findSurroundEnd(result.last, count, c0, c1, endDepth).getSome(closing): -# result = (opening, closing) -# # debugf"vimMotionSurround: found inside {result}" -# if inside: -# result.first = editor.doMoveCursorColumn(result.first, 1) -# result.last = editor.doMoveCursorColumn(result.last, -1) -# return +proc vimMotionSurround*(data: uint32, text: Rope, selection: Selection, count: int, includeEol: bool, c0: char, c1: char, inside: bool): Selection = + result = selection + while true: + let lastChar = text.charAt(result.last) + let (startDepth, endDepth) = if lastChar == c0: + (1, 0) + elif lastChar == c1: + (0, 1) + else: + (1, 1) + + # debugf"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find around: {startDepth}, {endDepth}" + if editor.findSurroundStart(result.first, count, c0, c1, startDepth).getSome(opening) and editor.findSurroundEnd(result.last, count, c0, c1, endDepth).getSome(closing): + result = (opening, closing) + # debugf"vimMotionSurround: found inside {result}" + if inside: + result.first = editor.doMoveCursorColumn(result.first, 1) + result.last = editor.doMoveCursorColumn(result.last, -1) + return + + # debugf"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find ahead: {startDepth}, {endDepth}" + if editor.findSurroundEnd(result.first, count, c0, c1, -1).getSome(opening) and editor.findSurroundEnd(opening, count, c0, c1, 0).getSome(closing): + result = (opening, closing) + # debugf"vimMotionSurround: found ahead {result}" + if inside: + result.first = editor.doMoveCursorColumn(result.first, 1) + result.last = editor.doMoveCursorColumn(result.last, -1) + return + else: + # debugf"vimMotionSurround: found nothing {result}" + return -# # debugf"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find ahead: {startDepth}, {endDepth}" -# if editor.findSurroundEnd(result.first, count, c0, c1, -1).getSome(opening) and editor.findSurroundEnd(opening, count, c0, c1, 0).getSome(closing): -# result = (opening, closing) -# # debugf"vimMotionSurround: found ahead {result}" -# if inside: -# result.first = editor.doMoveCursorColumn(result.first, 1) -# result.last = editor.doMoveCursorColumn(result.last, -1) -# return -# else: -# # debugf"vimMotionSurround: found nothing {result}" -# return +proc vimMotionSurround*(data: uint32, text: Rope, selections: openArray[Selection], count: int, includeEol: bool, c0: char, c1: char, inside: bool): seq[Selection] = + debugf"vimMotionSurround: {selections}, {count}, {c0}, {c1}, {inside}" + # result = selections.mapIt(vimMotionSurround(data, text, it.last.toSelection, count, includeEol, c0, c1, inside)) + # result = # proc vimMoveToMatching(editor: TextEditor) {.exposeActive(editorContext, "vim-move-to-matching").} = # # todo: pass as parameter @@ -547,18 +551,18 @@ proc vimMotionParagraphOuter*(data: uint32, text: sink Rope, selections: openArr # editor.scrollToCursor() # editor.updateTargetColumn() -# proc vimMotionSurroundBracesInner*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '{', '}', true) -# proc vimMotionSurroundBracesOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '{', '}', false) -# proc vimMotionSurroundParensInner*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '(', ')', true) -# proc vimMotionSurroundParensOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '(', ')', false) -# proc vimMotionSurroundBracketsInner*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '[', ']', true) -# proc vimMotionSurroundBracketsOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '[', ']', false) -# proc vimMotionSurroundAngleInner*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '<', '>', true) -# proc vimMotionSurroundAngleOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '<', '>', false) -# proc vimMotionSurroundDoubleQuotesInner*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '"', '"', true) -# proc vimMotionSurroundDoubleQuotesOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '"', '"', false) -# proc vimMotionSurroundSingleQuotesInner*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '\'', '\'', true) -# proc vimMotionSurroundSingleQuotesOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '\'', '\'', false) +proc vimMotionSurroundBracesInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '{', '}', true) +proc vimMotionSurroundBracesOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '{', '}', false) +proc vimMotionSurroundParensInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '(', ')', true) +proc vimMotionSurroundParensOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '(', ')', false) +proc vimMotionSurroundBracketsInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '[', ']', true) +proc vimMotionSurroundBracketsOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '[', ']', false) +proc vimMotionSurroundAngleInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '<', '>', true) +proc vimMotionSurroundAngleOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '<', '>', false) +proc vimMotionSurroundDoubleQuotesInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '"', '"', true) +proc vimMotionSurroundDoubleQuotesOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '"', '"', false) +proc vimMotionSurroundSingleQuotesInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '\'', '\'', true) +proc vimMotionSurroundSingleQuotesOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '\'', '\'', false) # # todo # addCustomTextMove "vim-word-outer", vimMotionWordOuter @@ -635,20 +639,28 @@ proc selectTextObject(editor: TextEditor, textObject: string, backwards: bool = editor.scrollToCursor() editor.updateTargetColumn() -# proc vimSelectSurrounding(editor: TextEditor, textObject: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1, textObjectRange: VimTextObjectRange = Inner) {.exposeActive(editorContext, "vim-select-surrounding").} = -# # debugf"vimSelectSurrounding({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count})" +proc selectSurrounding(editor: TextEditor, textObject: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1, textObjectRange: VimTextObjectRange = Inner) {.exposeActive(editorContext).} = + debugf"selectSurrounding({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count})" -# editor.setSelections editor.selections.mapIt(block: -# let resultSelection = editor.getSelectionForMove(it.last, textObject, count) -# # debugf"vimSelectSurrounding({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count}): {resultSelection}" -# if it.isBackwards: -# resultSelection.reverse -# else: -# resultSelection -# ) + let selections = editor.selections + let newSelections = mergeSelections(selections, editor.multiMove(selections, ws(textObject), count, wrap = false, includeEol = editor.vimState.cursorIncludeEol)): + if it1.isBackwards: + it2.reverse + else: + it2 + editor.setSelections newSelections + + # editor.setSelections editor.selections.mapIt(block: + # let resultSelection = editor.getSelectionForMove(it.last, textObject, count) + # # debugf"selectSurrounding({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count}): {resultSelection}" + # if it.isBackwards: + # resultSelection.reverse + # else: + # resultSelection + # ) -# editor.scrollToCursor() -# editor.updateTargetColumn() + editor.scrollToCursor() + editor.updateTargetColumn() proc moveSelectionNext(editor: TextEditor, move: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1) {.exposeActive(editorContext).} = # debugf"moveSelectionNext '{move}' {count} {backwards} {allowEmpty}" diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index 8fac0d606..ea5e3a0ec 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -2988,6 +2988,33 @@ proc vimMotionWordBig*(self: TextDocumentEditor, cursor: Cursor, count: int = 0) let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c notin Whitespace) return ((cursor.line, startColumn), (cursor.line, endColumn)) +proc getSurrounding*(self: TextDocumentEditor, selection: Selection, count: int, includeEol: bool, c0: char, c1: char, inside: bool): Selection = + result = selection + while true: + let lastChar = self.getChar(result.last) + let (startDepth, endDepth) = if lastChar == c0: + (1, 0) + elif lastChar == c1: + (0, 1) + else: + (1, 1) + + if self.findSurroundStart(result.first, count, c0, c1, startDepth).getSome(opening) and self.findSurroundEnd(result.last, count, c0, c1, endDepth).getSome(closing): + result = (opening, closing) + if inside: + result.first = self.doMoveCursorColumn(result.first, 1) + result.last = self.doMoveCursorColumn(result.last, -1) + return + + if self.findSurroundEnd(result.first, count, c0, c1, -1).getSome(opening) and self.findSurroundEnd(opening, count, c0, c1, 0).getSome(closing): + result = (opening, closing) + if inside: + result.first = self.doMoveCursorColumn(result.first, 1) + result.last = self.doMoveCursorColumn(result.last, -1) + return + else: + return + proc getSelectionsForMove*(self: TextDocumentEditor, selections: openArray[Selection], move: string, count: int = 0, includeEol: bool = true): seq[Selection] = @@ -3018,6 +3045,13 @@ proc getSelectionsForMove*(self: TextDocumentEditor, selections: openArray[Selec default case move + of "surround": + let c0 = getArg(0, string, "(") + let c1 = getArg(1, string, ")") + let inside = getArg(2, bool, true) + if c0.len > 0 and c1.len > 0: + result = selections.mapIt(self.getSurrounding(it, count, includeEol, c0[0], c1[0], inside)) + of "visual-line-up", "visual-line-down": var minLine = int.high var maxLine = int.low @@ -4569,14 +4603,12 @@ proc getSelections*(self: TextDocumentEditor): Selections {.expose("editor.text" proc setSelection*(self: TextDocumentEditor, selection: Selection, addToHistory: Option[bool] = bool.none) {.expose("editor.text").} = self.`selections=`(@[selection], addToHistory) - # self.selection = selection proc setSelections*(self: TextDocumentEditor, selections: Selections, addToHistory: Option[bool] = bool.none) {.expose("editor.text").} = if selections.len == 0: log lvlError, &"Failed to set selections for '{self.getFileName()}': no selections provided" return self.`selections=`(selections, addToHistory) - # self.selections = selections proc setTargetSelection*(self: TextDocumentEditor, selection: Selection) {.expose("editor.text").} = self.targetSelection = selection From 98a7520075312512ce661f56ae6468fc32a4a175 Mon Sep 17 00:00:00 2001 From: Nico Date: Tue, 16 Sep 2025 22:40:17 +0200 Subject: [PATCH 14/47] Create merge conflict [noci] --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 807609d69..d75d24861 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,7 @@ name: Build and test the editor on: # Runs on pushes targeting the default branch push: - branches: ["main", "vim-motions"] + branches: ["main", "vim-motions"] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: From 4171d07f49c658769e5b33fa84c89256b8f62ad6 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Wed, 17 Sep 2025 20:44:30 +0200 Subject: [PATCH 15/47] progress --- config/keybindings.json | 8 +- plugin_api/v0/text_editor.nim | 26 ++- plugins/vim/nimble.lock | 22 +-- plugins/vim/vim.nim | 285 +++++++++++++++--------------- src/generated/plugin_api_host.nim | 28 ++- src/move_database.nim | 5 +- src/plugin_api/plugin_api.nim | 4 +- src/text/text_editor.nim | 29 ++- wit/v0/api.wit | 2 +- 9 files changed, 224 insertions(+), 185 deletions(-) diff --git a/config/keybindings.json b/config/keybindings.json index 77ac80e26..116a52af8 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -763,10 +763,10 @@ "f": ["vim.move-to ", false, "<#move.count>"], "t": ["vim.move-to ", true, "<#move.count>"], - "": ["vim.move-cursor-visual-page", 0.5, "<#move.count>", true], - "": ["vim.move-cursor-visual-page", 1, "<#move.count>", true], - "": ["vim.move-cursor-visual-page", -0.5, "<#move.count>", true], - "": ["vim.move-cursor-visual-page", -1, "<#move.count>", true], + "": ["vim.move-cursor-visual-page", 50, "<#move.count>", true], + "": ["vim.move-cursor-visual-page", 100, "<#move.count>", true], + "": ["vim.move-cursor-visual-page", -50, "<#move.count>", true], + "": ["vim.move-cursor-visual-page", -100, "<#move.count>", true], "|": ["vim.move-to-column", "<#move.count>"], "-": ["vim.move-cursor-line-first-char", -1, "<#move.count>"], "+": ["vim.move-cursor-line-first-char", 1, "<#move.count>"], diff --git a/plugin_api/v0/text_editor.nim b/plugin_api/v0/text_editor.nim index e18c2c768..c18bd9201 100644 --- a/plugin_api/v0/text_editor.nim +++ b/plugin_api/v0/text_editor.nim @@ -349,24 +349,32 @@ proc copy*(editor: TextEditor; register: WitString; inclusiveEnd: bool): void {. arg3 = inclusiveEnd textEditorCopyImported(arg0, arg1, arg2, arg3) -proc textEditorPasteImported(a0: uint64; a1: int32; a2: int32; a3: bool): void {. +proc textEditorPasteImported(a0: uint64; a1: int32; a2: int32; a3: int32; + a4: int32; a5: bool): void {. wasmimport("paste", "nev:plugins/text-editor").} -proc paste*(editor: TextEditor; register: WitString; inclusiveEnd: bool): void {. - nodestroy.} = +proc paste*(editor: TextEditor; selections: WitList[Selection]; + register: WitString; inclusiveEnd: bool): void {.nodestroy.} = ## todo var arg0: uint64 arg1: int32 arg2: int32 - arg3: bool + arg3: int32 + arg4: int32 + arg5: bool arg0 = editor.id - if register.len > 0: - arg1 = cast[int32](register[0].addr) + if selections.len > 0: + arg1 = cast[int32](selections[0].addr) else: arg1 = 0.int32 - arg2 = cast[int32](register.len) - arg3 = inclusiveEnd - textEditorPasteImported(arg0, arg1, arg2, arg3) + arg2 = cast[int32](selections.len) + if register.len > 0: + arg3 = cast[int32](register[0].addr) + else: + arg3 = 0.int32 + arg4 = cast[int32](register.len) + arg5 = inclusiveEnd + textEditorPasteImported(arg0, arg1, arg2, arg3, arg4, arg5) proc textEditorSetSearchQueryFromMoveImported(a0: uint64; a1: int32; a2: int32; a3: int32; a4: int32; a5: int32; a6: int32; a7: int32; a8: int32): void {. diff --git a/plugins/vim/nimble.lock b/plugins/vim/nimble.lock index 6ae9dd253..c9e57bb07 100644 --- a/plugins/vim/nimble.lock +++ b/plugins/vim/nimble.lock @@ -53,16 +53,6 @@ "sha1": "408bde8679bce63dfb6f424e107eb37c5eb31510" } }, - "glob": { - "version": "0.11.2", - "vcsRevision": "64f71af2fa4572c2bdf8987a56a427c1d88fc34f", - "url": "https://github.com/haltcase/glob", - "downloadMethod": "git", - "dependencies": [], - "checksums": { - "sha1": "58fadfae8cb82cab1061dded8066c03b0460ad4d" - } - }, "unicodedb": { "version": "0.12.0", "vcsRevision": "b055310c08db8f879057b4fec15c8301ee93bb2a", @@ -85,6 +75,18 @@ "sha1": "cb2e24397c051e55ad5b72b3c0d4f270a90084df" } }, + "glob": { + "version": "0.11.2", + "vcsRevision": "64f71af2fa4572c2bdf8987a56a427c1d88fc34f", + "url": "https://github.com/haltcase/glob", + "downloadMethod": "git", + "dependencies": [ + "regex" + ], + "checksums": { + "sha1": "58fadfae8cb82cab1061dded8066c03b0460ad4d" + } + }, "nimgen": { "version": "0.5.4", "vcsRevision": "e6697aabbe36e42e4dd261cfd0b8a8be16f6f342", diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 37c19a213..e06cb32a3 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -481,49 +481,49 @@ proc vimMotionParagraphOuter*(data: uint32, text: sink Rope, selections: openArr # else: # return '\0' -proc vimMotionSurround*(data: uint32, text: Rope, selection: Selection, count: int, includeEol: bool, c0: char, c1: char, inside: bool): Selection = - result = selection - while true: - let lastChar = text.charAt(result.last) - let (startDepth, endDepth) = if lastChar == c0: - (1, 0) - elif lastChar == c1: - (0, 1) - else: - (1, 1) - - # debugf"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find around: {startDepth}, {endDepth}" - if editor.findSurroundStart(result.first, count, c0, c1, startDepth).getSome(opening) and editor.findSurroundEnd(result.last, count, c0, c1, endDepth).getSome(closing): - result = (opening, closing) - # debugf"vimMotionSurround: found inside {result}" - if inside: - result.first = editor.doMoveCursorColumn(result.first, 1) - result.last = editor.doMoveCursorColumn(result.last, -1) - return - - # debugf"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find ahead: {startDepth}, {endDepth}" - if editor.findSurroundEnd(result.first, count, c0, c1, -1).getSome(opening) and editor.findSurroundEnd(opening, count, c0, c1, 0).getSome(closing): - result = (opening, closing) - # debugf"vimMotionSurround: found ahead {result}" - if inside: - result.first = editor.doMoveCursorColumn(result.first, 1) - result.last = editor.doMoveCursorColumn(result.last, -1) - return - else: - # debugf"vimMotionSurround: found nothing {result}" - return +# proc vimMotionSurround*(data: uint32, text: Rope, selection: Selection, count: int, includeEol: bool, c0: char, c1: char, inside: bool): Selection = +# result = selection +# while true: +# let lastChar = text.charAt(result.last) +# let (startDepth, endDepth) = if lastChar == c0: +# (1, 0) +# elif lastChar == c1: +# (0, 1) +# else: +# (1, 1) + +# # debugf"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find around: {startDepth}, {endDepth}" +# if editor.findSurroundStart(result.first, count, c0, c1, startDepth).getSome(opening) and editor.findSurroundEnd(result.last, count, c0, c1, endDepth).getSome(closing): +# result = (opening, closing) +# # debugf"vimMotionSurround: found inside {result}" +# if inside: +# result.first = editor.doMoveCursorColumn(result.first, 1) +# result.last = editor.doMoveCursorColumn(result.last, -1) +# return -proc vimMotionSurround*(data: uint32, text: Rope, selections: openArray[Selection], count: int, includeEol: bool, c0: char, c1: char, inside: bool): seq[Selection] = - debugf"vimMotionSurround: {selections}, {count}, {c0}, {c1}, {inside}" - # result = selections.mapIt(vimMotionSurround(data, text, it.last.toSelection, count, includeEol, c0, c1, inside)) - # result = +# # debugf"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find ahead: {startDepth}, {endDepth}" +# if editor.findSurroundEnd(result.first, count, c0, c1, -1).getSome(opening) and editor.findSurroundEnd(opening, count, c0, c1, 0).getSome(closing): +# result = (opening, closing) +# # debugf"vimMotionSurround: found ahead {result}" +# if inside: +# result.first = editor.doMoveCursorColumn(result.first, 1) +# result.last = editor.doMoveCursorColumn(result.last, -1) +# return +# else: +# # debugf"vimMotionSurround: found nothing {result}" +# return + +# proc vimMotionSurround*(data: uint32, text: Rope, selections: openArray[Selection], count: int, includeEol: bool, c0: char, c1: char, inside: bool): seq[Selection] = +# debugf"vimMotionSurround: {selections}, {count}, {c0}, {c1}, {inside}" +# # result = selections.mapIt(vimMotionSurround(data, text, it.last.toSelection, count, includeEol, c0, c1, inside)) +# # result = # proc vimMoveToMatching(editor: TextEditor) {.exposeActive(editorContext, "vim-move-to-matching").} = # # todo: pass as parameter # let which = if editor.mode == "vim-new.visual" or editor.mode == "vim-new.visual-line": -# SelectionCursor.Last +# sca.SelectionCursor.Last # else: -# SelectionCursor.Both +# sca.SelectionCursor.Both # editor.setSelections editor.selections.mapIt(block: # let c = editor.charAt(it.last) @@ -551,18 +551,18 @@ proc vimMotionSurround*(data: uint32, text: Rope, selections: openArray[Selectio # editor.scrollToCursor() # editor.updateTargetColumn() -proc vimMotionSurroundBracesInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '{', '}', true) -proc vimMotionSurroundBracesOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '{', '}', false) -proc vimMotionSurroundParensInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '(', ')', true) -proc vimMotionSurroundParensOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '(', ')', false) -proc vimMotionSurroundBracketsInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '[', ']', true) -proc vimMotionSurroundBracketsOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '[', ']', false) -proc vimMotionSurroundAngleInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '<', '>', true) -proc vimMotionSurroundAngleOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '<', '>', false) -proc vimMotionSurroundDoubleQuotesInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '"', '"', true) -proc vimMotionSurroundDoubleQuotesOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '"', '"', false) -proc vimMotionSurroundSingleQuotesInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '\'', '\'', true) -proc vimMotionSurroundSingleQuotesOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '\'', '\'', false) +# proc vimMotionSurroundBracesInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '{', '}', true) +# proc vimMotionSurroundBracesOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '{', '}', false) +# proc vimMotionSurroundParensInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '(', ')', true) +# proc vimMotionSurroundParensOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '(', ')', false) +# proc vimMotionSurroundBracketsInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '[', ']', true) +# proc vimMotionSurroundBracketsOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '[', ']', false) +# proc vimMotionSurroundAngleInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '<', '>', true) +# proc vimMotionSurroundAngleOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '<', '>', false) +# proc vimMotionSurroundDoubleQuotesInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '"', '"', true) +# proc vimMotionSurroundDoubleQuotesOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '"', '"', false) +# proc vimMotionSurroundSingleQuotesInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '\'', '\'', true) +# proc vimMotionSurroundSingleQuotesOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '\'', '\'', false) # # todo # addCustomTextMove "vim-word-outer", vimMotionWordOuter @@ -768,26 +768,30 @@ proc moveParagraph(editor: TextEditor, backwards: bool, count: int = 1) {.expose # exposeActive editorContext, "vim-delete-left", vimDeleteLeft # exposeActive editorContext, "vim-delete-right", vimDeleteRight -# proc vimMoveCursorPage(editor: TextEditor, direction: float, count: int = 1, center: bool = false) {.exposeActive(editorContext, "vim-move-cursor-page").} = -# editor.moveCursorPage(direction * max(count, 1).float, includeAfter=editor.vimState.cursorIncludeEol) -# let nextScrollBehaviour = if center: CenterAlways.some else: ScrollBehaviour.none -# editor.scrollToCursor(scrollBehaviour = nextScrollBehaviour) -# editor.setNextSnapBehaviour(Never) -# if editor.vimState.selectLines: -# editor.selectLine() - -# proc vimMoveCursorVisualPage(editor: TextEditor, direction: float, count: int = 1, center: bool = false) {.exposeActive(editorContext, "vim-move-cursor-visual-page").} = -# if editor.vimState.selectLines: -# editor.moveCursorPage(direction * max(count, 1).float, includeAfter=editor.vimState.cursorIncludeEol) -# else: -# editor.moveCursorVisualPage(direction * max(count, 1).float, includeAfter=editor.vimState.cursorIncludeEol) -# let defaultScrollBehaviour = editor.getDefaultScrollBehaviour -# let defaultCenter = defaultScrollBehaviour in {CenterAlways, CenterOffscreen} -# let nextScrollBehaviour = if center and defaultCenter: CenterAlways.some else: ScrollBehaviour.none -# editor.scrollToCursor(scrollBehaviour = nextScrollBehaviour) -# editor.setNextSnapBehaviour(Never) -# if editor.vimState.selectLines: -# editor.selectLine() +proc moveCursorPage(editor: TextEditor, direction: int, count: int = 1, center: bool = false) {.exposeActive(editorContext).} = + ## Direction 100 means 100% of window height downwards -100 is upwards, 50 would be 50% + editor.setSelections editor.multiMove(editor.selections, ws"page", direction * max(count, 1), true, includeEol = editor.vimState.cursorIncludeEol) + let nextScrollBehaviour = if center: CenterAlways.some else: ScrollBehaviour.none + editor.scrollToCursor(behaviour = nextScrollBehaviour) + editor.setNextSnapBehaviour(Never) + if editor.vimState.selectLines: + editor.selectLine() + +proc moveCursorVisualPage(editor: TextEditor, direction: int, count: int = 1, center: bool = false) {.exposeActive(editorContext).} = + ## Direction 100 means 100% of window height downwards -100 is upwards, 50 would be 50% + if editor.vimState.selectLines: + editor.setSelections editor.multiMove(editor.selections, ws"page", direction * max(count, 1), true, includeEol = editor.vimState.cursorIncludeEol) + else: + editor.setSelections editor.multiMove(editor.selections, ws"visual-page", direction * max(count, 1), true, includeEol = editor.vimState.cursorIncludeEol) + # todo + # let defaultScrollBehaviour = editor.getDefaultScrollBehaviour + # let defaultCenter = defaultScrollBehaviour in {CenterAlways, CenterOffscreen} + # let nextScrollBehaviour = if center and defaultCenter: CenterAlways.some else: ScrollBehaviour.none + let nextScrollBehaviour = ScrollBehaviour.none + editor.scrollToCursor(behaviour = nextScrollBehaviour) + editor.setNextSnapBehaviour(Never) + if editor.vimState.selectLines: + editor.selectLine() func toSelection*(cursor: Cursor, default: Selection, which: sca.SelectionCursor): Selection = case which @@ -857,29 +861,25 @@ proc moveDirection(editor: TextEditor, move: string, direction: int, count: int # editor.scrollToCursor() # editor.updateTargetColumn() -# proc vimPaste(editor: TextEditor, pasteRight: bool = false, inclusiveEnd: bool = false, register: string = "") {.exposeActive(editorContext, "vim-paste").} = -# # debugf"vimPaste {register}, lines: {yankedLines}" -# let register = if register == "vim-default-register": -# getVimDefaultRegister() -# else: -# register - -# editor.addNextCheckpoint "insert" +proc paste(editor: TextEditor, pasteRight: bool = false, inclusiveEnd: bool = false, register: string = "") {.exposeActive(editorContext).} = + debugf"vimPaste {register}, lines: {yankedLines}" + let register = if register == "vim.default-register": + getVimDefaultRegister() + else: + register -# let selectionsToDelete = editor.selections -# editor.setSelections editor.delete(selectionsToDelete, inclusiveEnd=false) + editor.addNextCheckpoint ws"insert" -# if yankedLines: -# # todo: pass bool as parameter -# if editor.mode != "vim-new.visual-line": -# editor.moveLast "line", Both -# editor.insertText "\n", autoIndent=false + var selections = editor.getSelections -# if pasteRight: -# editor.setSelections editor.selections.mapIt(editor.doMoveCursorColumn(it.last, 1, wrap=false).toSelection) + if yankedLines and $editor.mode == "vim-new.normal": + selections = editor.multiMove(selections, ws"line", 1, wrap = false, includeEol = true).mapIt(it.last.toSelection).stackWitList() + selections = editor.edit(selections, @@[ws("\n")], inclusive=false).mapIt(it.last.toSelection).stackWitList() + elif pasteRight: + selections = editor.multiMove(selections, ws"column", 1, true, true).mapIt(it.last.toSelection).stackWitList() -# editor.setMode "vim-new.normal" -# editor.paste register, inclusiveEnd=inclusiveEnd + editor.setMode "vim-new.normal" + editor.paste selections, register.stackWitString, inclusiveEnd=inclusiveEnd # proc vimToggleCase(editor: TextEditor, moveCursorRight: bool) {.exposeActive(editorContext, "vim-toggle-case").} = # var editTexts: seq[string] @@ -933,67 +933,72 @@ proc moveDirection(editor: TextEditor, move: string, direction: int, count: int # editor.addNextCheckpoint "insert" # editor.insertText("\n") -# proc vimYankLine(editor: TextEditor) {.exposeActive(editorContext, "vim-yank-line").} = -# editor.vimState.selectLines = true -# editor.selectLine() -# editor.yankSelection() -# editor.vimState.selectLines = false +proc yankLine(editor: TextEditor) {.exposeActive(editorContext).} = + editor.vimState.selectLines = true + editor.selectLine() + editor.yankSelection() + editor.vimState.selectLines = false -# proc vimDeleteLine(editor: TextEditor) {.exposeActive(editorContext, "vim-delete-line").} = -# editor.vimState.selectLines = true -# let oldSelections = editor.selections -# editor.selectLine() -# editor.deleteSelection(true, oldSelections=oldSelections.some) -# editor.vimState.selectLines = false +proc deleteLine(editor: TextEditor) {.exposeActive(editorContext).} = + editor.vimState.selectLines = true + let oldSelections = editor.getSelections + editor.selectLine() + editor.deleteSelection(true, oldSelections=some(@oldSelections)) + editor.vimState.selectLines = false -# proc vimChangeLine(editor: TextEditor) {.exposeActive(editorContext, "vim-change-line").} = -# let oldSelections = editor.selections -# editor.selectLine() -# editor.changeSelection(true, oldSelections=oldSelections.some) +proc changeLine(editor: TextEditor) {.exposeActive(editorContext).} = + let oldSelections = editor.getSelections + editor.selectLine() + editor.changeSelection(true, oldSelections=some(@oldSelections)) -# proc vimDeleteToLineEnd(editor: TextEditor) {.exposeActive(editorContext, "vim-delete-to-line-end").} = -# let oldSelections = editor.selections -# editor.setSelections editor.selections.mapIt (it.last, editor.vimMotionLine(it.last, 0).last) -# editor.deleteSelection(true, oldSelections=oldSelections.some) -# editor.vimState.selectLines = false +proc deleteToLineEnd(editor: TextEditor) {.exposeActive(editorContext).} = + let oldSelections = editor.getSelections + let selections = mergeSelections(editor.selections, editor.multiMove(editor.selections, ws"line", 1, wrap = false, includeEol = true)): + (it1.last, it2.last).toSelection + editor.setSelections selections + editor.deleteSelection(true, oldSelections=some(@oldSelections)) + editor.vimState.selectLines = false -# proc vimChangeToLineEnd(editor: TextEditor) {.exposeActive(editorContext, "vim-change-to-line-end").} = -# let oldSelections = editor.selections -# editor.setSelections editor.selections.mapIt (it.last, editor.vimMotionLine(it.last, 0).last) -# editor.changeSelection(true, oldSelections=oldSelections.some) -# editor.vimState.selectLines = false - -# proc vimYankToLineEnd(editor: TextEditor) {.exposeActive(editorContext, "vim-yank-to-line-end").} = -# editor.setSelections editor.selections.mapIt (it.last, editor.vimMotionLine(it.last, 0).last) -# editor.yankSelection() -# editor.vimState.selectLines = false - -# proc vimMoveFileStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-file-start").} = -# let which = getSetting[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) -# editor.setSelection (count - 1, 0).toSelection(editor.selection, which) -# editor.moveFirst "line-no-indent" -# editor.scrollToCursor() -# editor.setNextSnapBehaviour(MinDistanceOffscreen) +proc changeToLineEnd(editor: TextEditor) {.exposeActive(editorContext).} = + let oldSelections = editor.getSelections + let selections = mergeSelections(editor.selections, editor.multiMove(editor.selections, ws"line", 1, wrap = false, includeEol = true)): + (it1.last, it2.last).toSelection + editor.setSelections selections + editor.changeSelection(true, oldSelections=some(@oldSelections)) + editor.vimState.selectLines = false -# proc vimMoveFileEnd(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-file-end").} = -# let line = if count == 0: editor.lineCount - 1 else: count - 1 -# let which = getSetting[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) -# var newSelection = (line, 0).toSelection(editor.selection, which) -# if newSelection == editor.selection: -# let lineLen = editor.lineLength(line) -# editor.setSelection (line, lineLen).toSelection(editor.selection, which) -# else: -# editor.setSelection newSelection -# editor.moveFirst "line-no-indent" -# editor.scrollToCursor() -# editor.setNextSnapBehaviour(MinDistanceOffscreen) +proc yankToLineEnd(editor: TextEditor) {.exposeActive(editorContext).} = + let selections = mergeSelections(editor.selections, editor.multiMove(editor.selections, ws"line", 1, wrap = false, includeEol = true)): + (it1.last, it2.last).toSelection + editor.setSelections selections + editor.yankSelection() + +proc moveFileStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = + let which = getSetting[sca.SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), sca.SelectionCursor.Both) + editor.setSelection (count - 1, 0).toSelection(editor.getSelection, which) + editor.moveFirst "line-no-indent" + editor.scrollToCursor() + editor.setNextSnapBehaviour(MinDistanceOffscreen) + +proc moveFileEnd(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = + let line = if count == 0: editor.content.lines.int - 1 else: count - 1 + let which = getSetting[sca.SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), sca.SelectionCursor.Both) + var newSelection = (line, 0).toSelection(editor.getSelection, which) + if newSelection == editor.getSelection: + let lineLen = editor.lineLength(line.int32).int + editor.setSelection (line, lineLen).toSelection(editor.getSelection, which) + else: + editor.setSelection newSelection + editor.moveFirst "line-no-indent" + editor.scrollToCursor() + editor.setNextSnapBehaviour(MinDistanceOffscreen) # proc vimMoveToMatchingOrFileOffset(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-matching-or-file-offset").} = # if count == 0: # editor.vimMoveToMatching() # else: # let line = clamp((count * editor.lineCount) div 100, 0, editor.lineCount - 1) -# let which = getSetting[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) +# let which = getSetting[sca.SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), sca.SelectionCursor.Both) # editor.setSelection (line, 0).toSelection(editor.selection, which) # editor.moveFirst "line-no-indent" # editor.scrollToCursor() @@ -1222,12 +1227,12 @@ proc setSearchQueryOrAddCursor(editor: TextEditor) {.exposeActive(editorContext) # editor.setNextSnapBehaviour(MinDistanceOffscreen) # proc vimDeleteWordBack(editor: TextEditor) {.exposeActive(editorContext, "vim-delete-word-back").} = -# let selections = editor.applyMove(editor.selections, "vim-word", true, true, 1, which = SelectionCursor.Last.some) +# let selections = editor.applyMove(editor.selections, "vim-word", true, true, 1, which = sca.SelectionCursor.Last.some) # editor.setSelections editor.delete(selections) # editor.autoShowCompletions() # proc vimDeleteLineBack(editor: TextEditor) {.exposeActive(editorContext, "vim-delete-line-back").} = -# let selections = editor.applyMove(editor.selections, "vim-line", true, true, 1, which = SelectionCursor.Last.some) +# let selections = editor.applyMove(editor.selections, "vim-line", true, true, 1, which = sca.SelectionCursor.Last.some) # editor.setSelections editor.delete(selections) # editor.autoShowCompletions() diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index 501194c3e..8ab5b8553 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -489,7 +489,8 @@ proc textEditorAddNextCheckpoint(instance: ptr InstanceData; editor: TextEditor; proc textEditorCopy(instance: ptr InstanceData; editor: TextEditor; register: sink string; inclusiveEnd: bool): void proc textEditorPaste(instance: ptr InstanceData; editor: TextEditor; - register: sink string; inclusiveEnd: bool): void + selections: sink seq[Selection]; register: sink string; + inclusiveEnd: bool): void proc textEditorSetSearchQueryFromMove(instance: ptr InstanceData; editor: TextEditor; move: sink string; count: int32; prefix: sink string; @@ -1872,8 +1873,8 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = return e block: let e = block: - var ty: ptr WasmFunctypeT = newFunctype( - [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], []) linker.defineFuncUnchecked("nev:plugins/text-editor", "paste", ty): var instance = cast[ptr InstanceData](store.getData()) @@ -1890,16 +1891,29 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = else: assert false var editor: TextEditor + var selections: seq[Selection] var register: string var inclusiveEnd: bool editor.id = convert(parameters[0].i64, uint64) block: - let p0 = cast[ptr UncheckedArray[char]](memory[parameters[1].i32].addr) - register = newString(parameters[2].i32) + let p0 = cast[ptr UncheckedArray[uint8]](memory[parameters[1].i32].addr) + selections = newSeq[typeof(selections[0])](parameters[2].i32) + for i0 in 0 ..< selections.len: + selections[i0].first.line = convert( + cast[ptr int32](p0[i0 * 16 + 0].addr)[], int32) + selections[i0].first.column = convert( + cast[ptr int32](p0[i0 * 16 + 4].addr)[], int32) + selections[i0].last.line = convert( + cast[ptr int32](p0[i0 * 16 + 8].addr)[], int32) + selections[i0].last.column = convert( + cast[ptr int32](p0[i0 * 16 + 12].addr)[], int32) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[3].i32].addr) + register = newString(parameters[4].i32) for i0 in 0 ..< register.len: register[i0] = p0[i0] - inclusiveEnd = parameters[3].i32.bool - textEditorPaste(instance, editor, register, inclusiveEnd) + inclusiveEnd = parameters[5].i32.bool + textEditorPaste(instance, editor, selections, register, inclusiveEnd) if e.isErr: return e block: diff --git a/src/move_database.nim b/src/move_database.nim index 87f2edad8..f04b2e34d 100644 --- a/src/move_database.nim +++ b/src/move_database.nim @@ -268,9 +268,8 @@ proc applyMove*(self: MoveDatabase, displayMap: DisplayMap, move: string, select # if result.first.line > 0: # result.first = (result.first.line - 1, self.document.lineLength(result.first.line - 1)) - # of "line-no-indent": - # let indent = self.document.rope.indentBytes(cursor.line) - # result = ((cursor.line, indent), (cursor.line, self.document.lineLength(cursor.line))) + of "line-no-indent": + result = selections.mapIt ((it.last.line, rope.indentBytes(it.last.line)), (it.last.line, rope.lineLen(it.last.line))) # of "file": # result.first = (0, 0) diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index a943d7afd..b58e2c894 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -471,11 +471,11 @@ proc textEditorCopy(instance: ptr InstanceData; editor: TextEditor, register: si if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: editor.TextDocumentEditor.copy(register, inclusiveEnd) -proc textEditorPaste(instance: ptr InstanceData; editor: TextEditor, register: sink string, inclusiveEnd: bool): void = +proc textEditorPaste(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection], register: sink string, inclusiveEnd: bool): void = if instance.host == nil: return if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: - editor.TextDocumentEditor.paste(register, inclusiveEnd) + asyncSpawn editor.TextDocumentEditor.pasteAsync(selections.mapIt(it.toInternal), register, inclusiveEnd) proc textEditorApplyMove(instance: ptr InstanceData; editor: TextEditor; selection: Selection; move: sink string; count: int32; wrap: bool; includeEol: bool): seq[Selection] = if instance.host == nil: diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index aea4955fa..da4cc4fcc 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -2175,7 +2175,7 @@ proc copy*(self: TextDocumentEditor, register: string = "", inclusiveEnd: bool = expose("editor.text").} = asyncSpawn self.copyAsync(register, inclusiveEnd) -proc pasteAsync*(self: TextDocumentEditor, registerName: string, inclusiveEnd: bool = false): +proc pasteAsync*(self: TextDocumentEditor, selections: seq[Selection], registerName: string, inclusiveEnd: bool = false): Future[void] {.async.} = log lvlInfo, fmt"paste register from '{registerName}', inclusiveEnd: {inclusiveEnd}" @@ -2188,26 +2188,26 @@ proc pasteAsync*(self: TextDocumentEditor, registerName: string, inclusiveEnd: b let numLines = register.numLines() - let newSelections = if numLines == self.selections.len and numLines > 1: + let newSelections = if numLines == selections.len and numLines > 1: case register.kind of RegisterKind.Text: let lines = register.text.splitLines() - self.document.edit(self.selections, self.selections, lines, notify=true, record=true, inclusiveEnd=inclusiveEnd).mapIt(it.last.toSelection) + self.document.edit(selections, selections, lines, notify=true, record=true, inclusiveEnd=inclusiveEnd).mapIt(it.last.toSelection) of RegisterKind.Rope: let lines = register.rope.splitLines() - self.document.edit(self.selections, self.selections, lines, notify=true, record=true, inclusiveEnd=inclusiveEnd).mapIt(it.last.toSelection) + self.document.edit(selections, selections, lines, notify=true, record=true, inclusiveEnd=inclusiveEnd).mapIt(it.last.toSelection) else: case register.kind of RegisterKind.Text: - self.document.edit(self.selections, self.selections, [register.text.move], notify=true, record=true, inclusiveEnd=inclusiveEnd).mapIt(it.last.toSelection) + self.document.edit(selections, selections, [register.text.move], notify=true, record=true, inclusiveEnd=inclusiveEnd).mapIt(it.last.toSelection) of RegisterKind.Rope: - self.document.edit(self.selections, self.selections, [register.rope.move], notify=true, record=true, inclusiveEnd=inclusiveEnd).mapIt(it.last.toSelection) + self.document.edit(selections, selections, [register.rope.move], notify=true, record=true, inclusiveEnd=inclusiveEnd).mapIt(it.last.toSelection) # add list of selections for what was just pasted to history - if newSelections.len == self.selections.len: + if newSelections.len == selections.len: var tempSelections = newSelections for i in 0..tempSelections.high: - tempSelections[i].first = self.selections[i].first + tempSelections[i].first = selections[i].first self.selections = tempSelections self.selections = newSelections @@ -2217,7 +2217,7 @@ proc pasteAsync*(self: TextDocumentEditor, registerName: string, inclusiveEnd: b proc paste*(self: TextDocumentEditor, registerName: string = "", inclusiveEnd: bool = false) {. expose("editor.text").} = - asyncSpawn self.pasteAsync(registerName, inclusiveEnd) + asyncSpawn self.pasteAsync(self.selections, registerName, inclusiveEnd) proc scrollText*(self: TextDocumentEditor, amount: float32) {.expose("editor.text").} = if self.disableScrolling: @@ -3077,6 +3077,17 @@ proc getSelectionsForMove*(self: TextDocumentEditor, selections: openArray[Selec if c0.len > 0 and c1.len > 0: result = selections.mapIt(self.getSurrounding(it, count, includeEol, c0[0], c1[0], inside)) + of "page": + let linesToMove = int(self.screenLineCount() * count div 100) + return selections.mapIt(self.doMoveCursorLine(it.last, linesToMove, false, includeEol).toSelection(it, cursorSelector)) + + of "visual-page": + let linesToMove = int(self.screenLineCount() * count div 100) + return selections.mapIt(self.doMoveCursorVisualLine(it.last, linesToMove, false, includeEol).toSelection(it, cursorSelector)) + # let visibleLines = self.screenLineCount() + # let linesToMove = int(visibleLines.float * distance) + # self.moveCursorVisualLine(linesToMove, cursor, all, wrap, includeAfter) + of "visual-line-up", "visual-line-down": var minLine = int.high var maxLine = int.low diff --git a/wit/v0/api.wit b/wit/v0/api.wit index ab0d2b0e8..f3c8e0d4d 100644 --- a/wit/v0/api.wit +++ b/wit/v0/api.wit @@ -275,7 +275,7 @@ interface text-editor { /// todo copy: func(editor: text-editor, register: string, inclusive-end: bool); /// todo - paste: func(editor: text-editor, register: string, inclusive-end: bool); + paste: func(editor: text-editor, selections: list, register: string, inclusive-end: bool); /// Set the search query by applying the given move to the current selection. /// 'prefix' and 'suffix' are appended to the string after escaping. From 90f0a89515e56546ec6e4369a1bd268bb2f40151 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Wed, 17 Sep 2025 21:17:15 +0200 Subject: [PATCH 16/47] more progress --- plugin_api/v0/text_editor.nim | 8 ++++++++ plugins/vim/vim.nim | 29 ++++++++++++++--------------- src/generated/plugin_api_host.nim | 13 +++++++++++++ src/plugin_api/plugin_api.nim | 10 ++++++++-- src/text/text_editor.nim | 4 ++-- wit/v0/api.wit | 2 ++ 6 files changed, 47 insertions(+), 19 deletions(-) diff --git a/plugin_api/v0/text_editor.nim b/plugin_api/v0/text_editor.nim index c18bd9201..2cec511c4 100644 --- a/plugin_api/v0/text_editor.nim +++ b/plugin_api/v0/text_editor.nim @@ -376,6 +376,14 @@ proc paste*(editor: TextEditor; selections: WitList[Selection]; arg5 = inclusiveEnd textEditorPasteImported(arg0, arg1, arg2, arg3, arg4, arg5) +proc textEditorAutoShowCompletionsImported(a0: uint64): void {. + wasmimport("auto-show-completions", "nev:plugins/text-editor").} +proc autoShowCompletions*(editor: TextEditor): void {.nodestroy.} = + ## todo + var arg0: uint64 + arg0 = editor.id + textEditorAutoShowCompletionsImported(arg0) + proc textEditorSetSearchQueryFromMoveImported(a0: uint64; a1: int32; a2: int32; a3: int32; a4: int32; a5: int32; a6: int32; a7: int32; a8: int32): void {. wasmimport("set-search-query-from-move", "nev:plugins/text-editor").} diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index e06cb32a3..4f0d1ae88 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -753,20 +753,19 @@ proc moveParagraph(editor: TextEditor, backwards: bool, count: int = 1) {.expose editor.scrollToCursor() editor.updateTargetColumn() -# proc vimDeleteLeft*(editor: TextEditor) = -# yankedLines = editor.vimState.selectLines -# editor.copy() -# editor.addNextCheckpoint "insert" -# editor.deleteLeft() - -# proc vimDeleteRight*(editor: TextEditor) = -# yankedLines = editor.vimState.selectLines -# editor.copy() -# editor.addNextCheckpoint "insert" -# editor.deleteRight(includeAfter=editor.vimState.cursorIncludeEol) +proc deleteLeft*(editor: TextEditor) {.exposeActive(editorContext).} = + yankedLines = editor.vimState.selectLines + editor.copy(ws"", inclusiveEnd = false) + editor.addNextCheckpoint ws"insert" + let selections = editor.multiMove(editor.getSelections, ws"column", -1, wrap = false, includeEol = true) + editor.setSelections editor.edit(selections, @@[ws""], inclusive=editor.vimState.cursorIncludeEol) -# exposeActive editorContext, "vim-delete-left", vimDeleteLeft -# exposeActive editorContext, "vim-delete-right", vimDeleteRight +proc deleteRight*(editor: TextEditor) {.exposeActive(editorContext).} = + yankedLines = editor.vimState.selectLines + editor.copy(ws"", inclusiveEnd = false) + editor.addNextCheckpoint ws"insert" + let selections = editor.getSelections + editor.setSelections editor.edit(selections, @@[ws""], inclusive=true) proc moveCursorPage(editor: TextEditor, direction: int, count: int = 1, center: bool = false) {.exposeActive(editorContext).} = ## Direction 100 means 100% of window height downwards -100 is upwards, 50 would be 50% @@ -862,7 +861,7 @@ proc moveDirection(editor: TextEditor, move: string, direction: int, count: int # editor.updateTargetColumn() proc paste(editor: TextEditor, pasteRight: bool = false, inclusiveEnd: bool = false, register: string = "") {.exposeActive(editorContext).} = - debugf"vimPaste {register}, lines: {yankedLines}" + # debugf"vimPaste {register}, lines: {yankedLines}, pasteRight: {pasteRight}" let register = if register == "vim.default-register": getVimDefaultRegister() else: @@ -876,7 +875,7 @@ proc paste(editor: TextEditor, pasteRight: bool = false, inclusiveEnd: bool = fa selections = editor.multiMove(selections, ws"line", 1, wrap = false, includeEol = true).mapIt(it.last.toSelection).stackWitList() selections = editor.edit(selections, @@[ws("\n")], inclusive=false).mapIt(it.last.toSelection).stackWitList() elif pasteRight: - selections = editor.multiMove(selections, ws"column", 1, true, true).mapIt(it.last.toSelection).stackWitList() + selections = editor.multiMove(selections, ws"column", 1, false, true).mapIt(it.last.toSelection).stackWitList() editor.setMode "vim-new.normal" editor.paste selections, register.stackWitString, inclusiveEnd=inclusiveEnd diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index 8ab5b8553..2fe5e0af4 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -491,6 +491,8 @@ proc textEditorCopy(instance: ptr InstanceData; editor: TextEditor; proc textEditorPaste(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection]; register: sink string; inclusiveEnd: bool): void +proc textEditorAutoShowCompletions(instance: ptr InstanceData; + editor: TextEditor): void proc textEditorSetSearchQueryFromMove(instance: ptr InstanceData; editor: TextEditor; move: sink string; count: int32; prefix: sink string; @@ -1916,6 +1918,17 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = textEditorPaste(instance, editor, selections, register, inclusiveEnd) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", + "auto-show-completions", ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + textEditorAutoShowCompletions(instance, editor) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index b58e2c894..1f32c51a1 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -477,19 +477,25 @@ proc textEditorPaste(instance: ptr InstanceData; editor: TextEditor; selections: if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: asyncSpawn editor.TextDocumentEditor.pasteAsync(selections.mapIt(it.toInternal), register, inclusiveEnd) +proc textEditorAutoShowCompletions(instance: ptr InstanceData; editor: TextEditor): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.autoShowCompletions() + proc textEditorApplyMove(instance: ptr InstanceData; editor: TextEditor; selection: Selection; move: sink string; count: int32; wrap: bool; includeEol: bool): seq[Selection] = if instance.host == nil: return if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: let textEditor = editor.TextDocumentEditor - return textEditor.getSelectionsForMove(@[selection.toInternal], move, count, includeEol).mapIt(it.toWasm) + return textEditor.getSelectionsForMove(@[selection.toInternal], move, count, includeEol, wrap).mapIt(it.toWasm) proc textEditorMultiMove(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection]; move: sink string; count: int32; wrap: bool; includeEol: bool): seq[Selection] = if instance.host == nil: return if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: let textEditor = editor.TextDocumentEditor - return textEditor.getSelectionsForMove(selections.mapIt(it.toInternal), move, count, includeEol).mapIt(it.toWasm) + return textEditor.getSelectionsForMove(selections.mapIt(it.toInternal), move, count, includeEol, wrap).mapIt(it.toWasm) proc textEditorSetSelection(instance: ptr InstanceData; editor: TextEditor; s: Selection): void = if instance.host == nil: diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index da4cc4fcc..eec45245e 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -3041,7 +3041,7 @@ proc getSurrounding*(self: TextDocumentEditor, selection: Selection, count: int, return proc getSelectionsForMove*(self: TextDocumentEditor, selections: openArray[Selection], move: string, - count: int = 0, includeEol: bool = true): seq[Selection] = + count: int = 0, includeEol: bool = true, wrap: bool = true): seq[Selection] = let cursorSelector = self.config.get(self.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) @@ -3110,7 +3110,7 @@ proc getSelectionsForMove*(self: TextDocumentEditor, selections: openArray[Selec return selections.mapIt(self.doMoveCursorLine(it.last, direction * max(count, 1), false, includeEol).toSelection(it, cursorSelector)) of "column": - result = selections.mapIt(self.doMoveCursorColumn(it.last, count, includeAfter = includeEol).toSelection(it, cursorSelector)) + result = selections.mapIt(self.doMoveCursorColumn(it.last, count, wrap = wrap, includeAfter = includeEol).toSelection(it, cursorSelector)) of "prev-search-result": let count = getArg(0, int, 0) diff --git a/wit/v0/api.wit b/wit/v0/api.wit index f3c8e0d4d..5b76ff0f3 100644 --- a/wit/v0/api.wit +++ b/wit/v0/api.wit @@ -276,6 +276,8 @@ interface text-editor { copy: func(editor: text-editor, register: string, inclusive-end: bool); /// todo paste: func(editor: text-editor, selections: list, register: string, inclusive-end: bool); + /// todo + auto-show-completions: func(editor: text-editor); /// Set the search query by applying the given move to the current selection. /// 'prefix' and 'suffix' are appended to the string after escaping. From 63b03085b59b6a2c1da03cf84997597e11033876 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Tue, 30 Sep 2025 18:35:15 +0200 Subject: [PATCH 17/47] Fixed crash, fixed memory leak --- src/channel.nim | 49 ++++++++++++++++++----- src/misc/binary_encoder.nim | 3 ++ src/platform/gui_platform.nim | 2 +- src/text/language/language_server_lsp.nim | 4 +- src/text/text_editor.nim | 2 +- 5 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/channel.nim b/src/channel.nim index c1088b2df..19f96c995 100644 --- a/src/channel.nim +++ b/src/channel.nim @@ -17,17 +17,22 @@ type isOpenImpl*: proc(self: ptr BaseChannel): bool {.gcsafe, raises: [].} peekImpl*: proc(self: ptr BaseChannel, to: Option[uint8]): int {.gcsafe, raises: [].} writeImpl*: proc(self: ptr BaseChannel, data: openArray[uint8]) {.gcsafe, raises: [IOError].} + writeSinkImpl*: proc(self: ptr BaseChannel, data: sink seq[uint8]) {.gcsafe, raises: [IOError].} flushReadImpl*: proc(self: ptr BaseChannel): int {.gcsafe, raises: [IOError].} readImpl*: proc(self: ptr BaseChannel, res: var openArray[uint8]): int {.gcsafe, raises: [IOError].} listenImpl*: proc(self: Arc[BaseChannel], cb: ChannelListener): ListenId {.gcsafe, raises: [].} # todo: make this thread save + InMemoryBuffer = object + data: ptr UncheckedArray[uint8] + len: int InMemoryChannel* = object of BaseChannel isOpen: bool isWaiting: bool data: seq[uint8] # todo: use something more efficient which doesn't require moving memory + dataStart: int writeChannelPeekable: Atomic[int] - channel: ptr Channel[seq[uint8]] + channel: ptr Channel[InMemoryBuffer] ChannelRegistry* = object lock*: Lock @@ -47,6 +52,7 @@ proc close*(self: var BaseChannel) {.gcsafe, raises: [].} = self.closeImpl(self. proc isOpen*(self: var BaseChannel): bool {.raises: [].} = self.isOpenImpl(self.addr) proc peek*(self: var BaseChannel, to: Option[uint8] = uint8.none): int {.raises: [].} = self.peekImpl(self.addr, to) proc write*(self: var BaseChannel, data: openArray[uint8]) {.raises: [IOError].} = self.writeImpl(self.addr, data) +proc write*(self: var BaseChannel, data: sink seq[uint8]) {.raises: [IOError].} = self.writeSinkImpl(self.addr, data.ensureMove) proc read*(self: var BaseChannel, res: var openArray[uint8]): int {.raises: [IOError].} = self.readImpl(self.addr, res) proc flushRead*(self: var BaseChannel): int {.raises: [IOError].} = self.flushReadImpl(self.addr) @@ -55,6 +61,7 @@ proc isOpen*(self: Arc[BaseChannel]): bool {.raises: [].} = self.getMutUnsafe.is proc peek*(self: Arc[BaseChannel], to: Option[uint8] = uint8.none): int {.raises: [].} = self.getMutUnsafe.peek(to) proc write*(self: Arc[BaseChannel], data: openArray[uint8]) {.raises: [IOError].} = self.getMutUnsafe.write(data) proc write*(self: Arc[BaseChannel], data: openArray[char]) {.raises: [IOError].} = self.getMutUnsafe.write(cast[ptr UncheckedArray[uint8]](data[0].addr).toOpenArray(0, data.high)) +proc write*(self: Arc[BaseChannel], data: sink seq[uint8]) {.raises: [IOError].} = self.getMutUnsafe.write(data.ensureMove) proc read*(self: Arc[BaseChannel], res: var openArray[uint8]): int {.raises: [IOError].} = self.getMutUnsafe.read(res) proc flushRead*(self: Arc[BaseChannel]): int {.raises: [IOError].} = self.getMutUnsafe.flushRead() proc listen*(self: Arc[BaseChannel], cb: ChannelListener): ListenId {.gcsafe, raises: [].} = self.get.listenImpl(self, cb) @@ -137,13 +144,22 @@ proc close(self: ptr InMemoryChannel) {.gcsafe, raises: [].} = proc isOpen(self: ptr InMemoryChannel): bool = self.isOpen proc peek(self: ptr InMemoryChannel, to: Option[uint8] = uint8.none): int = if to.getSome(to): - self.data.find(to) + self.data.find(to, self.dataStart) - self.dataStart else: - self.data.len + self.data.len - self.dataStart proc write(self: ptr InMemoryChannel, data: openArray[uint8]) = if data.len > 0: - self.channel[].send(@data) + var buff = InMemoryBuffer(data: cast[ptr UncheckedArray[uint8]](alloc(data.len)), len: data.len) + copyMem(buff.data[0].addr, data[0].addr, data.len) + self.channel[].send(buff) + discard self.signal.fireSync() + +proc write(self: ptr InMemoryChannel, data: sink seq[uint8]) = + if data.len > 0: + var buff = InMemoryBuffer(data: cast[ptr UncheckedArray[uint8]](alloc(data.len)), len: data.len) + copyMem(buff.data[0].addr, data[0].addr, data.len) + self.channel[].send(buff) discard self.signal.fireSync() proc flushRead(self: ptr InMemoryChannel): int = @@ -152,17 +168,27 @@ proc flushRead(self: ptr InMemoryChannel): int = var (ok, data) = self.channel[].tryRecv() if not ok: break - self.data.add data.ensureMove + let oldLen = self.data.len + self.data.setLen(oldLen + data.len) + copyMem(self.data[oldLen].addr, data.data, data.len) + dealloc(data.data) except ValueError as e: echo "Failed to read memory channel: ", e.msg - return self.data.len + return self.data.len - self.dataStart proc read(self: ptr InMemoryChannel, res: var openArray[uint8]): int = discard self.flushRead() - if self.data.len > 0 and res.len > 0: - let toRead = min(self.data.len, res.len) - copyMem(res[0].addr, self.data[0].addr, toRead) - self.data = self.data[toRead..^1] + if self.data.len > self.dataStart and res.len > 0: + let toRead = min(self.data.len - self.dataStart, res.len) + copyMem(res[0].addr, self.data[self.dataStart].addr, toRead) + self.dataStart += toRead + if self.dataStart == self.data.len: + self.data.setLen(0) + self.dataStart = 0 + elif self.dataStart > (self.data.len - self.dataStart) * 5: + moveMem(self.data[0].addr, self.data[self.dataStart].addr, self.data.len - self.dataStart) + self.data.setLen(self.data.len - self.dataStart) + self.dataStart = 0 return toRead return 0 @@ -203,7 +229,7 @@ proc cloneAs*[B](self: Arc[B], T: typedesc[B]): Arc[T] = proc newInMemoryChannel*(): Arc[BaseChannel] = let signal = ThreadSignalPtr.new() - var channel = create(Channel[seq[uint8]]) + var channel = create(Channel[InMemoryBuffer]) channel[].open() var res = Arc[InMemoryChannel].new() @@ -216,6 +242,7 @@ proc newInMemoryChannel*(): Arc[BaseChannel] = isOpenImpl: proc(self: ptr BaseChannel): bool {.gcsafe, raises: [].} = isOpen(cast[ptr InMemoryChannel](self)), peekImpl: proc(self: ptr BaseChannel, to: Option[uint8]): int {.gcsafe, raises: [].} = peek(cast[ptr InMemoryChannel](self), to), writeImpl: proc(self: ptr BaseChannel, data: openArray[uint8]) {.gcsafe, raises: [IOError].} = write(cast[ptr InMemoryChannel](self), data), + writeSinkImpl: proc(self: ptr BaseChannel, data: sink seq[uint8]) {.gcsafe, raises: [IOError].} = write(cast[ptr InMemoryChannel](self), data.ensureMove), readImpl: proc(self: ptr BaseChannel, res: var openArray[uint8]): int {.gcsafe, raises: [IOError].} = read(cast[ptr InMemoryChannel](self), res), flushReadImpl: proc(self: ptr BaseChannel): int {.gcsafe, raises: [IOError].} = flushRead(cast[ptr InMemoryChannel](self)), listenImpl: proc(self: Arc[BaseChannel], cb: ChannelListener): ListenId {.gcsafe, raises: [].} = listenInMemoryChannel(self.cloneAs(InMemoryChannel), cb), diff --git a/src/misc/binary_encoder.nim b/src/misc/binary_encoder.nim index 5b5dc9a34..355c6e4fc 100644 --- a/src/misc/binary_encoder.nim +++ b/src/misc/binary_encoder.nim @@ -3,6 +3,9 @@ type BinaryEncoder* = object buffer*: seq[byte] +template toOpenArray*(self: BinaryEncoder): openArray[uint8] = + self.buffer.toOpenArray(0, self.buffer.high) + macro getByte(v: typed, i: static[int]): untyped = let shift = i * 8 result = genAst(v, shift): diff --git a/src/platform/gui_platform.nim b/src/platform/gui_platform.nim index 4f7307dd2..4e7358bdf 100644 --- a/src/platform/gui_platform.nim +++ b/src/platform/gui_platform.nim @@ -843,7 +843,7 @@ proc handleRenderCommand(platform: GuiPlatform, renderCommands: ptr RenderComman maskBounds.add(command.bounds + nodePos) of RenderCommandKind.ScissorEnd: if maskBounds.len == 0: - log lvlError, &"Unbalanced ScisscorStart/End pairs" + # log lvlError, &"Unbalanced ScisscorStart/End pairs" return let bounds = maskBounds.pop() platform.boxy.pushLayer() diff --git a/src/text/language/language_server_lsp.nim b/src/text/language/language_server_lsp.nim index 2d8380ad2..c95f22897 100644 --- a/src/text/language/language_server_lsp.nim +++ b/src/text/language/language_server_lsp.nim @@ -601,7 +601,7 @@ method getSymbols*(self: LanguageServerLSP, filename: string): Future[seq[Symbol location: (line: r.range.start.line, column: r.range.start.character), name: r.name, symbolType: r.kind.toInternalSymbolKind, - filename: self.localVfs.normalize(filename), + filename: filename, ) for child in r.children: @@ -609,7 +609,7 @@ method getSymbols*(self: LanguageServerLSP, filename: string): Future[seq[Symbol location: (line: child.range.start.line, column: child.range.start.character), name: child.name, symbolType: child.kind.toInternalSymbolKind, - filename: self.localVfs.normalize(filename), + filename: filename, ) diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index e5a5aed65..8f21fd730 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -957,7 +957,7 @@ proc updateSearchResultsAsync(self: TextDocumentEditor) {.async.} = return let searchResults = await findAllAsync(buffer.visibleText.slice(int), searchQuery) - if self.document.isNil: + if self.document.isNil or self.document.buffer.isNil: return self.searchResults = searchResults From 88132a41feb39fce1154b9eddb6c32fb9c4206b5 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Tue, 30 Sep 2025 19:19:20 +0200 Subject: [PATCH 18/47] Implement macros --- plugin_api/v0/registers.nim | 14 +++++++++++ plugins/vim/config.nims | 1 + plugins/vim/vim.nim | 39 +++++++++++++++++-------------- scripting/editor_api_wasm.nim | 9 ------- scripting/registers_api_wasm.nim | 10 ++++++++ src/app.nim | 18 -------------- src/generated/plugin_api_host.nim | 28 ++++++++++++++++++++++ src/plugin_api/plugin_api.nim | 5 ++++ src/register.nim | 23 +++++++++++++++++- wit/v0/api.wit | 2 ++ 10 files changed, 104 insertions(+), 45 deletions(-) diff --git a/plugin_api/v0/registers.nim b/plugin_api/v0/registers.nim index 90d500ae1..7713ef5ea 100644 --- a/plugin_api/v0/registers.nim +++ b/plugin_api/v0/registers.nim @@ -96,3 +96,17 @@ proc stopRecordingCommands*(register: WitString): void {.nodestroy.} = arg0 = 0.int32 arg1 = cast[int32](register.len) registersStopRecordingCommandsImported(arg0, arg1) + +proc registersReplayCommandsImported(a0: int32; a1: int32): void {. + wasmimport("replay-commands", "nev:plugins/registers").} +proc replayCommands*(register: WitString): void {.nodestroy.} = + ## todo + var + arg0: int32 + arg1: int32 + if register.len > 0: + arg0 = cast[int32](register[0].addr) + else: + arg0 = 0.int32 + arg1 = cast[int32](register.len) + registersReplayCommandsImported(arg0, arg1) diff --git a/plugins/vim/config.nims b/plugins/vim/config.nims index 4bf8df5dd..ec5b26355 100644 --- a/plugins/vim/config.nims +++ b/plugins/vim/config.nims @@ -1,5 +1,6 @@ include "../wasm.config.nims" switch("d", "pluginApiVersion=0") +switch("d", "witRebuild") switch("nimcache", "../../nimcache/vim") # Put custom build configs which shouldn't be commited in local.nims diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 4f0d1ae88..77f3e5a18 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -1266,25 +1266,30 @@ proc setSearchQueryOrAddCursor(editor: TextEditor) {.exposeActive(editorContext) # editor.addNextCheckpoint "insert" # editor.toggleLineComment() -# proc vimStartMacro(editor: TextEditor, name: string) {.exposeActive(editorContext, "vim-start-macro").} = -# if isReplayingCommands() or isRecordingCommands(getCurrentMacroRegister()): -# return -# setOption("editor.current-macro-register", name) -# setRegisterText(ws"", name) -# startRecordingCommands(name) - -# proc vimPlayMacro(editor: TextEditor, name: string) {.exposeActive(editorContext, "vim-play-macro").} = -# let register = if name == "@": -# getCurrentMacroRegister() -# else: -# name +proc startMacro(editor: TextEditor, name: string) {.exposeActive(editorContext).} = + if isReplayingCommands() or isRecordingCommands(getCurrentMacroRegister().stackWitString): + return + setSetting("editor.current-macro-register", name) + let name = name.stackWitString + setRegisterText(ws"", name) + startRecordingCommands(name) + +proc playMacro(editor: TextEditor, name: string) {.exposeActive(editorContext).} = + let register = if name == "@": + getCurrentMacroRegister() + else: + name -# replayCommands(register) + let text = getRegisterText(register.stackWitString) + replayCommands(register.stackWitString) -# proc vimStopMacro(editor: TextEditor) {.exposeActive(editorContext, "vim-stop-macro").} = -# if isReplayingCommands() or not isRecordingCommands(getCurrentMacroRegister()): -# return -# stopRecordingCommands(getCurrentMacroRegister()) +proc stopMacro(editor: TextEditor) {.exposeActive(editorContext).} = + if isReplayingCommands(): + return + let register = getCurrentMacroRegister().stackWitString + if isReplayingCommands() or not isRecordingCommands(register): + return + stopRecordingCommands(register) # proc vimInvertSelections(editor: TextEditor) {.exposeActive(editorContext, "vim-invert-selections").} = # editor.setSelections editor.selections.mapIt((it.last, it.first)) diff --git a/scripting/editor_api_wasm.nim b/scripting/editor_api_wasm.nim index 81192660d..75c4742f3 100644 --- a/scripting/editor_api_wasm.nim +++ b/scripting/editor_api_wasm.nim @@ -754,15 +754,6 @@ proc scriptSetCallback*(path: string; id: int) {.gcsafe, raises: [].} = argsJsonString.cstring) -proc editor_replayCommands_void_App_string_wasm(arg: cstring): cstring {.importc.} -proc replayCommands*(register: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add register.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_replayCommands_void_App_string_wasm( - argsJsonString.cstring) - - proc editor_replayKeys_void_App_string_wasm(arg: cstring): cstring {.importc.} proc replayKeys*(register: string) {.gcsafe, raises: [].} = var argsJson = newJArray() diff --git a/scripting/registers_api_wasm.nim b/scripting/registers_api_wasm.nim index 9b1363b23..2dae62744 100644 --- a/scripting/registers_api_wasm.nim +++ b/scripting/registers_api_wasm.nim @@ -108,3 +108,13 @@ proc isRecordingCommands*(registry: string): bool {.gcsafe, raises: [].} = except CatchableError: raiseAssert(getCurrentExceptionMsg()) + +proc registers_replayCommands_void_Registers_string_wasm(arg: cstring): cstring {. + importc.} +proc replayCommands*(register: string) {.gcsafe, raises: [].} = + var argsJson = newJArray() + argsJson.add register.toJson() + let argsJsonString = $argsJson + let res {.used.} = registers_replayCommands_void_Registers_string_wasm( + argsJsonString.cstring) + diff --git a/src/app.nim b/src/app.nim index a28ddcde7..004386118 100644 --- a/src/app.nim +++ b/src/app.nim @@ -3224,24 +3224,6 @@ proc scriptSetCallback*(path: string, id: int) {.expose("editor").} = return gEditor.plugins.callbacks[path] = id -proc replayCommands*(self: App, register: string) {.expose("editor").} = - if not self.registers.registers.contains(register) or self.registers.registers[register].kind != RegisterKind.Text: - log lvlError, fmt"No commands recorded in register '{register}'" - return - - if self.registers.bIsReplayingCommands: - log lvlError, fmt"replayCommands '{register}': Already replaying commands" - return - - log lvlInfo, &"replayCommands '{register}':\n{self.registers.registers[register].text}" - self.registers.bIsReplayingCommands = true - defer: - self.registers.bIsReplayingCommands = false - - for command in self.registers.registers[register].text.splitLines: - let (action, arg) = parseAction(command) - discard self.handleAction(action, arg, record=false) - proc replayKeys*(self: App, register: string) {.expose("editor").} = if not self.registers.registers.contains(register) or self.registers.registers[register].kind != RegisterKind.Text: log lvlError, fmt"No commands recorded in register '{register}'" diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index 2fe5e0af4..3f6413c88 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -633,6 +633,7 @@ proc registersStartRecordingCommands(instance: ptr InstanceData; register: sink string): void proc registersStopRecordingCommands(instance: ptr InstanceData; register: sink string): void +proc registersReplayCommands(instance: ptr InstanceData; register: sink string): void proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = block: let e = block: @@ -4147,3 +4148,30 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = registersStopRecordingCommands(instance, register) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/registers", "replay-commands", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var register: string + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[0].i32].addr) + register = newString(parameters[1].i32) + for i0 in 0 ..< register.len: + register[i0] = p0[i0] + registersReplayCommands(instance, register) + if e.isErr: + return e diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index 1f32c51a1..7e26fea3e 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -913,6 +913,11 @@ proc registersStopRecordingCommands(instance: ptr InstanceData; register: sink s return instance.host.registers.stopRecordingCommands(register) +proc registersReplayCommands(instance: ptr InstanceData; register: sink string): void = + if instance.host == nil: + return + instance.host.registers.replayCommands(register) + proc commandsDefineCommand(instance: ptr InstanceData, name: sink string, active: bool, docs: sink string, params: sink seq[(string, string)], returnType: sink string, context: sink string; fun: uint32; data: uint32): void = if instance.host == nil: diff --git a/src/register.nim b/src/register.nim index e78c535b9..e9da3653c 100644 --- a/src/register.nim +++ b/src/register.nim @@ -2,7 +2,7 @@ import std/[strutils, tables, options] import nimsumtree/rope import scripting/[expose] import misc/[custom_logger, custom_async, util, rope_utils, array_set] -import clipboard, service, dispatch_tables +import clipboard, service, dispatch_tables, command_service {.push gcsafe.} {.push raises: [].} @@ -98,6 +98,9 @@ proc getRegisterAsync*(self: Registers, register: string, res: ptr Register): Fu return false proc recordCommand*(self: Registers, command: string, args: string, registers: openArray[string] = []) = + if self.bIsReplayingCommands: + return + template iterate(commands: untyped) = for register in commands: if not self.registers.contains(register) or self.registers[register].kind != RegisterKind.Text: @@ -159,4 +162,22 @@ proc isReplayingKeys*(self: Registers): bool {.expose("registers").} = proc isRecordingCommands*(self: Registers, registry: string): bool {.expose("registers").} = self.recordingCommands.contains(registry) +proc replayCommands*(self: Registers, register: string) {.expose("registers").} = + if not self.registers.contains(register) or self.registers[register].kind != RegisterKind.Text: + log lvlError, fmt"No commands recorded in register '{register}'" + return + + if self.bIsReplayingCommands: + log lvlError, fmt"replayCommands '{register}': Already replaying commands" + return + + log lvlInfo, &"replayCommands '{register}':\n{self.registers[register].text}" + self.bIsReplayingCommands = true + defer: + self.bIsReplayingCommands = false + + let commandService = self.services.getService(CommandService).get + for command in self.registers[register].text.splitLines: + discard commandService.handleCommand(command) + addGlobalDispatchTable "registers", genDispatchTable("registers") diff --git a/wit/v0/api.wit b/wit/v0/api.wit index 5b76ff0f3..95cff576f 100644 --- a/wit/v0/api.wit +++ b/wit/v0/api.wit @@ -336,6 +336,8 @@ interface registers { start-recording-commands: func(register: string); /// todo stop-recording-commands: func(register: string); + /// todo + replay-commands: func(register: string); } interface text-document { From 8967036364b28a0350dce5f6a9fea5b743dc4ba9 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Fri, 3 Oct 2025 16:10:52 +0200 Subject: [PATCH 19/47] progress --- config/wasm/keybindings_plugin.wasm | Bin 1617060 -> 1617082 bytes plugin_api/v0/text_editor.nim | 27 ++++++++++++ plugins/vim/vim.nim | 61 ++++++++++++++++------------ src/generated/plugin_api_host.nim | 46 +++++++++++++++++++++ src/plugin_api/plugin_api.nim | 12 ++++++ wit/v0/api.wit | 4 ++ 6 files changed, 123 insertions(+), 27 deletions(-) diff --git a/config/wasm/keybindings_plugin.wasm b/config/wasm/keybindings_plugin.wasm index 1d67cb836f5fc44f2efd81de1749efb6f2f638f6..e934140fba9c5bb36508fdbf8b17872ec44d5737 100644 GIT binary patch delta 28924 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 delta 28792 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 c?~ 0: + arg1 = cast[int32](text[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](text.len) + arg3 = autoIndent + textEditorInsertTextImported(arg0, arg1, arg2, arg3) + proc textEditorSetSearchQueryFromMoveImported(a0: uint64; a1: int32; a2: int32; a3: int32; a4: int32; a5: int32; a6: int32; a7: int32; a8: int32): void {. wasmimport("set-search-query-from-move", "nev:plugins/text-editor").} diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 77f3e5a18..2a9daa2d3 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -920,17 +920,24 @@ proc paste(editor: TextEditor, pasteRight: bool = false, inclusiveEnd: bool = fa # editor.addNextCheckpoint "insert" # editor.unindent() -# proc vimAddCursorAbove(editor: TextEditor) {.exposeActive(editorContext, "vim-add-cursor-above").} = -# editor.addCursorAbove() -# editor.scrollToCursor() +proc addCursorAbove(editor: TextEditor) {.exposeActive(editorContext).} = + var selections = @(editor.getSelections.toOpenArray) + let newSelections = editor.multiMove(@@[selections.last], ws"line-up", 0, wrap=false, includeEol=false).mapIt(it.last.toSelection) + selections.add newSelections + editor.setSelections selections + editor.scrollToCursor() -# proc vimAddCursorBelow(editor: TextEditor) {.exposeActive(editorContext, "vim-add-cursor-below").} = -# editor.addCursorBelow() -# editor.scrollToCursor() +proc addCursorBelow(editor: TextEditor) {.exposeActive(editorContext).} = + var selections = @(editor.getSelections.toOpenArray) + let newSelections = editor.multiMove(@@[selections.last], ws"line-down", 0, wrap=false, includeEol=false).mapIt(it.last.toSelection) + selections.add newSelections + editor.setSelections selections + editor.scrollToCursor() -# proc vimEnter(editor: TextEditor) {.exposeActive(editorContext, "vim-enter").} = -# editor.addNextCheckpoint "insert" -# editor.insertText("\n") +proc enter(editor: TextEditor) {.exposeActive(editorContext).} = + debugf"vim.enter" + editor.addNextCheckpoint ws"insert" + editor.insertText ws("\n"), autoIndent=true proc yankLine(editor: TextEditor) {.exposeActive(editorContext).} = editor.vimState.selectLines = true @@ -1054,14 +1061,14 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont editor.setMode "vim-new.insert" editor.addNextCheckpoint ws"insert" -# proc vimInsertLineBelow(editor: TextEditor) {.exposeActive(editorContext, "vim-insert-line-below").} = -# editor.moveLast "line", Both -# editor.addNextCheckpoint "insert" -# editor.insertText "\n" -# editor.setMode "vim-new.insert" +proc insertLineBelow(editor: TextEditor) {.exposeActive(editorContext).} = + editor.setSelections editor.multiMove(editor.getSelections, ws"line", 0, wrap=false, includeEol=true).mapIt(it.last.toSelection) + editor.addNextCheckpoint ws"insert" + editor.insertText ws("\n"), autoIndent = true + editor.setMode "vim-new.insert" # proc vimInsertLineAbove(editor: TextEditor, move: string = "") {.exposeActive(editorContext, "vim-insert-line-above").} = -# editor.moveFirst "line", Both +# editor.moveFirst "line", sca.SelectionCursor.Both # editor.addNextCheckpoint "insert" # editor.insertText "\n", autoIndent=false # editor.vimMoveCursorLine -1 @@ -1253,7 +1260,7 @@ proc setSearchQueryOrAddCursor(editor: TextEditor) {.exposeActive(editorContext) # insertTexts.add left # insertTexts.add right -# editor.addNextCheckpoint "insert" +# editor.addNextCheckpoint ws"insert" # let newSelections = editor.insertMulti(insertSelections, insertTexts) # if newSelections.len mod 2 != 0: # return @@ -1262,9 +1269,9 @@ proc setSearchQueryOrAddCursor(editor: TextEditor) {.exposeActive(editorContext) # for i in 0.., register: string, inclusive-end: bool); /// todo auto-show-completions: func(editor: text-editor); + /// todo + toggle-line-comment: func(editor: text-editor); + /// todo + insert-text: func(editor: text-editor, text: string, auto-indent: bool); /// Set the search query by applying the given move to the current selection. /// 'prefix' and 'suffix' are appended to the string after escaping. From 875ee9398d3b4a8d6847024a622d07f9d2e62c4b Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Fri, 3 Oct 2025 17:53:37 +0200 Subject: [PATCH 20/47] Progress --- config/keybindings.json | 17 ++++- plugin_api/v0/editor.nim | 11 +-- plugin_api/v0/text_editor.nim | 34 +++++++-- plugin_api/v0/types.nim | 4 ++ plugins/vim/vim.nim | 116 ++++++++++++++++-------------- src/generated/plugin_api_host.nim | 116 +++++++++++++++++++++--------- src/plugin_api/plugin_api.nim | 20 +++++- src/text/text_editor.nim | 2 +- wit/v0/api.wit | 15 ++-- 9 files changed, 231 insertions(+), 104 deletions(-) diff --git a/config/keybindings.json b/config/keybindings.json index 116a52af8..e8305643a 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -12,6 +12,16 @@ "": "vim-exit-command-line", }, + "vim-new.command-line-low": { + "": "vim.exit-command-line", + "": "execute-command-line", + "": "select-previous-command-in-history", + "": "select-next-command-in-history", + }, + "vim-new.command-line-result-low": { + "": "vim.exit-command-line", + }, + "editor": { "": "quit", "": "escape", @@ -151,7 +161,8 @@ "": ["accept"], "": ["accept"], // "": ["cancel"], - "": ["vim-exit-popup"], + "": ["vim-exit-popup"], // todo: replace this with vim.exit-popup + "": ["vim.exit-popup"], // todo: remove this "": ["prev"], "": ["prev"], "": ["next"], @@ -733,6 +744,8 @@ "j": ["vim.move-direction", "visual-line-down", 1, "<#move.count>", false, false], "gm": ["move-cursor-line-center"], "gM": ["move-cursor-center"], + "n": ["vim.next-search-result", "<#move.count>"], + "N": ["vim.prev-search-result", "<#move.count>"], "0": ["vim.move-first", "line"], "": ["vim.move-first", "visual-line"], @@ -840,8 +853,6 @@ "/": ["vim.open-search-bar"], "gc": ["vim.toggle-line-comment"], "*": ["vim.set-search-query-from-word"], - "n": ["vim.next-search-result"], - "N": ["vim.prev-search-result"], ".": ["replay-commands", "."], "L": ["vim.select-word-or-add-cursor"], diff --git a/plugin_api/v0/editor.nim b/plugin_api/v0/editor.nim index 888a1048d..b42a76190 100644 --- a/plugin_api/v0/editor.nim +++ b/plugin_api/v0/editor.nim @@ -12,12 +12,15 @@ import import types -proc editorActiveEditorImported(a0: int32): void {. +proc editorActiveEditorImported(a0: uint8; a1: int32): void {. wasmimport("active-editor", "nev:plugins/editor").} -proc activeEditor*(): Option[Editor] {.nodestroy.} = +proc activeEditor*(options: ActiveEditorFlags): Option[Editor] {.nodestroy.} = ## Returns a handle for the currently active text editor. - var retArea: array[16, uint8] - editorActiveEditorImported(cast[int32](retArea[0].addr)) + var + retArea: array[16, uint8] + arg0: uint8 + arg0 = cast[uint8](options) + editorActiveEditorImported(arg0, cast[int32](retArea[0].addr)) if cast[ptr int64](retArea[0].addr)[] != 0: var temp: Editor temp.id = convert(cast[ptr uint64](retArea[8].addr)[], uint64) diff --git a/plugin_api/v0/text_editor.nim b/plugin_api/v0/text_editor.nim index 83eefe678..8c7e2313a 100644 --- a/plugin_api/v0/text_editor.nim +++ b/plugin_api/v0/text_editor.nim @@ -24,12 +24,16 @@ type Never = "never", Always = "always", MinDistanceOffscreen = "min-distance-offscreen", MinDistanceCenter = "min-distance-center" -proc textEditorActiveTextEditorImported(a0: int32): void {. +proc textEditorActiveTextEditorImported(a0: uint8; a1: int32): void {. wasmimport("active-text-editor", "nev:plugins/text-editor").} -proc activeTextEditor*(): Option[TextEditor] {.nodestroy.} = +proc activeTextEditor*(options: ActiveEditorFlags): Option[TextEditor] {. + nodestroy.} = ## Returns a handle for the currently active text editor. - var retArea: array[16, uint8] - textEditorActiveTextEditorImported(cast[int32](retArea[0].addr)) + var + retArea: array[16, uint8] + arg0: uint8 + arg0 = cast[uint8](options) + textEditorActiveTextEditorImported(arg0, cast[int32](retArea[0].addr)) if cast[ptr int64](retArea[0].addr)[] != 0: var temp: TextEditor temp.id = convert(cast[ptr uint64](retArea[8].addr)[], uint64) @@ -411,6 +415,28 @@ proc insertText*(editor: TextEditor; text: WitString; autoIndent: bool): void {. arg3 = autoIndent textEditorInsertTextImported(arg0, arg1, arg2, arg3) +proc textEditorOpenSearchBarImported(a0: uint64; a1: int32; a2: int32; a3: bool; + a4: bool): void {. + wasmimport("open-search-bar", "nev:plugins/text-editor").} +proc openSearchBar*(editor: TextEditor; query: WitString; scrollToPreview: bool; + selectResult: bool): void {.nodestroy.} = + ## todo + var + arg0: uint64 + arg1: int32 + arg2: int32 + arg3: bool + arg4: bool + arg0 = editor.id + if query.len > 0: + arg1 = cast[int32](query[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](query.len) + arg3 = scrollToPreview + arg4 = selectResult + textEditorOpenSearchBarImported(arg0, arg1, arg2, arg3, arg4) + proc textEditorSetSearchQueryFromMoveImported(a0: uint64; a1: int32; a2: int32; a3: int32; a4: int32; a5: int32; a6: int32; a7: int32; a8: int32): void {. wasmimport("set-search-query-from-move", "nev:plugins/text-editor").} diff --git a/plugin_api/v0/types.nim b/plugin_api/v0/types.nim index 3a9ecd4fc..423a6d8a4 100644 --- a/plugin_api/v0/types.nim +++ b/plugin_api/v0/types.nim @@ -25,6 +25,10 @@ type Rect* = object pos*: Vec2f size*: Vec2f + ActiveEditorFlag* = enum + IncludeCommandLine = "include-command-line", + IncludePopups = "include-popups" + ActiveEditorFlags* = set[ActiveEditorFlag] ## Shared reference to a rope. The rope data is stored in the editor, not in the plugin, so ropes ## can be used to efficiently access any document content or share a string with another plugin. ## Ropes are reference counted internally, and this resource also affects that reference count. diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 2a9daa2d3..e2526599d 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -7,6 +7,20 @@ import api from "../../src/scripting_api.nim" as sca import nil import "../../src/input_api.nim" +proc last*[T](list: WitList[T]): lent T = + assert list.len > 0 + return list[list.len - 1] + +proc `==`*[T](a, b: WitList[T]): bool = + if a.len != b.len: + return false + return equalMem(a.data, b.data, a.len) + +proc `==`*(a, b: WitString): bool = + if a.len != b.len: + return false + return equalMem(a.data, b.data, a.len) + var yankedLines: bool = false ## Whether the last thing we yanked was in a line mode type EditorVimState = object @@ -145,12 +159,9 @@ proc exposeImpl*(context: NimNode, name: string, fun: NimNode, active: bool): Ni return ws"" -macro expose*(name: string, fun: typed): untyped = - return exposeImpl(newLit"script", name.repr, fun, active=false) - -macro expose*(context, string: string, fun: typed): untyped = +macro command*(fun: typed): untyped = let name = fun.name.repr.splitCase.parts.joinCase(Kebab) - return exposeImpl(context, name, fun, active=false) + return exposeImpl(newLit(""), name, fun, active=false) macro exposeActive*(context: string, fun: typed): untyped = let name = fun.name.repr.splitCase.parts.joinCase(Kebab) @@ -1027,10 +1038,8 @@ proc moveFileEnd(editor: TextEditor, count: int = 1) {.exposeActive(editorContex # editor.moveFirst "line-no-indent" # editor.centerCursor() -# proc vimCenterLine(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-center-line").} = -# if editor.getCommandCount != 0: -# editor.setSelection (editor.getCommandCount, editor.selection.last.column).toSelection -# editor.centerCursor() +proc centerLine(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = + editor.scrollToCursor(ScrollBehaviour.CenterAlways.some) # proc vimScrollLineToBottomAndMoveLineStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-scroll-line-to-bottom-and-move-line-start").} = # if editor.getCommandCount != 0: @@ -1064,61 +1073,64 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont proc insertLineBelow(editor: TextEditor) {.exposeActive(editorContext).} = editor.setSelections editor.multiMove(editor.getSelections, ws"line", 0, wrap=false, includeEol=true).mapIt(it.last.toSelection) editor.addNextCheckpoint ws"insert" - editor.insertText ws("\n"), autoIndent = true + editor.insertText ws("\n"), autoIndent=true editor.setMode "vim-new.insert" -# proc vimInsertLineAbove(editor: TextEditor, move: string = "") {.exposeActive(editorContext, "vim-insert-line-above").} = -# editor.moveFirst "line", sca.SelectionCursor.Both -# editor.addNextCheckpoint "insert" -# editor.insertText "\n", autoIndent=false -# editor.vimMoveCursorLine -1 -# editor.setMode "vim-new.insert" +proc insertLineAbove(editor: TextEditor, move: string = "") {.exposeActive(editorContext).} = + editor.setSelections editor.multiMove(editor.getSelections, ws"line", 0, wrap=false, includeEol=true).mapIt(it.first.toSelection) + editor.addNextCheckpoint ws"insert" + editor.insertText ws("\n"), autoIndent=false + editor.moveDirection("line-up", 1, 1, false, false) + editor.setMode "vim-new.insert" -# proc vimSetSearchQueryFromWord(editor: TextEditor) {.exposeActive(editorContext, "vim-set-search-query-from-word").} = -# editor.setSelection editor.setSearchQueryFromMove("word", prefix=r"\b", suffix=r"\b").first.toSelection +proc setSearchQueryFromWord(editor: TextEditor) {.exposeActive(editorContext).} = + editor.setSelection editor.setSearchQueryFromMove(ws"word", 1, prefix=ws(r"\b"), suffix=ws(r"\b")).first.toSelection -# proc vimSetSearchQueryFromSelection(editor: TextEditor) {.exposeActive(editorContext, "vim-set-search-query-from-selection").} = -# discard editor.setSearchQuery(editor.getText(editor.selection, inclusiveEnd=true), escapeRegex=true) -# editor.setSelection editor.selection.first.toSelection -# editor.setMode("vim-new.normal") +proc setSearchQueryFromSelection(editor: TextEditor) {.exposeActive(editorContext).} = + let content = editor.content.sliceSelection(editor.getSelection, inclusive=true) + discard editor.setSearchQuery(content.text, escapeRegex=true, prefix=ws"", suffix=ws"") + editor.setSelection editor.getSelection.first.toSelection + editor.normalMode() -# proc vimNextSearchResult(editor: TextEditor) {.exposeActive(editorContext, "vim-next-search-result").} = -# editor.setSelection editor.getNextFindResult(editor.selection.last).first.toSelection -# editor.scrollToCursor() -# editor.setNextSnapBehaviour(MinDistanceOffscreen) -# editor.updateTargetColumn() +proc nextSearchResult(editor: TextEditor, count: int = 0) {.exposeActive(editorContext).} = + let selections = editor.getSelections + let next = editor.multiMove(selections, ws"next-search-result", count, wrap = true, includeEol=false) + let newSelections = mergeSelections(selections, next): + (it1.first, it2.first).toSelection -# proc vimPrevSearchResult(editor: TextEditor) {.exposeActive(editorContext, "vim-prev-search-result").} = -# editor.setSelection editor.getPrevFindResult(editor.selection.last).first.toSelection -# editor.scrollToCursor() -# editor.setNextSnapBehaviour(MinDistanceOffscreen) -# editor.updateTargetColumn() + editor.setSelections @@newSelections + editor.scrollToCursor() + editor.setNextSnapBehaviour(MinDistanceOffscreen) + editor.updateTargetColumn() -# proc vimOpenSearchBar(editor: TextEditor) {.exposeActive(editorContext, "vim-open-search-bar").} = -# editor.openSearchBar() -# if getActiveEditor().isTextEditor(editor): -# editor.setMode("vim-new.insert") +proc prevSearchResult(editor: TextEditor, count: int = 0) {.exposeActive(editorContext).} = + let selections = editor.getSelections + let prev = editor.multiMove(selections, ws"prev-search-result", count, wrap = true, includeEol=false) + let newSelections = mergeSelections(selections, prev): + (it1.first, it2.first).toSelection -# proc vimExitCommandLine() {.expose("vim-exit-command-line").} = -# if getActiveEditor().isTextEditor(editor): -# if editor.mode == "vim-new.normal": -# exitCommandLine() -# return + editor.setSelections @@newSelections + editor.scrollToCursor() + editor.setNextSnapBehaviour(MinDistanceOffscreen) + editor.updateTargetColumn() -# editor.setMode("vim-new.normal") +proc openSearchBar(editor: TextEditor) {.exposeActive(editorContext).} = + editor.openSearchBar(ws"", scrollToPreview=true, selectResult=true) -# proc vimExitPopup() {.expose("vim-exit-popup").} = -# if getActiveEditor().isTextEditor(editor): -# if editor.mode == "vim-new.normal": -# if getActivePopup().isSelectorPopup(popup): -# popup.cancel() -# return +proc exitCommandLine() {.command.} = + if activeTextEditor({IncludeCommandLine}).getSome(editor): + if editor.mode == ws"vim-new.normal": + discard runCommand(ws"exit-command-line", ws"") + return -# editor.setMode("vim-new.normal") + editor.setMode("vim-new.normal") -proc last*[T](list: WitList[T]): lent T = - assert list.len > 0 - return list[list.len - 1] +proc exitPopup() {.command.} = + if activeTextEditor({IncludePopups}).getSome(editor) and editor.mode != ws"vim-new.normal": + editor.setMode("vim-new.normal") + return + + discard runCommand(ws"close-active-view", ws"") proc selectWordOrAddCursor(editor: TextEditor) {.exposeActive(editorContext).} = let selections = editor.selections diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index 3cb468f68..fc33cd0aa 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -25,6 +25,10 @@ type Rect* = object pos*: Vec2f size*: Vec2f + ActiveEditorFlag* = enum + IncludeCommandLine = "include-command-line", + IncludePopups = "include-popups" + ActiveEditorFlags* = set[ActiveEditorFlag] ## Shared reference to a rope. The rope data is stored in the editor, not in the plugin, so ropes ## can be used to efficiently access any document content or share a string with another plugin. ## Ropes are reference counted internally, and this resource also affects that reference count. @@ -116,47 +120,47 @@ proc collectExports*(funcs: var ExportedFuncs; instance: InstanceT; funcs.mStackAlloc = instance.getExport(context, "mem_stack_alloc") funcs.mStackSave = instance.getExport(context, "mem_stack_save") funcs.mStackRestore = instance.getExport(context, "mem_stack_restore") - let f_9697232495 = instance.getExport(context, "init_plugin") - if f_9697232495.isSome: - assert f_9697232495.get.kind == WASMTIME_EXTERN_FUNC - funcs.initPlugin = f_9697232495.get.of_field.func_field + let f_9697232496 = instance.getExport(context, "init_plugin") + if f_9697232496.isSome: + assert f_9697232496.get.kind == WASMTIME_EXTERN_FUNC + funcs.initPlugin = f_9697232496.get.of_field.func_field else: echo "Failed to find exported function \'", "init_plugin", "\'" - let f_9697232511 = instance.getExport(context, "handle_command") - if f_9697232511.isSome: - assert f_9697232511.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleCommand = f_9697232511.get.of_field.func_field + let f_9697232512 = instance.getExport(context, "handle_command") + if f_9697232512.isSome: + assert f_9697232512.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleCommand = f_9697232512.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_command", "\'" - let f_9697232561 = instance.getExport(context, "handle_mode_changed") - if f_9697232561.isSome: - assert f_9697232561.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleModeChanged = f_9697232561.get.of_field.func_field - else: - echo "Failed to find exported function \'", "handle_mode_changed", "\'" - let f_9697232562 = instance.getExport(context, "handle_view_render_callback") + let f_9697232562 = instance.getExport(context, "handle_mode_changed") if f_9697232562.isSome: assert f_9697232562.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleViewRenderCallback = f_9697232562.get.of_field.func_field + funcs.handleModeChanged = f_9697232562.get.of_field.func_field + else: + echo "Failed to find exported function \'", "handle_mode_changed", "\'" + let f_9697232563 = instance.getExport(context, "handle_view_render_callback") + if f_9697232563.isSome: + assert f_9697232563.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleViewRenderCallback = f_9697232563.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_view_render_callback", "\'" - let f_9697232586 = instance.getExport(context, "handle_channel_update") - if f_9697232586.isSome: - assert f_9697232586.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleChannelUpdate = f_9697232586.get.of_field.func_field - else: - echo "Failed to find exported function \'", "handle_channel_update", "\'" - let f_9697232587 = instance.getExport(context, "notify_task_complete") + let f_9697232587 = instance.getExport(context, "handle_channel_update") if f_9697232587.isSome: assert f_9697232587.get.kind == WASMTIME_EXTERN_FUNC - funcs.notifyTaskComplete = f_9697232587.get.of_field.func_field + funcs.handleChannelUpdate = f_9697232587.get.of_field.func_field else: - echo "Failed to find exported function \'", "notify_task_complete", "\'" - let f_9697232588 = instance.getExport(context, "handle_move") + echo "Failed to find exported function \'", "handle_channel_update", "\'" + let f_9697232588 = instance.getExport(context, "notify_task_complete") if f_9697232588.isSome: assert f_9697232588.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleMove = f_9697232588.get.of_field.func_field + funcs.notifyTaskComplete = f_9697232588.get.of_field.func_field + else: + echo "Failed to find exported function \'", "notify_task_complete", "\'" + let f_9697232589 = instance.getExport(context, "handle_move") + if f_9697232589.isSome: + assert f_9697232589.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleMove = f_9697232589.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_move", "\'" @@ -444,10 +448,12 @@ proc typesLineLength(instance: ptr InstanceData; self: var RopeResource; line: int64): int64 proc typesRuneAt(instance: ptr InstanceData; self: var RopeResource; a: Cursor): Rune proc typesByteAt(instance: ptr InstanceData; self: var RopeResource; a: Cursor): uint8 -proc editorActiveEditor(instance: ptr InstanceData): Option[Editor] +proc editorActiveEditor(instance: ptr InstanceData; options: ActiveEditorFlags): Option[ + Editor] proc editorGetDocument(instance: ptr InstanceData; editor: Editor): Option[ Document] -proc textEditorActiveTextEditor(instance: ptr InstanceData): Option[TextEditor] +proc textEditorActiveTextEditor(instance: ptr InstanceData; + options: ActiveEditorFlags): Option[TextEditor] proc textEditorGetDocument(instance: ptr InstanceData; editor: TextEditor): Option[ TextDocument] proc textEditorAsTextEditor(instance: ptr InstanceData; editor: Editor): Option[ @@ -496,6 +502,9 @@ proc textEditorAutoShowCompletions(instance: ptr InstanceData; proc textEditorToggleLineComment(instance: ptr InstanceData; editor: TextEditor): void proc textEditorInsertText(instance: ptr InstanceData; editor: TextEditor; text: sink string; autoIndent: bool): void +proc textEditorOpenSearchBar(instance: ptr InstanceData; editor: TextEditor; + query: sink string; scrollToPreview: bool; + selectResult: bool): void proc textEditorSetSearchQueryFromMove(instance: ptr InstanceData; editor: TextEditor; move: sink string; count: int32; prefix: sink string; @@ -1244,7 +1253,8 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = return e block: let e = block: - var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I32], []) + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32], []) linker.defineFuncUnchecked("nev:plugins/editor", "active-editor", ty): var instance = cast[ptr InstanceData](store.getData()) var mainMemory = caller.getExport("memory") @@ -1259,7 +1269,9 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = mainMemory.get.of_field.memory.addr)) else: assert false - let res = editorActiveEditor(instance) + var options: ActiveEditorFlags + options = cast[ActiveEditorFlags](parameters[0].i32) + let res = editorActiveEditor(instance, options) let retArea = parameters[^1].i32 cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int64 if res.isSome: @@ -1295,7 +1307,8 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = return e block: let e = block: - var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I32], []) + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32], []) linker.defineFuncUnchecked("nev:plugins/text-editor", "active-text-editor", ty): var instance = cast[ptr InstanceData](store.getData()) @@ -1311,7 +1324,9 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = mainMemory.get.of_field.memory.addr)) else: assert false - let res = textEditorActiveTextEditor(instance) + var options: ActiveEditorFlags + options = cast[ActiveEditorFlags](parameters[0].i32) + let res = textEditorActiveTextEditor(instance, options) let retArea = parameters[^1].i32 cast[ptr int64](memory[retArea + 0].addr)[] = res.isSome.int64 if res.isSome: @@ -1976,6 +1991,41 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = textEditorInsertText(instance, editor, text, autoIndent) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "open-search-bar", + ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var editor: TextEditor + var query: string + var scrollToPreview: bool + var selectResult: bool + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[1].i32].addr) + query = newString(parameters[2].i32) + for i0 in 0 ..< query.len: + query[i0] = p0[i0] + scrollToPreview = parameters[3].i32.bool + selectResult = parameters[4].i32.bool + textEditorOpenSearchBar(instance, editor, query, scrollToPreview, + selectResult) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index dc9f4ec5a..95a045f04 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -7,7 +7,7 @@ import text/[text_editor, text_document] import render_view, view import platform/platform, platform_service import config_provider, command_service -import plugin_service, document_editor, vfs, vfs_service, channel, register, terminal_service, move_database +import plugin_service, document_editor, vfs, vfs_service, channel, register, terminal_service, move_database, popup import wasmtime, wit_host_module, plugin_api_base, wasi, plugin_thread_pool from scripting_api as sca import nil @@ -377,9 +377,13 @@ converter toInternal(flags: ReadFlags): set[vfs.ReadFlag] = ###################################### API implementations ##################################### -proc editorActiveEditor(instance: ptr InstanceData): Option[Editor] = +proc editorActiveEditor(instance: ptr InstanceData, options: ActiveEditorFlags): Option[Editor] = if instance.host == nil: return + if ActiveEditorFlag.IncludeCommandLine in options and instance.host.commands.commandLineMode(): + return Editor(id: instance.host.commands.commandLineEditor.id.uint64).some + if ActiveEditorFlag.IncludePopups in options and instance.host.layout.popups.len > 0 and instance.host.layout.popups.last.getActiveEditor().getSome(editor): + return Editor(id: editor.id.uint64).some if instance.host.layout.tryGetCurrentEditorView().getSome(view): return Editor(id: view.editor.DocumentEditor.id.uint64).some return Editor.none @@ -393,9 +397,13 @@ proc editorGetDocument(instance: ptr InstanceData; editor: Editor): Option[Docum return Document(id: document.id.uint64).some return Document.none -proc textEditorActiveTextEditor(instance: ptr InstanceData): Option[TextEditor] = +proc textEditorActiveTextEditor(instance: ptr InstanceData, options: ActiveEditorFlags): Option[TextEditor] = if instance.host == nil: return + if ActiveEditorFlag.IncludeCommandLine in options and instance.host.commands.commandLineMode() and instance.host.commands.commandLineEditor of TextDocumentEditor: + return TextEditor(id: instance.host.commands.commandLineEditor.id.uint64).some + if ActiveEditorFlag.IncludePopups in options and instance.host.layout.popups.len > 0 and instance.host.layout.popups.last.getActiveEditor().getSome(editor) and editor of TextDocumentEditor: + return TextEditor(id: editor.id.uint64).some if instance.host.layout.tryGetCurrentEditorView().getSome(view) and view.editor of TextDocumentEditor: return TextEditor(id: view.editor.TextDocumentEditor.id.uint64).some return TextEditor.none @@ -715,6 +723,12 @@ proc textEditorInsertText(instance: ptr InstanceData, editor: TextEditor, text: if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: editor.TextDocumentEditor.insertText(text, autoIndent) +proc textEditorOpenSearchBar(instance: ptr InstanceData; editor: TextEditor; query: sink string; scrollToPreview: bool; selectResult: bool): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.openSearchBar(query, scrollToPreview, selectResult) + proc typesNewRope(instance: ptr InstanceData, content: sink string): RopeResource = return RopeResource(rope: createRope(content).slice().suffix(Point())) diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index 2bf4eb290..eec45245e 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -958,7 +958,7 @@ proc updateSearchResultsAsync(self: TextDocumentEditor) {.async.} = return let searchResults = await findAllAsync(buffer.visibleText.slice(int), searchQuery) - if self.document.isNil or self.document.buffer.isNil: + if self.document.isNil: return self.searchResults = searchResults diff --git a/wit/v0/api.wit b/wit/v0/api.wit index 7acde7d75..fc4c7203d 100644 --- a/wit/v0/api.wit +++ b/wit/v0/api.wit @@ -24,6 +24,11 @@ interface types { size: vec2f, } + flags active-editor-flags { + include-command-line, + include-popups, + } + /// Shared reference to a rope. The rope data is stored in the editor, not in the plugin, so ropes /// can be used to efficiently access any document content or share a string with another plugin. /// Ropes are reference counted internally, and this resource also affects that reference count. @@ -194,15 +199,15 @@ interface vfs { } interface editor { - use types.{editor, document}; + use types.{editor, document, active-editor-flags}; /// Returns a handle for the currently active text editor. - active-editor: func() -> option; + active-editor: func(options: active-editor-flags) -> option; /// Returns the document the given editor is currently editing. get-document: func(editor: editor) -> option; } interface text-editor { - use types.{editor, document, text-editor, text-document, cursor, selection, rope}; + use types.{editor, document, text-editor, text-document, cursor, selection, rope, active-editor-flags}; use commands.{command-error}; enum scroll-behaviour { @@ -221,7 +226,7 @@ interface text-editor { } /// Returns a handle for the currently active text editor. - active-text-editor: func() -> option; + active-text-editor: func(options: active-editor-flags) -> option; /// Returns the text document the given editor is currently editing. get-document: func(editor: text-editor) -> option; /// Try to cast the given editor handle to a text editor handle. @@ -282,6 +287,8 @@ interface text-editor { toggle-line-comment: func(editor: text-editor); /// todo insert-text: func(editor: text-editor, text: string, auto-indent: bool); + /// todo + open-search-bar: func(editor: text-editor, query: string, scroll-to-preview: bool, select-result: bool); /// Set the search query by applying the given move to the current selection. /// 'prefix' and 'suffix' are appended to the string after escaping. From 9d44f1f4b5b8aa608500d5c8597a8718c7feea9a Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Fri, 3 Oct 2025 18:11:46 +0200 Subject: [PATCH 21/47] Fixed compile error in test plugin --- plugins/test_plugin/test_plugin.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/test_plugin/test_plugin.nim b/plugins/test_plugin/test_plugin.nim index c993ccdd5..5146c88ed 100644 --- a/plugins/test_plugin/test_plugin.nim +++ b/plugins/test_plugin/test_plugin.nim @@ -217,7 +217,7 @@ defineCommand(ws"test-command-5", context = ws"", data = 0): proc(data: uint32, args: WitString): WitString {.cdecl.} = - if activeTextEditor().getSome(editor): + if activeTextEditor({}).getSome(editor): let s = editor.getSelection echo editor.id echo "========== ", s @@ -275,7 +275,7 @@ defineCommand(ws"test-start-process", context = ws"", data = 0): proc(data: uint32, args: WitString): WitString {.cdecl.} = - if activeTextEditor().getSome(editor): + if activeTextEditor({}).getSome(editor): var p = processStart(ws"powershell", @@[ws"-Command", ws"ls", ws"plugins"]) let stdout = p.stdout() From b6d3c5d1fa70e9c0e05952f33e505baafb754f21 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sat, 18 Oct 2025 13:29:48 +0200 Subject: [PATCH 22/47] Progress --- config/keybindings.json | 4 +-- plugins/vim/vim.nim | 70 ++++++++++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/config/keybindings.json b/config/keybindings.json index e8305643a..3c973c162 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -746,6 +746,8 @@ "gM": ["move-cursor-center"], "n": ["vim.next-search-result", "<#move.count>"], "N": ["vim.prev-search-result", "<#move.count>"], + "gf": ["vim.goto-next-change", "<#move.count>"], + "gh": ["vim.goto-prev-change", "<#move.count>"], "0": ["vim.move-first", "line"], "": ["vim.move-first", "visual-line"], @@ -876,8 +878,6 @@ "": ["select-prev-tab-stop"], "gt": ["vim.goto-next-diagnostic"], "gn": ["vim.goto-prev-diagnostic"], - "gf": ["vim.goto-next-change"], - "gh": ["vim.goto-prev-change"], // copy/paste "p": ["vim.paste", true, false, "vim.default-register"], diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index e2526599d..7332671b1 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -1114,6 +1114,50 @@ proc prevSearchResult(editor: TextEditor, count: int = 0) {.exposeActive(editorC editor.setNextSnapBehaviour(MinDistanceOffscreen) editor.updateTargetColumn() +proc gotoNextChange(editor: TextEditor, count: int = 0) {.exposeActive(editorContext).} = + let selections = editor.getSelections + let next = editor.multiMove(selections, ws"next-change", count, wrap = true, includeEol=false) + let newSelections = mergeSelections(selections, next): + (it1.first, it2.first).toSelection + + editor.setSelections @@newSelections + editor.scrollToCursor() + editor.setNextSnapBehaviour(MinDistanceOffscreen) + editor.updateTargetColumn() + +proc gotoPrevChange(editor: TextEditor, count: int = 0) {.exposeActive(editorContext).} = + let selections = editor.getSelections + let prev = editor.multiMove(selections, ws"prev-change", count, wrap = true, includeEol=false) + let newSelections = mergeSelections(selections, prev): + (it1.first, it2.first).toSelection + + editor.setSelections @@newSelections + editor.scrollToCursor() + editor.setNextSnapBehaviour(MinDistanceOffscreen) + editor.updateTargetColumn() + +proc gotoNextDiagnostic(editor: TextEditor, count: int = 0) {.exposeActive(editorContext).} = + let selections = editor.getSelections + let next = editor.multiMove(selections, ws"next-diagnostic", count, wrap = true, includeEol=false) + let newSelections = mergeSelections(selections, next): + (it1.first, it2.first).toSelection + + editor.setSelections @@newSelections + editor.scrollToCursor() + editor.setNextSnapBehaviour(MinDistanceOffscreen) + editor.updateTargetColumn() + +proc gotoPrevDiagnostic(editor: TextEditor, count: int = 0) {.exposeActive(editorContext).} = + let selections = editor.getSelections + let prev = editor.multiMove(selections, ws"prev-diagnostic", count, wrap = true, includeEol=false) + let newSelections = mergeSelections(selections, prev): + (it1.first, it2.first).toSelection + + editor.setSelections @@newSelections + editor.scrollToCursor() + editor.setNextSnapBehaviour(MinDistanceOffscreen) + editor.updateTargetColumn() + proc openSearchBar(editor: TextEditor) {.exposeActive(editorContext).} = editor.openSearchBar(ws"", scrollToPreview=true, selectResult=true) @@ -1427,32 +1471,6 @@ proc stopMacro(editor: TextEditor) {.exposeActive(editorContext).} = # editor.evaluateExpressions(editor.selections, false, addSelectionIndex = true) # editor.setSelections editor.selections.mapIt(editor.doMoveCursorColumn(it.last, -1).toSelection) -# proc vimGotoNextDiagnostic(editor: TextEditor) {.exposeActive(editorContext, "vim-goto-next-diagnostic").} = -# let severity = getSetting("text.jump-diagnostic-severity", 1) -# editor.setSelection editor.getNextDiagnostic(editor.selection.last, severity).first.toSelection -# editor.scrollToCursor() -# editor.updateTargetColumn() -# editor.setNextSnapBehaviour(MinDistanceOffscreen) - -# proc vimGotoPrevDiagnostic(editor: TextEditor) {.exposeActive(editorContext, "vim-goto-prev-diagnostic").} = -# let severity = getSetting("text.jump-diagnostic-severity", 1) -# editor.setSelection editor.getPrevDiagnostic(editor.selection.last, severity).first.toSelection -# editor.scrollToCursor() -# editor.updateTargetColumn() -# editor.setNextSnapBehaviour(MinDistanceOffscreen) - -# proc vimGotoNextChange(editor: TextEditor) {.exposeActive(editorContext, "vim-goto-next-change").} = -# editor.setSelection editor.getNextChange(editor.selection.last).first.toSelection -# editor.scrollToCursor() -# editor.centerCursor() -# editor.setNextSnapBehaviour(MinDistanceOffscreen) - -# proc vimGotoPrevChange(editor: TextEditor) {.exposeActive(editorContext, "vim-goto-prev-change").} = -# editor.setSelection editor.getPrevChange(editor.selection.last).first.toSelection -# editor.scrollToCursor() -# editor.centerCursor() -# editor.setNextSnapBehaviour(MinDistanceOffscreen) - proc replaceInputHandler(editor: TextEditor, input: string) {.exposeActive(editorContext).} = editor.vimReplace(input) From be96ba416d8755b1cda6d692abb52f9c2d0811be Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sat, 18 Oct 2025 14:28:53 +0200 Subject: [PATCH 23/47] Implement expression evaluation commands --- plugin_api/v0/text_editor.nim | 38 ++++++++++++++++++ plugins/vim/vim.nim | 67 ++++++++++++++++--------------- src/generated/plugin_api_host.nim | 60 +++++++++++++++++++++++++++ src/plugin_api/plugin_api.nim | 6 +++ src/text/text_editor.nim | 43 +++++++++++++++++++- wit/v0/api.wit | 3 ++ 6 files changed, 183 insertions(+), 34 deletions(-) diff --git a/plugin_api/v0/text_editor.nim b/plugin_api/v0/text_editor.nim index 8c7e2313a..9d66da3fd 100644 --- a/plugin_api/v0/text_editor.nim +++ b/plugin_api/v0/text_editor.nim @@ -729,6 +729,44 @@ proc setSettingRaw*(editor: TextEditor; name: WitString; value: WitString): void arg4 = cast[int32](value.len) textEditorSetSettingRawImported(arg0, arg1, arg2, arg3, arg4) +proc textEditorEvaluateExpressionsImported(a0: uint64; a1: int32; a2: int32; + a3: bool; a4: int32; a5: int32; a6: int32; a7: int32; a8: bool): void {. + wasmimport("evaluate-expressions", "nev:plugins/text-editor").} +proc evaluateExpressions*(editor: TextEditor; selections: WitList[Selection]; + inclusive: bool; prefix: WitString; suffix: WitString; + addSelectionIndex: bool): void {.nodestroy.} = + ## todo + var + arg0: uint64 + arg1: int32 + arg2: int32 + arg3: bool + arg4: int32 + arg5: int32 + arg6: int32 + arg7: int32 + arg8: bool + arg0 = editor.id + if selections.len > 0: + arg1 = cast[int32](selections[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](selections.len) + arg3 = inclusive + if prefix.len > 0: + arg4 = cast[int32](prefix[0].addr) + else: + arg4 = 0.int32 + arg5 = cast[int32](prefix.len) + if suffix.len > 0: + arg6 = cast[int32](suffix[0].addr) + else: + arg6 = 0.int32 + arg7 = cast[int32](suffix.len) + arg8 = addSelectionIndex + textEditorEvaluateExpressionsImported(arg0, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8) + proc textEditorEditImported(a0: uint64; a1: int32; a2: int32; a3: int32; a4: int32; a5: bool; a6: int32): void {. wasmimport("edit", "nev:plugins/text-editor").} diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 7332671b1..f60437f89 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -1437,40 +1437,41 @@ proc stopMacro(editor: TextEditor) {.exposeActive(editorContext).} = # editor.setNextSnapBehaviour(MinDistanceOffscreen) # editor.updateTargetColumn() -# proc vimEvaluateSelection(editor: TextEditor) {.exposeActive(editorContext, "vim-evaluate-selection").} = -# editor.addNextCheckpoint(ws"insert") -# editor.evaluateExpressions(editor.selections, true) - -# proc vimIncrementSelection(editor: TextEditor) {.exposeActive(editorContext, "vim-increment-selection").} = -# editor.addNextCheckpoint(ws"insert") -# editor.evaluateExpressions(editor.selections, true, suffix = "+1") - -# proc vimDecrementSelection(editor: TextEditor) {.exposeActive(editorContext, "vim-decrement-selection").} = -# editor.addNextCheckpoint(ws"insert") -# editor.evaluateExpressions(editor.selections, true, suffix = "-1") - -# proc vimIncrementSelectionByIndex(editor: TextEditor) {.exposeActive(editorContext, "vim-increment-selection-by-index").} = -# editor.addNextCheckpoint(ws"insert") -# editor.evaluateExpressions(editor.selections, true, addSelectionIndex = true) - -# proc vimIncrement(editor: TextEditor) {.exposeActive(editorContext, "vim-increment").} = -# editor.setSelections editor.selections.mapIt(editor.getSelectionForMove(it.last, "number")) -# editor.addNextCheckpoint(ws"insert") -# editor.evaluateExpressions(editor.selections, false, suffix = "+1") -# editor.setSelections editor.selections.mapIt(editor.doMoveCursorColumn(it.last, -1).toSelection) - -# proc vimDecrement(editor: TextEditor) {.exposeActive(editorContext, "vim-decrement").} = -# editor.setSelections editor.selections.mapIt(editor.getSelectionForMove(it.last, "number")) -# editor.addNextCheckpoint(ws"insert") -# editor.evaluateExpressions(editor.selections, false, suffix = "-1") -# editor.setSelections editor.selections.mapIt(editor.doMoveCursorColumn(it.last, -1).toSelection) - -# proc vimIncrementByIndex(editor: TextEditor) {.exposeActive(editorContext, "vim-increment-by-index").} = -# editor.setSelections editor.selections.mapIt(editor.getSelectionForMove(it.last, "number")) -# editor.addNextCheckpoint(ws"insert") -# editor.evaluateExpressions(editor.selections, false, addSelectionIndex = true) -# editor.setSelections editor.selections.mapIt(editor.doMoveCursorColumn(it.last, -1).toSelection) +proc evaluateSelection(editor: TextEditor) {.exposeActive(editorContext).} = + editor.addNextCheckpoint(ws"insert") + editor.evaluateExpressions(editor.selections, true, prefix = ws"", suffix = ws"", addSelectionIndex = false) + +proc incrementSelection(editor: TextEditor) {.exposeActive(editorContext).} = + editor.addNextCheckpoint(ws"insert") + editor.evaluateExpressions(editor.selections, true, prefix = ws"", suffix = ws"+1", addSelectionIndex = false) + +proc decrementSelection(editor: TextEditor) {.exposeActive(editorContext).} = + editor.addNextCheckpoint(ws"insert") + editor.evaluateExpressions(editor.selections, true, prefix = ws"", suffix = ws"-1", addSelectionIndex = false) + +proc incrementSelectionByIndex(editor: TextEditor) {.exposeActive(editorContext).} = + editor.addNextCheckpoint(ws"insert") + editor.evaluateExpressions(editor.selections, true, prefix = ws"", suffix = ws"", addSelectionIndex = true) +proc increment(editor: TextEditor) {.exposeActive(editorContext).} = + editor.addNextCheckpoint(ws"insert") + editor.setSelections editor.multiMove(editor.selections, ws"number", 1, wrap = false, includeEol = true) + editor.evaluateExpressions(editor.selections, false, prefix = ws"", suffix = ws"+1", addSelectionIndex = false) + editor.setSelections editor.multiMove(editor.selections, ws"column", -1, wrap = false, includeEol = true).mapIt(it.last.toSelection).stackWitList() + +proc decrement(editor: TextEditor) {.exposeActive(editorContext).} = + editor.addNextCheckpoint(ws"insert") + editor.setSelections editor.multiMove(editor.selections, ws"number", 1, wrap = false, includeEol = true) + editor.evaluateExpressions(editor.selections, false, prefix = ws"", suffix = ws"-1", addSelectionIndex = false) + editor.setSelections editor.multiMove(editor.selections, ws"column", -1, wrap = false, includeEol = true).mapIt(it.last.toSelection).stackWitList() + +proc incrementByIndex(editor: TextEditor) {.exposeActive(editorContext).} = + editor.addNextCheckpoint(ws"insert") + editor.setSelections editor.multiMove(editor.selections, ws"number", 1, wrap = false, includeEol = true) + editor.evaluateExpressions(editor.selections, false, prefix = ws"", suffix = ws"", addSelectionIndex = true) + editor.setSelections editor.multiMove(editor.selections, ws"column", -1, wrap = false, includeEol = true).mapIt(it.last.toSelection).stackWitList() + + # let selections = mergeSelections(editor.selections, editor.multiMove(editor.selections, ws"column", 1, true, true)): proc replaceInputHandler(editor: TextEditor, input: string) {.exposeActive(editorContext).} = editor.vimReplace(input) diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index fc33cd0aa..1a64c35a1 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -534,6 +534,11 @@ proc textEditorGetSettingRaw(instance: ptr InstanceData; editor: TextEditor; name: sink string): string proc textEditorSetSettingRaw(instance: ptr InstanceData; editor: TextEditor; name: sink string; value: sink string): void +proc textEditorEvaluateExpressions(instance: ptr InstanceData; + editor: TextEditor; + selections: sink seq[Selection]; + inclusive: bool; prefix: sink string; + suffix: sink string; addSelectionIndex: bool): void proc textEditorEdit(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection]; contents: sink seq[string]; inclusive: bool): seq[Selection] @@ -2519,6 +2524,61 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = textEditorSetSettingRaw(instance, editor, name, value) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32, + WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/text-editor", + "evaluate-expressions", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + var editor: TextEditor + var selections: seq[Selection] + var inclusive: bool + var prefix: string + var suffix: string + var addSelectionIndex: bool + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[uint8]](memory[parameters[1].i32].addr) + selections = newSeq[typeof(selections[0])](parameters[2].i32) + for i0 in 0 ..< selections.len: + selections[i0].first.line = convert( + cast[ptr int32](p0[i0 * 16 + 0].addr)[], int32) + selections[i0].first.column = convert( + cast[ptr int32](p0[i0 * 16 + 4].addr)[], int32) + selections[i0].last.line = convert( + cast[ptr int32](p0[i0 * 16 + 8].addr)[], int32) + selections[i0].last.column = convert( + cast[ptr int32](p0[i0 * 16 + 12].addr)[], int32) + inclusive = parameters[3].i32.bool + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[4].i32].addr) + prefix = newString(parameters[5].i32) + for i0 in 0 ..< prefix.len: + prefix[i0] = p0[i0] + block: + let p0 = cast[ptr UncheckedArray[char]](memory[parameters[6].i32].addr) + suffix = newString(parameters[7].i32) + for i0 in 0 ..< suffix.len: + suffix[i0] = p0[i0] + addSelectionIndex = parameters[8].i32.bool + textEditorEvaluateExpressions(instance, editor, selections, inclusive, + prefix, suffix, addSelectionIndex) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index 95a045f04..3b6bec4ef 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -729,6 +729,12 @@ proc textEditorOpenSearchBar(instance: ptr InstanceData; editor: TextEditor; que if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: editor.TextDocumentEditor.openSearchBar(query, scrollToPreview, selectResult) +proc textEditorEvaluateExpressions(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection]; inclusive: bool; prefix: sink string; suffix: sink string; addSelectionIndex: bool): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.evaluateExpressions(selections.mapIt(it.toInternal), inclusive, prefix, suffix, addSelectionIndex) + proc typesNewRope(instance: ptr InstanceData, content: sink string): RopeResource = return RopeResource(rope: createRope(content).slice().suffix(Point())) diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index eec45245e..ce19f0846 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -1365,7 +1365,7 @@ proc evaluateExpressionAsync(self: TextDocumentEditor, selections: Selections, i else: self.selections = selections -proc evaluateExpressions(self: TextDocumentEditor, selections: Selections, inclusiveEnd: bool = false, prefix: string = "", suffix: string = "", addSelectionIndex: bool = false) {.expose: "editor.text".} = +proc evaluateExpressions*(self: TextDocumentEditor, selections: Selections, inclusiveEnd: bool = false, prefix: string = "", suffix: string = "", addSelectionIndex: bool = false) {.expose: "editor.text".} = asyncSpawn self.evaluateExpressionAsync(selections, inclusiveEnd, prefix, suffix, addSelectionIndex) proc doMoveCursorLine(self: TextDocumentEditor, cursor: Cursor, offset: int, @@ -3122,6 +3122,47 @@ proc getSelectionsForMove*(self: TextDocumentEditor, selections: openArray[Selec let wrap = getArg(1, bool, true) result = selections.mapIt(self.getNextFindResult(it.last, count, includeEol, wrap)) + of "prev-change": + result = selections.mapIt(self.getPrevChange(it.last)) + + of "next-change": + result = selections.mapIt(self.getNextChange(it.last)) + + of "prev-diagnostic": + let severity = getArg(0, int, 0) + let count = getArg(1, int, 0) + let wrap = getArg(2, bool, true) + result = selections.mapIt(self.getPrevDiagnostic(it.last, severity, count, includeEol, wrap)) + + of "next-diagnostic": + let severity = getArg(0, int, 0) + let count = getArg(1, int, 0) + let wrap = getArg(2, bool, true) + result = selections.mapIt(self.getNextDiagnostic(it.last, severity, count, includeEol, wrap)) + + of "number": + result = selections.mapIt: + var r = it.last.toPoint...it.last.toPoint + var c = self.document.rope.cursorT(it.last.toPoint) + while c.currentChar in {'0'..'9'}: + c.seekNextRune() + r.b = c.position + + if r.a.column > 0: + c = self.document.rope.cursorT(it.last.toPoint) + while c.position.column > 0: + c.seekPrevRune() + if c.currentChar == '-': + r.a = c.position + break + + if c.currentChar notin {'0'..'9'}: + c.seekNextRune() + break + r.a = c.position + + r.toSelection + else: return self.moveDatabase.applyMove(self.displayMap, move, selections, count, includeEol) diff --git a/wit/v0/api.wit b/wit/v0/api.wit index fc4c7203d..33c617b3d 100644 --- a/wit/v0/api.wit +++ b/wit/v0/api.wit @@ -322,6 +322,9 @@ interface text-editor { /// Set the value of the setting with the given path. The value must be encoded as JSON. set-setting-raw: func(editor: text-editor, name: string, value: string); + /// todo + evaluate-expressions: func(editor: text-editor, selections: list, inclusive: bool, prefix: string, suffix: string, add-selection-index: bool); + /// todo edit: func(editor: text-editor, selections: list, contents: list, inclusive: bool) -> list; From cc8d7b4cbaf79cfe182ae79f2149561b890052ca Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sat, 18 Oct 2025 15:02:30 +0200 Subject: [PATCH 24/47] Progress --- config/keybindings.json | 3 ++- plugins/vim/vim.nim | 26 ++++++++++------------- src/move_database.nim | 24 --------------------- src/text/text_editor.nim | 46 ++++++++++++++++++++++++++-------------- 4 files changed, 43 insertions(+), 56 deletions(-) diff --git a/config/keybindings.json b/config/keybindings.json index 3c973c162..abe423305 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -1009,7 +1009,8 @@ "gh": ["vim.move-selection-to-next-same-node"], "gs": ["vim.add-next-sibling-to-selection"], "gd": ["vim.move-selection-to-next-sibling"], - "gi": ["vim.shrink-selection"], + "gi": ["vim.grow-selection", -1], + "ga": ["vim.grow-selection", 1], "gxx": ["vim.evaluate-selection"], "gxf": ["vim.increment-selection"], "gxh": ["vim.decrement-selection"], diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index f60437f89..43c7e35ed 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -1056,7 +1056,7 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # debugf"insertMode '{move}'" case move of "right": - editor.setSelections editor.selections.mapIt(editor.applyMove(it.last, "column", 1)) + editor.setSelections editor.multiMove(editor.selections, ws"column", 1, wrap = false, includeEol = true) of "line-end": editor.setSelections editor.selections.mapIt(editor.applyMove(it.last, "line", 1).last.toSelection) of "line-no-indent": @@ -1288,10 +1288,11 @@ proc setSearchQueryOrAddCursor(editor: TextEditor) {.exposeActive(editorContext) # editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) -# proc vimDeleteWordBack(editor: TextEditor) {.exposeActive(editorContext, "vim-delete-word-back").} = -# let selections = editor.applyMove(editor.selections, "vim-word", true, true, 1, which = sca.SelectionCursor.Last.some) -# editor.setSelections editor.delete(selections) -# editor.autoShowCompletions() +proc deleteWordBack(editor: TextEditor) {.exposeActive(editorContext).} = + let wordSelections = editor.multiMove(editor.selections, ws"vim.word", 1, wrap = false, includeEol = true) + let selectionsToDelete = mergeSelections(wordSelections, editor.selections, (it1.first, it2.last).toSelection) + editor.setSelections editor.edit(selectionsToDelete.stackWitList(), @@[ws""], inclusive = false) + editor.autoShowCompletions() # proc vimDeleteLineBack(editor: TextEditor) {.exposeActive(editorContext, "vim-delete-line-back").} = # let selections = editor.applyMove(editor.selections, "vim-line", true, true, 1, which = sca.SelectionCursor.Last.some) @@ -1426,16 +1427,11 @@ proc stopMacro(editor: TextEditor) {.exposeActive(editorContext).} = # editor.setNextSnapBehaviour(MinDistanceOffscreen) # editor.updateTargetColumn() -# proc vimShrinkSelection(editor: TextEditor) {.exposeActive(editorContext, "vim-shrink-selection").} = -# editor.setSelections editor.selections.mapIt(block: -# if it.first.line == it.last.line and abs(it.first.column - it.last.column) < 2: -# it -# else: -# (editor.doMoveCursorColumn(it.first, 1), editor.doMoveCursorColumn(it.last, -1)) -# ) -# editor.scrollToCursor() -# editor.setNextSnapBehaviour(MinDistanceOffscreen) -# editor.updateTargetColumn() +proc growSelection(editor: TextEditor, amount: int = 1) {.exposeActive(editorContext).} = + editor.setSelections editor.multiMove(editor.selections, ws"grow", amount, wrap = true, includeEol = true) + editor.scrollToCursor() + editor.setNextSnapBehaviour(MinDistanceOffscreen) + editor.updateTargetColumn() proc evaluateSelection(editor: TextEditor) {.exposeActive(editorContext).} = editor.addNextCheckpoint(ws"insert") diff --git a/src/move_database.nim b/src/move_database.nim index f04b2e34d..91e494811 100644 --- a/src/move_database.nim +++ b/src/move_database.nim @@ -307,30 +307,6 @@ proc applyMove*(self: MoveDatabase, displayMap: DisplayMap, move: string, select let r = c.runeAt(0) return selections.mapIt((it.first, rope.findNext(it.last, r))) - # let str = move[8..^1] - # let line = self.document.getLine cursor.line - # result = cursor.toSelection - # let index = line.suffix(cursor.column).find(str) - # if index >= 0: - # result.last = (cursor.line, index + 1 + cursor.column) - # for _ in 1..= 0: - # result.last = (result.last.line, index + 1 + result.last.column) - - # elif move.startsWith("move-before "): - # # todo: use RopeCursor - # let str = move[12..^1] - # let line = self.document.getLine cursor.line - # result = cursor.toSelection - # let index = line.suffix(cursor.column + 1).find(str) - # if index >= 0: - # result.last = (cursor.line, index + cursor.column + 1) - # for _ in 1..= 0: - # result.last = (result.last.line, index + result.last.column + 1) - else: log lvlError, &"Unknown move '{move}'" return @selections diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index ce19f0846..60b5aedad 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -3047,27 +3047,34 @@ proc getSelectionsForMove*(self: TextDocumentEditor, selections: openArray[Selec var move = move var args: JsonNode = nil + var parsedArgs = false - let argsStart = move.find(" ") - if argsStart > 0: - try: - args = newJArray() - for a in newStringStream(move[(argsStart + 1)..^1]).parseJsonFragments(): - args.add a - except CatchableError as e: - log lvlError, &"getSelectionsForMove: '{move}', Failed to parse args: {e.msg}" + proc parseArgs(move: var string, args: var JsonNode) = + let argsStart = move.find(" ") + if argsStart > 0: + try: + args = newJArray() + for a in newStringStream(move[(argsStart + 1)..^1]).parseJsonFragments(): + args.add a + except CatchableError as e: + log lvlError, &"getSelectionsForMove: '{move}', Failed to parse args: {e.msg}" - move = move[0.. Date: Sat, 18 Oct 2025 21:02:46 +0200 Subject: [PATCH 25/47] Progress --- plugin_api/v0/api.nim | 2 + plugin_api/v0/text_editor.nim | 11 ++ plugins/vim/vim.nim | 218 ++++++++++++++++-------------- src/generated/plugin_api_host.nim | 15 ++ src/move_database.nim | 33 +++++ src/plugin_api/plugin_api.nim | 11 ++ wit/v0/api.wit | 3 + 7 files changed, 192 insertions(+), 101 deletions(-) diff --git a/plugin_api/v0/api.nim b/plugin_api/v0/api.nim index 1ac6aea07..13c507110 100644 --- a/plugin_api/v0/api.nim +++ b/plugin_api/v0/api.nim @@ -231,6 +231,8 @@ func toSelection*(cursor: Cursor, default: Selection, which: sca.SelectionCursor proc charAt*(rope: Rope, cursor: Cursor): char = rope.byteAt(cursor).char proc slice*(rope: Rope, a: Natural, b: Natural): Rope = rope.slice(a.int64, b.int64, inclusive = false) +proc `[]`*(rope: Rope, a: Natural, b: Natural): Rope = rope.slice(a.int64, b.int64, inclusive = false) +proc `[]`*(rope: Rope, s: Selection): Rope = rope.sliceSelection(s, inclusive = false) proc asEditor*(editor: TextEditor): Editor = Editor(id: editor.id) proc asDocument*(document: TextDocument): Document = Document(id: document.id) diff --git a/plugin_api/v0/text_editor.nim b/plugin_api/v0/text_editor.nim index 9d66da3fd..55fb0def9 100644 --- a/plugin_api/v0/text_editor.nim +++ b/plugin_api/v0/text_editor.nim @@ -767,6 +767,17 @@ proc evaluateExpressions*(editor: TextEditor; selections: WitList[Selection]; textEditorEvaluateExpressionsImported(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) +proc textEditorIndentImported(a0: uint64; a1: int32): void {. + wasmimport("indent", "nev:plugins/text-editor").} +proc indent*(editor: TextEditor; delta: int32): void {.nodestroy.} = + ## todo + var + arg0: uint64 + arg1: int32 + arg0 = editor.id + arg1 = delta + textEditorIndentImported(arg0, arg1) + proc textEditorEditImported(a0: uint64; a1: int32; a2: int32; a3: int32; a4: int32; a5: bool; a6: int32): void {. wasmimport("edit", "nev:plugins/text-editor").} diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 43c7e35ed..0f11d3c79 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -1,5 +1,5 @@ # import std/[strutils, macros, genasts, sequtils, sets, algorithm, jsonutils] -import std/[strformat, json, jsonutils, strutils, tables, macros, genasts, streams, sequtils, sets, os, terminal, colors] +import std/[strformat, json, jsonutils, strutils, tables, macros, genasts, streams, sequtils, sets, os, terminal, colors, algorithm, unicode] import results import util, custom_unicode, myjsonutils, id, wrap, sugar, custom_regex # import input_api @@ -40,6 +40,12 @@ const editorContext = "editor.text" type IdentifierCase = enum Camel, Pascal, Kebab, Snake, ScreamingSnake +proc moveCursorColumn(editor: TextEditor, amount: int, wrap: bool = false, includeEol: bool = true) = + editor.setSelections editor.multiMove(editor.selections, ws"column", 1, wrap, includeEol).mapIt(it.last.toSelection).stackWitList() + +proc moveCursorColumn(editor: TextEditor, selections: WitList[Selection], amount: int, wrap: bool = false, includeEol: bool = true): WitList[Selection] = + editor.multiMove(selections, ws"column", 1, wrap, includeEol).mapIt(it.last.toSelection).stackWitList() + proc splitCase(s: string): tuple[cas: IdentifierCase, parts: seq[string]] = if s == "": return (IdentifierCase.Camel, @[]) @@ -891,30 +897,30 @@ proc paste(editor: TextEditor, pasteRight: bool = false, inclusiveEnd: bool = fa editor.setMode "vim-new.normal" editor.paste selections, register.stackWitString, inclusiveEnd=inclusiveEnd -# proc vimToggleCase(editor: TextEditor, moveCursorRight: bool) {.exposeActive(editorContext, "vim-toggle-case").} = -# var editTexts: seq[string] +proc toggleCase(editor: TextEditor, moveCursorRight: bool) {.exposeActive(editorContext).} = + var editTexts: seq[WitString] -# for s in editor.selections: -# let text = editor.getText(s, inclusiveEnd=true) -# var newText = "" -# for r in text.runes: -# if r.isLower: -# newText.add $r.toUpper -# else: -# newText.add $r.toLower -# editTexts.add newText + let content = editor.content + for s in editor.selections: + let text = content.sliceSelection(s, inclusive=true).text + var newText = newStringOfCap(text.len) + for r in text.toOpenArray.runes: + if r.isLower: + newText.add $r.toUpper + else: + newText.add $r.toLower + editTexts.add newText.stackWitString() -# editor.addNextCheckpoint "insert" -# let oldSelections = editor.selections -# discard editor.edit(editor.selections, editTexts, inclusiveEnd=true) -# editor.setSelections oldSelections.mapIt(it.first.toSelection) + editor.addNextCheckpoint ws"insert" + let oldSelections = editor.selections + discard editor.edit(editor.selections, editTexts.stackWitList(), inclusive=true) + editor.setSelections oldSelections.mapIt(it.first.toSelection).stackWitList() -# editor.setMode "vim-new.normal" + editor.setMode "vim-new.normal" -# if moveCursorRight: -# editor.moveCursorColumn(1, Both, wrap=false, -# includeAfter=editor.vimState.cursorIncludeEol) -# editor.updateTargetColumn() + if moveCursorRight: + editor.moveCursorColumn(1, wrap=false, includeEol=editor.vimState.cursorIncludeEol) + editor.updateTargetColumn() # proc vimCloseCurrentViewOrQuit() {.exposeActive(editorContext, "vim-close-current-view-or-quit").} = # let openEditors = getNumVisibleViews() + getNumHiddenViews() @@ -923,13 +929,13 @@ proc paste(editor: TextEditor, pasteRight: bool = false, inclusiveEnd: bool = fa # else: # closeActiveView() -# proc vimIndent(editor: TextEditor) {.exposeActive(editorContext, "vim-indent").} = -# editor.addNextCheckpoint "insert" -# editor.indent() +proc indent(editor: TextEditor) {.exposeActive(editorContext).} = + editor.addNextCheckpoint ws"insert" + editor.indent(1) -# proc vimUnindent(editor: TextEditor) {.exposeActive(editorContext, "vim-unindent").} = -# editor.addNextCheckpoint "insert" -# editor.unindent() +proc unindent(editor: TextEditor) {.exposeActive(editorContext).} = + editor.addNextCheckpoint ws"insert" + editor.indent(-1) proc addCursorAbove(editor: TextEditor) {.exposeActive(editorContext).} = var selections = @(editor.getSelections.toOpenArray) @@ -1056,7 +1062,7 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont # debugf"insertMode '{move}'" case move of "right": - editor.setSelections editor.multiMove(editor.selections, ws"column", 1, wrap = false, includeEol = true) + editor.setSelections editor.multiMove(editor.selections, ws"column", 1, wrap = false, includeEol = true).mapIt(it.last.toSelection).stackWitList() of "line-end": editor.setSelections editor.selections.mapIt(editor.applyMove(it.last, "line", 1).last.toSelection) of "line-no-indent": @@ -1289,42 +1295,51 @@ proc setSearchQueryOrAddCursor(editor: TextEditor) {.exposeActive(editorContext) # editor.setNextSnapBehaviour(MinDistanceOffscreen) proc deleteWordBack(editor: TextEditor) {.exposeActive(editorContext).} = - let wordSelections = editor.multiMove(editor.selections, ws"vim.word", 1, wrap = false, includeEol = true) + let wordSelections = editor.multiMove(editor.selections, ws"vim.word-back", 1, wrap = false, includeEol = true) let selectionsToDelete = mergeSelections(wordSelections, editor.selections, (it1.first, it2.last).toSelection) editor.setSelections editor.edit(selectionsToDelete.stackWitList(), @@[ws""], inclusive = false) editor.autoShowCompletions() -# proc vimDeleteLineBack(editor: TextEditor) {.exposeActive(editorContext, "vim-delete-line-back").} = -# let selections = editor.applyMove(editor.selections, "vim-line", true, true, 1, which = sca.SelectionCursor.Last.some) -# editor.setSelections editor.delete(selections) -# editor.autoShowCompletions() - -# proc vimSurround(editor: TextEditor, text: string) {.exposeActive(editorContext, "vim-surround").} = -# let (left, right) = case text -# of "(", ")": ("(", ")") -# of "{", "}": ("{", "}") -# of "[", "]": ("[", "]") -# of "<", ">": ("<", ">") -# else: -# (text, text) - -# var insertSelections: seq[Selection] = @[] -# var insertTexts: seq[string] = @[] -# for s in editor.selections: -# let s = s.normalized -# insertSelections.add s.first.toSelection -# insertSelections.add editor.doMoveCursorColumn(s.last, 1).toSelection -# insertTexts.add left -# insertTexts.add right - -# editor.addNextCheckpoint ws"insert" -# let newSelections = editor.insertMulti(insertSelections, insertTexts) -# if newSelections.len mod 2 != 0: -# return - -# editor.setSelections collect: -# for i in 0..": (ws"<", ws">") + else: + let text = text.stackWitString() + (text, text) + + let selections = editor.selections.mapIt(it.normalized).stackWitList() + let rightCursors = editor.multiMove(selections, ws"column", 1, wrap = false, includeEol = true) + + var insertSelections = newSeq[Selection](selections.len * 2) + var insertTexts = newSeq[WitString](selections.len * 2) + for i, s in selections: + insertSelections[i * 2 + 0] = s.first.toSelection + insertSelections[i * 2 + 1] = rightCursors[i].last.toSelection + insertTexts[i * 2 + 0] = left + insertTexts[i * 2 + 1] = right + + editor.addNextCheckpoint ws"insert" + let newSelections = editor.edit(insertSelections.stackWitList(), insertTexts.stackWitList(), inclusive = false) + if newSelections.len mod 2 != 0: + return + + let newSelectionsInclusive = collect: + for i in 0.. c notin Whitespace and c notin AlphaNumeric) return ((cursor.line, startColumn), (cursor.line, endColumn)) +type VimWordCharCategory {.pure.} = enum Word, Whitespace, Other + +proc vimWordCharCategory*(c: Rune): VimWordCharCategory = + if c.int < 128: + const AlphaNumeric = {'A'..'Z', 'a'..'z', '0'..'9', '_'} + if c.char in Whitespace: + return VimWordCharCategory.Whitespace + if c.char in AlphaNumeric: + return VimWordCharCategory.Word + return VimWordCharCategory.Other + if c.isWhitespace: + return VimWordCharCategory.Whitespace + if c.isAlpha: + return VimWordCharCategory.Word + return VimWordCharCategory.Other + +proc vimMotionWordBack*(text: Rope, cursor: Cursor): Selection = + if cursor.column == 0: + return cursor.toSelection + + let prevCursor = text.clipPoint(point(cursor.line, cursor.column - 1), Left).toCursor + let current = text.runeAt(cursor.toPoint) + let prev = text.runeAt(prevCursor.toPoint) + if current.vimWordCharCategory == prev.vimWordCharCategory: + return vimMotionWord(text, cursor) + return vimMotionWord(text, prevCursor) + proc vimMotionWordBig*(text: Rope, cursor: Cursor): Selection = var line = text.getLine(cursor.line) if line.len == 0: @@ -145,6 +172,12 @@ proc applyMove*(self: MoveDatabase, displayMap: DisplayMap, move: string, select for s in result.mitems: s = s or vimMotionWord(rope, s.last) or vimMotionWord(rope, s.first) + of "vim.word-back": + result = selections.mapIt(vimMotionWordBack(rope, it.last)) + for _ in 1..= 0: + for i in 0.., inclusive: bool, prefix: string, suffix: string, add-selection-index: bool); + /// todo + indent: func(editor: text-editor, delta: s32); + /// todo edit: func(editor: text-editor, selections: list, contents: list, inclusive: bool) -> list; From b9411e62a42830257cf7f80dc3124e6c529082e5 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sat, 18 Oct 2025 22:06:54 +0200 Subject: [PATCH 26/47] progress --- config/keybindings.json | 58 +++++----- plugins/vim/vim.nim | 223 ++++++++++++--------------------------- src/text/text_editor.nim | 22 ++-- 3 files changed, 108 insertions(+), 195 deletions(-) diff --git a/config/keybindings.json b/config/keybindings.json index abe423305..cbadaa747 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -697,33 +697,33 @@ }, "vim-new#text_object": { - "iw": ["vim.select-text-object", "vim.word-inner", false, true, "<#text_object.count>"], - "aw": ["vim.select-text-object", "vim.word-outer", false, true, "<#text_object.count>"], - "iW": ["vim.select-text-object", "vim.WORD-inner", false, true, "<#text_object.count>"], - "aW": ["vim.select-text-object", "vim.WORD-outer", false, true, "<#text_object.count>"], - "ip": ["vim.select-text-object", "vim.paragraph-inner", false, true, "<#text_object.count>"], - "ap": ["vim.select-text-object", "vim.paragraph-outer", false, true, "<#text_object.count>"], - - "i{": ["vim.select-surrounding", "surround \"{\" \"}\" true", false, true, "<#text_object.count>"], - "a{": ["vim.select-surrounding", "surround \"{\" \"}\" false", false, true, "<#text_object.count>"], - "i}": ["vim.select-surrounding", "surround \"{\" \"}\" true", false, true, "<#text_object.count>"], - "a}": ["vim.select-surrounding", "surround \"{\" \"}\" false", false, true, "<#text_object.count>"], - "i(": ["vim.select-surrounding", "surround \"(\" \")\" true", false, true, "<#text_object.count>"], - "a(": ["vim.select-surrounding", "surround \"(\" \")\" false", false, true, "<#text_object.count>"], - "i)": ["vim.select-surrounding", "surround \"(\" \")\" true", false, true, "<#text_object.count>"], - "a)": ["vim.select-surrounding", "surround \"(\" \")\" false", false, true, "<#text_object.count>"], - "i[": ["vim.select-surrounding", "surround \"[\" \"]\" true", false, true, "<#text_object.count>"], - "a[": ["vim.select-surrounding", "surround \"[\" \"]\" false", false, true, "<#text_object.count>"], - "i]": ["vim.select-surrounding", "surround \"[\" \"]\" true", false, true, "<#text_object.count>"], - "a]": ["vim.select-surrounding", "surround \"[\" \"]\" false", false, true, "<#text_object.count>"], - "i\\<": ["vim.select-surrounding", "surround \"<\" \">\" true", false, true, "<#text_object.count>"], - "a\\<": ["vim.select-surrounding", "surround \"<\" \">\" false", false, true, "<#text_object.count>"], - "i\\>": ["vim.select-surrounding", "surround \"<\" \">\" true", false, true, "<#text_object.count>"], - "a\\>": ["vim.select-surrounding", "surround \"<\" \">\" false", false, true, "<#text_object.count>"], - "i\"": ["vim.select-surrounding", "surround \"\\\"\" \"\\\"\" true", false, true, "<#text_object.count>"], - "a\"": ["vim.select-surrounding", "surround \"\\\"\" \"\\\"\" false", false, true, "<#text_object.count>"], - "i'": ["vim.select-surrounding", "surround \"'\" \"'\" true", false, true, "<#text_object.count>"], - "a'": ["vim.select-surrounding", "surround \"'\" \"'\" false", false, true, "<#text_object.count>"], + "iw": ["vim.select-text-object", "vim.word-inner", "<#text_object.count>"], + "aw": ["vim.select-text-object", "vim.word-inner", "<#text_object.count>"], + "iW": ["vim.select-text-object", "vim.WORD-inner", "<#text_object.count>"], + "aW": ["vim.select-text-object", "vim.WORD-inner", "<#text_object.count>"], + "ip": ["vim.select-text-object", "vim.paragraph-inner", "<#text_object.count>"], + "ap": ["vim.select-text-object", "vim.paragraph-outer", "<#text_object.count>"], + + "i{": ["vim.select-surrounding", "surround \"{\" \"}\" true", "<#text_object.count>"], + "a{": ["vim.select-surrounding", "surround \"{\" \"}\" false", "<#text_object.count>"], + "i}": ["vim.select-surrounding", "surround \"{\" \"}\" true", "<#text_object.count>"], + "a}": ["vim.select-surrounding", "surround \"{\" \"}\" false", "<#text_object.count>"], + "i(": ["vim.select-surrounding", "surround \"(\" \")\" true", "<#text_object.count>"], + "a(": ["vim.select-surrounding", "surround \"(\" \")\" false", "<#text_object.count>"], + "i)": ["vim.select-surrounding", "surround \"(\" \")\" true", "<#text_object.count>"], + "a)": ["vim.select-surrounding", "surround \"(\" \")\" false", "<#text_object.count>"], + "i[": ["vim.select-surrounding", "surround \"[\" \"]\" true", "<#text_object.count>"], + "a[": ["vim.select-surrounding", "surround \"[\" \"]\" false", "<#text_object.count>"], + "i]": ["vim.select-surrounding", "surround \"[\" \"]\" true", "<#text_object.count>"], + "a]": ["vim.select-surrounding", "surround \"[\" \"]\" false", "<#text_object.count>"], + "i\\<": ["vim.select-surrounding", "surround \"<\" \">\" true", "<#text_object.count>"], + "a\\<": ["vim.select-surrounding", "surround \"<\" \">\" false", "<#text_object.count>"], + "i\\>": ["vim.select-surrounding", "surround \"<\" \">\" true", "<#text_object.count>"], + "a\\>": ["vim.select-surrounding", "surround \"<\" \">\" false", "<#text_object.count>"], + "i\"": ["vim.select-surrounding", "surround \"\\\"\" \"\\\"\" true", "<#text_object.count>"], + "a\"": ["vim.select-surrounding", "surround \"\\\"\" \"\\\"\" false", "<#text_object.count>"], + "i'": ["vim.select-surrounding", "surround \"'\" \"'\" true", "<#text_object.count>"], + "a'": ["vim.select-surrounding", "surround \"'\" \"'\" false", "<#text_object.count>"], }, "vim-new.visual-line#move": { @@ -752,11 +752,11 @@ "0": ["vim.move-first", "line"], "": ["vim.move-first", "visual-line"], "^": ["vim.move-first", "line-no-indent"], - "$": ["vim.move-to-end-of-line", "<#move.count>"], + "$": ["vim.move-last", "line", "<#move.count>"], "": ["vim.move-last", "visual-line", "<#move.count>"], "g0": ["vim.move-first", "line"], "g^": ["vim.move-first", "line-no-indent"], - "g$": ["vim.move-to-end-of-line", "<#move.count>"], + "g$": ["vim.move-last", "line", "<#move.count>"], "gg": ["vim.move-file-start", "<#move.count>"], "G": ["vim.move-file-end", "<#move.count>"], "%": ["vim.move-to-matching-or-file-offset", "<#move.count>"], diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 0f11d3c79..a37aa4a87 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -46,6 +46,9 @@ proc moveCursorColumn(editor: TextEditor, amount: int, wrap: bool = false, inclu proc moveCursorColumn(editor: TextEditor, selections: WitList[Selection], amount: int, wrap: bool = false, includeEol: bool = true): WitList[Selection] = editor.multiMove(selections, ws"column", 1, wrap, includeEol).mapIt(it.last.toSelection).stackWitList() +proc moveCursorLine(editor: TextEditor, amount: int, includeEol: bool = true) = + editor.setSelections editor.multiMove(editor.selections, ws"line-down", amount, false, includeEol).mapIt(it.last.toSelection).stackWitList() + proc splitCase(s: string): tuple[cas: IdentifierCase, parts: seq[string]] = if s == "": return (IdentifierCase.Camel, @[]) @@ -480,124 +483,8 @@ proc vimMotionParagraphOuter*(data: uint32, text: sink Rope, selections: openArr # if result.last.line + 1 < editor.lineCount: # result = result or vimMotionParagraphInner(data, text, (result.last.line.int + 1, 0).toCursor, 1, includeEol) -# proc vimMotionWordOuter(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl, raises: [].} = -# # proc vimMotionWordOuter*(rope: sink Rope, cursor: Cursor, count: int): Selection = -# result = vimMotionWord(editor, cursor, count) -# if result.last.column < editor.lineLength(result.last.line) and editor.getChar(result.last) in Whitespace: -# result.last = editor.vimMotionWord(result.last, 1).last - -# proc vimMotionWordBigOuter*(editor: TextEditor, cursor: Cursor, count: int): Selection = -# result = vimMotionWordBig(editor, cursor, count) -# if result.last.column < editor.lineLength(result.last.line) and editor.getChar(result.last) in Whitespace: -# result.last = editor.vimMotionWordBig(result.last, 1).last - -# proc charAt*(editor: TextEditor, cursor: Cursor): char = -# let res = editor.getText(cursor.toSelection, inclusiveEnd=true) -# if res.len > 0: -# return res[0] -# else: -# return '\0' - -# proc vimMotionSurround*(data: uint32, text: Rope, selection: Selection, count: int, includeEol: bool, c0: char, c1: char, inside: bool): Selection = -# result = selection -# while true: -# let lastChar = text.charAt(result.last) -# let (startDepth, endDepth) = if lastChar == c0: -# (1, 0) -# elif lastChar == c1: -# (0, 1) -# else: -# (1, 1) - -# # debugf"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find around: {startDepth}, {endDepth}" -# if editor.findSurroundStart(result.first, count, c0, c1, startDepth).getSome(opening) and editor.findSurroundEnd(result.last, count, c0, c1, endDepth).getSome(closing): -# result = (opening, closing) -# # debugf"vimMotionSurround: found inside {result}" -# if inside: -# result.first = editor.doMoveCursorColumn(result.first, 1) -# result.last = editor.doMoveCursorColumn(result.last, -1) -# return - -# # debugf"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find ahead: {startDepth}, {endDepth}" -# if editor.findSurroundEnd(result.first, count, c0, c1, -1).getSome(opening) and editor.findSurroundEnd(opening, count, c0, c1, 0).getSome(closing): -# result = (opening, closing) -# # debugf"vimMotionSurround: found ahead {result}" -# if inside: -# result.first = editor.doMoveCursorColumn(result.first, 1) -# result.last = editor.doMoveCursorColumn(result.last, -1) -# return -# else: -# # debugf"vimMotionSurround: found nothing {result}" -# return - -# proc vimMotionSurround*(data: uint32, text: Rope, selections: openArray[Selection], count: int, includeEol: bool, c0: char, c1: char, inside: bool): seq[Selection] = -# debugf"vimMotionSurround: {selections}, {count}, {c0}, {c1}, {inside}" -# # result = selections.mapIt(vimMotionSurround(data, text, it.last.toSelection, count, includeEol, c0, c1, inside)) -# # result = - -# proc vimMoveToMatching(editor: TextEditor) {.exposeActive(editorContext, "vim-move-to-matching").} = -# # todo: pass as parameter -# let which = if editor.mode == "vim-new.visual" or editor.mode == "vim-new.visual-line": -# sca.SelectionCursor.Last -# else: -# sca.SelectionCursor.Both - -# editor.setSelections editor.selections.mapIt(block: -# let c = editor.charAt(it.last) -# let (open, close, last) = case c -# of '(': ('(', ')', true) -# of '{': ('{', '}', true) -# of '[': ('[', ']', true) -# of '<': ('<', '>', true) -# of ')': ('(', ')', false) -# of '}': ('{', '}', false) -# of ']': ('[', ']', false) -# of '>': ('<', '>', false) -# of '"': ('"', '"', true) -# of '\'': ('\'', '\'', true) -# else: return - -# let selection = editor.vimMotionSurround(it.last, 0, open, close, false) - -# if last: -# selection.last.toSelection(it, which) -# else: -# selection.first.toSelection(it, which) -# ) - -# editor.scrollToCursor() -# editor.updateTargetColumn() - -# proc vimMotionSurroundBracesInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '{', '}', true) -# proc vimMotionSurroundBracesOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '{', '}', false) -# proc vimMotionSurroundParensInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '(', ')', true) -# proc vimMotionSurroundParensOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '(', ')', false) -# proc vimMotionSurroundBracketsInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '[', ']', true) -# proc vimMotionSurroundBracketsOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '[', ']', false) -# proc vimMotionSurroundAngleInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '<', '>', true) -# proc vimMotionSurroundAngleOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '<', '>', false) -# proc vimMotionSurroundDoubleQuotesInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '"', '"', true) -# proc vimMotionSurroundDoubleQuotesOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '"', '"', false) -# proc vimMotionSurroundSingleQuotesInner*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '\'', '\'', true) -# proc vimMotionSurroundSingleQuotesOuter*(data: uint32, text: sink Rope, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.cdecl.} = vimMotionSurround(data, text, selections, count, includeEol, '\'', '\'', false) - -# # todo -# addCustomTextMove "vim-word-outer", vimMotionWordOuter -# addCustomTextMove "vim-WORD-outer", vimMotionWordBigOuter addCustomTextMove "vim-paragraph-inner", vimMotionParagraphInner addCustomTextMove "vim-paragraph-outer", vimMotionParagraphOuter -# addCustomTextMove "vim-surround-{-inner", vimMotionSurroundBracesInner -# addCustomTextMove "vim-surround-{-outer", vimMotionSurroundBracesOuter -# addCustomTextMove "vim-surround-(-inner", vimMotionSurroundParensInner -# addCustomTextMove "vim-surround-(-outer", vimMotionSurroundParensOuter -# addCustomTextMove "vim-surround-[-inner", vimMotionSurroundBracketsInner -# addCustomTextMove "vim-surround-[-outer", vimMotionSurroundBracketsOuter -# addCustomTextMove "vim-surround-angle-inner", vimMotionSurroundAngleInner -# addCustomTextMove "vim-surround-angle-outer", vimMotionSurroundAngleOuter -# addCustomTextMove "vim-surround-\"-inner", vimMotionSurroundDoubleQuotesInner -# addCustomTextMove "vim-surround-\"-outer", vimMotionSurroundDoubleQuotesOuter -# addCustomTextMove "vim-surround-'-inner", vimMotionSurroundSingleQuotesInner -# addCustomTextMove "vim-surround-'-outer", vimMotionSurroundSingleQuotesOuter iterator iterateTextObjects*(editor: TextEditor, cursor: Cursor, move: string, backwards: bool = false): Selection = var selection = editor.applyMove(cursor, move, 0) @@ -632,21 +519,21 @@ iterator enumerateTextObjects*(editor: TextEditor, cursor: Cursor, move: string, yield (i, selection) inc i -proc selectTextObject(editor: TextEditor, textObject: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1, textObjectRange: VimTextObjectRange = Inner) {.exposeActive(editorContext).} = - debugf"selectTextObject({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count})" +proc selectTextObject(editor: TextEditor, textObject: string, count: int = 1, textObjectRange: VimTextObjectRange = Inner) {.exposeActive(editorContext).} = + debugf"selectTextObject({textObject}, {textObjectRange}, {count})" editor.setSelections editor.selections.mapIt(block: var res = it.last var resultSelection = it # debugf"-> {resultSelection}" - for i, selection in enumerateTextObjects(editor, res, textObject, backwards): + for i, selection in enumerateTextObjects(editor, res, textObject, false): debugf"{i}: {res} -> {selection}" resultSelection = resultSelection or selection if i == max(count, 1) - 1: break - # debugf"selectTextObject({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count}): {resultSelection}" + # debugf"selectTextObject({textObject}, {textObjectRange}, {count}): {resultSelection}" if it.isBackwards: resultSelection.reverse else: @@ -656,8 +543,8 @@ proc selectTextObject(editor: TextEditor, textObject: string, backwards: bool = editor.scrollToCursor() editor.updateTargetColumn() -proc selectSurrounding(editor: TextEditor, textObject: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1, textObjectRange: VimTextObjectRange = Inner) {.exposeActive(editorContext).} = - debugf"selectSurrounding({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count})" +proc selectSurrounding(editor: TextEditor, textObject: string, count: int = 1, textObjectRange: VimTextObjectRange = Inner) {.exposeActive(editorContext).} = + debugf"selectSurrounding({textObject}, {textObjectRange}, {count})" let selections = editor.selections let newSelections = mergeSelections(selections, editor.multiMove(selections, ws(textObject), count, wrap = false, includeEol = editor.vimState.cursorIncludeEol)): @@ -667,15 +554,6 @@ proc selectSurrounding(editor: TextEditor, textObject: string, backwards: bool = it2 editor.setSelections newSelections - # editor.setSelections editor.selections.mapIt(block: - # let resultSelection = editor.getSelectionForMove(it.last, textObject, count) - # # debugf"selectSurrounding({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count}): {resultSelection}" - # if it.isBackwards: - # resultSelection.reverse - # else: - # resultSelection - # ) - editor.scrollToCursor() editor.updateTargetColumn() @@ -854,28 +732,19 @@ proc moveDirection(editor: TextEditor, move: string, direction: int, count: int if updateTargetColumn: editor.updateTargetColumn() -# proc vimMoveToEndOfLine(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-end-of-line").} = -# # debugf"vimMoveToEndOfLine {count}" -# let count = max(1, count) -# if count > 1: -# editor.moveCursorLine(count - 1) -# editor.moveLast("vim-line") -# editor.scrollToCursor() -# editor.updateTargetColumn() - -# proc vimMoveCursorLineFirstChar(editor: TextEditor, direction: int, count: int = 1) {.exposeActive(editorContext, "vim-move-cursor-line-first-char").} = -# editor.moveCursorLine(direction * max(count, 1)) -# editor.moveFirst "line-no-indent" -# editor.updateTargetColumn() +proc moveCursorLineFirstChar(editor: TextEditor, direction: int, count: int = 1) {.exposeActive(editorContext).} = + let count = if count == 0: 1 else: count + editor.moveCursorLine(direction * count) + editor.moveFirst "line-no-indent" + editor.updateTargetColumn() -# proc vimMoveToStartOfLine(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-start-of-line").} = -# # debugf"vimMoveToStartOfLine {count}" -# let count = max(1, count) -# if count > 1: -# editor.moveCursorLine(count - 1) -# editor.moveFirst "line-no-indent" -# editor.scrollToCursor() -# editor.updateTargetColumn() +proc moveToStartOfLine(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = + let count = max(1, count) + if count > 1: + editor.moveCursorLine(count - 1) + editor.moveFirst "line-no-indent" + editor.scrollToCursor() + editor.updateTargetColumn() proc paste(editor: TextEditor, pasteRight: bool = false, inclusiveEnd: bool = false, register: string = "") {.exposeActive(editorContext).} = # debugf"vimPaste {register}, lines: {yankedLines}, pasteRight: {pasteRight}" @@ -1016,10 +885,49 @@ proc moveFileEnd(editor: TextEditor, count: int = 1) {.exposeActive(editorContex editor.scrollToCursor() editor.setNextSnapBehaviour(MinDistanceOffscreen) -# proc vimMoveToMatchingOrFileOffset(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-matching-or-file-offset").} = -# if count == 0: -# editor.vimMoveToMatching() -# else: +proc moveToMatching(editor: TextEditor) {.exposeActive(editorContext).} = + # todo: pass as parameter + let mode = $editor.mode + let which = if mode == "vim-new.visual" or mode == "vim-new.visual-line": + sca.SelectionCursor.Last + else: + sca.SelectionCursor.Both + + let content = editor.content + + editor.setSelections editor.selections.mapIt(block: + let c = content.charAt(it.last) + let (open, close, last) = case c + of '(': ('(', ')', true) + of '{': ('{', '}', true) + of '[': ('[', ']', true) + of '<': ('<', '>', true) + of ')': ('(', ')', false) + of '}': ('{', '}', false) + of ']': ('[', ']', false) + of '>': ('<', '>', false) + of '"': ('"', '"', true) + of '\'': ('\'', '\'', true) + else: return + + let selection = editor.applyMove(it, stackWitString(&"surround \"{open}\" \"{close}\" false"), 0, wrap=false, includeEol=true)[0] + # let selection = editor.vimMotionSurround(it.last, 0, open, close, false) + + if last: + selection.last.toSelection(it, which) + else: + selection.first.toSelection(it, which) + ).stackWitList() + + editor.scrollToCursor() + editor.updateTargetColumn() + +proc moveToMatchingOrFileOffset(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = + if count == 0: + editor.moveToMatching() + else: + debugf"not implemented: moveToMatchingOrFileOffset {count}" + # todo # let line = clamp((count * editor.lineCount) div 100, 0, editor.lineCount - 1) # let which = getSetting[sca.SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), sca.SelectionCursor.Both) # editor.setSelection (line, 0).toSelection(editor.selection, which) @@ -1053,7 +961,7 @@ proc centerLine(editor: TextEditor, count: int = 1) {.exposeActive(editorContext # editor.moveFirst "line-no-indent" # editor.setCursorScrollOffset (editor.screenLineCount.float - getVimLineMargin()) * platformTotalLineHeight() -# proc vimScrollLineToBottom(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-scroll-line-to-bottom").} = +# proc scrollLineToBottom(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = # if editor.getCommandCount != 0: # editor.setSelection (editor.getCommandCount, editor.selection.last.column).toSelection # editor.setCursorScrollOffset (editor.screenLineCount.float - getVimLineMargin()) * platformTotalLineHeight() @@ -1483,7 +1391,6 @@ proc incrementByIndex(editor: TextEditor) {.exposeActive(editorContext).} = editor.evaluateExpressions(editor.selections, false, prefix = ws"", suffix = ws"", addSelectionIndex = true) editor.setSelections editor.multiMove(editor.selections, ws"column", -1, wrap = false, includeEol = true).mapIt(it.last.toSelection).stackWitList() - # let selections = mergeSelections(editor.selections, editor.multiMove(editor.selections, ws"column", 1, true, true)): proc replaceInputHandler(editor: TextEditor, input: string) {.exposeActive(editorContext).} = editor.vimReplace(input) diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index 60b5aedad..686fa105b 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -3045,26 +3045,31 @@ proc getSelectionsForMove*(self: TextDocumentEditor, selections: openArray[Selec let cursorSelector = self.config.get(self.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) + var moveOriginal = move var move = move var args: JsonNode = nil + var argsString = "" + var hasArgs = false var parsedArgs = false + let argsStart = move.find(" ") + if argsStart > 0: + hasArgs = true + argsString = move[(argsStart + 1)..^1] + move = move[0.. 0: + parsedArgs = true + if hasArgs: try: args = newJArray() - for a in newStringStream(move[(argsStart + 1)..^1]).parseJsonFragments(): + for a in newStringStream(argsString).parseJsonFragments(): args.add a except CatchableError as e: log lvlError, &"getSelectionsForMove: '{move}', Failed to parse args: {e.msg}" - move = move[0.. Date: Sat, 18 Oct 2025 22:38:23 +0200 Subject: [PATCH 27/47] Progress --- plugin_api/v0/api.nim | 2 +- plugin_api/v0/text_editor.nim | 46 +++++++++++++++++++++--- plugins/vim/vim.nim | 56 ++++++++++++++--------------- src/generated/plugin_api_host.nim | 60 +++++++++++++++++++++++++++++-- src/plugin_api/plugin_api.nim | 22 ++++++++++-- src/text/text_editor.nim | 7 +++- wit/v0/api.wit | 11 +++++- 7 files changed, 164 insertions(+), 40 deletions(-) diff --git a/plugin_api/v0/api.nim b/plugin_api/v0/api.nim index 13c507110..931b2cb3d 100644 --- a/plugin_api/v0/api.nim +++ b/plugin_api/v0/api.nim @@ -216,7 +216,7 @@ when pluginWorld == "plugin": return editor.applyMove(cursor.toSelection, move.ws, count, wrap, includeEol)[0] proc scrollToCursor*(editor: TextEditor) = - editor.scrollToCursor(ScrollBehaviour.none) + editor.scrollToCursor(ScrollBehaviour.none, 0.5) proc addCustomTextMove*(name: string, move: MoveHandler) = defineMove(name.ws, cast[uint32](move), 0) diff --git a/plugin_api/v0/text_editor.nim b/plugin_api/v0/text_editor.nim index 55fb0def9..30f785b28 100644 --- a/plugin_api/v0/text_editor.nim +++ b/plugin_api/v0/text_editor.nim @@ -162,20 +162,23 @@ proc hideCompletions*(editor: TextEditor): void {.nodestroy.} = arg0 = editor.id textEditorHideCompletionsImported(arg0) -proc textEditorScrollToCursorImported(a0: uint64; a1: int8; a2: int8): void {. +proc textEditorScrollToCursorImported(a0: uint64; a1: int8; a2: int8; + a3: float32): void {. wasmimport("scroll-to-cursor", "nev:plugins/text-editor").} -proc scrollToCursor*(editor: TextEditor; behaviour: Option[ScrollBehaviour]): void {. - nodestroy.} = +proc scrollToCursor*(editor: TextEditor; behaviour: Option[ScrollBehaviour]; + relativePosition: float32): void {.nodestroy.} = ## todo var arg0: uint64 arg1: int8 arg2: int8 + arg3: float32 arg0 = editor.id arg1 = behaviour.isSome.int8 if behaviour.isSome: arg2 = cast[int8](behaviour.get) - textEditorScrollToCursorImported(arg0, arg1, arg2) + arg3 = relativePosition + textEditorScrollToCursorImported(arg0, arg1, arg2, arg3) proc textEditorSetNextSnapBehaviourImported(a0: uint64; a1: int8): void {. wasmimport("set-next-snap-behaviour", "nev:plugins/text-editor").} @@ -778,6 +781,41 @@ proc indent*(editor: TextEditor; delta: int32): void {.nodestroy.} = arg1 = delta textEditorIndentImported(arg0, arg1) +proc textEditorGetCommandCountImported(a0: uint64): int32 {. + wasmimport("get-command-count", "nev:plugins/text-editor").} +proc getCommandCount*(editor: TextEditor): int32 {.nodestroy.} = + ## todo + var arg0: uint64 + arg0 = editor.id + let res = textEditorGetCommandCountImported(arg0) + result = convert(res, int32) + +proc textEditorSetCursorScrollOffsetImported(a0: uint64; a1: int32; a2: int32; + a3: float32): void {.wasmimport("set-cursor-scroll-offset", + "nev:plugins/text-editor").} +proc setCursorScrollOffset*(editor: TextEditor; cursor: Cursor; + scrollOffset: float32): void {.nodestroy.} = + ## todo + var + arg0: uint64 + arg1: int32 + arg2: int32 + arg3: float32 + arg0 = editor.id + arg1 = cursor.line + arg2 = cursor.column + arg3 = scrollOffset + textEditorSetCursorScrollOffsetImported(arg0, arg1, arg2, arg3) + +proc textEditorGetVisibleLineCountImported(a0: uint64): int32 {. + wasmimport("get-visible-line-count", "nev:plugins/text-editor").} +proc getVisibleLineCount*(editor: TextEditor): int32 {.nodestroy.} = + ## Returns the number of lines that fit on the current screen size for this editor. + var arg0: uint64 + arg0 = editor.id + let res = textEditorGetVisibleLineCountImported(arg0) + result = convert(res, int32) + proc textEditorEditImported(a0: uint64; a1: int32; a2: int32; a3: int32; a4: int32; a5: bool; a6: int32): void {. wasmimport("edit", "nev:plugins/text-editor").} diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index a37aa4a87..334b4b24f 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -666,7 +666,7 @@ proc moveCursorPage(editor: TextEditor, direction: int, count: int = 1, center: ## Direction 100 means 100% of window height downwards -100 is upwards, 50 would be 50% editor.setSelections editor.multiMove(editor.selections, ws"page", direction * max(count, 1), true, includeEol = editor.vimState.cursorIncludeEol) let nextScrollBehaviour = if center: CenterAlways.some else: ScrollBehaviour.none - editor.scrollToCursor(behaviour = nextScrollBehaviour) + editor.scrollToCursor(behaviour = nextScrollBehaviour, 0.5) editor.setNextSnapBehaviour(Never) if editor.vimState.selectLines: editor.selectLine() @@ -682,7 +682,7 @@ proc moveCursorVisualPage(editor: TextEditor, direction: int, count: int = 1, ce # let defaultCenter = defaultScrollBehaviour in {CenterAlways, CenterOffscreen} # let nextScrollBehaviour = if center and defaultCenter: CenterAlways.some else: ScrollBehaviour.none let nextScrollBehaviour = ScrollBehaviour.none - editor.scrollToCursor(behaviour = nextScrollBehaviour) + editor.scrollToCursor(behaviour = nextScrollBehaviour, 0.5) editor.setNextSnapBehaviour(Never) if editor.vimState.selectLines: editor.selectLine() @@ -935,36 +935,36 @@ proc moveToMatchingOrFileOffset(editor: TextEditor, count: int = 1) {.exposeActi # editor.scrollToCursor() # editor.setNextSnapBehaviour(MinDistanceOffscreen) -# proc vimScrollLineToTopAndMoveLineStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-scroll-line-to-top-and-move-line-start").} = -# if editor.getCommandCount != 0: -# editor.setSelection (editor.getCommandCount, 0).toSelection -# editor.moveFirst "line-no-indent" -# editor.setCursorScrollOffset getVimLineMargin() * platformTotalLineHeight() +proc scrollLineToTopAndMoveLineStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = + if editor.getCommandCount != 0: + editor.setSelection (editor.getCommandCount.int, 0).toCursor.toSelection + editor.setSelections editor.multiMove(editor.selections, ws"line-no-indent", 1, wrap = false, includeEol = editor.vimState.cursorIncludeEol).mapIt(it.first.toSelection).stackWitList() + editor.setCursorScrollOffset editor.selections.last.last, getVimLineMargin() -# proc vimScrollLineToTop(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-scroll-line-to-top").} = -# if editor.getCommandCount != 0: -# editor.setSelection (editor.getCommandCount, editor.selection.last.column).toSelection -# editor.setCursorScrollOffset getVimLineMargin() * platformTotalLineHeight() +proc scrollLineToTop(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = + if editor.getCommandCount != 0: + editor.setSelection (editor.getCommandCount, editor.selections.last.last.column).toSelection + editor.setCursorScrollOffset editor.selections.last.last, getVimLineMargin() -# proc vimCenterLineAndMoveLineStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-center-line-and-move-line-start").} = -# if editor.getCommandCount != 0: -# editor.setSelection (editor.getCommandCount, 0).toSelection -# editor.moveFirst "line-no-indent" -# editor.centerCursor() +proc centerLineAndMoveLineStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = + if editor.getCommandCount != 0: + editor.setSelection (editor.getCommandCount.int, 0).toCursor.toSelection + editor.setSelections editor.multiMove(editor.selections, ws"line-no-indent", 1, wrap = false, includeEol = editor.vimState.cursorIncludeEol).mapIt(it.first.toSelection).stackWitList() + editor.scrollToCursor(ScrollBehaviour.CenterAlways.some, 0.5) proc centerLine(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = - editor.scrollToCursor(ScrollBehaviour.CenterAlways.some) - -# proc vimScrollLineToBottomAndMoveLineStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext, "vim-scroll-line-to-bottom-and-move-line-start").} = -# if editor.getCommandCount != 0: -# editor.setSelection (editor.getCommandCount, 0).toSelection -# editor.moveFirst "line-no-indent" -# editor.setCursorScrollOffset (editor.screenLineCount.float - getVimLineMargin()) * platformTotalLineHeight() - -# proc scrollLineToBottom(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = -# if editor.getCommandCount != 0: -# editor.setSelection (editor.getCommandCount, editor.selection.last.column).toSelection -# editor.setCursorScrollOffset (editor.screenLineCount.float - getVimLineMargin()) * platformTotalLineHeight() + editor.scrollToCursor(ScrollBehaviour.CenterAlways.some, 0.5) + +proc scrollLineToBottomAndMoveLineStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = + if editor.getCommandCount != 0: + editor.setSelection (editor.getCommandCount.int, 0).toCursor.toSelection + editor.setSelections editor.multiMove(editor.selections, ws"line-no-indent", 1, wrap = false, includeEol = editor.vimState.cursorIncludeEol).mapIt(it.first.toSelection).stackWitList() + editor.setCursorScrollOffset editor.selections.last.last, (editor.getVisibleLineCount().float - getVimLineMargin()) + +proc scrollLineToBottom(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = + if editor.getCommandCount != 0: + editor.setSelection (editor.getCommandCount, editor.selections.last.last.column).toSelection + editor.setCursorScrollOffset editor.selections.last.last, (editor.getVisibleLineCount().float - getVimLineMargin()) proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorContext).} = # debugf"insertMode '{move}'" diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index 9d0f14b4e..f07897fba 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -472,7 +472,8 @@ proc textEditorSaveCurrentCommandHistory(instance: ptr InstanceData; editor: TextEditor): void proc textEditorHideCompletions(instance: ptr InstanceData; editor: TextEditor): void proc textEditorScrollToCursor(instance: ptr InstanceData; editor: TextEditor; - behaviour: Option[ScrollBehaviour]): void + behaviour: Option[ScrollBehaviour]; + relativePosition: float32): void proc textEditorSetNextSnapBehaviour(instance: ptr InstanceData; editor: TextEditor; behaviour: ScrollSnapBehaviour): void @@ -541,6 +542,12 @@ proc textEditorEvaluateExpressions(instance: ptr InstanceData; suffix: sink string; addSelectionIndex: bool): void proc textEditorIndent(instance: ptr InstanceData; editor: TextEditor; delta: int32): void +proc textEditorGetCommandCount(instance: ptr InstanceData; editor: TextEditor): int32 +proc textEditorSetCursorScrollOffset(instance: ptr InstanceData; + editor: TextEditor; cursor: Cursor; + scrollOffset: float32): void +proc textEditorGetVisibleLineCount(instance: ptr InstanceData; + editor: TextEditor): int32 proc textEditorEdit(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection]; contents: sink seq[string]; inclusive: bool): seq[Selection] @@ -1548,18 +1555,21 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = block: let e = block: var ty: ptr WasmFunctypeT = newFunctype( - [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32], []) + [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32, WasmValkind.F32], + []) linker.defineFuncUnchecked("nev:plugins/text-editor", "scroll-to-cursor", ty): var instance = cast[ptr InstanceData](store.getData()) var editor: TextEditor var behaviour: Option[ScrollBehaviour] + var relativePosition: float32 editor.id = convert(parameters[0].i64, uint64) if parameters[1].i32 != 0: var temp: ScrollBehaviour temp = cast[ScrollBehaviour](parameters[2].i32) behaviour = temp.some - textEditorScrollToCursor(instance, editor, behaviour) + relativePosition = convert(parameters[3].f32, float32) + textEditorScrollToCursor(instance, editor, behaviour, relativePosition) if e.isErr: return e block: @@ -2594,6 +2604,50 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = textEditorIndent(instance, editor, delta) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64], + [WasmValkind.I32]) + linker.defineFuncUnchecked("nev:plugins/text-editor", "get-command-count", + ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + let res = textEditorGetCommandCount(instance, editor) + parameters[0].i32 = cast[int32](res) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32, WasmValkind.F32], + []) + linker.defineFuncUnchecked("nev:plugins/text-editor", + "set-cursor-scroll-offset", ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + var cursor: Cursor + var scrollOffset: float32 + editor.id = convert(parameters[0].i64, uint64) + cursor.line = convert(parameters[1].i32, int32) + cursor.column = convert(parameters[2].i32, int32) + scrollOffset = convert(parameters[3].f32, float32) + textEditorSetCursorScrollOffset(instance, editor, cursor, scrollOffset) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64], + [WasmValkind.I32]) + linker.defineFuncUnchecked("nev:plugins/text-editor", + "get-visible-line-count", ty): + var instance = cast[ptr InstanceData](store.getData()) + var editor: TextEditor + editor.id = convert(parameters[0].i64, uint64) + let res = textEditorGetVisibleLineCount(instance, editor) + parameters[0].i32 = cast[int32](res) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index 64f397cae..a61cbed77 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -612,11 +612,11 @@ proc textEditorHideCompletions(instance: ptr InstanceData; editor: TextEditor) = if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: editor.TextDocumentEditor.hideCompletions() -proc textEditorScrollToCursor(instance: ptr InstanceData; editor: TextEditor; behaviour: Option[ScrollBehaviour]): void = +proc textEditorScrollToCursor(instance: ptr InstanceData; editor: TextEditor; behaviour: Option[ScrollBehaviour]; relativePosition: float32): void = if instance.host == nil: return if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: - editor.TextDocumentEditor.scrollToCursor(sca.SelectionCursor.Last, scrollBehaviour = behaviour.mapIt(it.toInternal)) + editor.TextDocumentEditor.scrollToCursor(sca.SelectionCursor.Last, scrollBehaviour = behaviour.mapIt(it.toInternal), relativePosition=relativePosition) proc textEditorSetNextSnapBehaviour(instance: ptr InstanceData; editor: TextEditor; behaviour: ScrollSnapBehaviour): void = if instance.host == nil: @@ -746,6 +746,24 @@ proc textEditorIndent(instance: ptr InstanceData; editor: TextEditor; delta: int for i in 0..<(-delta): editor.TextDocumentEditor.unindent() +proc textEditorGetCommandCount(instance: ptr InstanceData; editor: TextEditor): int32 = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + return editor.TextDocumentEditor.getCommandCount().int32 + +proc textEditorGetVisibleLineCount(instance: ptr InstanceData; editor: TextEditor): int32 = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + return editor.TextDocumentEditor.screenLineCount().int32 + +proc textEditorSetCursorScrollOffset(instance: ptr InstanceData; editor: TextEditor; cursor: Cursor; scrollOffset: float32): void = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + editor.TextDocumentEditor.setCursorScrollOffset(cursor.toInternal, scrollOffset) + proc typesNewRope(instance: ptr InstanceData, content: sink string): RopeResource = return RopeResource(rope: createRope(content).slice().suffix(Point())) diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index 686fa105b..0f4a5b11b 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -1233,7 +1233,7 @@ proc numWrapLines*(self: TextDocumentEditor): int {.expose: "editor.text".} = proc wrapEndPoint*(self: TextDocumentEditor): WrapPoint = return self.displayMap.wrapMap.endWrapPoint -proc screenLineCount(self: TextDocumentEditor): int {.expose: "editor.text".} = +proc screenLineCount*(self: TextDocumentEditor): int {.expose: "editor.text".} = ## Returns the number of lines that can be shown on the screen ## This value depends on the size of the view this editor is in and the font size # todo @@ -2900,6 +2900,11 @@ proc setCursorScrollOffset*(self: TextDocumentEditor, offset: float, self.scrollOffset.y = offset - displayPoint.row.float * self.platform.totalLineHeight self.markDirty() +proc setCursorScrollOffset*(self: TextDocumentEditor, cursor: Cursor, offset: float) = + let displayPoint = self.displayMap.toDisplayPoint(cursor.toPoint) + self.scrollOffset.y = offset * self.platform.totalLineHeight - displayPoint.row.float * self.platform.totalLineHeight + self.markDirty() + proc getContentBounds*(self: TextDocumentEditor): Vec2 {.expose("editor.text").} = # todo return self.lastContentBounds.wh diff --git a/wit/v0/api.wit b/wit/v0/api.wit index 607dd5399..0566a71cf 100644 --- a/wit/v0/api.wit +++ b/wit/v0/api.wit @@ -248,7 +248,7 @@ interface text-editor { hide-completions: func(editor: text-editor); /// todo - scroll-to-cursor: func(editor: text-editor, behaviour: option); + scroll-to-cursor: func(editor: text-editor, behaviour: option, relative-position: f32); /// todo set-next-snap-behaviour: func(editor: text-editor, behaviour: scroll-snap-behaviour); @@ -328,6 +328,15 @@ interface text-editor { /// todo indent: func(editor: text-editor, delta: s32); + /// todo + get-command-count: func(editor: text-editor) -> s32; + + /// todo + set-cursor-scroll-offset: func(editor: text-editor, cursor: cursor, scroll-offset: f32); + + /// Returns the number of lines that fit on the current screen size for this editor. + get-visible-line-count: func(editor: text-editor) -> s32; + /// todo edit: func(editor: text-editor, selections: list, contents: list, inclusive: bool) -> list; From fa6036def8d64a9ebb1676ba70f031db33c7424c Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sat, 18 Oct 2025 23:15:41 +0200 Subject: [PATCH 28/47] Marks --- config/settings.json | 6 +- nimble.lock | 4 +- plugin_api/v0/text_editor.nim | 43 +++++++ plugin_api/v0/types.nim | 9 ++ plugins/vim/vim.nim | 77 ++++++------ src/generated/plugin_api_host.nim | 198 +++++++++++++++++++++++++----- src/plugin_api/plugin_api.nim | 28 ++++- wit/v0/api.wit | 24 +++- 8 files changed, 316 insertions(+), 73 deletions(-) diff --git a/config/settings.json b/config/settings.json index 16022e787..ee3c550cb 100644 --- a/config/settings.json +++ b/config/settings.json @@ -257,12 +257,12 @@ ], "line-comment": "#", "+search-regexes": { - "goto-definition": "((proc|method|func|template|macro|iterator|type|declare|declareSettings|^var|^let) [[0]]\\b)|(\\b[[0]]\\b.*?= (object|ref|ptr|array|set|tuple|enum|distinct|proc))|((type | +)\\b[[0]]\\b\\*?( \\{.*?)?:.+[^,;]$)", + "goto-definition": "((proc|method|func|converter|template|macro|iterator|type|declare|declareSettings|^var|^let) [[0]]\\b)|(\\b[[0]]\\b.*?= (object|ref|ptr|array|set|tuple|enum|distinct|proc))|((type | +)\\b[[0]]\\b\\*?( \\{.*?)?:.+[^,;]$)", "goto-implementation": "object of [[0]]", - "symbols": "((proc|method|func|template|macro|iterator|type|declare|declareSettings) (\\b([a-zA-Z0-9_]+\\b)|(`.+?`)))|((\\b([a-zA-Z0-9_]+)|(`.+?`)\\b).*?= (object|ref|ptr|array|set|tuple|enum|distinct|proc))", + "symbols": "((proc|method|func|converter|template|macro|iterator|type|declare|declareSettings) (\\b([a-zA-Z0-9_]+\\b)|(`.+?`)))|((\\b([a-zA-Z0-9_]+)|(`.+?`)\\b).*?= (object|ref|ptr|array|set|tuple|enum|distinct|proc))", "workspace-symbols-by-kind": { "Class": "(\\b([a-zA-Z0-9_]+)|(`.+?`)\\b).*?= (object|ref|ptr|array|set|tuple|distinct)", - "Function": "((proc|func|template|macro|iterator|type|declare|declareSettings) (\\b([a-zA-Z0-9_]+\\b)|(`.+?`)))|((\\b([a-zA-Z0-9_]+)|(`.+?`)\\b).*?= (proc))", + "Function": "((proc|func|converter|template|macro|iterator|type|declare|declareSettings) (\\b([a-zA-Z0-9_]+\\b)|(`.+?`)))|((\\b([a-zA-Z0-9_]+)|(`.+?`)\\b).*?= (proc))", "Method": "((method) (\\b([a-zA-Z0-9_]+\\b)|(`.+?`)))", "Enum": "(\\b([a-zA-Z0-9_]+)|(`.+?`)\\b).*?= (enum)" } diff --git a/nimble.lock b/nimble.lock index b44d08573..3049dd0fd 100644 --- a/nimble.lock +++ b/nimble.lock @@ -348,12 +348,12 @@ }, "nimsumtree": { "version": "0.5.6", - "vcsRevision": "47f7208faeeb8ee6a9c6913af7be40ba494857df", + "vcsRevision": "2a6e442a7fd3ad58975810ff266ddf2887b5d78c", "url": "https://github.com/Nimaoth/nimsumtree", "downloadMethod": "git", "dependencies": [], "checksums": { - "sha1": "83cdadd2e88aabc7e7edbc26dd0d9b7b370c612d" + "sha1": "3a62d7ebfac045adeda6cadfc883b8b96ff58a89" } }, "nimtreesitter": { diff --git a/plugin_api/v0/text_editor.nim b/plugin_api/v0/text_editor.nim index 30f785b28..2adfaff7e 100644 --- a/plugin_api/v0/text_editor.nim +++ b/plugin_api/v0/text_editor.nim @@ -816,6 +816,49 @@ proc getVisibleLineCount*(editor: TextEditor): int32 {.nodestroy.} = let res = textEditorGetVisibleLineCountImported(arg0) result = convert(res, int32) +proc textEditorCreateAnchorsImported(a0: uint64; a1: int32; a2: int32; a3: int32): void {. + wasmimport("create-anchors", "nev:plugins/text-editor").} +proc createAnchors*(editor: TextEditor; selections: WitList[Selection]): WitList[ + (Anchor, Anchor)] {.nodestroy.} = + ## todo + var + retArea: array[16, uint8] + arg0: uint64 + arg1: int32 + arg2: int32 + arg0 = editor.id + if selections.len > 0: + arg1 = cast[int32](selections[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](selections.len) + textEditorCreateAnchorsImported(arg0, arg1, arg2, + cast[int32](retArea[0].addr)) + result = wl(cast[ptr typeof(result[0])](cast[ptr int32](retArea[0].addr)[]), + cast[ptr int32](retArea[4].addr)[]) + +proc textEditorResolveAnchorsImported(a0: uint64; a1: int32; a2: int32; + a3: int32): void {. + wasmimport("resolve-anchors", "nev:plugins/text-editor").} +proc resolveAnchors*(editor: TextEditor; anchors: WitList[(Anchor, Anchor)]): WitList[ + Selection] {.nodestroy.} = + ## todo + var + retArea: array[16, uint8] + arg0: uint64 + arg1: int32 + arg2: int32 + arg0 = editor.id + if anchors.len > 0: + arg1 = cast[int32](anchors[0].addr) + else: + arg1 = 0.int32 + arg2 = cast[int32](anchors.len) + textEditorResolveAnchorsImported(arg0, arg1, arg2, + cast[int32](retArea[0].addr)) + result = wl(cast[ptr typeof(result[0])](cast[ptr int32](retArea[0].addr)[]), + cast[ptr int32](retArea[4].addr)[]) + proc textEditorEditImported(a0: uint64; a1: int32; a2: int32; a3: int32; a4: int32; a5: bool; a6: int32): void {. wasmimport("edit", "nev:plugins/text-editor").} diff --git a/plugin_api/v0/types.nim b/plugin_api/v0/types.nim index 423a6d8a4..2104ed56c 100644 --- a/plugin_api/v0/types.nim +++ b/plugin_api/v0/types.nim @@ -29,6 +29,15 @@ type IncludeCommandLine = "include-command-line", IncludePopups = "include-popups" ActiveEditorFlags* = set[ActiveEditorFlag] + Lamport* = object + replicaId*: uint16 + value*: uint32 + Bias* = enum + Left = "left", Right = "right" + Anchor* = object + timestamp*: Lamport + offset*: uint32 + bias*: Bias ## Shared reference to a rope. The rope data is stored in the editor, not in the plugin, so ropes ## can be used to efficiently access any document content or share a string with another plugin. ## Ropes are reference counted internally, and this resource also affects that reference count. diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 334b4b24f..d1688784a 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -30,8 +30,8 @@ type EditorVimState = object cursorIncludeEol: bool = false ## Whether the cursor can be after the last character in a line (e.g. in insert mode) currentUndoCheckpoint: string = "insert" ## Which checkpoint to undo to (depends on mode) revisionBeforeImplicitInsertMacro: int - marks: Table[string, seq[(sca.Anchor, sca.Anchor)]] - unresolveMarks: Table[string, seq[sca.Selection]] + marks: Table[string, seq[(Anchor, Anchor)]] + unresolvedMarks: Table[string, seq[Selection]] var editorStates: Table[sca.EditorId, EditorVimState] @@ -1154,7 +1154,7 @@ proc setSearchQueryOrAddCursor(editor: TextEditor) {.exposeActive(editorContext) # for name, anchors in editor.vimState.marks: # let selections = editor.resolveAnchors(anchors) # marks[name] = selections -# for name, selections in editor.vimState.unresolveMarks: +# for name, selections in editor.vimState.unresolvedMarks: # marks[name] = selections # if marks.len > 0: @@ -1166,41 +1166,42 @@ proc setSearchQueryOrAddCursor(editor: TextEditor) {.exposeActive(editorContext) # except: # debugf"Failed to save vim editor states" -# proc resolveMarks(editor: TextEditor) = -# let unresolveMarks = editor.vimState.unresolveMarks -# for name, selections in unresolveMarks: -# let anchors = editor.createAnchors(selections) -# if anchors.len > 0: -# editor.vimState.marks[name] = anchors -# editor.vimState.unresolveMarks.del(name) - -# proc vimAddMark(editor: TextEditor, name: string) {.exposeActive(editorContext, "vim-add-mark").} = -# editor.resolveMarks() -# editor.vimState.marks[name] = editor.createAnchors(editor.selections) - -# proc vimGotoMark(editor: TextEditor, name: string) {.exposeActive(editorContext, "vim-goto-mark").} = -# editor.resolveMarks() - -# if name in editor.vimState.marks: -# let newSelections = editor.resolveAnchors(editor.vimState.marks[name]) -# if newSelections.len == 0: -# return - -# case editor.mode -# of "vim-new.visual", "vim-new.visual-line": -# let oldSelections = editor.selections -# if newSelections.len == oldSelections.len: -# editor.setSelections collect: -# for i in 0..newSelections.high: -# oldSelections[i] or newSelections[i] -# else: -# editor.setSelections newSelections -# else: -# editor.setSelections newSelections +proc resolveMarks(editor: TextEditor) = + let unresolvedMarks = editor.vimState.unresolvedMarks + for name, selections in unresolvedMarks: + let anchors = @(editor.createAnchors(selections.stackWitList())) + if anchors.len > 0: + editor.vimState.marks[name] = anchors + editor.vimState.unresolvedMarks.del(name) -# editor.updateTargetColumn() -# editor.scrollToCursor() -# editor.setNextSnapBehaviour(MinDistanceOffscreen) +proc addMark(editor: TextEditor, name: string) {.exposeActive(editorContext).} = + editor.resolveMarks() + editor.vimState.marks[name] = @(editor.createAnchors(editor.selections)) + +proc gotoMark(editor: TextEditor, name: string) {.exposeActive(editorContext).} = + editor.resolveMarks() + + if name in editor.vimState.marks: + let newSelections = editor.resolveAnchors(editor.vimState.marks[name].stackWitList()) + if newSelections.len == 0: + return + + case $editor.mode + of "vim-new.visual", "vim-new.visual-line": + let oldSelections = editor.selections + if newSelections.len == oldSelections.len: + editor.setSelections stackWitList(collect(block: + for i in 0..newSelections.high: + oldSelections[i] or newSelections[i] + )) + else: + editor.setSelections newSelections + else: + editor.setSelections newSelections + + editor.updateTargetColumn() + editor.scrollToCursor() + editor.setNextSnapBehaviour(MinDistanceOffscreen) proc deleteWordBack(editor: TextEditor) {.exposeActive(editorContext).} = let wordSelections = editor.multiMove(editor.selections, ws"vim.word-back", 1, wrap = false, includeEol = true) @@ -1477,7 +1478,7 @@ proc modeChangedHandler(editor: TextEditor, oldModes: seq[string], newModes: seq # if editorState.hasKey("marks"): # let marks = editorState["marks"].jsonTo(Table[string, seq[Selection]]) # for name, selections in marks: -# editor.vimState.unresolveMarks[name] = selections +# editor.vimState.unresolvedMarks[name] = selections # except: # debugf"Failed to restore marks for {editor}" # scriptSetCallback("after-restore-session", afterRestoreSessionHandle) diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index f07897fba..a9a296b9d 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -29,6 +29,15 @@ type IncludeCommandLine = "include-command-line", IncludePopups = "include-popups" ActiveEditorFlags* = set[ActiveEditorFlag] + Lamport* = object + replicaId*: uint16 + value*: uint32 + Bias* = enum + Left = "left", Right = "right" + Anchor* = object + timestamp*: Lamport + offset*: uint32 + bias*: Bias ## Shared reference to a rope. The rope data is stored in the editor, not in the plugin, so ropes ## can be used to efficiently access any document content or share a string with another plugin. ## Ropes are reference counted internally, and this resource also affects that reference count. @@ -120,47 +129,47 @@ proc collectExports*(funcs: var ExportedFuncs; instance: InstanceT; funcs.mStackAlloc = instance.getExport(context, "mem_stack_alloc") funcs.mStackSave = instance.getExport(context, "mem_stack_save") funcs.mStackRestore = instance.getExport(context, "mem_stack_restore") - let f_9697232496 = instance.getExport(context, "init_plugin") - if f_9697232496.isSome: - assert f_9697232496.get.kind == WASMTIME_EXTERN_FUNC - funcs.initPlugin = f_9697232496.get.of_field.func_field + let f_9697232498 = instance.getExport(context, "init_plugin") + if f_9697232498.isSome: + assert f_9697232498.get.kind == WASMTIME_EXTERN_FUNC + funcs.initPlugin = f_9697232498.get.of_field.func_field else: echo "Failed to find exported function \'", "init_plugin", "\'" - let f_9697232512 = instance.getExport(context, "handle_command") - if f_9697232512.isSome: - assert f_9697232512.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleCommand = f_9697232512.get.of_field.func_field + let f_9697232514 = instance.getExport(context, "handle_command") + if f_9697232514.isSome: + assert f_9697232514.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleCommand = f_9697232514.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_command", "\'" - let f_9697232562 = instance.getExport(context, "handle_mode_changed") - if f_9697232562.isSome: - assert f_9697232562.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleModeChanged = f_9697232562.get.of_field.func_field + let f_9697232564 = instance.getExport(context, "handle_mode_changed") + if f_9697232564.isSome: + assert f_9697232564.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleModeChanged = f_9697232564.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_mode_changed", "\'" - let f_9697232563 = instance.getExport(context, "handle_view_render_callback") - if f_9697232563.isSome: - assert f_9697232563.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleViewRenderCallback = f_9697232563.get.of_field.func_field + let f_9697232565 = instance.getExport(context, "handle_view_render_callback") + if f_9697232565.isSome: + assert f_9697232565.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleViewRenderCallback = f_9697232565.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_view_render_callback", "\'" - let f_9697232587 = instance.getExport(context, "handle_channel_update") - if f_9697232587.isSome: - assert f_9697232587.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleChannelUpdate = f_9697232587.get.of_field.func_field + let f_9697232589 = instance.getExport(context, "handle_channel_update") + if f_9697232589.isSome: + assert f_9697232589.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleChannelUpdate = f_9697232589.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_channel_update", "\'" - let f_9697232588 = instance.getExport(context, "notify_task_complete") - if f_9697232588.isSome: - assert f_9697232588.get.kind == WASMTIME_EXTERN_FUNC - funcs.notifyTaskComplete = f_9697232588.get.of_field.func_field + let f_9697232590 = instance.getExport(context, "notify_task_complete") + if f_9697232590.isSome: + assert f_9697232590.get.kind == WASMTIME_EXTERN_FUNC + funcs.notifyTaskComplete = f_9697232590.get.of_field.func_field else: echo "Failed to find exported function \'", "notify_task_complete", "\'" - let f_9697232589 = instance.getExport(context, "handle_move") - if f_9697232589.isSome: - assert f_9697232589.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleMove = f_9697232589.get.of_field.func_field + let f_9697232591 = instance.getExport(context, "handle_move") + if f_9697232591.isSome: + assert f_9697232591.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleMove = f_9697232591.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_move", "\'" @@ -548,6 +557,12 @@ proc textEditorSetCursorScrollOffset(instance: ptr InstanceData; scrollOffset: float32): void proc textEditorGetVisibleLineCount(instance: ptr InstanceData; editor: TextEditor): int32 +proc textEditorCreateAnchors(instance: ptr InstanceData; editor: TextEditor; + selections: sink seq[Selection]): seq[ + (Anchor, Anchor)] +proc textEditorResolveAnchors(instance: ptr InstanceData; editor: TextEditor; + anchors: sink seq[(Anchor, Anchor)]): seq[ + Selection] proc textEditorEdit(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection]; contents: sink seq[string]; inclusive: bool): seq[Selection] @@ -2648,6 +2663,133 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = parameters[0].i32 = cast[int32](res) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], + []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "create-anchors", ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + let stackAllocFunc = caller.getExport("mem_stack_alloc").get.of_field.func_field + var editor: TextEditor + var selections: seq[Selection] + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[uint8]](memory[parameters[1].i32].addr) + selections = newSeq[typeof(selections[0])](parameters[2].i32) + for i0 in 0 ..< selections.len: + selections[i0].first.line = convert( + cast[ptr int32](p0[i0 * 16 + 0].addr)[], int32) + selections[i0].first.column = convert( + cast[ptr int32](p0[i0 * 16 + 4].addr)[], int32) + selections[i0].last.line = convert( + cast[ptr int32](p0[i0 * 16 + 8].addr)[], int32) + selections[i0].last.column = convert( + cast[ptr int32](p0[i0 * 16 + 12].addr)[], int32) + let res = textEditorCreateAnchors(instance, editor, selections) + let retArea = parameters[^1].i32 + if res.len > 0: + let dataPtrWasm0 = int32(?stackAlloc(stackAllocFunc, store, + (res.len * 28).int32, 4)) + cast[ptr int32](memory[retArea + 0].addr)[] = cast[int32](dataPtrWasm0) + block: + for i0 in 0 ..< res.len: + cast[ptr uint16](memory[dataPtrWasm0 + i0 * 28 + 0].addr)[] = res[ + i0][0].timestamp.replicaId + cast[ptr uint32](memory[dataPtrWasm0 + i0 * 28 + 4].addr)[] = res[ + i0][0].timestamp.value + cast[ptr uint32](memory[dataPtrWasm0 + i0 * 28 + 8].addr)[] = res[ + i0][0].offset + cast[ptr int8](memory[dataPtrWasm0 + i0 * 28 + 12].addr)[] = cast[int8](res[ + i0][0].bias) + cast[ptr uint16](memory[dataPtrWasm0 + i0 * 28 + 14].addr)[] = res[ + i0][1].timestamp.replicaId + cast[ptr uint32](memory[dataPtrWasm0 + i0 * 28 + 16].addr)[] = res[ + i0][1].timestamp.value + cast[ptr uint32](memory[dataPtrWasm0 + i0 * 28 + 20].addr)[] = res[ + i0][1].offset + cast[ptr int8](memory[dataPtrWasm0 + i0 * 28 + 24].addr)[] = cast[int8](res[ + i0][1].bias) + else: + cast[ptr int32](memory[retArea + 0].addr)[] = 0.int32 + cast[ptr int32](memory[retArea + 4].addr)[] = cast[int32](res.len) + if e.isErr: + return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I64, WasmValkind.I32, WasmValkind.I32, WasmValkind.I32], + []) + linker.defineFuncUnchecked("nev:plugins/text-editor", "resolve-anchors", + ty): + var instance = cast[ptr InstanceData](store.getData()) + var mainMemory = caller.getExport("memory") + if mainMemory.isNone: + mainMemory = instance.getMemoryFor(caller) + var memory: ptr UncheckedArray[uint8] = nil + if mainMemory.get.kind == WASMTIME_EXTERN_SHAREDMEMORY: + memory = cast[ptr UncheckedArray[uint8]](data( + mainMemory.get.of_field.sharedmemory)) + elif mainMemory.get.kind == WASMTIME_EXTERN_MEMORY: + memory = cast[ptr UncheckedArray[uint8]](store.data( + mainMemory.get.of_field.memory.addr)) + else: + assert false + let stackAllocFunc = caller.getExport("mem_stack_alloc").get.of_field.func_field + var editor: TextEditor + var anchors: seq[(Anchor, Anchor)] + editor.id = convert(parameters[0].i64, uint64) + block: + let p0 = cast[ptr UncheckedArray[uint8]](memory[parameters[1].i32].addr) + anchors = newSeq[typeof(anchors[0])](parameters[2].i32) + for i0 in 0 ..< anchors.len: + anchors[i0][0].timestamp.replicaId = convert( + cast[ptr uint16](p0[i0 * 28 + 0].addr)[], uint16) + anchors[i0][0].timestamp.value = convert( + cast[ptr uint32](p0[i0 * 28 + 4].addr)[], uint32) + anchors[i0][0].offset = convert( + cast[ptr uint32](p0[i0 * 28 + 8].addr)[], uint32) + anchors[i0][0].bias = cast[Bias](cast[ptr int8](p0[i0 * 28 + 12].addr)[]) + anchors[i0][1].timestamp.replicaId = convert( + cast[ptr uint16](p0[i0 * 28 + 14].addr)[], uint16) + anchors[i0][1].timestamp.value = convert( + cast[ptr uint32](p0[i0 * 28 + 16].addr)[], uint32) + anchors[i0][1].offset = convert( + cast[ptr uint32](p0[i0 * 28 + 20].addr)[], uint32) + anchors[i0][1].bias = cast[Bias](cast[ptr int8](p0[i0 * 28 + 24].addr)[]) + let res = textEditorResolveAnchors(instance, editor, anchors) + let retArea = parameters[^1].i32 + if res.len > 0: + let dataPtrWasm0 = int32(?stackAlloc(stackAllocFunc, store, + (res.len * 16).int32, 4)) + cast[ptr int32](memory[retArea + 0].addr)[] = cast[int32](dataPtrWasm0) + block: + for i0 in 0 ..< res.len: + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 0].addr)[] = res[ + i0].first.line + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 4].addr)[] = res[ + i0].first.column + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 8].addr)[] = res[ + i0].last.line + cast[ptr int32](memory[dataPtrWasm0 + i0 * 16 + 12].addr)[] = res[ + i0].last.column + else: + cast[ptr int32](memory[retArea + 0].addr)[] = 0.int32 + cast[ptr int32](memory[retArea + 4].addr)[] = cast[int32](res.len) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([WasmValkind.I64, WasmValkind.I32, diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index a61cbed77..02897cf55 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -1,6 +1,6 @@ import std/[macros, strutils, os, strformat, sequtils, json, sets, pathnorm, locks, tables] import misc/[custom_logger, custom_async, util, event, jsonex, timer, myjsonutils, render_command, binary_encoder, async_process, rope_utils] -import nimsumtree/[rope, sumtree, arc] +import nimsumtree/[rope, sumtree, arc, clock, buffer] import service import layout import text/[text_editor, text_document] @@ -352,6 +352,13 @@ var threadPool = newPluginThreadPool[InstanceDataImpl](10, runInstanceThread) converter toPoint(c: Cursor): Point = point(c.line.int, c.column.int) converter toInternal(c: Cursor): sca.Cursor = (c.line.int, c.column.int) converter toInternal(c: Selection): sca.Selection = (c.first.toInternal, c.last.toInternal) +converter toInternal(c: Lamport): clock.Lamport = clock.Lamport(replicaId: clock.ReplicaId(c.replicaId), value: clock.SeqNumber(c.value)) +converter toInternal(c: Bias): sumtree.Bias = + case c + of Left: sumtree.Left + of Right: sumtree.Right +converter toInternal(c: Anchor): buffer.Anchor = buffer.Anchor(timestamp: c.timestamp, offset: c.offset.int, bias: c.bias) +converter toInternal(c: (Anchor, Anchor)): (buffer.Anchor, buffer.Anchor) = (c[0].toInternal, c[1].toInternal) converter toInternal(c: ScrollBehaviour): sca.ScrollBehaviour = case c of CenterAlways: sca.CenterAlways @@ -369,6 +376,13 @@ converter toInternal(c: ScrollSnapBehaviour): sca.ScrollSnapBehaviour = converter toWasm(c: sca.Cursor): Cursor = Cursor(line: c.line.int32, column: c.column.int32) converter toWasm(c: sca.Selection): Selection = Selection(first: c.first.toWasm, last: c.last.toWasm) +converter toWasm(c: clock.Lamport): Lamport = Lamport(replicaId: c.replicaId.uint16, value: c.value.uint32) +converter toWasm(c: sumtree.Bias): Bias = + case c + of sumtree.Left: Left + of sumtree.Right: Right +converter toWasm(c: buffer.Anchor): Anchor = Anchor(timestamp: c.timestamp, offset: c.offset.uint32, bias: c.bias) +converter toWasm(c: (buffer.Anchor, buffer.Anchor)): (Anchor, Anchor) = (c[0].toWasm, c[1].toWasm) converter toInternal(flags: ReadFlags): set[vfs.ReadFlag] = result = {} @@ -764,6 +778,18 @@ proc textEditorSetCursorScrollOffset(instance: ptr InstanceData; editor: TextEdi if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: editor.TextDocumentEditor.setCursorScrollOffset(cursor.toInternal, scrollOffset) +proc textEditorCreateAnchors(instance: ptr InstanceData; editor: TextEditor; selections: sink seq[Selection]): seq[(Anchor, Anchor)] = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + return editor.TextDocumentEditor.createAnchors(selections.mapIt(it.toInternal)).mapIt(it.toWasm) + +proc textEditorResolveAnchors(instance: ptr InstanceData; editor: TextEditor; anchors: sink seq[(Anchor, Anchor)]): seq[Selection] = + if instance.host == nil: + return + if instance.host.editors.getEditor(editor.id.EditorIdNew).getSome(editor) and editor of TextDocumentEditor: + return editor.TextDocumentEditor.resolveAnchors(anchors.mapIt(it.toInternal)).mapIt(it.toWasm) + proc typesNewRope(instance: ptr InstanceData, content: sink string): RopeResource = return RopeResource(rope: createRope(content).slice().suffix(Point())) diff --git a/wit/v0/api.wit b/wit/v0/api.wit index 0566a71cf..5da569704 100644 --- a/wit/v0/api.wit +++ b/wit/v0/api.wit @@ -29,6 +29,22 @@ interface types { include-popups, } + record lamport { + replica-id: u16, + value: u32, + } + + enum bias { + left, + right, + } + + record anchor { + timestamp: lamport, + offset: u32, + bias: bias, + } + /// Shared reference to a rope. The rope data is stored in the editor, not in the plugin, so ropes /// can be used to efficiently access any document content or share a string with another plugin. /// Ropes are reference counted internally, and this resource also affects that reference count. @@ -207,7 +223,7 @@ interface editor { } interface text-editor { - use types.{editor, document, text-editor, text-document, cursor, selection, rope, active-editor-flags}; + use types.{editor, document, text-editor, text-document, cursor, selection, rope, active-editor-flags, anchor}; use commands.{command-error}; enum scroll-behaviour { @@ -337,6 +353,12 @@ interface text-editor { /// Returns the number of lines that fit on the current screen size for this editor. get-visible-line-count: func(editor: text-editor) -> s32; + /// todo + create-anchors: func(editor: text-editor, selections: list) -> list>; + + /// todo + resolve-anchors: func(editor: text-editor, anchors: list>) -> list; + /// todo edit: func(editor: text-editor, selections: list, contents: list, inclusive: bool) -> list; From 574d617187362f8578dbc6d52e4e1ff26446085f Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 19 Oct 2025 00:52:44 +0200 Subject: [PATCH 29/47] Fixed some bugs --- config/keybindings.json | 3 +- plugin_api/v0/commands.nim | 6 +++ plugin_api/v0/layout.nim | 13 +++++ plugins/vim/vim.nim | 16 ++++--- src/command_service_api.nim | 4 +- src/generated/plugin_api_host.nim | 80 ++++++++++++++++++++----------- src/plugin_api/plugin_api.nim | 11 ++++- src/text/text_editor.nim | 4 +- wit/v0/api.wit | 6 +++ 9 files changed, 102 insertions(+), 41 deletions(-) diff --git a/config/keybindings.json b/config/keybindings.json index cbadaa747..74e9b3957 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -161,8 +161,7 @@ "": ["accept"], "": ["accept"], // "": ["cancel"], - "": ["vim-exit-popup"], // todo: replace this with vim.exit-popup - "": ["vim.exit-popup"], // todo: remove this + "": ["vim.exit-popup"], "": ["prev"], "": ["prev"], "": ["next"], diff --git a/plugin_api/v0/commands.nim b/plugin_api/v0/commands.nim index 7debb28e3..528d73f69 100644 --- a/plugin_api/v0/commands.nim +++ b/plugin_api/v0/commands.nim @@ -100,3 +100,9 @@ proc runCommand*(name: WitString; arguments: WitString): Result[WitString, var tempErr: CommandError tempErr = cast[CommandError](cast[ptr int32](retArea[4].addr)[]) result = results.Result[WitString, CommandError].err(tempErr) + +proc commandsExitCommandLineImported(): void {. + wasmimport("exit-command-line", "nev:plugins/commands").} +proc exitCommandLine*(): void {.nodestroy.} = + ## Close the command line. + commandsExitCommandLineImported() diff --git a/plugin_api/v0/layout.nim b/plugin_api/v0/layout.nim index aaddebba8..c3c01326a 100644 --- a/plugin_api/v0/layout.nim +++ b/plugin_api/v0/layout.nim @@ -62,3 +62,16 @@ proc focus*(slot: WitString): void {.nodestroy.} = arg0 = 0.int32 arg1 = cast[int32](slot.len) layoutFocusImported(arg0, arg1) + +proc layoutCloseActiveViewImported(a0: bool; a1: bool): void {. + wasmimport("close-active-view", "nev:plugins/layout").} +proc closeActiveView*(closeOpenPopup: bool; restoreHidden: bool): void {. + nodestroy.} = + ## Close the active view. If 'close-open-popup' is set and a popup is open then the popup will be closed. + ## If 'restore-hidden' is true then the view will be replaced by a hidden view, otherwise the slot will be removed from the layout.. + var + arg0: bool + arg1: bool + arg0 = closeOpenPopup + arg1 = restoreHidden + layoutCloseActiveViewImported(arg0, arg1) diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index d1688784a..f5367347f 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -720,11 +720,7 @@ proc moveLast(editor: TextEditor, move: string, count: int = 1, wrap: bool = fal proc moveDirection(editor: TextEditor, move: string, direction: int, count: int = 1, wrap: bool = false, updateTargetColumn: bool = true) {.exposeActive(editorContext).} = let cursorSelector = editor.getSetting(editor.getContextWithMode("editor.text.cursor.movement"), sca.SelectionCursor.Both) - debugf"moveDirection '{move}', dir: {direction}, count: {count}, includeEol: {editor.vimState.cursorIncludeEol}" editor.setSelections editor.multiMove(editor.selections, move.ws, direction * max(count, 1), wrap, includeEol = editor.vimState.cursorIncludeEol) - # editor.setSelections editor.selections.mapIt( - # editor.applyMove(it.last, move, direction * max(count, 1), wrap = wrap, includeEol = editor.vimState.cursorIncludeEol).last.toSelection(it, cursorSelector) - # ) if editor.vimState.selectLines: editor.selectLine() @@ -791,6 +787,7 @@ proc toggleCase(editor: TextEditor, moveCursorRight: bool) {.exposeActive(editor editor.moveCursorColumn(1, wrap=false, includeEol=editor.vimState.cursorIncludeEol) editor.updateTargetColumn() +# todo # proc vimCloseCurrentViewOrQuit() {.exposeActive(editorContext, "vim-close-current-view-or-quit").} = # let openEditors = getNumVisibleViews() + getNumHiddenViews() # if openEditors == 1: @@ -1077,14 +1074,15 @@ proc openSearchBar(editor: TextEditor) {.exposeActive(editorContext).} = proc exitCommandLine() {.command.} = if activeTextEditor({IncludeCommandLine}).getSome(editor): - if editor.mode == ws"vim-new.normal": - discard runCommand(ws"exit-command-line", ws"") + let mode = $editor.mode + if mode == "vim-new.normal": + commands.exitCommandLine() return editor.setMode("vim-new.normal") proc exitPopup() {.command.} = - if activeTextEditor({IncludePopups}).getSome(editor) and editor.mode != ws"vim-new.normal": + if activeTextEditor({IncludePopups}).getSome(editor) and $editor.mode != "vim-new.normal": editor.setMode("vim-new.normal") return @@ -1140,6 +1138,7 @@ proc setSearchQueryOrAddCursor(editor: TextEditor) {.exposeActive(editorContext) editor.scrollToCursor() editor.updateTargetColumn() +# todo # proc vimSaveState() {.expose("vim-save-state").} = # try: # var states = initTable[string, JsonNode]() @@ -1324,6 +1323,7 @@ proc moveToColumn(editor: TextEditor, count: int = 1) {.exposeActive(editorConte editor.scrollToCursor() editor.updateTargetColumn() +# todo # proc vimAddNextSameNodeToSelection(editor: TextEditor) {.exposeActive(editorContext, "vim-add-next-same-node-to-selection").} = # if editor.getNextNodeWithSameType(editor.selection, includeAfter=false).getSome(selection): # editor.setSelections editor.selections & selection @@ -1395,6 +1395,7 @@ proc incrementByIndex(editor: TextEditor) {.exposeActive(editorContext).} = proc replaceInputHandler(editor: TextEditor, input: string) {.exposeActive(editorContext).} = editor.vimReplace(input) +# todo # proc vimInsertRegisterInputHandler(editor: TextEditor, input: string) {.exposeActive(editorContext, "vim-insert-register-input-handler").} = # editor.vimPaste register=input, inclusiveEnd=true # editor.setMode "vim-new.insert" @@ -1466,6 +1467,7 @@ proc modeChangedHandler(editor: TextEditor, oldModes: seq[string], newModes: seq else: editor.setSetting "text.inclusive-selection", false +# todo # proc loadVimKeybindings*() {.expose("load-vim-keybindings").} = # let afterRestoreSessionHandle = addCallback proc(args: JsonNode): JsonNode = # let states = getSessionData[Table[string, JsonNode]]("vim.states") diff --git a/src/command_service_api.nim b/src/command_service_api.nim index bca03d965..012df0036 100644 --- a/src/command_service_api.nim +++ b/src/command_service_api.nim @@ -1,4 +1,4 @@ -import std/[strutils, options, json] +import std/[strutils, options, json, tables] import scripting_api except DocumentEditor, TextDocumentEditor, AstDocumentEditor from scripting_api as api import nil import misc/[util, custom_logger, custom_async, custom_unicode, myjsonutils, async_process, delayed_task, timer] @@ -73,6 +73,7 @@ proc exitCommandLine*(self: CommandService) {.expose("commands").} = discard self.defaultCommandHandler(string.none) except Exception as e: log lvlError, &"exitCommandLine: {e.msg}" + self.commandHandler = nil self.requestRender() proc commandLineResult*(self: CommandService, value: string, showInCommandLine: bool = false, @@ -117,6 +118,7 @@ commandLineImpl = commandLine proc executeCommandLine*(self: CommandService): bool {.expose("commands").} = defer: self.requestRender() + self.commandHandler = nil let editor = self.commandLineEditor.TextDocumentEditor self.commandLineInputMode = false diff --git a/src/generated/plugin_api_host.nim b/src/generated/plugin_api_host.nim index a9a296b9d..961e41b4c 100644 --- a/src/generated/plugin_api_host.nim +++ b/src/generated/plugin_api_host.nim @@ -129,47 +129,47 @@ proc collectExports*(funcs: var ExportedFuncs; instance: InstanceT; funcs.mStackAlloc = instance.getExport(context, "mem_stack_alloc") funcs.mStackSave = instance.getExport(context, "mem_stack_save") funcs.mStackRestore = instance.getExport(context, "mem_stack_restore") - let f_9697232498 = instance.getExport(context, "init_plugin") - if f_9697232498.isSome: - assert f_9697232498.get.kind == WASMTIME_EXTERN_FUNC - funcs.initPlugin = f_9697232498.get.of_field.func_field + let f_9126807155 = instance.getExport(context, "init_plugin") + if f_9126807155.isSome: + assert f_9126807155.get.kind == WASMTIME_EXTERN_FUNC + funcs.initPlugin = f_9126807155.get.of_field.func_field else: echo "Failed to find exported function \'", "init_plugin", "\'" - let f_9697232514 = instance.getExport(context, "handle_command") - if f_9697232514.isSome: - assert f_9697232514.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleCommand = f_9697232514.get.of_field.func_field + let f_9126807171 = instance.getExport(context, "handle_command") + if f_9126807171.isSome: + assert f_9126807171.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleCommand = f_9126807171.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_command", "\'" - let f_9697232564 = instance.getExport(context, "handle_mode_changed") - if f_9697232564.isSome: - assert f_9697232564.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleModeChanged = f_9697232564.get.of_field.func_field + let f_9126807221 = instance.getExport(context, "handle_mode_changed") + if f_9126807221.isSome: + assert f_9126807221.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleModeChanged = f_9126807221.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_mode_changed", "\'" - let f_9697232565 = instance.getExport(context, "handle_view_render_callback") - if f_9697232565.isSome: - assert f_9697232565.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleViewRenderCallback = f_9697232565.get.of_field.func_field + let f_9126807222 = instance.getExport(context, "handle_view_render_callback") + if f_9126807222.isSome: + assert f_9126807222.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleViewRenderCallback = f_9126807222.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_view_render_callback", "\'" - let f_9697232589 = instance.getExport(context, "handle_channel_update") - if f_9697232589.isSome: - assert f_9697232589.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleChannelUpdate = f_9697232589.get.of_field.func_field + let f_9126807246 = instance.getExport(context, "handle_channel_update") + if f_9126807246.isSome: + assert f_9126807246.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleChannelUpdate = f_9126807246.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_channel_update", "\'" - let f_9697232590 = instance.getExport(context, "notify_task_complete") - if f_9697232590.isSome: - assert f_9697232590.get.kind == WASMTIME_EXTERN_FUNC - funcs.notifyTaskComplete = f_9697232590.get.of_field.func_field + let f_9126807247 = instance.getExport(context, "notify_task_complete") + if f_9126807247.isSome: + assert f_9126807247.get.kind == WASMTIME_EXTERN_FUNC + funcs.notifyTaskComplete = f_9126807247.get.of_field.func_field else: echo "Failed to find exported function \'", "notify_task_complete", "\'" - let f_9697232591 = instance.getExport(context, "handle_move") - if f_9697232591.isSome: - assert f_9697232591.get.kind == WASMTIME_EXTERN_FUNC - funcs.handleMove = f_9697232591.get.of_field.func_field + let f_9126807248 = instance.getExport(context, "handle_move") + if f_9126807248.isSome: + assert f_9126807248.get.kind == WASMTIME_EXTERN_FUNC + funcs.handleMove = f_9126807248.get.of_field.func_field else: echo "Failed to find exported function \'", "handle_move", "\'" @@ -436,6 +436,7 @@ proc commandsDefineCommand(instance: ptr InstanceData; name: sink string; fun: uint32; data: uint32): void proc commandsRunCommand(instance: ptr InstanceData; name: sink string; arguments: sink string): Result[string, CommandError] +proc commandsExitCommandLine(instance: ptr InstanceData): void proc settingsGetSettingRaw(instance: ptr InstanceData; name: sink string): string proc settingsSetSettingRaw(instance: ptr InstanceData; name: sink string; value: sink string): void @@ -575,6 +576,8 @@ proc layoutShow(instance: ptr InstanceData; v: View; slot: sink string; proc layoutClose(instance: ptr InstanceData; v: View; keepHidden: bool; restoreHidden: bool): void proc layoutFocus(instance: ptr InstanceData; slot: sink string): void +proc layoutCloseActiveView(instance: ptr InstanceData; closeOpenPopup: bool; + restoreHidden: bool): void proc renderNewRenderView(instance: ptr InstanceData): RenderViewResource proc renderRenderViewFromUserId(instance: ptr InstanceData; id: sink string): Option[ RenderViewResource] @@ -945,6 +948,14 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = cast[ptr int8](memory[retArea + 4].addr)[] = cast[int8](res.error) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype([], []) + linker.defineFuncUnchecked("nev:plugins/commands", "exit-command-line", ty): + var instance = cast[ptr InstanceData](store.getData()) + commandsExitCommandLine(instance) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype( @@ -2991,6 +3002,19 @@ proc defineComponent*(linker: ptr LinkerT): WasmtimeResult[void] = layoutFocus(instance, slot) if e.isErr: return e + block: + let e = block: + var ty: ptr WasmFunctypeT = newFunctype( + [WasmValkind.I32, WasmValkind.I32], []) + linker.defineFuncUnchecked("nev:plugins/layout", "close-active-view", ty): + var instance = cast[ptr InstanceData](store.getData()) + var closeOpenPopup: bool + var restoreHidden: bool + closeOpenPopup = parameters[0].i32.bool + restoreHidden = parameters[1].i32.bool + layoutCloseActiveView(instance, closeOpenPopup, restoreHidden) + if e.isErr: + return e block: let e = block: var ty: ptr WasmFunctypeT = newFunctype([], [WasmValkind.I32]) diff --git a/src/plugin_api/plugin_api.nim b/src/plugin_api/plugin_api.nim index 02897cf55..73f72e234 100644 --- a/src/plugin_api/plugin_api.nim +++ b/src/plugin_api/plugin_api.nim @@ -6,7 +6,7 @@ import layout import text/[text_editor, text_document] import render_view, view import platform/platform, platform_service -import config_provider, command_service +import config_provider, command_service, command_service_api import plugin_service, document_editor, vfs, vfs_service, channel, register, terminal_service, move_database, popup import wasmtime, wit_host_module, plugin_api_base, wasi, plugin_thread_pool from scripting_api as sca import nil @@ -415,6 +415,7 @@ proc textEditorActiveTextEditor(instance: ptr InstanceData, options: ActiveEdito if instance.host == nil: return if ActiveEditorFlag.IncludeCommandLine in options and instance.host.commands.commandLineMode() and instance.host.commands.commandLineEditor of TextDocumentEditor: + let editor = instance.host.commands.commandLineEditor return TextEditor(id: instance.host.commands.commandLineEditor.id.uint64).some if ActiveEditorFlag.IncludePopups in options and instance.host.layout.popups.len > 0 and instance.host.layout.popups.last.getActiveEditor().getSome(editor) and editor of TextDocumentEditor: return TextEditor(id: editor.id.uint64).some @@ -1037,6 +1038,11 @@ proc commandsRunCommand(instance: ptr InstanceData, name: sink string, arguments return results.ok(res) result.err(CommandError.NotFound) +proc commandsExitCommandLine(instance: ptr InstanceData) = + if instance.host == nil: + return + instance.host.commands.exitCommandLine() + proc settingsGetSettingRaw(instance: ptr InstanceData, name: sink string): string = return $instance.host.settings.get(name, JsonNodeEx) @@ -1061,6 +1067,9 @@ proc layoutClose(instance: ptr InstanceData; v: View, keepHidden: bool, restoreH proc layoutFocus(instance: ptr InstanceData, slot: sink string) = instance.host.layout.focusView(slot.ensureMove) +proc layoutCloseActiveView(instance: ptr InstanceData; closeOpenPopup: bool; restoreHidden: bool): void = + instance.host.layout.closeActiveView(closeOpenPopup, restoreHidden) + proc renderRenderViewFromUserId(instance: ptr InstanceData; id: sink string): Option[RenderViewResource] = if renderViewFromUserId(instance.host.layout, id).getSome(view): return RenderViewResource(view: view).some diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index 0f4a5b11b..d8222cc28 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -454,7 +454,7 @@ proc centerCursor*(self: TextDocumentEditor, cursor: SelectionCursor = Selection proc centerCursor*(self: TextDocumentEditor, cursor: Cursor, relativePosition: float = 0.5) proc getContextWithMode*(self: TextDocumentEditor, context: string): string proc scrollToCursor*(self: TextDocumentEditor, cursor: SelectionCursor = SelectionCursor.Config, margin: Option[float] = float.none, scrollBehaviour: Option[ScrollBehaviour] = ScrollBehaviour.none, relativePosition: float = 0.5) -proc getFileName(self: TextDocumentEditor): string +proc getFileName*(self: TextDocumentEditor): string proc closeDiff*(self: TextDocumentEditor) proc setNextSnapBehaviour*(self: TextDocumentEditor, snapBehaviour: ScrollSnapBehaviour) proc numDisplayLines*(self: TextDocumentEditor): int @@ -1204,7 +1204,7 @@ proc enableAutoReload(self: TextDocumentEditor, enabled: bool) {.expose: "editor proc setLanguage(self: TextDocumentEditor, language: string) {.expose: "editor.text".} = self.document.languageId = language -proc getFileName(self: TextDocumentEditor): string {.expose: "editor.text".} = +proc getFileName*(self: TextDocumentEditor): string {.expose: "editor.text".} = if self.document.isNil: return "" return self.document.filename diff --git a/wit/v0/api.wit b/wit/v0/api.wit index 5da569704..5db7a12a8 100644 --- a/wit/v0/api.wit +++ b/wit/v0/api.wit @@ -412,6 +412,9 @@ interface layout { close: func(v: view, keep-hidden: bool, restore-hidden: bool); /// Set focus to the view in the given slot. focus: func(slot: string); + /// Close the active view. If 'close-open-popup' is set and a popup is open then the popup will be closed. + /// If 'restore-hidden' is true then the view will be replaced by a hidden view, otherwise the slot will be removed from the layout.. + close-active-view: func(close-open-popup: bool, restore-hidden: bool); } interface render { @@ -519,6 +522,9 @@ interface commands { /// Run the given command. This requires 'command' permissions. run-command: func(name: string, arguments: string) -> result; + + /// Close the command line. + exit-command-line: func(); } interface settings { From 145d323f50b51ca68f5ca8740684e1fcd5c37530 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 19 Oct 2025 01:06:42 +0200 Subject: [PATCH 30/47] rename vim-new to vim --- config/keybindings.json | 418 ++----------------------------- config/settings-vim.json | 6 +- plugins/vim/vim.nim | 84 +++---- src/app.nim | 54 +--- src/desktop_main.nim | 6 +- src/move_database.nim | 2 +- src/scripting/expose.nim | 169 +------------ src/scripting/scripting_wasm.nim | 218 ---------------- 8 files changed, 76 insertions(+), 881 deletions(-) delete mode 100644 src/scripting/scripting_wasm.nim diff --git a/config/keybindings.json b/config/keybindings.json index 74e9b3957..29dc99c97 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -3,22 +3,12 @@ "": [""], "vim.command-line-low": { - "": "vim-exit-command-line", - "": "execute-command-line", - "": "select-previous-command-in-history", - "": "select-next-command-in-history", - }, - "vim.command-line-result-low": { - "": "vim-exit-command-line", - }, - - "vim-new.command-line-low": { "": "vim.exit-command-line", "": "execute-command-line", "": "select-previous-command-in-history", "": "select-next-command-in-history", }, - "vim-new.command-line-result-low": { + "vim.command-line-result-low": { "": "vim.exit-command-line", }, @@ -328,374 +318,6 @@ }, "vim#text_object": { - "iw": ["vim-select-text-object", "vim-word-inner", false, true, "<#text_object.count>"], - "aw": ["vim-select-text-object", "vim-word-outer", false, true, "<#text_object.count>"], - "iW": ["vim-select-text-object", "vim-WORD-inner", false, true, "<#text_object.count>"], - "aW": ["vim-select-text-object", "vim-WORD-outer", false, true, "<#text_object.count>"], - "ip": ["vim-select-text-object", "vim-paragraph-inner", false, true, "<#text_object.count>"], - "ap": ["vim-select-text-object", "vim-paragraph-outer", false, true, "<#text_object.count>"], - - "i{": ["vim-select-surrounding", "vim-surround-{-inner", false, true, "<#text_object.count>"], - "a{": ["vim-select-surrounding", "vim-surround-{-outer", false, true, "<#text_object.count>"], - "i}": ["vim-select-surrounding", "vim-surround-{-inner", false, true, "<#text_object.count>"], - "a}": ["vim-select-surrounding", "vim-surround-{-outer", false, true, "<#text_object.count>"], - "i(": ["vim-select-surrounding", "vim-surround-(-inner", false, true, "<#text_object.count>"], - "a(": ["vim-select-surrounding", "vim-surround-(-outer", false, true, "<#text_object.count>"], - "i)": ["vim-select-surrounding", "vim-surround-(-inner", false, true, "<#text_object.count>"], - "a)": ["vim-select-surrounding", "vim-surround-(-outer", false, true, "<#text_object.count>"], - "i[": ["vim-select-surrounding", "vim-surround-[-inner", false, true, "<#text_object.count>"], - "a[": ["vim-select-surrounding", "vim-surround-[-outer", false, true, "<#text_object.count>"], - "i]": ["vim-select-surrounding", "vim-surround-[-inner", false, true, "<#text_object.count>"], - "a]": ["vim-select-surrounding", "vim-surround-[-outer", false, true, "<#text_object.count>"], - "i\\<": ["vim-select-surrounding", "vim-surround-angle-inner", false, true, "<#text_object.count>"], - "a\\<": ["vim-select-surrounding", "vim-surround-angle-outer", false, true, "<#text_object.count>"], - "i\\>": ["vim-select-surrounding", "vim-surround-angle-inner", false, true, "<#text_object.count>"], - "a\\>": ["vim-select-surrounding", "vim-surround-angle-outer", false, true, "<#text_object.count>"], - "i\"": ["vim-select-surrounding", "vim-surround-\"-inner", false, true, "<#text_object.count>"], - "a\"": ["vim-select-surrounding", "vim-surround-\"-outer", false, true, "<#text_object.count>"], - "i'": ["vim-select-surrounding", "vim-surround-'-inner", false, true, "<#text_object.count>"], - "a'": ["vim-select-surrounding", "vim-surround-'-outer", false, true, "<#text_object.count>"], - }, - - "vim#move": { - "": ["vim-move-cursor-column", -1, "<#move.count>"], - "": ["vim-move-cursor-column", 1, "<#move.count>"], - "": ["vim-move-cursor-visual-line", -1, "<#move.count>"], - "": ["vim-move-cursor-visual-line", 1, "<#move.count>"], - "h": ["vim-move-cursor-column", -1, "<#move.count>"], - "j": ["vim-move-cursor-visual-line", 1, "<#move.count>"], - "k": ["vim-move-cursor-visual-line", -1, "<#move.count>"], - "l": ["vim-move-cursor-column", 1, "<#move.count>"], - "gm": ["move-cursor-line-center"], - "gM": ["move-cursor-center"], - - "0": ["vim-move-first", "line"], - "": ["vim-move-first", "visual-line"], - "^": ["vim-move-first", "line-no-indent"], - "$": ["vim-move-to-end-of-line", "<#move.count>"], - "": ["vim-move-to-end-of-visual-line", "<#move.count>"], - "g0": ["vim-move-first", "line"], - "g^": ["vim-move-first", "line-no-indent"], - "g$": ["vim-move-to-end-of-line", "<#move.count>"], - "gg": ["vim-move-file-start", "<#move.count>"], - "G": ["vim-move-file-end", "<#move.count>"], - "%": ["vim-move-to-matching-or-file-offset", "<#move.count>"], - - "w": ["move-selection-next", "vim-word", false, true, "<#move.count>"], - "": ["move-selection-next", "vim-word", false, true, "<#move.count>"], - "W": ["move-selection-next", "vim-WORD", false, true, "<#move.count>"], - "": ["move-selection-next", "vim-WORD", false, true, "<#move.count>"], - "e": ["move-selection-end", "vim-word", false, false, "<#move.count>"], - "E": ["move-selection-end", "vim-WORD", false, false, "<#move.count>"], - "b": ["move-selection-end", "vim-word", true, true, "<#move.count>"], - "": ["move-selection-end", "vim-word", true, true, "<#move.count>"], - "B": ["move-selection-end", "vim-WORD", true, true, "<#move.count>"], - "": ["move-selection-end", "vim-WORD", true, true, "<#move.count>"], - "ge": ["move-selection-next", "vim-word", true, false, "<#move.count>"], - "gE": ["move-selection-next", "vim-WORD", true, false, "<#move.count>"], - "}": ["move-paragraph", false, "<#move.count>"], - "{": ["move-paragraph", true, "<#move.count>"], - "f": ["vim-move-to ", false, "<#move.count>"], - "t": ["vim-move-to ", true, "<#move.count>"], - - "": ["vim-move-cursor-visual-page", 0.5, "<#move.count>", true], - "": ["vim-move-cursor-visual-page", 1, "<#move.count>", true], - "": ["vim-move-cursor-visual-page", -0.5, "<#move.count>", true], - "": ["vim-move-cursor-visual-page", -1, "<#move.count>", true], - "|": ["vim-move-to-column", "<#move.count>"], - "-": ["vim-move-cursor-line-first-char", -1, "<#move.count>"], - "+": ["vim-move-cursor-line-first-char", 1, "<#move.count>"], - "_": ["vim-move-to-start-of-line", "<#move.count>"], - }, - - "vim.base": { - ":": ["command-line"], - - "h": ["focus-view-left"], - "": ["focus-view-left"], - "": ["focus-view-left"], - "l": ["focus-view-right"], - "": ["focus-view-right"], - "": ["focus-view-right"], - "k": ["focus-view-up"], - "": ["focus-view-up"], - "": ["focus-view-up"], - "j": ["focus-view-down"], - "": ["focus-view-down"], - "": ["focus-view-down"], - "w": ["focus-next-view", "**"], - "p": ["open-prev-view"], - "": ["open-prev-view"], - "q": ["vim-close-current-view-or-quit"], - "": ["close-active-view"], - "c": ["hide-active-view"], - "C": ["close-active-view"], - "o": ["close-other-views"], - "": ["close-other-views"], - - "": ["open-prev-view"], - - "m": ["toggle-maximize-view-local", "**"], - - "sr": "open-recent-session", - "to": ["select-terminal"], - "ts": [ - "run-in-terminal", - "default", - "", - { - "mode": "insert", - "closeOnTerminate": true, - "group": "scratch", - } - ], - }, - - "vim": { - "": ["vim-normal-mode"], - "": ["vim-add-cursor-above"], - "": ["vim-add-cursor-below"], - - // special - "d": ["vim-delete-move <#count>"], - "c": ["vim-change-move <#count>"], - "y": ["vim-yank-move <#count>"], - - "d": ["vim-delete-move <#count>"], - "c": ["vim-change-move <#count>"], - "y": ["vim-yank-move <#count>"], - - "": ["scroll-lines", -2], - "": ["scroll-text-horizontal", -4], - "": ["scroll-lines", 2], - "": ["scroll-text-horizontal", 4], - "": ["scroll-lines", -1], - "": ["scroll-lines", 1], - - "/": ["vim-open-search-bar"], - "gc": ["vim-toggle-line-comment"], - "*": ["vim-set-search-query-from-word"], - "n": ["vim-next-search-result"], - "N": ["vim-prev-search-result"], - - ".": ["replay-commands", "."], - "L": ["vim-select-word-or-add-cursor"], - "H": ["vim-move-last-selection-to-next-search-result"], - - "m": ["vim-add-mark "], - "'": ["vim-goto-mark "], - - "q": ["vim-start-macro "], - "@": ["vim-play-macro "], - "Q": ["vim-stop-macro"], - - "M": ["enter-choose-cursor-mode", "vim-handle-select-word"], - - // movement - "": ["select-prev"], - "": ["select-next"], - "": ["vim-select-last "], - "o": ["vim-reverse-selections"], - "": ["select-next-tab-stop"], - "": ["select-prev-tab-stop"], - "gt": ["vim-goto-next-diagnostic"], - "gn": ["vim-goto-prev-diagnostic"], - "gf": ["vim-goto-next-change"], - "gh": ["vim-goto-prev-change"], - - // copy/paste - "p": ["vim-paste", true, false, "vim-default-register"], - "P": ["vim-paste", true, false], - - // editing - "x": ["vim-delete-right"], - "": ["vim-delete-right"], - "": ["vim-delete-left"], - "": ["vim-delete-left"], - "": ["vim-delete-left"], - "r": ["set-mode", "vim.replace"], - - // undo/redo - "u": ["vim-undo", true], - "U": ["vim-redo", false], - - // vcs - "gx": ["close-diff"], - "gc": ["update-diff"], - }, - - "vim.normal": { - // special - "a": ["vim-insert-mode", "right"], - "i": ["vim-insert-mode"], - "A": ["vim-insert-mode", "line-end"], - "I": ["vim-insert-mode", "line-no-indent"], - "gI": ["vim-insert-mode", "line-start"], - "o": ["vim-insert-line-below"], - "O": ["vim-insert-line-above"], - "v": ["set-mode", "vim.visual"], - "V": ["vim-visual-line-mode"], - - "dd": ["vim-delete-line"], - "cc": ["vim-change-line"], - "yy": ["vim-yank-line"], - "D": ["vim-delete-to-line-end"], - "C": ["vim-change-to-line-end"], - "Y": ["vim-yank-to-line-end"], - - // cursor movement - "gxf": ["vim-increment"], - "gxh": ["vim-decrement"], - "gxc": ["vim-increment-by-index"], - - // editing - "~": ["vim-toggle-case", true], - "\\>\\>": ["vim-indent"], - "\\<\\<": ["vim-unindent"], - "s": ["all", [".set-mode", "vim.visual"], [".vim-change-selection", true]], - "J": ["vim-join-lines", true], - "gJ": ["vim-join-lines", false], - - // lsp - "gd": ["goto-definition"], - "gD": ["goto-declaration"], - "gi": ["goto-implementation"], - "gT": ["goto-type-definition"], - "gs": ["goto-symbol"], - "gw": ["goto-workspace-symbol"], - "gr": ["goto-references"], - "go": ["switch-source-header"], - "ga": ["select-code-action"], - "gR": ["rename"], - "K": ["show-hover-for-current"], - "H": ["show-diagnostics-for-current"], - - // scroll - "z": ["vim-scroll-line-to-top-and-move-line-start"], - "zt": ["vim-scroll-line-to-top"], - "z.": ["vim-center-line-and-move-line-start"], - "zz": ["vim-center-line"], - "z-": ["vim-scroll-line-to-bottom-and-move-line-start"], - "zb": ["vim-scroll-line-to-bottom"], - - // vcs - "gy": ["stage-selected", false], - "gr": ["revert-selected", false], - }, - - "vim.insert": { - "cf": ["set-mode", "vim.normal"], - "": ["insert-text", " "], - "": ["vim-enter"], - "": ["vim-delete-word-back"], - "": ["vim-delete-line-back"], - "": ["get-completions"], - "": ["get-completions"], - "": ["insert-indent"], - "": ["vim-indent"], - "": ["vim-unindent"], - "": ["set-mode", "vim.insert-register"], - }, - - "vim.visual": { - // special - "A": ["vim-insert-mode", "right"], - "I": ["vim-insert-mode", "first"], - "": ["vim-select "], - "": ["vim-select-move <#count>"], - - "y": ["vim-yank-selection"], - "gy": ["vim-yank-selection-clipboard"], - "d": ["vim-delete-selection", true], - "x": ["vim-delete-selection", true], - "X": ["vim-delete-selection", true], - "": ["vim-delete-selection", true], - "": ["vim-delete-selection", true], - "c": ["vim-change-selection", true], - "s": ["vim-change-selection", true], - "*": ["vim-set-search-query-from-selection"], - - "L": ["vim-set-search-query-or-add-cursor"], - - // copy/paste - "p": ["vim-paste", false, true, "vim-default-register"], - "P": ["vim-paste", false, true], - "y": ["vim-yank-selection"], - "gy": ["vim-yank-selection-clipboard"], - "d": ["vim-delete-selection", true], - "c": ["vim-change-selection", true], - "s": ["vim-change-selection", true], - - // cursor movement - "o": ["vim-invert-selections"], - "gp": ["select-parent-current-ts", false], - "gl": ["vim-add-next-same-node-to-selection"], - "gh": ["vim-move-selection-to-next-same-node"], - "gs": ["vim-add-next-sibling-to-selection"], - "gd": ["vim-move-selection-to-next-sibling"], - "gi": ["vim-shrink-selection"], - "gxx": ["vim-evaluate-selection"], - "gxf": ["vim-increment-selection"], - "gxh": ["vim-decrement-selection"], - "gxc": ["vim-increment-selection-by-index"], - "gxg": "cycle-selected-case", - - // editing - "~": ["vim-toggle-case", false], - "\\>": ["vim-indent"], - "\\<": ["vim-unindent"], - "S": ["vim-surround "], - - // vcs - "gy": ["stage-selected", true], - "gr": ["revert-selected", true], - }, - - "vim.visual-line": { - // special - "": ["vim-select "], - "": ["vim-select-move <#count>"], - - // copy/paste - "p": ["vim-paste", false, true, "vim-default-register"], - "P": ["vim-paste", false, true], - "y": ["vim-yank-selection"], - "gy": ["vim-yank-selection-clipboard"], - "d": ["vim-delete-selection", true], - "c": ["vim-change-selection", true], - "s": ["vim-change-selection", true], - - // cursor movement - "o": ["vim-invert-line-selections"], - - // editing - "~": ["vim-toggle-case", false], - "\\>": ["vim-indent"], - "\\<": ["vim-unindent"], - - // vcs - "gy": ["stage-selected", true], - "gr": ["revert-selected", true], - }, - - "vim.replace": { - "": ["vim-replace", " "], - "": ["set-mode", "vim.normal"], - }, - - "vim.insert-register": { - "": ["set-mode", "vim.insert"], - "": ["all", [".vim-paste", false, true, "vim-default-register"], [".set-mode", "vim.insert"]], - }, - - "vim.completion": { - "": ["select-prev-completion"], - "": ["select-next-completion"], - "": ["apply-selected-completion"], - "": ["apply-selected-completion"], - }, - - "vim-new#text_object": { "iw": ["vim.select-text-object", "vim.word-inner", "<#text_object.count>"], "aw": ["vim.select-text-object", "vim.word-inner", "<#text_object.count>"], "iW": ["vim.select-text-object", "vim.WORD-inner", "<#text_object.count>"], @@ -725,14 +347,14 @@ "a'": ["vim.select-surrounding", "surround \"'\" \"'\" false", "<#text_object.count>"], }, - "vim-new.visual-line#move": { + "vim.visual-line#move": { "": ["vim.move-direction", "line-up", 1, "<#move.count>", false, false], "": ["vim.move-direction", "line-down", 1, "<#move.count>", false, false], "k": ["vim.move-direction", "line-up", 1, "<#move.count>", false, false], "j": ["vim.move-direction", "line-down", 1, "<#move.count>", false, false], }, - "vim-new#move": { + "vim#move": { "": ["vim.move-direction", "column", -1, "<#move.count>"], "": ["vim.move-direction", "column", 1, "<#move.count>"], "": ["vim.move-direction", "visual-line-up", 1, "<#move.count>", false, false], @@ -787,7 +409,7 @@ "_": ["vim.move-to-start-of-line", "<#move.count>"], }, - "vim-new.base": { + "vim.base": { ":": ["command-line"], "h": ["focus-view-left"], @@ -830,7 +452,7 @@ ], }, - "vim-new": { + "vim": { "": ["vim.normal-mode"], "": ["vim.add-cursor-above"], "": ["vim.add-cursor-below"], @@ -888,7 +510,7 @@ "": ["vim.delete-left"], "": ["vim.delete-left"], "": ["vim.delete-left"], - "r": ["set-mode", "vim-new.replace"], + "r": ["set-mode", "vim.replace"], // undo/redo "u": ["vim.undo", true], @@ -899,7 +521,7 @@ "gc": ["update-diff"], }, - "vim-new.normal": { + "vim.normal": { // special "a": ["vim.insert-mode", "right"], "i": ["vim.insert-mode"], @@ -908,7 +530,7 @@ "gI": ["vim.insert-mode", "line-start"], "o": ["vim.insert-line-below"], "O": ["vim.insert-line-above"], - "v": ["set-mode", "vim-new.visual"], + "v": ["set-mode", "vim.visual"], "V": ["vim.visual-line-mode"], "dd": ["vim.delete-line"], @@ -927,7 +549,7 @@ "~": ["vim.toggle-case", true], "\\>\\>": ["vim.indent"], "\\<\\<": ["vim.unindent"], - "s": ["all", [".set-mode", "vim-new.visual"], [".vim.change-selection", true]], + "s": ["all", [".set-mode", "vim.visual"], [".vim.change-selection", true]], "J": ["vim.join-lines", true], "gJ": ["vim.join-lines", false], @@ -958,8 +580,8 @@ "gr": ["revert-selected", false], }, - "vim-new.insert": { - "cf": ["set-mode", "vim-new.normal"], + "vim.insert": { + "cf": ["set-mode", "vim.normal"], "": ["insert-text", " "], "": ["vim.enter"], "": ["vim.delete-word-back"], @@ -969,10 +591,10 @@ "": ["insert-indent"], "": ["vim.indent"], "": ["vim.unindent"], - "": ["set-mode", "vim-new.insert-register"], + "": ["set-mode", "vim.insert-register"], }, - "vim-new.visual": { + "vim.visual": { // special "A": ["vim.insert-mode", "right"], "I": ["vim.insert-mode", "first"], @@ -1027,7 +649,7 @@ "gr": ["revert-selected", true], }, - "vim-new.visual-line": { + "vim.visual-line": { // special "": ["vim.select "], "": ["vim.select-move <#count>"], @@ -1054,17 +676,17 @@ "gr": ["revert-selected", true], }, - "vim-new.replace": { + "vim.replace": { "": ["vim.replace", " "], - "": ["set-mode", "vim-new.normal"], + "": ["set-mode", "vim.normal"], }, - "vim-new.insert-register": { - "": ["set-mode", "vim-new.insert"], - "": ["all", [".vim.paste", false, true, "vim.default-register"], [".set-mode", "vim-new.insert"]], + "vim.insert-register": { + "": ["set-mode", "vim.insert"], + "": ["all", [".vim.paste", false, true, "vim.default-register"], [".set-mode", "vim.insert"]], }, - "vim-new.completion": { + "vim.completion": { "": ["select-prev-completion"], "": ["select-next-completion"], "": ["apply-selected-completion"], diff --git a/config/settings-vim.json b/config/settings-vim.json index a5aa50a17..12efe2f8a 100644 --- a/config/settings-vim.json +++ b/config/settings-vim.json @@ -19,9 +19,9 @@ "editor.text.handle-inputs": true, "vim.insert.handle-inputs": true, "vim.replace.handle-inputs": true, - "vim.replace.input-handler-command": "vim-replace-input-handler", + "vim.replace.input-handler-command": "vim.replace-input-handler", "vim.insert-register.handle-inputs": true, - "vim.insert-register.input-handler-command": "vim-insert-register-input-handler", + "vim.insert-register.input-handler-command": "vim.insert-register-input-handler", "editor.text.temp.choose-cursor.handle-actions": true, "editor.text.temp.choose-cursor.handle-inputs": true, @@ -38,7 +38,7 @@ "terminal.insert.handle-keys": true, }, - "text.mode-changed-handler-command": "vim-mode-changed-handler", + "text.mode-changed-handler-command": "vim.mode-changed-handler", "text.inclusive-selection": false, "editor.text.cursor.movement.": "last", diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index f5367347f..c66fe64c6 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -247,13 +247,13 @@ proc selectLine(editor: TextEditor) {.exposeActive(editorContext).} = proc normalMode(editor: TextEditor) {.exposeActive(editorContext).} = ## Exit to normal mode and clear things - if $editor.mode == "vim-new.normal": + if $editor.mode == "vim.normal": editor.setSelection editor.getSelection.last.toSelection editor.clearTabStops() - editor.setMode("vim-new.normal") + editor.setMode("vim.normal") proc visualLineMode(editor: TextEditor) {.exposeActive(editorContext).} = - editor.setMode "vim-new.visual-line" + editor.setMode "vim.visual-line" editor.selectLine() proc selectLast(editor: TextEditor, move: string, count: int = 1) {.exposeActive(editorContext).} = @@ -270,25 +270,25 @@ proc select(editor: TextEditor, move: string, count: int = 1) {.exposeActive(edi proc undo(editor: TextEditor, enterNormalModeBefore: bool) {.exposeActive(editorContext).} = if enterNormalModeBefore: - editor.setMode "vim-new.normal" + editor.setMode "vim.normal" editor.undo(editor.vimState.currentUndoCheckpoint.ws) if enterNormalModeBefore: if not editor.selections.toOpenArray().allEmpty: - editor.setMode "vim-new.visual" + editor.setMode "vim.visual" else: - editor.setMode "vim-new.normal" + editor.setMode "vim.normal" proc redo(editor: TextEditor, enterNormalModeBefore: bool) {.exposeActive(editorContext).} = if enterNormalModeBefore: - editor.setMode "vim-new.normal" + editor.setMode "vim.normal" editor.redo(editor.vimState.currentUndoCheckpoint.ws) if enterNormalModeBefore: if not editor.selections.toOpenArray().allEmpty: - editor.setMode "vim-new.visual" + editor.setMode "vim.visual" else: - editor.setMode "vim-new.normal" + editor.setMode "vim.normal" proc copySelection(editor: TextEditor, register: string = ""): seq[Selection] = ## Copies the selected text @@ -327,7 +327,7 @@ proc deleteSelection(editor: TextEditor, forceInclusiveEnd: bool, oldSelections: editor.scrollToCursor() editor.updateTargetColumn() editor.vimState.deleteInclusiveEnd = true - editor.setMode "vim-new.normal" + editor.setMode "vim.normal" proc changeSelection*(editor: TextEditor, forceInclusiveEnd: bool, oldSelections: Option[seq[Selection]] = seq[Selection].none) {.exposeActive(editorContext).} = let newSelections = editor.copySelection(getVimDefaultRegister()) @@ -340,17 +340,17 @@ proc changeSelection*(editor: TextEditor, forceInclusiveEnd: bool, oldSelections editor.scrollToCursor() editor.updateTargetColumn() editor.vimState.deleteInclusiveEnd = true - editor.setMode "vim-new.insert" + editor.setMode "vim.insert" proc yankSelection*(editor: TextEditor) {.exposeActive(editorContext).} = let selections = editor.copySelection(getVimDefaultRegister()) editor.setSelections selections - editor.setMode "vim-new.normal" + editor.setMode "vim.normal" proc yankSelectionClipboard*(editor: TextEditor) {.exposeActive(editorContext).} = let selections = editor.copySelection() editor.setSelections selections - editor.setMode "vim-new.normal" + editor.setMode "vim.normal" template mergeSelections*(a, b: WitList[Selection], body: untyped): seq[Selection] = let aa = a @@ -753,13 +753,13 @@ proc paste(editor: TextEditor, pasteRight: bool = false, inclusiveEnd: bool = fa var selections = editor.getSelections - if yankedLines and $editor.mode == "vim-new.normal": + if yankedLines and $editor.mode == "vim.normal": selections = editor.multiMove(selections, ws"line", 1, wrap = false, includeEol = true).mapIt(it.last.toSelection).stackWitList() selections = editor.edit(selections, @@[ws("\n")], inclusive=false).mapIt(it.last.toSelection).stackWitList() elif pasteRight: selections = editor.multiMove(selections, ws"column", 1, false, true).mapIt(it.last.toSelection).stackWitList() - editor.setMode "vim-new.normal" + editor.setMode "vim.normal" editor.paste selections, register.stackWitString, inclusiveEnd=inclusiveEnd proc toggleCase(editor: TextEditor, moveCursorRight: bool) {.exposeActive(editorContext).} = @@ -781,7 +781,7 @@ proc toggleCase(editor: TextEditor, moveCursorRight: bool) {.exposeActive(editor discard editor.edit(editor.selections, editTexts.stackWitList(), inclusive=true) editor.setSelections oldSelections.mapIt(it.first.toSelection).stackWitList() - editor.setMode "vim-new.normal" + editor.setMode "vim.normal" if moveCursorRight: editor.moveCursorColumn(1, wrap=false, includeEol=editor.vimState.cursorIncludeEol) @@ -885,7 +885,7 @@ proc moveFileEnd(editor: TextEditor, count: int = 1) {.exposeActive(editorContex proc moveToMatching(editor: TextEditor) {.exposeActive(editorContext).} = # todo: pass as parameter let mode = $editor.mode - let which = if mode == "vim-new.visual" or mode == "vim-new.visual-line": + let which = if mode == "vim.visual" or mode == "vim.visual-line": sca.SelectionCursor.Last else: sca.SelectionCursor.Both @@ -978,21 +978,21 @@ proc insertMode(editor: TextEditor, move: string = "") {.exposeActive(editorCont editor.setSelections editor.selections.mapIt(editor.applyMove(it.last, "line", 1).first.toSelection) else: discard - editor.setMode "vim-new.insert" + editor.setMode "vim.insert" editor.addNextCheckpoint ws"insert" proc insertLineBelow(editor: TextEditor) {.exposeActive(editorContext).} = editor.setSelections editor.multiMove(editor.getSelections, ws"line", 0, wrap=false, includeEol=true).mapIt(it.last.toSelection) editor.addNextCheckpoint ws"insert" editor.insertText ws("\n"), autoIndent=true - editor.setMode "vim-new.insert" + editor.setMode "vim.insert" proc insertLineAbove(editor: TextEditor, move: string = "") {.exposeActive(editorContext).} = editor.setSelections editor.multiMove(editor.getSelections, ws"line", 0, wrap=false, includeEol=true).mapIt(it.first.toSelection) editor.addNextCheckpoint ws"insert" editor.insertText ws("\n"), autoIndent=false editor.moveDirection("line-up", 1, 1, false, false) - editor.setMode "vim-new.insert" + editor.setMode "vim.insert" proc setSearchQueryFromWord(editor: TextEditor) {.exposeActive(editorContext).} = editor.setSelection editor.setSearchQueryFromMove(ws"word", 1, prefix=ws(r"\b"), suffix=ws(r"\b")).first.toSelection @@ -1075,15 +1075,15 @@ proc openSearchBar(editor: TextEditor) {.exposeActive(editorContext).} = proc exitCommandLine() {.command.} = if activeTextEditor({IncludeCommandLine}).getSome(editor): let mode = $editor.mode - if mode == "vim-new.normal": + if mode == "vim.normal": commands.exitCommandLine() return - editor.setMode("vim-new.normal") + editor.setMode("vim.normal") proc exitPopup() {.command.} = - if activeTextEditor({IncludePopups}).getSome(editor) and $editor.mode != "vim-new.normal": - editor.setMode("vim-new.normal") + if activeTextEditor({IncludePopups}).getSome(editor) and $editor.mode != "vim.normal": + editor.setMode("vim.normal") return discard runCommand(ws"close-active-view", ws"") @@ -1102,7 +1102,7 @@ proc selectWordOrAddCursor(editor: TextEditor) {.exposeActive(editorContext).} = editor.setNextSnapBehaviour(MinDistanceOffscreen) editor.updateTargetColumn() - editor.setMode("vim-new.visual") + editor.setMode("vim.visual") proc moveLastSelectionToNextSearchResult(editor: TextEditor) {.exposeActive(editorContext).} = let selections = editor.selections @@ -1118,7 +1118,7 @@ proc moveLastSelectionToNextSearchResult(editor: TextEditor) {.exposeActive(edit editor.setNextSnapBehaviour(MinDistanceOffscreen) editor.updateTargetColumn() - editor.setMode("vim-new.visual") + editor.setMode("vim.visual") proc setSearchQueryOrAddCursor(editor: TextEditor) {.exposeActive(editorContext).} = let selections = editor.selections @@ -1186,7 +1186,7 @@ proc gotoMark(editor: TextEditor, name: string) {.exposeActive(editorContext).} return case $editor.mode - of "vim-new.visual", "vim-new.visual-line": + of "vim.visual", "vim.visual-line": let oldSelections = editor.selections if newSelections.len == oldSelections.len: editor.setSelections stackWitList(collect(block: @@ -1398,7 +1398,7 @@ proc replaceInputHandler(editor: TextEditor, input: string) {.exposeActive(edito # todo # proc vimInsertRegisterInputHandler(editor: TextEditor, input: string) {.exposeActive(editorContext, "vim-insert-register-input-handler").} = # editor.vimPaste register=input, inclusiveEnd=true -# editor.setMode "vim-new.insert" +# editor.setMode "vim.insert" proc modeChangedHandler(editor: TextEditor, oldModes: seq[string], newModes: seq[string]) {.exposeActive(editorContext).} = # log lvlInfo, &"modeChangedHandler {editor}, {oldModes} -> {newModes}" @@ -1413,24 +1413,24 @@ proc modeChangedHandler(editor: TextEditor, oldModes: seq[string], newModes: seq let newMode = newModes[0] - if not editor.modes().toOpenArray.contains(ws"vim-new"): + if not editor.modes().toOpenArray.contains(ws"vim"): return if newMode == "": - editor.setMode "vim-new.normal" + editor.setMode "vim.normal" return if not newMode.startsWith("vim"): return let recordModes = [ - "vim-new.visual", - "vim-new.visual-line", - "vim-new.insert", + "vim.visual", + "vim.visual-line", + "vim.insert", ].toHashSet # debugf"vim: handle mode change {oldMode} -> {newMode}" - if newMode == "vim-new.normal": + if newMode == "vim.normal": if not isReplayingCommands() and isRecordingCommands(ws".-temp"): stopRecordingCommands(ws".-temp") @@ -1439,29 +1439,29 @@ proc modeChangedHandler(editor: TextEditor, oldModes: seq[string], newModes: seq let text = getRegisterText(ws".-temp") setRegisterText(text, ws".") else: - if oldMode == "vim-new.normal" and newMode in recordModes: + if oldMode == "vim.normal" and newMode in recordModes: editor.startRecordingCurrentCommandInPeriodMacro() editor.clearCurrentCommandHistory(retainLast=true) - editor.vimState.selectLines = newMode == "vim-new.visual-line" - editor.vimState.cursorIncludeEol = newMode == "vim-new.insert" - editor.vimState.currentUndoCheckpoint = if newMode == "vim-new.insert": "word" else: "insert" + editor.vimState.selectLines = newMode == "vim.visual-line" + editor.vimState.cursorIncludeEol = newMode == "vim.insert" + editor.vimState.currentUndoCheckpoint = if newMode == "vim.insert": "word" else: "insert" case newMode - of "vim-new.normal": + of "vim.normal": editor.setSetting "text.inclusive-selection", false editor.setSelections editor.selections.mapIt(editor.vimClamp(it.last).toSelection) editor.saveCurrentCommandHistory() editor.hideCompletions() - of "vim-new.insert": + of "vim.insert": editor.setSetting "text.inclusive-selection", false - of "vim-new.visual": + of "vim.visual": editor.setSetting "text.inclusive-selection", true - of "vim-new.visual-line": + of "vim.visual-line": editor.setSetting "text.inclusive-selection", false else: diff --git a/src/app.nim b/src/app.nim index 004386118..88375cd57 100644 --- a/src/app.nim +++ b/src/app.nim @@ -20,7 +20,6 @@ import misc/async_process when enableAst: import ast/[model, project] -import scripting/[scripting_wasm] import plugin_system_wasm import scripting_api as api except DocumentEditor, TextDocumentEditor, AstDocumentEditor, ModelDocumentEditor, Popup, SelectorPopup @@ -108,7 +107,6 @@ type disableLogFrameTime*: bool = true logBuffer = "" - wasmScriptContext*: ScriptContextWasm pluginSystemWasm*: PluginSystemWasm initializeCalled: bool @@ -228,26 +226,6 @@ proc runConfigCommands(self: App, key: string) = discard self.handleAction(action, arg, record=false) proc initScripting(self: App, options: AppOptions) {.async.} = - if not options.disableOldWasmPlugins: - try: - log(lvlInfo, fmt"load wasm configs") - self.wasmScriptContext = new ScriptContextWasm - self.plugins.pluginSystems.add self.wasmScriptContext - self.wasmScriptContext.moduleVfs = VFS() - self.wasmScriptContext.vfs = self.vfs - self.vfs.mount("plugs://", self.wasmScriptContext.moduleVfs) - - withPluginSystem self.plugins, self.wasmScriptContext: - let t1 = startTimer() - await self.wasmScriptContext.init("app://config", self.vfs) - log(lvlInfo, fmt"init wasm configs ({t1.elapsed.ms}ms)") - - let t2 = startTimer() - discard self.wasmScriptContext.postInitialize() - log(lvlInfo, fmt"post init wasm configs ({t2.elapsed.ms}ms)") - except CatchableError: - log lvlError, &"Failed to load wasm configs: {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - self.runConfigCommands("wasm-plugin-post-load-commands") self.runConfigCommands("plugin-post-load-commands") @@ -1087,9 +1065,6 @@ proc shutdown*(self: App) = for document in self.editors.documents: document.deinit() - if self.wasmScriptContext.isNotNil: - self.wasmScriptContext.deinit() - {.gcsafe.}: gAppInterface = nil self[] = AppObject() @@ -2765,30 +2740,6 @@ proc exploreCurrentFileDirectory*(self: App) {.expose("editor").} = if self.layout.tryGetCurrentEditorView().getSome(view) and view.document.isNotNil: self.exploreFiles(view.document.filename.splitPath.head) -# todo: move to scripting_base -proc reloadPluginAsync*(self: App) {.async.} = - if self.wasmScriptContext.isNotNil: - log lvlInfo, "Reload wasm plugins" - try: - self.plugins.clearScriptActionsFor(self.wasmScriptContext) - - let t1 = startTimer() - withPluginSystem self.plugins, self.wasmScriptContext: - await self.wasmScriptContext.reload() - log(lvlInfo, fmt"Reload wasm plugins ({t1.elapsed.ms}ms)") - - withPluginSystem self.plugins, self.wasmScriptContext: - let t2 = startTimer() - discard self.wasmScriptContext.postInitialize() - log(lvlInfo, fmt"Post init wasm plugins ({t2.elapsed.ms}ms)") - - log lvlInfo, &"Successfully reloaded wasm plugins" - except CatchableError: - log lvlError, &"Failed to reload wasm plugins: {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - - self.runConfigCommands("wasm-plugin-post-reload-commands") - self.runConfigCommands("plugin-post-reload-commands") - proc reloadConfigAsync*(self: App) {.async.} = await self.loadConfigFrom(appConfigDir, "app") if not self.appOptions.skipUserSettings: @@ -2804,7 +2755,7 @@ proc reloadConfig*(self: App, clearOptions: bool = false) {.expose("editor").} = proc reloadPlugin*(self: App) {.expose("editor").} = log lvlInfo, &"Reload current plugin" - asyncSpawn self.reloadPluginAsync() + # todo: delete proc reloadTheme*(self: App) {.expose("editor").} = log lvlInfo, &"Reload theme" @@ -3486,6 +3437,3 @@ proc handleAction(self: App, action: string, arg: string, record: bool): Option[ log lvlError, fmt"Unknown command '{action}'" return JsonNode.none - -template generatePluginBindings*(): untyped = - createEditorWasmImportConstructor() diff --git a/src/desktop_main.nim b/src/desktop_main.nim index d3188f105..c0b63f30a 100644 --- a/src/desktop_main.nim +++ b/src/desktop_main.nim @@ -280,10 +280,6 @@ import language_server_regex import plugin_api/[process] import scripting/expose -generatePluginBindings() -static: - generateScriptingApiPerModule() - defineSetAllDefaultSettings() # Initialize renderer @@ -505,3 +501,5 @@ when defined(windows) and copyWasmtimeDll: const dllOut = querySetting(SingleValueSetting.outDir) / "wasmtime.dll" echo "[desktop_main.nim] run tools/copy_wasmtime_dll.nims" echo staticExec &"nim \"-d:inDir={dllIn}\" \"-d:outDir={dllOut}\" --hints:off --skipUserCfg --skipProjCfg --skipParentCfg ../tools/copy_wasmtime_dll.nims" + +printDispatchStats() diff --git a/src/move_database.nim b/src/move_database.nim index 1aa120acf..d78b384aa 100644 --- a/src/move_database.nim +++ b/src/move_database.nim @@ -152,7 +152,7 @@ proc moveCursorColumn(text: Rope, cursor: Cursor, offset: int, wrap: bool = true return text.clampCursor(cursor, includeEol) proc applyMove*(self: MoveDatabase, displayMap: DisplayMap, move: string, selections: openArray[Selection], count: int = 0, includeEol: bool = true): seq[Selection] = - debugf"applyMove '{move}', {selections}, {count}, {includeEol}" + # debugf"applyMove '{move}', {selections}, {count}, {includeEol}" let rope {.cursor.} = displayMap.buffer.visibleText diff --git a/src/scripting/expose.nim b/src/scripting/expose.nim index bb9ca4d12..14c460889 100644 --- a/src/scripting/expose.nim +++ b/src/scripting/expose.nim @@ -8,8 +8,6 @@ const mapperFunctions = CacheTable"MapperFunctions" # Maps from type name (refer const typeWrapper = CacheTable"TypeWrapper" # Maps from type name (referring to nim type) to type name of the api type (from scripting_api) const functions = CacheTable"DispatcherFunctions" # Maps from scope name to list of tuples of name and wrapper const injectors = CacheTable"Injectors" # Maps from type name (referring to nim type) to function -const exposedFunctions* = CacheTable"ExposedFunctions" # Maps from type name (referring to nim type) to type name of the api type (from scripting_api) -const wasmImportedFunctions* = CacheTable"WasmImportedFunctions" template varargs*() {.pragma.} @@ -20,14 +18,6 @@ macro addTypeMap*(source: untyped, wrapper: typed, mapperFunction: typed) = macro addInjector*(name: untyped, function: typed) = injectors[$name] = function -macro addWasmImportedFunction*(moduleName: static string, name: untyped, thisSide: untyped, wasmSide: untyped) = - let n = nnkStmtList.newTree(name, thisSide, wasmSide) - for name, _ in wasmImportedFunctions: - if name == moduleName: - wasmImportedFunctions[name].add n - return - wasmImportedFunctions[moduleName] = nnkStmtList.newTree(n) - macro addFunction(name: untyped, original: typed, wrapper: typed, moduleName: static string, docs: typed) = ## Registers the nim function `name` in the module `moduleName`. `script` is a symbol referring ## to the version of this function with the 'Script' suffix, which is exported to the nim script. @@ -39,13 +29,9 @@ macro addFunction(name: untyped, original: typed, wrapper: typed, moduleName: st return functions[moduleName] = nnkStmtList.newTree(n) -macro addScriptWrapper(name: untyped, moduleName: static string, lineNumber: static int) = - let val = nnkStmtList.newTree(name, newLit($lineNumber)) - for list, _ in exposedFunctions: - if list == moduleName: - exposedFunctions[list].add val - return - exposedFunctions[moduleName] = nnkStmtList.newTree(val) +macro printDispatchStats*() = + for moduleName, functions in functions: + echo &"module '{moduleName}': {functions.len} functions" proc generateUniqueName*(moduleName: string, def: NimNode): string = result = moduleName @@ -77,18 +63,9 @@ proc removePragmas(node: var NimNode) = param[0] = name macro expose*(moduleName: static string, def: untyped): untyped = - # echo moduleName, "::", def.name.repr if not exposeScriptingApi: return def - defer: - discard - # echo result.repr - # echo "====================================================================" - - # echo def.repr - # echo def.treeRepr - var def = def let uniqueName = generateUniqueName(moduleName, def) @@ -116,40 +93,17 @@ macro expose*(moduleName: static string, def: untyped): untyped = if pureFunctionNameStr == entry[0].repr: suffix.add("2") - # defer: - # if pureFunctionNameStr == "setMove": - # echo def.treeRepr - # echo result.repr - # The wrapper function which takes JSON arguments and returns JSON and internally calls the Nim function let jsonWrapperFunctionName = ident(uniqueName & "_json") # The script function is a wrapper around impl which translates some argument types - # and inserts some arguments automatically using injectors. This function will be called from the NimScript. + # and inserts some arguments automatically using injectors. This function will be called from the json wrapper. # This function has a unique name, otherwise we can't call it from nimscript let scriptFunctionSym = ident(uniqueName & "_impl") var scriptFunction = def.copy scriptFunction[0] = nnkPostfix.newTree(ident"*", scriptFunctionSym) var callImplFromScriptFunction = nnkCall.newTree(functionName) - # Wrapper function for the script function which is inserted into NimScript. - # This has the same name as the original function and is used to get function overloading back - var scriptFunctionWrapper = def.copy - scriptFunctionWrapper[0] = nnkPostfix.newTree(ident"*", pureFunctionName) - var callScriptFuncFromScriptFuncWrapper = nnkCall.newTree(scriptFunctionSym) - - # Wrapper function for wasm code in the nim wasm wrapper library - let jsonStringWrapperFunctionName = ident(uniqueName & "_wasm") - var jsonStringWrapperFunctionWasm = def.copy - jsonStringWrapperFunctionWasm[0] = nnkPostfix.newTree(ident"*", pureFunctionName) - var callJsonStringWrapperFunctionWasmArr = ident"argsJson" - var callJsonStringWrapperFunctionWasmRes = ident"res" - var callJsonStringWrapperFunctionWasm = genAst(f=jsonStringWrapperFunctionName, res=callJsonStringWrapperFunctionWasmRes, argsJson=callJsonStringWrapperFunctionWasmArr, - argsJsonString="argsJsonString".ident): - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = f(argsJsonString.cstring) - var callScriptFuncFromJson = nnkCall.newTree(scriptFunctionSym) var mappedArgIndices = initTable[int, int]() @@ -167,7 +121,7 @@ macro expose*(moduleName: static string, def: untyped): untyped = # Argument of the JSON wrapper which calls the script function. Defined here so we can use it in the loop. let jsonArg = nskParam.genSym - # Go through each parameter in reverse, fill out the args in `callImplFromScriptFunction`, `callScriptFuncFromScriptFuncWrapper` + # Go through each parameter in reverse, fill out the args in `callImplFromScriptFunction` # and `callScriptFuncFromJson` for k in 1..argCount: let i = argCount - k @@ -184,8 +138,6 @@ macro expose*(moduleName: static string, def: untyped): untyped = if source == mappedArgumentType.repr: mappedArgumentType = target scriptFunction[3][i + 1][1] = target - scriptFunctionWrapper[3][i + 1][1] = target - jsonStringWrapperFunctionWasm[3][i + 1][1] = target break # The argument for the call to scriptFunction in the wrapper @@ -253,8 +205,6 @@ macro expose*(moduleName: static string, def: untyped): untyped = if mappedArgumentType.repr == typeName: wasInjected = true scriptFunction[3].del(i + 1) - scriptFunctionWrapper[3].del(i + 1) - jsonStringWrapperFunctionWasm[3].del(i + 1) callFromScriptArg = quote do: block: let r = `function`() @@ -265,110 +215,42 @@ macro expose*(moduleName: static string, def: untyped): untyped = if not wasInjected: callScriptFuncFromJson.insert(1, resWrapper) - callScriptFuncFromScriptFuncWrapper.insert(1, scriptFunctionWrapper.argName(i)) - - let callJsonStringWrapperFunctionWasmArg = genAst(argsJson=callJsonStringWrapperFunctionWasmArr, arg = jsonStringWrapperFunctionWasm.argName(i)): - argsJson.add arg.toJson() - - callJsonStringWrapperFunctionWasm.insert(1, callJsonStringWrapperFunctionWasmArg) callImplFromScriptFunction.insert(1, callFromScriptArg) scriptFunction[6] = quote do: `callImplFromScriptFunction` - var scriptFunctionWrapperBody = nnkStmtList.newTree() - if documentation.isSome: - scriptFunctionWrapperBody.add documentation.get - for n in runnableExampls: - scriptFunctionWrapperBody.add n - scriptFunctionWrapperBody.add callScriptFuncFromScriptFuncWrapper - scriptFunctionWrapper[6] = scriptFunctionWrapperBody - - let scriptFunctionWrapperRepr = scriptFunctionWrapper.repr - let lineNumber = def.lineInfoObj.line - - if returnType.isSome: - let returnNode = genAst(res=callJsonStringWrapperFunctionWasmRes): - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - callJsonStringWrapperFunctionWasm.add returnNode - - jsonStringWrapperFunctionWasm[6] = callJsonStringWrapperFunctionWasm - let jsonWrapperFunction = createJsonWrapper(scriptFunction, jsonWrapperFunctionName) # todo: why do we remove pragmas again? removePragmas(def) removePragmas(scriptFunction) - removePragmas(scriptFunctionWrapper) - removePragmas(jsonStringWrapperFunctionWasm) jsonWrapperFunction.addPragma(ident"gcsafe") scriptFunction.addPragma(ident"gcsafe") def.addPragma(ident"gcsafe") - jsonStringWrapperFunctionWasm.addPragma(ident"gcsafe") scriptFunction.addPragma(nnkExprColonExpr.newTree(ident"raises", nnkBracket.newTree())) def.addPragma(nnkExprColonExpr.newTree(ident"raises", nnkBracket.newTree())) - jsonStringWrapperFunctionWasm.addPragma(nnkExprColonExpr.newTree(ident"raises", nnkBracket.newTree())) result = quote do: `def` `scriptFunction` - static: - # This causes the function wrapper to be emitted in a file, so it can be imported in configs - addScriptWrapper(`scriptFunctionWrapperRepr`, `moduleName`, `lineNumber`) - if not def.hasCustomPragma("nojsonwrapper"): - let jsonStringWrapperFunctionReturnValue = genSym(nskVar, functionName.strVal & "WasmReturnValue") - let arg = ident"arg" - result.add jsonWrapperFunction - result.add quote do: - var `jsonStringWrapperFunctionReturnValue`: string = "" - proc `jsonStringWrapperFunctionName`*(arg: cstring): cstring {.exportc, used, gcsafe, raises: [].} = - try: - let argJson = parseJson($arg) - let res = `jsonWrapperFunctionName`(argJson) - {.gcsafe.}: - if res.isNil: - `jsonStringWrapperFunctionReturnValue` = "" - else: - `jsonStringWrapperFunctionReturnValue` = $res - return `jsonStringWrapperFunctionReturnValue`.cstring - except CatchableError: - let name = `pureFunctionNameStr` - {.push warning[BareExcept]:off.} - try: - logging.log lvlError, "Failed to run function " & name & &": Invalid arguments: {getCurrentExceptionMsg()}\n{getStackTrace()}" - except Exception: - discard - {.pop.} - return "null" - - static: - addWasmImportedFunction(`moduleName`, `jsonStringWrapperFunctionName`, `jsonStringWrapperFunctionWasm`): - proc `jsonStringWrapperFunctionName`(`arg`: cstring): cstring {.importc.} - result.add quote do: static: # This makes the function dispatchable addFunction(`pureFunctionName`, `signature`, `jsonWrapperFunctionName`, `moduleName`, `documentationStr`) - # echo result[result.len - 1].repr + # echo "===========================" + # echo result.repr macro genDispatchTable*(moduleName: static string): untyped = - defer: - discard - # echo result.repr - # echo result.treeRepr - var funcs = genSym(nskVar, "functionList") let blk = nnkStmtList.newTree() @@ -443,40 +325,3 @@ macro genDispatcher*(moduleName: static string): untyped = return quote do: proc dispatch(`command`: string, `arg`: JsonNode): Option[JsonNode] {.gcsafe, raises: [JsonCallError].} = result = `switch` - -proc generateScriptingApiPerModule*() {.compileTime.} = - var imports_content = "import \"../src/scripting_api\"\nexport scripting_api\n\n## This file is auto generated, don't modify.\n\n" - - for moduleName, list in exposedFunctions: - var script_api_content_wasm = """ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - -""" - - for m, list in wasmImportedFunctions: - if moduleName != m: - continue - for f in list: - script_api_content_wasm.add f[2].repr - script_api_content_wasm.add "\n" - script_api_content_wasm.add f[1].repr - script_api_content_wasm.add "\n" - - let file_name = moduleName.replace(".", "_") - - echo fmt"Writing scripting/{file_name}_api_wasm.nim" - writeFile(fmt"scripting/{file_name}_api_wasm.nim", script_api_content_wasm) - - imports_content.add fmt"import {file_name}_api_wasm" & "\n" - imports_content.add fmt"export {file_name}_api_wasm" & "\n" - - when enableAst: - imports_content.add "\nconst enableAst* = true\n" - else: - imports_content.add "\nconst enableAst* = false\n" - - echo fmt"Writing scripting/plugin_api.nim" - writeFile(fmt"scripting/plugin_api.nim", imports_content) diff --git a/src/scripting/scripting_wasm.nim b/src/scripting/scripting_wasm.nim deleted file mode 100644 index ebf07147d..000000000 --- a/src/scripting/scripting_wasm.nim +++ /dev/null @@ -1,218 +0,0 @@ -import std/[macros, macrocache, genasts, json, strutils, os, sugar] -import misc/[custom_logger, custom_async, util] -import expose, vfs, plugin_service -import wasm -import wasm3, wasm3/wasmconversions - -export wasm - -{.push gcsafe.} - -logCategory "scripting-wasm" - -type - ScriptContextWasm* = ref object of PluginSystem - modules: seq[WasmModule] - - postInitializeCallbacks: seq[tuple[module: WasmModule, pfun: PFunction, callback: proc(module: WasmModule, pfun: PFunction): bool {.gcsafe.}]] - handleCallbackCallbacks: seq[tuple[module: WasmModule, pfun: PFunction, callback: proc(module: WasmModule, pfun: PFunction, id: int32, args: cstring): bool {.gcsafe.}]] - handleAnyCallbackCallbacks: seq[tuple[module: WasmModule, pfun: PFunction, callback: proc(module: WasmModule, pfun: PFunction, id: int32, args: cstring): cstring {.gcsafe.}]] - handleScriptActionCallbacks: seq[tuple[module: WasmModule, pfun: PFunction, callback: proc(module: WasmModule, pfun: PFunction, name: cstring, args: cstring): cstring {.gcsafe.}]] - - stack: seq[WasmModule] - - moduleVfs*: VFS - vfs*: VFS - -var createEditorWasmImports: proc(): WasmImports {.raises: [].} - -proc getVfsPath*(self: WasmModule): string = - result = "plugs://" - result.add self.path.splitFile.name - result.add "/" - -method getCurrentContext*(self: ScriptContextWasm): string = - result = "plugs://" - if self.stack.len > 0: - result = self.stack[^1].getVfsPath() - -macro invoke*(self: ScriptContextWasm; pName: untyped; args: varargs[typed]; returnType: typedesc): untyped = - result = quote do: - default(`returnType`) - -proc newWasmModuleAsync(file: string, editorImports: WasmImports, vfs: VFS): Future[Option[WasmModule]] {.async.} = - try: - log lvlInfo, fmt"Try to load wasm module '{file}' from app directory" - let module = await newWasmModule(file, @[editorImports], vfs) - log lvlInfo, fmt"Loaded wasm module '{file}' from app directory" - return module - except: - log lvlError, &"Failde to load wasm module '{file}': {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - return WasmModule.none - -proc loadModules(self: ScriptContextWasm, path: string): Future[void] {.async.} = - let listing = await self.vfs.getDirectoryListing(path) - - {.gcsafe.}: - var editorImports = createEditorWasmImports() - - var wasmModuleFutures = collect: - for file2 in listing.files: - if not file2.endsWith(".wasm"): - continue - if file2.endsWith(".m.wasm"): - continue - if file2.endsWith(".me.wasm"): - continue - if file2.endsWith(".c.wasm"): - continue - if not file2.endsWith(".wasm"): - continue - - let file = path // file2 - let module = newWasmModuleAsync(file, editorImports, self.vfs) - (file, module) - - for (file, future) in wasmModuleFutures: - try: - let module = await future - - if module.getSome(module): - self.moduleVfs.mount(file.splitFile.name, newInMemoryVFS()) - self.stack.add module - defer: discard self.stack.pop - - log(lvlInfo, fmt"Loaded wasm module '{file}'") - - # todo: shouldn't need to specify gcsafe here, findFunction should handle that - if findFunction(module, "postInitializeWasm", bool, proc(module: WasmModule, fun: PFunction): bool {.gcsafe.}).getSome(f): - self.postInitializeCallbacks.add (module, f.pfun, f.fun) - - if findFunction(module, "handleCallbackWasm", bool, proc(module: WasmModule, fun: PFunction, id: int32, arg: cstring): bool {.gcsafe.}).getSome(f): - self.handleCallbackCallbacks.add (module, f.pfun, f.fun) - - if findFunction(module, "handleAnyCallbackWasm", cstring, proc(module: WasmModule, fun: PFunction, id: int32, arg: cstring): cstring {.gcsafe.}).getSome(f): - self.handleAnyCallbackCallbacks.add (module, f.pfun, f.fun) - - if findFunction(module, "handleScriptActionWasm", cstring, proc(module: WasmModule, fun: PFunction, name: cstring, arg: cstring): cstring {.gcsafe.}).getSome(f): - self.handleScriptActionCallbacks.add (module, f.pfun, f.fun) - - self.modules.add module - - if findFunction(module, "plugin_main", void, proc(module: WasmModule, fun: PFunction): void {.gcsafe.}).getSome(f): - log lvlInfo, "Run plugin_main" - f.fun(module, f.pfun) - log lvlInfo, "Finished plugin_main" - - else: - log(lvlError, fmt"Failed to create wasm module for file {file}") - - except: - log lvlError, &"Failde to load wasm module '{file}': {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - -{.push raises: [].} - -method init*(self: ScriptContextWasm, path: string, vfs: VFS): Future[void] {.async.} = - self.vfs = vfs - await self.loadModules("app://config/wasm") - -method deinit*(self: ScriptContextWasm) = discard - -method reload*(self: ScriptContextWasm): Future[void] {.async.} = - self.postInitializeCallbacks.setLen 0 - self.handleCallbackCallbacks.setLen 0 - self.handleAnyCallbackCallbacks.setLen 0 - self.handleScriptActionCallbacks.setLen 0 - - self.modules.setLen 0 - - await self.loadModules("app://config/wasm") - -method tryLoadPlugin*(self: ScriptContextWasm, plugin: Plugin): Future[bool] {.async: (raises: [IOError]).} = - return false - -method postInitialize*(self: ScriptContextWasm): bool = - result = false - try: - for (m, p, f) in self.postInitializeCallbacks: - self.stack.add m - defer: discard self.stack.pop - result = f(m, p) or result - except: - log lvlError, &"Failed to run post initialize: {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - -method handleCallback*(self: ScriptContextWasm, id: int, arg: JsonNode): bool = - result = false - try: - let argStr = $arg - for (m, p, f) in self.handleCallbackCallbacks: - self.stack.add m - defer: discard self.stack.pop - if f(m, p, id.int32, argStr.cstring): - return true - except: - log lvlError, &"Failed to run callback: {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - -method handleAnyCallback*(self: ScriptContextWasm, id: int, arg: JsonNode): JsonNode = - var path = "" - try: - result = nil - let argStr = $arg - for (m, p, f) in self.handleAnyCallbackCallbacks: - path = m.path - self.stack.add m - defer: discard self.stack.pop - let str = $f(m, p, id.int32, argStr.cstring) - if str.len == 0: - continue - - try: - return str.parseJson - except: - log lvlError, &"Failed to parse json from callback {id}({arg}): '{str}' is not valid json.\n{getCurrentExceptionMsg()}" - continue - except: - log lvlError, &"Failed to run handleAnyCallback '{path}': {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - -method handleScriptAction*(self: ScriptContextWasm, name: string, args: JsonNode): JsonNode = - try: - result = nil - let argStr = $args - for (m, p, f) in self.handleScriptActionCallbacks: - self.stack.add m - defer: discard self.stack.pop - let res = $f(m, p, name.cstring, argStr.cstring) - if res.len == 0 or res.startsWith("error: "): - continue - - try: - return res.parseJson - except: - log lvlError, &"Failed to parse json from script action {name}({args}): '{res}' is not valid json.\n{getCurrentExceptionMsg()}" - continue - except: - log lvlError, &"Failed to run handleScriptAction: {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - -# Sets the implementation of createEditorWasmImports. This needs to happen late during compilation after any expose pragmas have been executed, -# because this goes through all exposed functions at compile time to create the wasm import data. -# That's why it's in a template -template createEditorWasmImportConstructor*() = - proc createEditorWasmImportsImpl(): WasmImports = - macro addEditorFunctions(imports: WasmImports): untyped = - var list = nnkStmtList.newTree() - for m, l in wasmImportedFunctions: - for f in l: - let name = f[0].strVal.newLit - let function = f[0] - - let imp = genAst(imports, name, function): - imports.addFunction(name, function) - list.add imp - - return list - - var imports = WasmImports(namespace: "env") - addEditorFunctions(imports) - return imports - - createEditorWasmImports = createEditorWasmImportsImpl From af642609e847c5d33f24f005db241c00fabcd609 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 19 Oct 2025 01:07:22 +0200 Subject: [PATCH 31/47] Delete old plugin api stuff --- scripting/collab_api_wasm.nim | 23 - scripting/commands_api_wasm.nim | 93 - scripting/config_api_wasm.nim | 62 - scripting/debugger_api_wasm.nim | 281 --- scripting/editor_api_wasm.nim | 804 ------- scripting/editor_model_api_wasm.nim | 709 ------- scripting/editor_text_api_wasm.nim | 2768 ------------------------- scripting/editors_api_wasm.nim | 32 - scripting/events_api_wasm.nim | 101 - scripting/layout_api_wasm.nim | 347 ---- scripting/lsp_api_wasm.nim | 29 - scripting/plugin_api.nim | 45 - scripting/plugin_api_internal.nim | 1005 --------- scripting/plugin_runtime.nim | 438 ---- scripting/plugin_runtime_impl.nim | 62 - scripting/plugins_api_wasm.nim | 60 - scripting/popup_selector_api_wasm.nim | 136 -- scripting/process_api_wasm.nim | 22 - scripting/registers_api_wasm.nim | 120 -- scripting/script_expose.nim | 62 - scripting/session_api_wasm.nim | 34 - scripting/terminal_api_wasm.nim | 128 -- scripting/toast_api_wasm.nim | 18 - scripting/vcs_api_wasm.nim | 15 - scripting/vfs_api_wasm.nim | 109 - 25 files changed, 7503 deletions(-) delete mode 100644 scripting/collab_api_wasm.nim delete mode 100644 scripting/commands_api_wasm.nim delete mode 100644 scripting/config_api_wasm.nim delete mode 100644 scripting/debugger_api_wasm.nim delete mode 100644 scripting/editor_api_wasm.nim delete mode 100644 scripting/editor_model_api_wasm.nim delete mode 100644 scripting/editor_text_api_wasm.nim delete mode 100644 scripting/editors_api_wasm.nim delete mode 100644 scripting/events_api_wasm.nim delete mode 100644 scripting/layout_api_wasm.nim delete mode 100644 scripting/lsp_api_wasm.nim delete mode 100644 scripting/plugin_api.nim delete mode 100644 scripting/plugin_api_internal.nim delete mode 100644 scripting/plugin_runtime.nim delete mode 100644 scripting/plugin_runtime_impl.nim delete mode 100644 scripting/plugins_api_wasm.nim delete mode 100644 scripting/popup_selector_api_wasm.nim delete mode 100644 scripting/process_api_wasm.nim delete mode 100644 scripting/registers_api_wasm.nim delete mode 100644 scripting/script_expose.nim delete mode 100644 scripting/session_api_wasm.nim delete mode 100644 scripting/terminal_api_wasm.nim delete mode 100644 scripting/toast_api_wasm.nim delete mode 100644 scripting/vcs_api_wasm.nim delete mode 100644 scripting/vfs_api_wasm.nim diff --git a/scripting/collab_api_wasm.nim b/scripting/collab_api_wasm.nim deleted file mode 100644 index ca9dbd19b..000000000 --- a/scripting/collab_api_wasm.nim +++ /dev/null @@ -1,23 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc collab_connectCollaborator_void_int_wasm(arg: cstring): cstring {.importc.} -proc connectCollaborator*(port: int = 6969) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add port.toJson() - let argsJsonString = $argsJson - let res {.used.} = collab_connectCollaborator_void_int_wasm( - argsJsonString.cstring) - - -proc collab_hostCollaborator_void_int_wasm(arg: cstring): cstring {.importc.} -proc hostCollaborator*(port: int = 6969) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add port.toJson() - let argsJsonString = $argsJson - let res {.used.} = collab_hostCollaborator_void_int_wasm( - argsJsonString.cstring) - diff --git a/scripting/commands_api_wasm.nim b/scripting/commands_api_wasm.nim deleted file mode 100644 index 8310d209e..000000000 --- a/scripting/commands_api_wasm.nim +++ /dev/null @@ -1,93 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc commands_commandLine_void_CommandService_string_string_wasm(arg: cstring): cstring {. - importc.} -proc commandLine*(initialValue: string = ""; prefix: string = "") {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add initialValue.toJson() - argsJson.add prefix.toJson() - let argsJsonString = $argsJson - let res {.used.} = commands_commandLine_void_CommandService_string_string_wasm( - argsJsonString.cstring) - - -proc commands_exitCommandLine_void_CommandService_wasm(arg: cstring): cstring {. - importc.} -proc exitCommandLine*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = commands_exitCommandLine_void_CommandService_wasm( - argsJsonString.cstring) - - -proc commands_commandLineResult_void_CommandService_string_bool_bool_string_wasm( - arg: cstring): cstring {.importc.} -proc commandLineResult*(value: string; showInCommandLine: bool = false; - appendAndShowInFile: bool = false; - filename: string = "ed://.shell-command-results") {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add value.toJson() - argsJson.add showInCommandLine.toJson() - argsJson.add appendAndShowInFile.toJson() - argsJson.add filename.toJson() - let argsJsonString = $argsJson - let res {.used.} = commands_commandLineResult_void_CommandService_string_bool_bool_string_wasm( - argsJsonString.cstring) - - -proc commands_clearCommandLineResults_void_CommandService_wasm(arg: cstring): cstring {. - importc.} -proc clearCommandLineResults*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = commands_clearCommandLineResults_void_CommandService_wasm( - argsJsonString.cstring) - - -proc commands_executeCommandLine_bool_CommandService_wasm(arg: cstring): cstring {. - importc.} -proc executeCommandLine*(): bool {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = commands_executeCommandLine_bool_CommandService_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc commands_selectPreviousCommandInHistory_void_CommandService_wasm( - arg: cstring): cstring {.importc.} -proc selectPreviousCommandInHistory*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = commands_selectPreviousCommandInHistory_void_CommandService_wasm( - argsJsonString.cstring) - - -proc commands_selectNextCommandInHistory_void_CommandService_wasm(arg: cstring): cstring {. - importc.} -proc selectNextCommandInHistory*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = commands_selectNextCommandInHistory_void_CommandService_wasm( - argsJsonString.cstring) - - -proc commands_runShellCommand_void_CommandService_RunShellCommandOptions_wasm( - arg: cstring): cstring {.importc.} -proc runShellCommand*(options: RunShellCommandOptions = RunShellCommandOptions()) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add options.toJson() - let argsJsonString = $argsJson - let res {.used.} = commands_runShellCommand_void_CommandService_RunShellCommandOptions_wasm( - argsJsonString.cstring) - diff --git a/scripting/config_api_wasm.nim b/scripting/config_api_wasm.nim deleted file mode 100644 index fd450e94a..000000000 --- a/scripting/config_api_wasm.nim +++ /dev/null @@ -1,62 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc config_logOptions_void_ConfigService_wasm(arg: cstring): cstring {.importc.} -proc logOptions*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = config_logOptions_void_ConfigService_wasm( - argsJsonString.cstring) - - -proc config_setOption_void_ConfigService_string_JsonNode_bool_wasm(arg: cstring): cstring {. - importc.} -proc setOption*(option: string; value: JsonNode; override: bool = true) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add option.toJson() - argsJson.add value.toJson() - argsJson.add override.toJson() - let argsJsonString = $argsJson - let res {.used.} = config_setOption_void_ConfigService_string_JsonNode_bool_wasm( - argsJsonString.cstring) - - -proc config_getFlag_bool_ConfigService_string_bool_wasm(arg: cstring): cstring {. - importc.} -proc getFlag*(flag: string; default: bool = false): bool {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add flag.toJson() - argsJson.add default.toJson() - let argsJsonString = $argsJson - let res {.used.} = config_getFlag_bool_ConfigService_string_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc config_setFlag_void_ConfigService_string_bool_wasm(arg: cstring): cstring {. - importc.} -proc setFlag*(flag: string; value: bool) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add flag.toJson() - argsJson.add value.toJson() - let argsJsonString = $argsJson - let res {.used.} = config_setFlag_void_ConfigService_string_bool_wasm( - argsJsonString.cstring) - - -proc config_toggleFlag_void_ConfigService_string_wasm(arg: cstring): cstring {. - importc.} -proc toggleFlag*(flag: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add flag.toJson() - let argsJsonString = $argsJson - let res {.used.} = config_toggleFlag_void_ConfigService_string_wasm( - argsJsonString.cstring) - diff --git a/scripting/debugger_api_wasm.nim b/scripting/debugger_api_wasm.nim deleted file mode 100644 index 8393dc569..000000000 --- a/scripting/debugger_api_wasm.nim +++ /dev/null @@ -1,281 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc debugger_prevDebuggerView_void_Debugger_wasm(arg: cstring): cstring {. - importc.} -proc prevDebuggerView*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_prevDebuggerView_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_nextDebuggerView_void_Debugger_wasm(arg: cstring): cstring {. - importc.} -proc nextDebuggerView*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_nextDebuggerView_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_setDebuggerView_void_Debugger_string_wasm(arg: cstring): cstring {. - importc.} -proc setDebuggerView*(view: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add view.toJson() - let argsJsonString = $argsJson - let res {.used.} = debugger_setDebuggerView_void_Debugger_string_wasm( - argsJsonString.cstring) - - -proc debugger_selectFirstVariable_void_Debugger_wasm(arg: cstring): cstring {. - importc.} -proc selectFirstVariable*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_selectFirstVariable_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_selectLastVariable_void_Debugger_wasm(arg: cstring): cstring {. - importc.} -proc selectLastVariable*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_selectLastVariable_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_prevThread_void_Debugger_wasm(arg: cstring): cstring {.importc.} -proc prevThread*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_prevThread_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_nextThread_void_Debugger_wasm(arg: cstring): cstring {.importc.} -proc nextThread*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_nextThread_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_prevStackFrame_void_Debugger_wasm(arg: cstring): cstring {.importc.} -proc prevStackFrame*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_prevStackFrame_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_nextStackFrame_void_Debugger_wasm(arg: cstring): cstring {.importc.} -proc nextStackFrame*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_nextStackFrame_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_openFileForCurrentFrame_void_Debugger_string_wasm(arg: cstring): cstring {. - importc.} -proc openFileForCurrentFrame*(slot: string = "") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add slot.toJson() - let argsJsonString = $argsJson - let res {.used.} = debugger_openFileForCurrentFrame_void_Debugger_string_wasm( - argsJsonString.cstring) - - -proc debugger_prevVariable_void_Debugger_wasm(arg: cstring): cstring {.importc.} -proc prevVariable*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_prevVariable_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_nextVariable_void_Debugger_wasm(arg: cstring): cstring {.importc.} -proc nextVariable*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_nextVariable_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_expandVariable_void_Debugger_wasm(arg: cstring): cstring {.importc.} -proc expandVariable*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_expandVariable_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_collapseVariable_void_Debugger_wasm(arg: cstring): cstring {. - importc.} -proc collapseVariable*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_collapseVariable_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_stopDebugSession_void_Debugger_wasm(arg: cstring): cstring {. - importc.} -proc stopDebugSession*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_stopDebugSession_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_stopDebugSessionDelayed_void_Debugger_wasm(arg: cstring): cstring {. - importc.} -proc stopDebugSessionDelayed*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_stopDebugSessionDelayed_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_runConfiguration_void_Debugger_string_wasm(arg: cstring): cstring {. - importc.} -proc runConfiguration*(name: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add name.toJson() - let argsJsonString = $argsJson - let res {.used.} = debugger_runConfiguration_void_Debugger_string_wasm( - argsJsonString.cstring) - - -proc debugger_chooseRunConfiguration_void_Debugger_wasm(arg: cstring): cstring {. - importc.} -proc chooseRunConfiguration*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_chooseRunConfiguration_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_runLastConfiguration_void_Debugger_wasm(arg: cstring): cstring {. - importc.} -proc runLastConfiguration*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_runLastConfiguration_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_toggleBreakpointAt_void_Debugger_EditorId_int_wasm(arg: cstring): cstring {. - importc.} -proc toggleBreakpointAt*(editorId: EditorId; line: int) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add editorId.toJson() - argsJson.add line.toJson() - let argsJsonString = $argsJson - let res {.used.} = debugger_toggleBreakpointAt_void_Debugger_EditorId_int_wasm( - argsJsonString.cstring) - - -proc debugger_toggleBreakpoint_void_Debugger_wasm(arg: cstring): cstring {. - importc.} -proc toggleBreakpoint*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_toggleBreakpoint_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_removeBreakpoint_void_Debugger_string_int_wasm(arg: cstring): cstring {. - importc.} -proc removeBreakpoint*(path: string; line: int) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - argsJson.add line.toJson() - let argsJsonString = $argsJson - let res {.used.} = debugger_removeBreakpoint_void_Debugger_string_int_wasm( - argsJsonString.cstring) - - -proc debugger_toggleBreakpointEnabled_void_Debugger_string_int_wasm(arg: cstring): cstring {. - importc.} -proc toggleBreakpointEnabled*(path: string; line: int) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - argsJson.add line.toJson() - let argsJsonString = $argsJson - let res {.used.} = debugger_toggleBreakpointEnabled_void_Debugger_string_int_wasm( - argsJsonString.cstring) - - -proc debugger_toggleAllBreakpointsEnabled_void_Debugger_wasm(arg: cstring): cstring {. - importc.} -proc toggleAllBreakpointsEnabled*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_toggleAllBreakpointsEnabled_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_toggleBreakpointsEnabled_void_Debugger_wasm(arg: cstring): cstring {. - importc.} -proc toggleBreakpointsEnabled*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_toggleBreakpointsEnabled_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_editBreakpoints_void_Debugger_wasm(arg: cstring): cstring {. - importc.} -proc editBreakpoints*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_editBreakpoints_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_continueExecution_void_Debugger_wasm(arg: cstring): cstring {. - importc.} -proc continueExecution*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_continueExecution_void_Debugger_wasm( - argsJsonString.cstring) - - -proc debugger_stepOver_void_Debugger_wasm(arg: cstring): cstring {.importc.} -proc stepOver*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_stepOver_void_Debugger_wasm(argsJsonString.cstring) - - -proc debugger_stepIn_void_Debugger_wasm(arg: cstring): cstring {.importc.} -proc stepIn*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_stepIn_void_Debugger_wasm(argsJsonString.cstring) - - -proc debugger_stepOut_void_Debugger_wasm(arg: cstring): cstring {.importc.} -proc stepOut*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = debugger_stepOut_void_Debugger_wasm(argsJsonString.cstring) - - -proc debugger_showDebuggerView_void_Debugger_string_wasm(arg: cstring): cstring {. - importc.} -proc showDebuggerView*(slot: string = "#debugger") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add slot.toJson() - let argsJsonString = $argsJson - let res {.used.} = debugger_showDebuggerView_void_Debugger_string_wasm( - argsJsonString.cstring) - diff --git a/scripting/editor_api_wasm.nim b/scripting/editor_api_wasm.nim deleted file mode 100644 index 75c4742f3..000000000 --- a/scripting/editor_api_wasm.nim +++ /dev/null @@ -1,804 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc editor_getOptionJson_JsonNode_ConfigService_string_JsonNode_wasm( - arg: cstring): cstring {.importc.} -proc getOptionJson*(path: string; default: JsonNode = newJNull()): JsonNode {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - argsJson.add default.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_getOptionJson_JsonNode_ConfigService_string_JsonNode_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_reapplyConfigKeybindings_void_App_bool_bool_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc reapplyConfigKeybindings*(app: bool = false; home: bool = false; - workspace: bool = false; wait: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add app.toJson() - argsJson.add home.toJson() - argsJson.add workspace.toJson() - argsJson.add wait.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_reapplyConfigKeybindings_void_App_bool_bool_bool_bool_wasm( - argsJsonString.cstring) - - -proc editor_runExternalCommand_void_App_string_seq_string_string_wasm( - arg: cstring): cstring {.importc.} -proc runExternalCommand*(command: string; args: seq[string] = @[]; - workingDir: string = "") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add command.toJson() - argsJson.add args.toJson() - argsJson.add workingDir.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_runExternalCommand_void_App_string_seq_string_string_wasm( - argsJsonString.cstring) - - -proc editor_disableLogFrameTime_void_App_bool_wasm(arg: cstring): cstring {. - importc.} -proc disableLogFrameTime*(disable: bool) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add disable.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_disableLogFrameTime_void_App_bool_wasm( - argsJsonString.cstring) - - -proc editor_enableDebugPrintAsyncAwaitStackTrace_void_App_bool_wasm(arg: cstring): cstring {. - importc.} -proc enableDebugPrintAsyncAwaitStackTrace*(enable: bool) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add enable.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_enableDebugPrintAsyncAwaitStackTrace_void_App_bool_wasm( - argsJsonString.cstring) - - -proc editor_setLocationListFromCurrentPopup_void_App_wasm(arg: cstring): cstring {. - importc.} -proc setLocationListFromCurrentPopup*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_setLocationListFromCurrentPopup_void_App_wasm( - argsJsonString.cstring) - - -proc editor_getBackend_Backend_App_wasm(arg: cstring): cstring {.importc.} -proc getBackend*(): Backend {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_getBackend_Backend_App_wasm(argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_getHostOs_string_App_wasm(arg: cstring): cstring {.importc.} -proc getHostOs*(): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_getHostOs_string_App_wasm(argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_toggleShowDrawnNodes_void_App_wasm(arg: cstring): cstring {.importc.} -proc toggleShowDrawnNodes*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_toggleShowDrawnNodes_void_App_wasm( - argsJsonString.cstring) - - -proc editor_saveAppState_void_App_wasm(arg: cstring): cstring {.importc.} -proc saveAppState*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_saveAppState_void_App_wasm(argsJsonString.cstring) - - -proc editor_requestRender_void_App_bool_wasm(arg: cstring): cstring {.importc.} -proc requestRender*(redrawEverything: bool = false) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add redrawEverything.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_requestRender_void_App_bool_wasm( - argsJsonString.cstring) - - -proc editor_clearWorkspaceCaches_void_App_wasm(arg: cstring): cstring {.importc.} -proc clearWorkspaceCaches*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_clearWorkspaceCaches_void_App_wasm( - argsJsonString.cstring) - - -proc editor_quit_void_App_wasm(arg: cstring): cstring {.importc.} -proc quit*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_quit_void_App_wasm(argsJsonString.cstring) - - -proc editor_quitImmediately_void_App_int_wasm(arg: cstring): cstring {.importc.} -proc quitImmediately*(exitCode: int = 0) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add exitCode.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_quitImmediately_void_App_int_wasm( - argsJsonString.cstring) - - -proc editor_help_void_App_string_wasm(arg: cstring): cstring {.importc.} -proc help*(about: string = "") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add about.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_help_void_App_string_wasm(argsJsonString.cstring) - - -proc editor_loadWorkspaceFile_void_App_string_string_wasm(arg: cstring): cstring {. - importc.} -proc loadWorkspaceFile*(path: string; callback: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - argsJson.add callback.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_loadWorkspaceFile_void_App_string_string_wasm( - argsJsonString.cstring) - - -proc editor_writeWorkspaceFile_void_App_string_string_wasm(arg: cstring): cstring {. - importc.} -proc writeWorkspaceFile*(path: string; content: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - argsJson.add content.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_writeWorkspaceFile_void_App_string_string_wasm( - argsJsonString.cstring) - - -proc editor_changeFontSize_void_App_float32_wasm(arg: cstring): cstring {. - importc.} -proc changeFontSize*(amount: float32) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add amount.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_changeFontSize_void_App_float32_wasm( - argsJsonString.cstring) - - -proc editor_changeLineDistance_void_App_float32_wasm(arg: cstring): cstring {. - importc.} -proc changeLineDistance*(amount: float32) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add amount.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_changeLineDistance_void_App_float32_wasm( - argsJsonString.cstring) - - -proc editor_platformTotalLineHeight_float32_App_wasm(arg: cstring): cstring {. - importc.} -proc platformTotalLineHeight*(): float32 {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_platformTotalLineHeight_float32_App_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_platformLineHeight_float32_App_wasm(arg: cstring): cstring {.importc.} -proc platformLineHeight*(): float32 {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_platformLineHeight_float32_App_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_platformLineDistance_float32_App_wasm(arg: cstring): cstring {. - importc.} -proc platformLineDistance*(): float32 {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_platformLineDistance_float32_App_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_toggleStatusBarLocation_void_App_wasm(arg: cstring): cstring {. - importc.} -proc toggleStatusBarLocation*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_toggleStatusBarLocation_void_App_wasm( - argsJsonString.cstring) - - -proc editor_logs_void_App_string_bool_bool_wasm(arg: cstring): cstring {.importc.} -proc logs*(slot: string = ""; focus: bool = true; scrollToBottom: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add slot.toJson() - argsJson.add focus.toJson() - argsJson.add scrollToBottom.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_logs_void_App_string_bool_bool_wasm( - argsJsonString.cstring) - - -proc editor_toggleConsoleLogger_void_App_wasm(arg: cstring): cstring {.importc.} -proc toggleConsoleLogger*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_toggleConsoleLogger_void_App_wasm( - argsJsonString.cstring) - - -proc editor_writeFile_void_App_string_bool_wasm(arg: cstring): cstring {.importc.} -proc writeFile*(path: string = ""; appFile: bool = false) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - argsJson.add appFile.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_writeFile_void_App_string_bool_wasm( - argsJsonString.cstring) - - -proc editor_loadFile_void_App_string_wasm(arg: cstring): cstring {.importc.} -proc loadFile*(path: string = "") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_loadFile_void_App_string_wasm(argsJsonString.cstring) - - -proc editor_loadTheme_void_App_string_bool_wasm(arg: cstring): cstring {.importc.} -proc loadTheme*(name: string; force: bool = false) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add name.toJson() - argsJson.add force.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_loadTheme_void_App_string_bool_wasm( - argsJsonString.cstring) - - -proc editor_openSession_void_App_bool_string_bool_float_float_float_wasm( - arg: cstring): cstring {.importc.} -proc openSession*(newWindow: bool = false; root: string = "home://"; - preview: bool = true; scaleX: float = 0.9; - scaleY: float = 0.8; previewScale: float = 0.4) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add newWindow.toJson() - argsJson.add root.toJson() - argsJson.add preview.toJson() - argsJson.add scaleX.toJson() - argsJson.add scaleY.toJson() - argsJson.add previewScale.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_openSession_void_App_bool_string_bool_float_float_float_wasm( - argsJsonString.cstring) - - -proc editor_openRecentSession_void_App_bool_float_float_float_wasm(arg: cstring): cstring {. - importc.} -proc openRecentSession*(preview: bool = true; scaleX: float = 0.9; - scaleY: float = 0.8; previewScale: float = 0.4) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add preview.toJson() - argsJson.add scaleX.toJson() - argsJson.add scaleY.toJson() - argsJson.add previewScale.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_openRecentSession_void_App_bool_float_float_float_wasm( - argsJsonString.cstring) - - -proc editor_chooseTheme_void_App_wasm(arg: cstring): cstring {.importc.} -proc chooseTheme*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_chooseTheme_void_App_wasm(argsJsonString.cstring) - - -proc editor_crash_void_App_string_wasm(arg: cstring): cstring {.importc.} -proc crash*(message: string = "") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add message.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_crash_void_App_string_wasm(argsJsonString.cstring) - - -proc editor_crash2_void_App_wasm(arg: cstring): cstring {.importc.} -proc crash2*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_crash2_void_App_wasm(argsJsonString.cstring) - - -proc editor_createFile_void_App_string_wasm(arg: cstring): cstring {.importc.} -proc createFile*(path: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_createFile_void_App_string_wasm( - argsJsonString.cstring) - - -proc editor_browseKeybinds_void_App_bool_float_float_float_wasm(arg: cstring): cstring {. - importc.} -proc browseKeybinds*(preview: bool = true; scaleX: float = 0.9; - scaleY: float = 0.8; previewScale: float = 0.4) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add preview.toJson() - argsJson.add scaleX.toJson() - argsJson.add scaleY.toJson() - argsJson.add previewScale.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_browseKeybinds_void_App_bool_float_float_float_wasm( - argsJsonString.cstring) - - -proc editor_browseSettings_void_App_bool_float_float_float_wasm(arg: cstring): cstring {. - importc.} -proc browseSettings*(includeActiveEditor: bool = false; scaleX: float = 0.8; - scaleY: float = 0.8; previewScale: float = 0.5) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add includeActiveEditor.toJson() - argsJson.add scaleX.toJson() - argsJson.add scaleY.toJson() - argsJson.add previewScale.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_browseSettings_void_App_bool_float_float_float_wasm( - argsJsonString.cstring) - - -proc editor_chooseFile_void_App_bool_float_float_float_wasm(arg: cstring): cstring {. - importc.} -proc chooseFile*(preview: bool = true; scaleX: float = 0.8; scaleY: float = 0.8; - previewScale: float = 0.5) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add preview.toJson() - argsJson.add scaleX.toJson() - argsJson.add scaleY.toJson() - argsJson.add previewScale.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_chooseFile_void_App_bool_float_float_float_wasm( - argsJsonString.cstring) - - -proc editor_chooseOpen_void_App_bool_float_float_float_wasm(arg: cstring): cstring {. - importc.} -proc chooseOpen*(preview: bool = true; scaleX: float = 0.8; scaleY: float = 0.8; - previewScale: float = 0.6) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add preview.toJson() - argsJson.add scaleX.toJson() - argsJson.add scaleY.toJson() - argsJson.add previewScale.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_chooseOpen_void_App_bool_float_float_float_wasm( - argsJsonString.cstring) - - -proc editor_chooseOpenDocument_void_App_wasm(arg: cstring): cstring {.importc.} -proc chooseOpenDocument*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_chooseOpenDocument_void_App_wasm( - argsJsonString.cstring) - - -proc editor_showPlugins_void_App_float_float_float_wasm(arg: cstring): cstring {. - importc.} -proc showPlugins*(scaleX: float = 0.9; scaleY: float = 0.9; - previewScale: float = 0.6) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add scaleX.toJson() - argsJson.add scaleY.toJson() - argsJson.add previewScale.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_showPlugins_void_App_float_float_float_wasm( - argsJsonString.cstring) - - -proc editor_gotoNextLocation_void_App_wasm(arg: cstring): cstring {.importc.} -proc gotoNextLocation*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_gotoNextLocation_void_App_wasm( - argsJsonString.cstring) - - -proc editor_gotoPrevLocation_void_App_wasm(arg: cstring): cstring {.importc.} -proc gotoPrevLocation*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_gotoPrevLocation_void_App_wasm( - argsJsonString.cstring) - - -proc editor_chooseLocation_void_App_wasm(arg: cstring): cstring {.importc.} -proc chooseLocation*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_chooseLocation_void_App_wasm(argsJsonString.cstring) - - -proc editor_searchGlobalInteractive_void_App_wasm(arg: cstring): cstring {. - importc.} -proc searchGlobalInteractive*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_searchGlobalInteractive_void_App_wasm( - argsJsonString.cstring) - - -proc editor_searchGlobal_void_App_string_wasm(arg: cstring): cstring {.importc.} -proc searchGlobal*(query: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add query.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_searchGlobal_void_App_string_wasm( - argsJsonString.cstring) - - -proc editor_installTreesitterParser_void_App_string_string_wasm(arg: cstring): cstring {. - importc.} -proc installTreesitterParser*(language: string; host: string = "github.com") {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add language.toJson() - argsJson.add host.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_installTreesitterParser_void_App_string_string_wasm( - argsJsonString.cstring) - - -proc editor_exploreFiles_void_App_string_bool_bool_wasm(arg: cstring): cstring {. - importc.} -proc exploreFiles*(root: string = ""; showVFS: bool = false; - normalize: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add root.toJson() - argsJson.add showVFS.toJson() - argsJson.add normalize.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_exploreFiles_void_App_string_bool_bool_wasm( - argsJsonString.cstring) - - -proc editor_exploreWorkspacePrimary_void_App_wasm(arg: cstring): cstring {. - importc.} -proc exploreWorkspacePrimary*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_exploreWorkspacePrimary_void_App_wasm( - argsJsonString.cstring) - - -proc editor_exploreCurrentFileDirectory_void_App_wasm(arg: cstring): cstring {. - importc.} -proc exploreCurrentFileDirectory*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_exploreCurrentFileDirectory_void_App_wasm( - argsJsonString.cstring) - - -proc editor_reloadConfig_void_App_bool_wasm(arg: cstring): cstring {.importc.} -proc reloadConfig*(clearOptions: bool = false) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add clearOptions.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_reloadConfig_void_App_bool_wasm( - argsJsonString.cstring) - - -proc editor_reloadPlugin_void_App_wasm(arg: cstring): cstring {.importc.} -proc reloadPlugin*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_reloadPlugin_void_App_wasm(argsJsonString.cstring) - - -proc editor_reloadTheme_void_App_wasm(arg: cstring): cstring {.importc.} -proc reloadTheme*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_reloadTheme_void_App_wasm(argsJsonString.cstring) - - -proc editor_saveSession_void_App_string_wasm(arg: cstring): cstring {.importc.} -proc saveSession*(sessionFile: string = "") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add sessionFile.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_saveSession_void_App_string_wasm( - argsJsonString.cstring) - - -proc editor_dumpKeymapGraphViz_void_App_string_wasm(arg: cstring): cstring {. - importc.} -proc dumpKeymapGraphViz*(context: string = "") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add context.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_dumpKeymapGraphViz_void_App_string_wasm( - argsJsonString.cstring) - - -proc editor_setMode_void_App_string_wasm(arg: cstring): cstring {.importc.} -proc setMode*(mode: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add mode.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_setMode_void_App_string_wasm(argsJsonString.cstring) - - -proc editor_mode_string_App_wasm(arg: cstring): cstring {.importc.} -proc mode*(): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_mode_string_App_wasm(argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_getContextWithMode_string_App_string_wasm(arg: cstring): cstring {. - importc.} -proc getContextWithMode*(context: string): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add context.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_getContextWithMode_string_App_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_scriptRunAction_JsonNode_string_string_wasm(arg: cstring): cstring {. - importc.} -proc scriptRunAction*(action: string; arg: string): JsonNode {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add action.toJson() - argsJson.add arg.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_scriptRunAction_JsonNode_string_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_scriptLog_void_string_wasm(arg: cstring): cstring {.importc.} -proc scriptLog*(message: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add message.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_scriptLog_void_string_wasm(argsJsonString.cstring) - - -proc editor_changeAnimationSpeed_void_App_float_wasm(arg: cstring): cstring {. - importc.} -proc changeAnimationSpeed*(factor: float) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add factor.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_changeAnimationSpeed_void_App_float_wasm( - argsJsonString.cstring) - - -proc editor_registerPluginSourceCode_void_App_string_string_wasm(arg: cstring): cstring {. - importc.} -proc registerPluginSourceCode*(path: string; content: string) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - argsJson.add content.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_registerPluginSourceCode_void_App_string_string_wasm( - argsJsonString.cstring) - - -proc editor_addCommandScript_void_App_string_string_string_string_string_string_tuple_filename_string_line_int_column_int_wasm( - arg: cstring): cstring {.importc.} -proc addCommandScript*(context: string; subContext: string; keys: string; - action: string; arg: string = ""; - description: string = ""; source: tuple[filename: string, - line: int, column: int] = ("", 0, 0)) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add context.toJson() - argsJson.add subContext.toJson() - argsJson.add keys.toJson() - argsJson.add action.toJson() - argsJson.add arg.toJson() - argsJson.add description.toJson() - argsJson.add source.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_addCommandScript_void_App_string_string_string_string_string_string_tuple_filename_string_line_int_column_int_wasm( - argsJsonString.cstring) - - -proc editor_getActivePopup_EditorId_wasm(arg: cstring): cstring {.importc.} -proc getActivePopup*(): EditorId {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_getActivePopup_EditorId_wasm(argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_getActiveEditor_EditorId_wasm(arg: cstring): cstring {.importc.} -proc getActiveEditor*(): EditorId {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_getActiveEditor_EditorId_wasm(argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_logRootNode_void_App_wasm(arg: cstring): cstring {.importc.} -proc logRootNode*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_logRootNode_void_App_wasm(argsJsonString.cstring) - - -proc editor_scriptIsSelectorPopup_bool_EditorId_wasm(arg: cstring): cstring {. - importc.} -proc scriptIsSelectorPopup*(editorId: EditorId): bool {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add editorId.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_scriptIsSelectorPopup_bool_EditorId_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_scriptIsTextEditor_bool_EditorId_wasm(arg: cstring): cstring {. - importc.} -proc scriptIsTextEditor*(editorId: EditorId): bool {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add editorId.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_scriptIsTextEditor_bool_EditorId_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_scriptIsModelEditor_bool_EditorId_wasm(arg: cstring): cstring {. - importc.} -proc scriptIsModelEditor*(editorId: EditorId): bool {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add editorId.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_scriptIsModelEditor_bool_EditorId_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_scriptRunActionFor_void_EditorId_string_string_wasm(arg: cstring): cstring {. - importc.} -proc scriptRunActionFor*(editorId: EditorId; action: string; arg: string) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add editorId.toJson() - argsJson.add action.toJson() - argsJson.add arg.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_scriptRunActionFor_void_EditorId_string_string_wasm( - argsJsonString.cstring) - - -proc editor_scriptSetCallback_void_string_int_wasm(arg: cstring): cstring {. - importc.} -proc scriptSetCallback*(path: string; id: int) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - argsJson.add id.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_scriptSetCallback_void_string_int_wasm( - argsJsonString.cstring) - - -proc editor_replayKeys_void_App_string_wasm(arg: cstring): cstring {.importc.} -proc replayKeys*(register: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add register.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_replayKeys_void_App_string_wasm( - argsJsonString.cstring) - - -proc editor_inputKeys_void_App_string_wasm(arg: cstring): cstring {.importc.} -proc inputKeys*(input: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add input.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_inputKeys_void_App_string_wasm( - argsJsonString.cstring) - - -proc editor_collectGarbage_void_App_wasm(arg: cstring): cstring {.importc.} -proc collectGarbage*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_collectGarbage_void_App_wasm(argsJsonString.cstring) - - -proc editor_echoArgs_void_App_JsonNode_wasm(arg: cstring): cstring {.importc.} -proc echoArgs*(args: JsonNode) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add args.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_echoArgs_void_App_JsonNode_wasm( - argsJsonString.cstring) - - -proc editor_all_void_App_JsonNode_wasm(arg: cstring): cstring {.importc.} -proc all*(args: JsonNode) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add args.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_all_void_App_JsonNode_wasm(argsJsonString.cstring) - - -proc editor_printStatistics_void_App_wasm(arg: cstring): cstring {.importc.} -proc printStatistics*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editor_printStatistics_void_App_wasm(argsJsonString.cstring) - diff --git a/scripting/editor_model_api_wasm.nim b/scripting/editor_model_api_wasm.nim deleted file mode 100644 index e5ccd9e86..000000000 --- a/scripting/editor_model_api_wasm.nim +++ /dev/null @@ -1,709 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc editor_model_scrollPixels_void_ModelDocumentEditor_float32_wasm( - arg: cstring): cstring {.importc.} -proc scrollPixels*(self: ModelDocumentEditor; amount: float32) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add amount.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_scrollPixels_void_ModelDocumentEditor_float32_wasm( - argsJsonString.cstring) - - -proc editor_model_scrollLines_void_ModelDocumentEditor_float32_wasm(arg: cstring): cstring {. - importc.} -proc scrollLines*(self: ModelDocumentEditor; lines: float32) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add lines.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_scrollLines_void_ModelDocumentEditor_float32_wasm( - argsJsonString.cstring) - - -proc editor_model_setMode_void_ModelDocumentEditor_string_wasm(arg: cstring): cstring {. - importc.} -proc setMode*(self: ModelDocumentEditor; mode: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add mode.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_setMode_void_ModelDocumentEditor_string_wasm( - argsJsonString.cstring) - - -proc editor_model_mode_string_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc mode*(self: ModelDocumentEditor): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_mode_string_ModelDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_model_getContextWithMode_string_ModelDocumentEditor_string_wasm( - arg: cstring): cstring {.importc.} -proc getContextWithMode*(self: ModelDocumentEditor; context: string): string {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add context.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_getContextWithMode_string_ModelDocumentEditor_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_model_isThickCursor_bool_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc isThickCursor*(self: ModelDocumentEditor): bool {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_isThickCursor_bool_ModelDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_model_gotoDefinition_void_ModelDocumentEditor_bool_wasm(arg: cstring): cstring {. - importc.} -proc gotoDefinition*(self: ModelDocumentEditor; select: bool = false) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_gotoDefinition_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_gotoPrevReference_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc gotoPrevReference*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_gotoPrevReference_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_gotoNextReference_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc gotoNextReference*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_gotoNextReference_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_gotoPrevInvalidNode_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc gotoPrevInvalidNode*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_gotoPrevInvalidNode_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_gotoNextInvalidNode_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc gotoNextInvalidNode*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_gotoNextInvalidNode_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_gotoPrevNodeOfClass_void_ModelDocumentEditor_string_bool_wasm( - arg: cstring): cstring {.importc.} -proc gotoPrevNodeOfClass*(self: ModelDocumentEditor; className: string; - select: bool = false) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add className.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_gotoPrevNodeOfClass_void_ModelDocumentEditor_string_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_gotoNextNodeOfClass_void_ModelDocumentEditor_string_bool_wasm( - arg: cstring): cstring {.importc.} -proc gotoNextNodeOfClass*(self: ModelDocumentEditor; className: string; - select: bool = false) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add className.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_gotoNextNodeOfClass_void_ModelDocumentEditor_string_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_toggleBoolCell_void_ModelDocumentEditor_bool_wasm(arg: cstring): cstring {. - importc.} -proc toggleBoolCell*(self: ModelDocumentEditor; select: bool = false) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_toggleBoolCell_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_invertSelection_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc invertSelection*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_invertSelection_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_selectPrev_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc selectPrev*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_selectPrev_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_selectNext_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc selectNext*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_selectNext_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_moveCursorLeft_void_ModelDocumentEditor_bool_wasm(arg: cstring): cstring {. - importc.} -proc moveCursorLeft*(self: ModelDocumentEditor; select: bool = false) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_moveCursorLeft_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_moveCursorRight_void_ModelDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorRight*(self: ModelDocumentEditor; select: bool = false) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_moveCursorRight_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_moveCursorLeftLine_void_ModelDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorLeftLine*(self: ModelDocumentEditor; select: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_moveCursorLeftLine_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_moveCursorRightLine_void_ModelDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorRightLine*(self: ModelDocumentEditor; select: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_moveCursorRightLine_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_moveCursorLineStart_void_ModelDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorLineStart*(self: ModelDocumentEditor; select: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_moveCursorLineStart_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_moveCursorLineEnd_void_ModelDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorLineEnd*(self: ModelDocumentEditor; select: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_moveCursorLineEnd_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_moveCursorLineStartInline_void_ModelDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorLineStartInline*(self: ModelDocumentEditor; select: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_moveCursorLineStartInline_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_moveCursorLineEndInline_void_ModelDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorLineEndInline*(self: ModelDocumentEditor; select: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_moveCursorLineEndInline_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_moveCursorUp_void_ModelDocumentEditor_bool_wasm(arg: cstring): cstring {. - importc.} -proc moveCursorUp*(self: ModelDocumentEditor; select: bool = false) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_moveCursorUp_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_moveCursorDown_void_ModelDocumentEditor_bool_wasm(arg: cstring): cstring {. - importc.} -proc moveCursorDown*(self: ModelDocumentEditor; select: bool = false) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_moveCursorDown_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_moveCursorLeftCell_void_ModelDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorLeftCell*(self: ModelDocumentEditor; select: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_moveCursorLeftCell_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_moveCursorRightCell_void_ModelDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorRightCell*(self: ModelDocumentEditor; select: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_moveCursorRightCell_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_selectNode_void_ModelDocumentEditor_bool_wasm(arg: cstring): cstring {. - importc.} -proc selectNode*(self: ModelDocumentEditor; select: bool = false) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_selectNode_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_selectPrevNeighbor_void_ModelDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc selectPrevNeighbor*(self: ModelDocumentEditor; select: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_selectPrevNeighbor_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_selectNextNeighbor_void_ModelDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc selectNextNeighbor*(self: ModelDocumentEditor; select: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_selectNextNeighbor_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_selectPrevPlaceholder_void_ModelDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc selectPrevPlaceholder*(self: ModelDocumentEditor; select: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_selectPrevPlaceholder_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_selectNextPlaceholder_void_ModelDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc selectNextPlaceholder*(self: ModelDocumentEditor; select: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_selectNextPlaceholder_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_model_deleteLeft_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc deleteLeft*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_deleteLeft_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_deleteRight_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc deleteRight*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_deleteRight_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_replaceLeft_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc replaceLeft*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_replaceLeft_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_replaceRight_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc replaceRight*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_replaceRight_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_createNewNode_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc createNewNode*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_createNewNode_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_insertTextAtCursor_bool_ModelDocumentEditor_string_wasm( - arg: cstring): cstring {.importc.} -proc insertTextAtCursor*(self: ModelDocumentEditor; input: string): bool {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add input.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_insertTextAtCursor_bool_ModelDocumentEditor_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_model_undo_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc undo*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_undo_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_redo_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc redo*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_redo_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_toggleUseDefaultCellBuilder_void_ModelDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc toggleUseDefaultCellBuilder*(self: ModelDocumentEditor) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_toggleUseDefaultCellBuilder_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_showCompletions_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc showCompletions*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_showCompletions_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_showCompletionWindow_void_ModelDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc showCompletionWindow*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_showCompletionWindow_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_hideCompletions_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc hideCompletions*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_hideCompletions_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_selectPrevCompletion_void_ModelDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc selectPrevCompletion*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_selectPrevCompletion_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_selectNextCompletion_void_ModelDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc selectNextCompletion*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_selectNextCompletion_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_applySelectedCompletion_void_ModelDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc applySelectedCompletion*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_applySelectedCompletion_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_printSelectionInfo_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc printSelectionInfo*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_printSelectionInfo_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_clearModelCache_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc clearModelCache*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_clearModelCache_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_runSelectedFunction_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc runSelectedFunction*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_runSelectedFunction_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_copyNode_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc copyNode*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_copyNode_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_pasteNode_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc pasteNode*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_pasteNode_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_addLanguage_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc addLanguage*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_addLanguage_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_createNewModel_void_ModelDocumentEditor_string_wasm( - arg: cstring): cstring {.importc.} -proc createNewModel*(self: ModelDocumentEditor; name: string) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add name.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_createNewModel_void_ModelDocumentEditor_string_wasm( - argsJsonString.cstring) - - -proc editor_model_addModelToProject_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc addModelToProject*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_addModelToProject_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_importModel_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc importModel*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_importModel_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_compileLanguage_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc compileLanguage*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_compileLanguage_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_addRootNode_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc addRootNode*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_addRootNode_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_saveProject_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc saveProject*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_saveProject_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_loadLanguageModel_void_ModelDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc loadLanguageModel*(self: ModelDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_loadLanguageModel_void_ModelDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_model_findDeclaration_void_ModelDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc findDeclaration*(self: ModelDocumentEditor; global: bool) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add global.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_model_findDeclaration_void_ModelDocumentEditor_bool_wasm( - argsJsonString.cstring) - diff --git a/scripting/editor_text_api_wasm.nim b/scripting/editor_text_api_wasm.nim deleted file mode 100644 index a6e2a4336..000000000 --- a/scripting/editor_text_api_wasm.nim +++ /dev/null @@ -1,2768 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc editor_text_enableAutoReload_void_TextDocumentEditor_bool_wasm(arg: cstring): cstring {. - importc.} -proc enableAutoReload*(self: TextDocumentEditor; enabled: bool) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add enabled.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_enableAutoReload_void_TextDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_setLanguage_void_TextDocumentEditor_string_wasm(arg: cstring): cstring {. - importc.} -proc setLanguage*(self: TextDocumentEditor; language: string) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add language.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setLanguage_void_TextDocumentEditor_string_wasm( - argsJsonString.cstring) - - -proc editor_text_getFileName_string_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc getFileName*(self: TextDocumentEditor): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getFileName_string_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_lineCount_int_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc lineCount*(self: TextDocumentEditor): int {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_lineCount_int_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_lineLength_int_TextDocumentEditor_int_wasm(arg: cstring): cstring {. - importc.} -proc lineLength*(self: TextDocumentEditor; line: int): int {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add line.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_lineLength_int_TextDocumentEditor_int_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_numDisplayLines_int_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc numDisplayLines*(self: TextDocumentEditor): int {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_numDisplayLines_int_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_numWrapLines_int_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc numWrapLines*(self: TextDocumentEditor): int {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_numWrapLines_int_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_screenLineCount_int_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc screenLineCount*(self: TextDocumentEditor): int {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_screenLineCount_int_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_evaluateExpressions_void_TextDocumentEditor_Selections_bool_string_string_bool_wasm( - arg: cstring): cstring {.importc.} -proc evaluateExpressions*(self: TextDocumentEditor; selections: Selections; - inclusiveEnd: bool = false; prefix: string = ""; - suffix: string = ""; addSelectionIndex: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selections.toJson() - argsJson.add inclusiveEnd.toJson() - argsJson.add prefix.toJson() - argsJson.add suffix.toJson() - argsJson.add addSelectionIndex.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_evaluateExpressions_void_TextDocumentEditor_Selections_bool_string_string_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_doMoveCursorLine_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc doMoveCursorLine*(self: TextDocumentEditor; cursor: Cursor; offset: int; - wrap: bool = false; includeAfter: bool = false): Cursor {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add offset.toJson() - argsJson.add wrap.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_doMoveCursorLine_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getDefaultScrollBehaviour_ScrollBehaviour_TextDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc getDefaultScrollBehaviour*(self: TextDocumentEditor): ScrollBehaviour {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getDefaultScrollBehaviour_ScrollBehaviour_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_setDefaultScrollBehaviour_void_TextDocumentEditor_ScrollBehaviour_wasm( - arg: cstring): cstring {.importc.} -proc setDefaultScrollBehaviour*(self: TextDocumentEditor; - scrollBehaviour: ScrollBehaviour) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add scrollBehaviour.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setDefaultScrollBehaviour_void_TextDocumentEditor_ScrollBehaviour_wasm( - argsJsonString.cstring) - - -proc editor_text_doMoveCursorVisualLine_Cursor_TextDocumentEditor_Cursor_int_bool_bool_Option_int_wasm( - arg: cstring): cstring {.importc.} -proc doMoveCursorVisualLine*(self: TextDocumentEditor; cursor: Cursor; - offset: int; wrap: bool = false; - includeAfter: bool = false; - targetColumn: Option[int] = int.none): Cursor {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add offset.toJson() - argsJson.add wrap.toJson() - argsJson.add includeAfter.toJson() - argsJson.add targetColumn.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_doMoveCursorVisualLine_Cursor_TextDocumentEditor_Cursor_int_bool_bool_Option_int_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_doMoveCursorHome_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc doMoveCursorHome*(self: TextDocumentEditor; cursor: Cursor; offset: int; - wrap: bool; includeAfter: bool): Cursor {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add offset.toJson() - argsJson.add wrap.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_doMoveCursorHome_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_doMoveCursorEnd_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc doMoveCursorEnd*(self: TextDocumentEditor; cursor: Cursor; offset: int; - wrap: bool; includeAfter: bool): Cursor {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add offset.toJson() - argsJson.add wrap.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_doMoveCursorEnd_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_doMoveCursorPrevFindResult_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc doMoveCursorPrevFindResult*(self: TextDocumentEditor; cursor: Cursor; - offset: int; wrap: bool; includeAfter: bool): Cursor {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add offset.toJson() - argsJson.add wrap.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_doMoveCursorPrevFindResult_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_doMoveCursorNextFindResult_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc doMoveCursorNextFindResult*(self: TextDocumentEditor; cursor: Cursor; - offset: int; wrap: bool; includeAfter: bool): Cursor {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add offset.toJson() - argsJson.add wrap.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_doMoveCursorNextFindResult_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_doMoveCursorLineCenter_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc doMoveCursorLineCenter*(self: TextDocumentEditor; cursor: Cursor; - offset: int; wrap: bool; includeAfter: bool): Cursor {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add offset.toJson() - argsJson.add wrap.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_doMoveCursorLineCenter_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_doMoveCursorCenter_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc doMoveCursorCenter*(self: TextDocumentEditor; cursor: Cursor; offset: int; - wrap: bool; includeAfter: bool): Cursor {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add offset.toJson() - argsJson.add wrap.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_doMoveCursorCenter_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_doMoveCursorColumn_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc doMoveCursorColumn*(self: TextDocumentEditor; cursor: Cursor; offset: int; - wrap: bool = true; includeAfter: bool = true): Cursor {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add offset.toJson() - argsJson.add wrap.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_doMoveCursorColumn_Cursor_TextDocumentEditor_Cursor_int_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_includeSelectionEnd_Selection_TextDocumentEditor_Selection_bool_wasm( - arg: cstring): cstring {.importc.} -proc includeSelectionEnd*(self: TextDocumentEditor; res: Selection; - includeAfter: bool = true): Selection {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add res.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_includeSelectionEnd_Selection_TextDocumentEditor_Selection_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_findSurroundStart_Option_Cursor_TextDocumentEditor_Cursor_int_char_char_int_wasm( - arg: cstring): cstring {.importc.} -proc findSurroundStart*(editor: TextDocumentEditor; cursor: Cursor; count: int; - c0: char; c1: char; depth: int = 1): Option[Cursor] {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add editor.toJson() - argsJson.add cursor.toJson() - argsJson.add count.toJson() - argsJson.add c0.toJson() - argsJson.add c1.toJson() - argsJson.add depth.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_findSurroundStart_Option_Cursor_TextDocumentEditor_Cursor_int_char_char_int_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_findSurroundEnd_Option_Cursor_TextDocumentEditor_Cursor_int_char_char_int_wasm( - arg: cstring): cstring {.importc.} -proc findSurroundEnd*(editor: TextDocumentEditor; cursor: Cursor; count: int; - c0: char; c1: char; depth: int = 1): Option[Cursor] {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add editor.toJson() - argsJson.add cursor.toJson() - argsJson.add count.toJson() - argsJson.add c0.toJson() - argsJson.add c1.toJson() - argsJson.add depth.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_findSurroundEnd_Option_Cursor_TextDocumentEditor_Cursor_int_char_char_int_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_toggleFlag_void_TextDocumentEditor_string_wasm(arg: cstring): cstring {. - importc.} -proc toggleFlag*(self: TextDocumentEditor; key: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add key.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_toggleFlag_void_TextDocumentEditor_string_wasm( - argsJsonString.cstring) - - -proc editor_text_setConfig_void_TextDocumentEditor_string_JsonNode_wasm( - arg: cstring): cstring {.importc.} -proc setConfig*(self: TextDocumentEditor; key: string; value: JsonNode) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add key.toJson() - argsJson.add value.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setConfig_void_TextDocumentEditor_string_JsonNode_wasm( - argsJsonString.cstring) - - -proc editor_text_getConfig_JsonNode_TextDocumentEditor_string_wasm(arg: cstring): cstring {. - importc.} -proc getConfig*(self: TextDocumentEditor; key: string): JsonNode {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add key.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getConfig_JsonNode_TextDocumentEditor_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_removeMode_void_TextDocumentEditor_string_wasm(arg: cstring): cstring {. - importc.} -proc removeMode*(self: TextDocumentEditor; mode: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add mode.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_removeMode_void_TextDocumentEditor_string_wasm( - argsJsonString.cstring) - - -proc editor_text_setMode_void_TextDocumentEditor_string_bool_wasm(arg: cstring): cstring {. - importc.} -proc setMode*(self: TextDocumentEditor; mode: string; exclusive: bool = true) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add mode.toJson() - argsJson.add exclusive.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setMode_void_TextDocumentEditor_string_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_setDefaultMode_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc setDefaultMode*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setDefaultMode_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_mode_string_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc mode*(self: TextDocumentEditor): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_mode_string_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_modes_seq_string_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc modes*(self: TextDocumentEditor): seq[string] {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_modes_seq_string_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getContextWithMode_string_TextDocumentEditor_string_wasm( - arg: cstring): cstring {.importc.} -proc getContextWithMode*(self: TextDocumentEditor; context: string): string {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add context.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getContextWithMode_string_TextDocumentEditor_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_updateTargetColumn_void_TextDocumentEditor_SelectionCursor_wasm( - arg: cstring): cstring {.importc.} -proc updateTargetColumn*(self: TextDocumentEditor; - cursor: SelectionCursor = Last) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_updateTargetColumn_void_TextDocumentEditor_SelectionCursor_wasm( - argsJsonString.cstring) - - -proc editor_text_invertSelection_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc invertSelection*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_invertSelection_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_getRevision_int_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc getRevision*(self: TextDocumentEditor): int {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getRevision_int_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getUsage_string_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc getUsage*(self: TextDocumentEditor): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getUsage_string_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getChar_char_TextDocumentEditor_Cursor_wasm(arg: cstring): cstring {. - importc.} -proc getChar*(self: TextDocumentEditor; cursor: Cursor): char {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getChar_char_TextDocumentEditor_Cursor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getText_string_TextDocumentEditor_Selection_bool_wasm( - arg: cstring): cstring {.importc.} -proc getText*(self: TextDocumentEditor; selection: Selection; - inclusiveEnd: bool = false): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selection.toJson() - argsJson.add inclusiveEnd.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getText_string_TextDocumentEditor_Selection_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getLine_string_TextDocumentEditor_int_wasm(arg: cstring): cstring {. - importc.} -proc getLine*(self: TextDocumentEditor; line: int): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add line.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getLine_string_TextDocumentEditor_int_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_insert_seq_Selection_TextDocumentEditor_seq_Selection_string_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc insert*(self: TextDocumentEditor; selections: seq[Selection]; text: string; - notify: bool = true; record: bool = true): seq[Selection] {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selections.toJson() - argsJson.add text.toJson() - argsJson.add notify.toJson() - argsJson.add record.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_insert_seq_Selection_TextDocumentEditor_seq_Selection_string_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_insertMulti_seq_Selection_TextDocumentEditor_seq_Selection_seq_string_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc insertMulti*(self: TextDocumentEditor; selections: seq[Selection]; - texts: seq[string]; notify: bool = true; record: bool = true): seq[ - Selection] {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selections.toJson() - argsJson.add texts.toJson() - argsJson.add notify.toJson() - argsJson.add record.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_insertMulti_seq_Selection_TextDocumentEditor_seq_Selection_seq_string_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_delete_seq_Selection_TextDocumentEditor_seq_Selection_bool_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc delete*(self: TextDocumentEditor; selections: seq[Selection]; - notify: bool = true; record: bool = true; - inclusiveEnd: bool = false): seq[Selection] {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selections.toJson() - argsJson.add notify.toJson() - argsJson.add record.toJson() - argsJson.add inclusiveEnd.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_delete_seq_Selection_TextDocumentEditor_seq_Selection_bool_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_edit_seq_Selection_TextDocumentEditor_seq_Selection_seq_string_bool_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc edit*(self: TextDocumentEditor; selections: seq[Selection]; - texts: seq[string]; notify: bool = true; record: bool = true; - inclusiveEnd: bool = false): seq[Selection] {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selections.toJson() - argsJson.add texts.toJson() - argsJson.add notify.toJson() - argsJson.add record.toJson() - argsJson.add inclusiveEnd.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_edit_seq_Selection_TextDocumentEditor_seq_Selection_seq_string_bool_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_deleteLines_void_TextDocumentEditor_Slice_int_Selections_wasm( - arg: cstring): cstring {.importc.} -proc deleteLines*(self: TextDocumentEditor; slice: Slice[int]; - oldSelections: Selections) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add slice.toJson() - argsJson.add oldSelections.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_deleteLines_void_TextDocumentEditor_Slice_int_Selections_wasm( - argsJsonString.cstring) - - -proc editor_text_selectPrev_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc selectPrev*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectPrev_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_selectNext_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc selectNext*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectNext_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_selectInside_void_TextDocumentEditor_Cursor_wasm(arg: cstring): cstring {. - importc.} -proc selectInside*(self: TextDocumentEditor; cursor: Cursor) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectInside_void_TextDocumentEditor_Cursor_wasm( - argsJsonString.cstring) - - -proc editor_text_selectInsideCurrent_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc selectInsideCurrent*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectInsideCurrent_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_selectLine_void_TextDocumentEditor_int_wasm(arg: cstring): cstring {. - importc.} -proc selectLine*(self: TextDocumentEditor; line: int) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add line.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectLine_void_TextDocumentEditor_int_wasm( - argsJsonString.cstring) - - -proc editor_text_selectLineCurrent_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc selectLineCurrent*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectLineCurrent_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_getParentNodeSelection_Selection_TextDocumentEditor_Selection_bool_wasm( - arg: cstring): cstring {.importc.} -proc getParentNodeSelection*(self: TextDocumentEditor; selection: Selection; - includeAfter: bool = true): Selection {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selection.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getParentNodeSelection_Selection_TextDocumentEditor_Selection_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getNextNamedSiblingNodeSelection_Option_Selection_TextDocumentEditor_Selection_bool_wasm( - arg: cstring): cstring {.importc.} -proc getNextNamedSiblingNodeSelection*(self: TextDocumentEditor; - selection: Selection; - includeAfter: bool = true): Option[ - Selection] {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selection.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getNextNamedSiblingNodeSelection_Option_Selection_TextDocumentEditor_Selection_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getNextSiblingNodeSelection_Option_Selection_TextDocumentEditor_Selection_bool_wasm( - arg: cstring): cstring {.importc.} -proc getNextSiblingNodeSelection*(self: TextDocumentEditor; - selection: Selection; - includeAfter: bool = true): Option[Selection] {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selection.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getNextSiblingNodeSelection_Option_Selection_TextDocumentEditor_Selection_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getParentNodeSelections_Selections_TextDocumentEditor_Selections_bool_wasm( - arg: cstring): cstring {.importc.} -proc getParentNodeSelections*(self: TextDocumentEditor; selections: Selections; - includeAfter: bool = true): Selections {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selections.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getParentNodeSelections_Selections_TextDocumentEditor_Selections_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_selectParentTs_void_TextDocumentEditor_Selection_bool_wasm( - arg: cstring): cstring {.importc.} -proc selectParentTs*(self: TextDocumentEditor; selection: Selection; - includeAfter: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selection.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectParentTs_void_TextDocumentEditor_Selection_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_printTreesitterMemoryUsage_void_TextDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc printTreesitterMemoryUsage*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_printTreesitterMemoryUsage_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_printTreesitterTree_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc printTreesitterTree*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_printTreesitterTree_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_printTreesitterTreeUnderCursor_void_TextDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc printTreesitterTreeUnderCursor*(self: TextDocumentEditor) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_printTreesitterTreeUnderCursor_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_selectParentCurrentTs_void_TextDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc selectParentCurrentTs*(self: TextDocumentEditor; includeAfter: bool = true) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectParentCurrentTs_void_TextDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_getNextNodeWithSameType_Option_Selection_TextDocumentEditor_Selection_int_bool_bool_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc getNextNodeWithSameType*(self: TextDocumentEditor; selection: Selection; - offset: int = 0; includeAfter: bool = true; - wrap: bool = true; stepIn: bool = true; - stepOut: bool = true): Option[Selection] {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selection.toJson() - argsJson.add offset.toJson() - argsJson.add includeAfter.toJson() - argsJson.add wrap.toJson() - argsJson.add stepIn.toJson() - argsJson.add stepOut.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getNextNodeWithSameType_Option_Selection_TextDocumentEditor_Selection_int_bool_bool_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_shouldShowCompletionsAt_bool_TextDocumentEditor_Cursor_wasm( - arg: cstring): cstring {.importc.} -proc shouldShowCompletionsAt*(self: TextDocumentEditor; cursor: Cursor): bool {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_shouldShowCompletionsAt_bool_TextDocumentEditor_Cursor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_autoShowCompletions_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc autoShowCompletions*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_autoShowCompletions_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_insertText_void_TextDocumentEditor_string_bool_wasm( - arg: cstring): cstring {.importc.} -proc insertText*(self: TextDocumentEditor; text: string; autoIndent: bool = true) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add text.toJson() - argsJson.add autoIndent.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_insertText_void_TextDocumentEditor_string_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_insertRaw_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc insertRaw*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_insertRaw_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_indent_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc indent*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_indent_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_unindent_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc unindent*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_unindent_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_insertIndent_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc insertIndent*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_insertIndent_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_undo_void_TextDocumentEditor_string_wasm(arg: cstring): cstring {. - importc.} -proc undo*(self: TextDocumentEditor; checkpoint: string = "word") {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add checkpoint.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_undo_void_TextDocumentEditor_string_wasm( - argsJsonString.cstring) - - -proc editor_text_redo_void_TextDocumentEditor_string_wasm(arg: cstring): cstring {. - importc.} -proc redo*(self: TextDocumentEditor; checkpoint: string = "word") {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add checkpoint.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_redo_void_TextDocumentEditor_string_wasm( - argsJsonString.cstring) - - -proc editor_text_addNextCheckpoint_void_TextDocumentEditor_string_wasm( - arg: cstring): cstring {.importc.} -proc addNextCheckpoint*(self: TextDocumentEditor; checkpoint: string) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add checkpoint.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_addNextCheckpoint_void_TextDocumentEditor_string_wasm( - argsJsonString.cstring) - - -proc editor_text_copy_void_TextDocumentEditor_string_bool_wasm(arg: cstring): cstring {. - importc.} -proc copy*(self: TextDocumentEditor; register: string = ""; - inclusiveEnd: bool = false) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add register.toJson() - argsJson.add inclusiveEnd.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_copy_void_TextDocumentEditor_string_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_paste_void_TextDocumentEditor_string_bool_wasm(arg: cstring): cstring {. - importc.} -proc paste*(self: TextDocumentEditor; registerName: string = ""; - inclusiveEnd: bool = false) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add registerName.toJson() - argsJson.add inclusiveEnd.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_paste_void_TextDocumentEditor_string_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_scrollText_void_TextDocumentEditor_float32_wasm(arg: cstring): cstring {. - importc.} -proc scrollText*(self: TextDocumentEditor; amount: float32) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add amount.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_scrollText_void_TextDocumentEditor_float32_wasm( - argsJsonString.cstring) - - -proc editor_text_scrollTextHorizontal_void_TextDocumentEditor_float32_wasm( - arg: cstring): cstring {.importc.} -proc scrollTextHorizontal*(self: TextDocumentEditor; amount: float32) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add amount.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_scrollTextHorizontal_void_TextDocumentEditor_float32_wasm( - argsJsonString.cstring) - - -proc editor_text_scrollLines_void_TextDocumentEditor_int_wasm(arg: cstring): cstring {. - importc.} -proc scrollLines*(self: TextDocumentEditor; amount: int) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add amount.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_scrollLines_void_TextDocumentEditor_int_wasm( - argsJsonString.cstring) - - -proc editor_text_duplicateLastSelection_void_TextDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc duplicateLastSelection*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_duplicateLastSelection_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_addCursorBelow_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc addCursorBelow*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_addCursorBelow_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_addCursorAbove_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc addCursorAbove*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_addCursorAbove_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_getPrevFindResult_Selection_TextDocumentEditor_Cursor_int_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc getPrevFindResult*(self: TextDocumentEditor; cursor: Cursor; - offset: int = 0; includeAfter: bool = true; - wrap: bool = true): Selection {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add offset.toJson() - argsJson.add includeAfter.toJson() - argsJson.add wrap.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getPrevFindResult_Selection_TextDocumentEditor_Cursor_int_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getNextFindResult_Selection_TextDocumentEditor_Cursor_int_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc getNextFindResult*(self: TextDocumentEditor; cursor: Cursor; - offset: int = 0; includeAfter: bool = true; - wrap: bool = true): Selection {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add offset.toJson() - argsJson.add includeAfter.toJson() - argsJson.add wrap.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getNextFindResult_Selection_TextDocumentEditor_Cursor_int_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_createAnchors_seq_Anchor_Anchor_TextDocumentEditor_Selections_wasm( - arg: cstring): cstring {.importc.} -proc createAnchors*(self: TextDocumentEditor; selections: Selections): seq[ - (Anchor, Anchor)] {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selections.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_createAnchors_seq_Anchor_Anchor_TextDocumentEditor_Selections_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_resolveAnchors_Selections_TextDocumentEditor_seq_Anchor_Anchor_wasm( - arg: cstring): cstring {.importc.} -proc resolveAnchors*(self: TextDocumentEditor; anchors: seq[(Anchor, Anchor)]): Selections {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add anchors.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_resolveAnchors_Selections_TextDocumentEditor_seq_Anchor_Anchor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getPrevDiagnostic_Selection_TextDocumentEditor_Cursor_int_int_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc getPrevDiagnostic*(self: TextDocumentEditor; cursor: Cursor; - severity: int = 0; offset: int = 0; - includeAfter: bool = true; wrap: bool = true): Selection {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add severity.toJson() - argsJson.add offset.toJson() - argsJson.add includeAfter.toJson() - argsJson.add wrap.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getPrevDiagnostic_Selection_TextDocumentEditor_Cursor_int_int_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getNextDiagnostic_Selection_TextDocumentEditor_Cursor_int_int_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc getNextDiagnostic*(self: TextDocumentEditor; cursor: Cursor; - severity: int = 0; offset: int = 0; - includeAfter: bool = true; wrap: bool = true): Selection {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add severity.toJson() - argsJson.add offset.toJson() - argsJson.add includeAfter.toJson() - argsJson.add wrap.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getNextDiagnostic_Selection_TextDocumentEditor_Cursor_int_int_bool_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_closeDiff_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc closeDiff*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_closeDiff_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_getPrevChange_Selection_TextDocumentEditor_Cursor_wasm( - arg: cstring): cstring {.importc.} -proc getPrevChange*(self: TextDocumentEditor; cursor: Cursor): Selection {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getPrevChange_Selection_TextDocumentEditor_Cursor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getNextChange_Selection_TextDocumentEditor_Cursor_wasm( - arg: cstring): cstring {.importc.} -proc getNextChange*(self: TextDocumentEditor; cursor: Cursor): Selection {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getNextChange_Selection_TextDocumentEditor_Cursor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_clearOverlays_void_TextDocumentEditor_int_wasm(arg: cstring): cstring {. - importc.} -proc clearOverlays*(self: TextDocumentEditor; id: int = -1) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add id.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_clearOverlays_void_TextDocumentEditor_int_wasm( - argsJsonString.cstring) - - -proc editor_text_addOverlay_void_TextDocumentEditor_Selection_string_int_string_Bias_wasm( - arg: cstring): cstring {.importc.} -proc addOverlay*(self: TextDocumentEditor; selection: Selection; text: string; - id: int; scope: string; bias: Bias) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selection.toJson() - argsJson.add text.toJson() - argsJson.add id.toJson() - argsJson.add scope.toJson() - argsJson.add bias.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_addOverlay_void_TextDocumentEditor_Selection_string_int_string_Bias_wasm( - argsJsonString.cstring) - - -proc editor_text_updateDiff_void_TextDocumentEditor_bool_wasm(arg: cstring): cstring {. - importc.} -proc updateDiff*(self: TextDocumentEditor; gotoFirstDiff: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add gotoFirstDiff.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_updateDiff_void_TextDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_revertSelected_void_TextDocumentEditor_bool_wasm(arg: cstring): cstring {. - importc.} -proc revertSelected*(self: TextDocumentEditor; inclusiveEnd: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add inclusiveEnd.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_revertSelected_void_TextDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_stageSelected_void_TextDocumentEditor_bool_wasm(arg: cstring): cstring {. - importc.} -proc stageSelected*(self: TextDocumentEditor; inclusiveEnd: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add inclusiveEnd.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_stageSelected_void_TextDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_stageFile_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc stageFile*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_stageFile_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_format_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc format*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_format_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_checkoutFile_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc checkoutFile*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_checkoutFile_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_addNextFindResultToSelection_void_TextDocumentEditor_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc addNextFindResultToSelection*(self: TextDocumentEditor; - includeAfter: bool = true; wrap: bool = true) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add includeAfter.toJson() - argsJson.add wrap.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_addNextFindResultToSelection_void_TextDocumentEditor_bool_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_addPrevFindResultToSelection_void_TextDocumentEditor_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc addPrevFindResultToSelection*(self: TextDocumentEditor; - includeAfter: bool = true; wrap: bool = true) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add includeAfter.toJson() - argsJson.add wrap.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_addPrevFindResultToSelection_void_TextDocumentEditor_bool_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_setAllFindResultToSelection_void_TextDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc setAllFindResultToSelection*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setAllFindResultToSelection_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_clearSelections_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc clearSelections*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_clearSelections_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_moveCursorColumn_void_TextDocumentEditor_int_SelectionCursor_bool_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorColumn*(self: TextDocumentEditor; distance: int; - cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true; wrap: bool = true; - includeAfter: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add distance.toJson() - argsJson.add cursor.toJson() - argsJson.add all.toJson() - argsJson.add wrap.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveCursorColumn_void_TextDocumentEditor_int_SelectionCursor_bool_bool_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_moveCursorLine_void_TextDocumentEditor_int_SelectionCursor_bool_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorLine*(self: TextDocumentEditor; distance: int; - cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true; wrap: bool = true; - includeAfter: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add distance.toJson() - argsJson.add cursor.toJson() - argsJson.add all.toJson() - argsJson.add wrap.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveCursorLine_void_TextDocumentEditor_int_SelectionCursor_bool_bool_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_moveCursorVisualLine_void_TextDocumentEditor_int_SelectionCursor_bool_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorVisualLine*(self: TextDocumentEditor; distance: int; - cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true; wrap: bool = true; - includeAfter: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add distance.toJson() - argsJson.add cursor.toJson() - argsJson.add all.toJson() - argsJson.add wrap.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveCursorVisualLine_void_TextDocumentEditor_int_SelectionCursor_bool_bool_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_moveCursorPage_void_TextDocumentEditor_float_SelectionCursor_bool_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorPage*(self: TextDocumentEditor; distance: float; - cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true; wrap: bool = true; - includeAfter: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add distance.toJson() - argsJson.add cursor.toJson() - argsJson.add all.toJson() - argsJson.add wrap.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveCursorPage_void_TextDocumentEditor_float_SelectionCursor_bool_bool_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_moveCursorVisualPage_void_TextDocumentEditor_float_SelectionCursor_bool_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorVisualPage*(self: TextDocumentEditor; distance: float; - cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true; wrap: bool = true; - includeAfter: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add distance.toJson() - argsJson.add cursor.toJson() - argsJson.add all.toJson() - argsJson.add wrap.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveCursorVisualPage_void_TextDocumentEditor_float_SelectionCursor_bool_bool_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_moveCursorHome_void_TextDocumentEditor_SelectionCursor_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorHome*(self: TextDocumentEditor; - cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add all.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveCursorHome_void_TextDocumentEditor_SelectionCursor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_moveCursorEnd_void_TextDocumentEditor_SelectionCursor_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorEnd*(self: TextDocumentEditor; - cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true; includeAfter: bool = true) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add all.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveCursorEnd_void_TextDocumentEditor_SelectionCursor_bool_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_moveCursorTo_void_TextDocumentEditor_string_SelectionCursor_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorTo*(self: TextDocumentEditor; str: string; - cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add str.toJson() - argsJson.add cursor.toJson() - argsJson.add all.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveCursorTo_void_TextDocumentEditor_string_SelectionCursor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_moveCursorBefore_void_TextDocumentEditor_string_SelectionCursor_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorBefore*(self: TextDocumentEditor; str: string; - cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add str.toJson() - argsJson.add cursor.toJson() - argsJson.add all.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveCursorBefore_void_TextDocumentEditor_string_SelectionCursor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_moveCursorNextFindResult_void_TextDocumentEditor_SelectionCursor_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorNextFindResult*(self: TextDocumentEditor; cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true; wrap: bool = true) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add all.toJson() - argsJson.add wrap.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveCursorNextFindResult_void_TextDocumentEditor_SelectionCursor_bool_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_moveCursorPrevFindResult_void_TextDocumentEditor_SelectionCursor_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorPrevFindResult*(self: TextDocumentEditor; cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true; wrap: bool = true) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add all.toJson() - argsJson.add wrap.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveCursorPrevFindResult_void_TextDocumentEditor_SelectionCursor_bool_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_moveCursorLineCenter_void_TextDocumentEditor_SelectionCursor_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorLineCenter*(self: TextDocumentEditor; - cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add all.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveCursorLineCenter_void_TextDocumentEditor_SelectionCursor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_moveCursorCenter_void_TextDocumentEditor_SelectionCursor_bool_wasm( - arg: cstring): cstring {.importc.} -proc moveCursorCenter*(self: TextDocumentEditor; - cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add all.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveCursorCenter_void_TextDocumentEditor_SelectionCursor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_scrollToCursor_void_TextDocumentEditor_SelectionCursor_Option_float_Option_ScrollBehaviour_float_wasm( - arg: cstring): cstring {.importc.} -proc scrollToCursor*(self: TextDocumentEditor; - cursor: SelectionCursor = SelectionCursor.Config; - margin: Option[float] = float.none; scrollBehaviour: Option[ - ScrollBehaviour] = ScrollBehaviour.none; relativePosition: float = 0.5) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add margin.toJson() - argsJson.add scrollBehaviour.toJson() - argsJson.add relativePosition.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_scrollToCursor_void_TextDocumentEditor_SelectionCursor_Option_float_Option_ScrollBehaviour_float_wasm( - argsJsonString.cstring) - - -proc editor_text_setNextScrollBehaviour_void_TextDocumentEditor_ScrollBehaviour_wasm( - arg: cstring): cstring {.importc.} -proc setNextScrollBehaviour*(self: TextDocumentEditor; - scrollBehaviour: ScrollBehaviour) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add scrollBehaviour.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setNextScrollBehaviour_void_TextDocumentEditor_ScrollBehaviour_wasm( - argsJsonString.cstring) - - -proc editor_text_setDefaultSnapBehaviour_void_TextDocumentEditor_ScrollSnapBehaviour_wasm( - arg: cstring): cstring {.importc.} -proc setDefaultSnapBehaviour*(self: TextDocumentEditor; - snapBehaviour: ScrollSnapBehaviour) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add snapBehaviour.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setDefaultSnapBehaviour_void_TextDocumentEditor_ScrollSnapBehaviour_wasm( - argsJsonString.cstring) - - -proc editor_text_setNextSnapBehaviour_void_TextDocumentEditor_ScrollSnapBehaviour_wasm( - arg: cstring): cstring {.importc.} -proc setNextSnapBehaviour*(self: TextDocumentEditor; - snapBehaviour: ScrollSnapBehaviour) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add snapBehaviour.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setNextSnapBehaviour_void_TextDocumentEditor_ScrollSnapBehaviour_wasm( - argsJsonString.cstring) - - -proc editor_text_setCursorScrollOffset_void_TextDocumentEditor_float_SelectionCursor_wasm( - arg: cstring): cstring {.importc.} -proc setCursorScrollOffset*(self: TextDocumentEditor; offset: float; - cursor: SelectionCursor = SelectionCursor.Config) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add offset.toJson() - argsJson.add cursor.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setCursorScrollOffset_void_TextDocumentEditor_float_SelectionCursor_wasm( - argsJsonString.cstring) - - -proc editor_text_getContentBounds_Vec2_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc getContentBounds*(self: TextDocumentEditor): Vec2 {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getContentBounds_Vec2_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_centerCursor_void_TextDocumentEditor_SelectionCursor_wasm( - arg: cstring): cstring {.importc.} -proc centerCursor*(self: TextDocumentEditor; - cursor: SelectionCursor = SelectionCursor.Config) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_centerCursor_void_TextDocumentEditor_SelectionCursor_wasm( - argsJsonString.cstring) - - -proc editor_text_reloadTreesitter_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc reloadTreesitter*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_reloadTreesitter_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_deleteLeft_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc deleteLeft*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_deleteLeft_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_deleteRight_void_TextDocumentEditor_bool_wasm(arg: cstring): cstring {. - importc.} -proc deleteRight*(self: TextDocumentEditor; includeAfter: bool = true) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add includeAfter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_deleteRight_void_TextDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_getCommandCount_int_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc getCommandCount*(self: TextDocumentEditor): int {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getCommandCount_int_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_setCommandCount_void_TextDocumentEditor_int_wasm(arg: cstring): cstring {. - importc.} -proc setCommandCount*(self: TextDocumentEditor; count: int) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add count.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setCommandCount_void_TextDocumentEditor_int_wasm( - argsJsonString.cstring) - - -proc editor_text_setCommandCountRestore_void_TextDocumentEditor_int_wasm( - arg: cstring): cstring {.importc.} -proc setCommandCountRestore*(self: TextDocumentEditor; count: int) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add count.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setCommandCountRestore_void_TextDocumentEditor_int_wasm( - argsJsonString.cstring) - - -proc editor_text_updateCommandCount_void_TextDocumentEditor_int_wasm( - arg: cstring): cstring {.importc.} -proc updateCommandCount*(self: TextDocumentEditor; digit: int) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add digit.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_updateCommandCount_void_TextDocumentEditor_int_wasm( - argsJsonString.cstring) - - -proc editor_text_runAction_Option_JsonNode_TextDocumentEditor_string_JsonNode_wasm( - arg: cstring): cstring {.importc.} -proc runAction*(self: TextDocumentEditor; action: string; args: JsonNode): Option[ - JsonNode] {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add action.toJson() - argsJson.add args.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_runAction_Option_JsonNode_TextDocumentEditor_string_JsonNode_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_findWordBoundary_Selection_TextDocumentEditor_Cursor_wasm( - arg: cstring): cstring {.importc.} -proc findWordBoundary*(self: TextDocumentEditor; cursor: Cursor): Selection {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_findWordBoundary_Selection_TextDocumentEditor_Cursor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getSelectionInPair_Selection_TextDocumentEditor_Cursor_char_wasm( - arg: cstring): cstring {.importc.} -proc getSelectionInPair*(self: TextDocumentEditor; cursor: Cursor; - delimiter: char): Selection {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add delimiter.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getSelectionInPair_Selection_TextDocumentEditor_Cursor_char_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getSelectionInPairNested_Selection_TextDocumentEditor_Cursor_char_char_wasm( - arg: cstring): cstring {.importc.} -proc getSelectionInPairNested*(self: TextDocumentEditor; cursor: Cursor; - open: char; close: char): Selection {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add open.toJson() - argsJson.add close.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getSelectionInPairNested_Selection_TextDocumentEditor_Cursor_char_char_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_extendSelectionWithMove_Selection_TextDocumentEditor_Selection_string_int_wasm( - arg: cstring): cstring {.importc.} -proc extendSelectionWithMove*(self: TextDocumentEditor; selection: Selection; - move: string; count: int = 0): Selection {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selection.toJson() - argsJson.add move.toJson() - argsJson.add count.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_extendSelectionWithMove_Selection_TextDocumentEditor_Selection_string_int_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_vimMotionWord_Selection_TextDocumentEditor_Cursor_int_wasm( - arg: cstring): cstring {.importc.} -proc vimMotionWord*(self: TextDocumentEditor; cursor: Cursor; count: int = 0): Selection {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add count.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_vimMotionWord_Selection_TextDocumentEditor_Cursor_int_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_vimMotionWordBig_Selection_TextDocumentEditor_Cursor_int_wasm( - arg: cstring): cstring {.importc.} -proc vimMotionWordBig*(self: TextDocumentEditor; cursor: Cursor; count: int = 0): Selection {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add count.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_vimMotionWordBig_Selection_TextDocumentEditor_Cursor_int_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getSelectionForMove_Selection_TextDocumentEditor_Cursor_string_int_bool_wasm( - arg: cstring): cstring {.importc.} -proc getSelectionForMove*(self: TextDocumentEditor; cursor: Cursor; - move: string; count: int = 0; includeEol: bool = true): Selection {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - argsJson.add move.toJson() - argsJson.add count.toJson() - argsJson.add includeEol.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getSelectionForMove_Selection_TextDocumentEditor_Cursor_string_int_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_applyMove_void_TextDocumentEditor_JsonNode_wasm(arg: cstring): cstring {. - importc.} -proc applyMove*(self: TextDocumentEditor; args: JsonNode) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add args.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_applyMove_void_TextDocumentEditor_JsonNode_wasm( - argsJsonString.cstring) - - -proc editor_text_deleteMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_wasm( - arg: cstring): cstring {.importc.} -proc deleteMove*(self: TextDocumentEditor; move: string; inside: bool = false; - which: SelectionCursor = SelectionCursor.Config; - all: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add move.toJson() - argsJson.add inside.toJson() - argsJson.add which.toJson() - argsJson.add all.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_deleteMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_selectMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_wasm( - arg: cstring): cstring {.importc.} -proc selectMove*(self: TextDocumentEditor; move: string; inside: bool = false; - which: SelectionCursor = SelectionCursor.Config; - all: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add move.toJson() - argsJson.add inside.toJson() - argsJson.add which.toJson() - argsJson.add all.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_extendSelectMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_wasm( - arg: cstring): cstring {.importc.} -proc extendSelectMove*(self: TextDocumentEditor; move: string; - inside: bool = false; - which: SelectionCursor = SelectionCursor.Config; - all: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add move.toJson() - argsJson.add inside.toJson() - argsJson.add which.toJson() - argsJson.add all.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_extendSelectMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_copyMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_wasm( - arg: cstring): cstring {.importc.} -proc copyMove*(self: TextDocumentEditor; move: string; inside: bool = false; - which: SelectionCursor = SelectionCursor.Config; all: bool = true) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add move.toJson() - argsJson.add inside.toJson() - argsJson.add which.toJson() - argsJson.add all.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_copyMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_changeMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_wasm( - arg: cstring): cstring {.importc.} -proc changeMove*(self: TextDocumentEditor; move: string; inside: bool = false; - which: SelectionCursor = SelectionCursor.Config; - all: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add move.toJson() - argsJson.add inside.toJson() - argsJson.add which.toJson() - argsJson.add all.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_changeMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_moveLast_void_TextDocumentEditor_string_SelectionCursor_bool_int_wasm( - arg: cstring): cstring {.importc.} -proc moveLast*(self: TextDocumentEditor; move: string; - which: SelectionCursor = SelectionCursor.Config; - all: bool = true; count: int = 0) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add move.toJson() - argsJson.add which.toJson() - argsJson.add all.toJson() - argsJson.add count.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveLast_void_TextDocumentEditor_string_SelectionCursor_bool_int_wasm( - argsJsonString.cstring) - - -proc editor_text_moveFirst_void_TextDocumentEditor_string_SelectionCursor_bool_int_wasm( - arg: cstring): cstring {.importc.} -proc moveFirst*(self: TextDocumentEditor; move: string; - which: SelectionCursor = SelectionCursor.Config; - all: bool = true; count: int = 0) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add move.toJson() - argsJson.add which.toJson() - argsJson.add all.toJson() - argsJson.add count.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_moveFirst_void_TextDocumentEditor_string_SelectionCursor_bool_int_wasm( - argsJsonString.cstring) - - -proc editor_text_getSearchQuery_string_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc getSearchQuery*(self: TextDocumentEditor): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getSearchQuery_string_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_setSearchQuery_bool_TextDocumentEditor_string_bool_string_string_wasm( - arg: cstring): cstring {.importc.} -proc setSearchQuery*(self: TextDocumentEditor; query: string; - escapeRegex: bool = false; prefix: string = ""; - suffix: string = ""): bool {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add query.toJson() - argsJson.add escapeRegex.toJson() - argsJson.add prefix.toJson() - argsJson.add suffix.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setSearchQuery_bool_TextDocumentEditor_string_bool_string_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_openSearchBar_void_TextDocumentEditor_string_bool_bool_wasm( - arg: cstring): cstring {.importc.} -proc openSearchBar*(self: TextDocumentEditor; query: string = ""; - scrollToPreview: bool = true; select: bool = true) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add query.toJson() - argsJson.add scrollToPreview.toJson() - argsJson.add select.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_openSearchBar_void_TextDocumentEditor_string_bool_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_setSearchQueryFromMove_Selection_TextDocumentEditor_string_int_string_string_wasm( - arg: cstring): cstring {.importc.} -proc setSearchQueryFromMove*(self: TextDocumentEditor; move: string; - count: int = 0; prefix: string = ""; - suffix: string = ""): Selection {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add move.toJson() - argsJson.add count.toJson() - argsJson.add prefix.toJson() - argsJson.add suffix.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setSearchQueryFromMove_Selection_TextDocumentEditor_string_int_string_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_toggleLineComment_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc toggleLineComment*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_toggleLineComment_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_gotoDefinition_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc gotoDefinition*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_gotoDefinition_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_gotoDeclaration_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc gotoDeclaration*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_gotoDeclaration_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_gotoTypeDefinition_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc gotoTypeDefinition*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_gotoTypeDefinition_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_gotoImplementation_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc gotoImplementation*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_gotoImplementation_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_gotoReferences_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc gotoReferences*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_gotoReferences_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_switchSourceHeader_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc switchSourceHeader*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_switchSourceHeader_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_getCompletions_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc getCompletions*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getCompletions_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_gotoSymbol_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc gotoSymbol*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_gotoSymbol_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_fuzzySearchLines_void_TextDocumentEditor_float_bool_wasm( - arg: cstring): cstring {.importc.} -proc fuzzySearchLines*(self: TextDocumentEditor; minScore: float = 0.2; - sort: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add minScore.toJson() - argsJson.add sort.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_fuzzySearchLines_void_TextDocumentEditor_float_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_gotoWorkspaceSymbol_void_TextDocumentEditor_string_wasm( - arg: cstring): cstring {.importc.} -proc gotoWorkspaceSymbol*(self: TextDocumentEditor; query: string = "") {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add query.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_gotoWorkspaceSymbol_void_TextDocumentEditor_string_wasm( - argsJsonString.cstring) - - -proc editor_text_rename_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc rename*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_rename_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_hideCompletions_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc hideCompletions*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_hideCompletions_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_selectPrevCompletion_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc selectPrevCompletion*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectPrevCompletion_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_selectNextCompletion_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc selectNextCompletion*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectNextCompletion_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_selectPrevCompletionVisual_void_TextDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc selectPrevCompletionVisual*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectPrevCompletionVisual_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_selectNextCompletionVisual_void_TextDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc selectNextCompletionVisual*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectNextCompletionVisual_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_hasTabStops_bool_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc hasTabStops*(self: TextDocumentEditor): bool {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_hasTabStops_bool_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_clearTabStops_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc clearTabStops*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_clearTabStops_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_selectNextTabStop_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc selectNextTabStop*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectNextTabStop_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_selectPrevTabStop_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc selectPrevTabStop*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectPrevTabStop_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_applyCompletion_void_TextDocumentEditor_JsonNode_wasm( - arg: cstring): cstring {.importc.} -proc applyCompletion*(self: TextDocumentEditor; completion: JsonNode) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add completion.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_applyCompletion_void_TextDocumentEditor_JsonNode_wasm( - argsJsonString.cstring) - - -proc editor_text_isShowingCompletions_bool_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc isShowingCompletions*(self: TextDocumentEditor): bool {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_isShowingCompletions_bool_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_applySelectedCompletion_void_TextDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc applySelectedCompletion*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_applySelectedCompletion_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_showHoverFor_void_TextDocumentEditor_Cursor_wasm(arg: cstring): cstring {. - importc.} -proc showHoverFor*(self: TextDocumentEditor; cursor: Cursor) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add cursor.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_showHoverFor_void_TextDocumentEditor_Cursor_wasm( - argsJsonString.cstring) - - -proc editor_text_showHoverForCurrent_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc showHoverForCurrent*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_showHoverForCurrent_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_hideHover_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc hideHover*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_hideHover_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_cancelDelayedHideHover_void_TextDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc cancelDelayedHideHover*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_cancelDelayedHideHover_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_hideHoverDelayed_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc hideHoverDelayed*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_hideHoverDelayed_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_selectCodeAction_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc selectCodeAction*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_selectCodeAction_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_clearDiagnostics_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc clearDiagnostics*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_clearDiagnostics_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_updateInlayHints_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc updateInlayHints*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_updateInlayHints_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_lspInfo_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc lspInfo*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_lspInfo_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_setReadOnly_void_TextDocumentEditor_bool_wasm(arg: cstring): cstring {. - importc.} -proc setReadOnly*(self: TextDocumentEditor; readOnly: bool) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add readOnly.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setReadOnly_void_TextDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_setFileReadOnly_void_TextDocumentEditor_bool_wasm(arg: cstring): cstring {. - importc.} -proc setFileReadOnly*(self: TextDocumentEditor; readOnly: bool) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add readOnly.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setFileReadOnly_void_TextDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_isRunningSavedCommands_bool_TextDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc isRunningSavedCommands*(self: TextDocumentEditor): bool {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_isRunningSavedCommands_bool_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_runSavedCommands_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc runSavedCommands*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_runSavedCommands_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_clearCurrentCommandHistory_void_TextDocumentEditor_bool_wasm( - arg: cstring): cstring {.importc.} -proc clearCurrentCommandHistory*(self: TextDocumentEditor; - retainLast: bool = false) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add retainLast.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_clearCurrentCommandHistory_void_TextDocumentEditor_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_saveCurrentCommandHistory_void_TextDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc saveCurrentCommandHistory*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_saveCurrentCommandHistory_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_getSelection_Selection_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc getSelection*(self: TextDocumentEditor): Selection {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getSelection_Selection_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_getSelections_Selections_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc getSelections*(self: TextDocumentEditor): Selections {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getSelections_Selections_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_setSelection_void_TextDocumentEditor_Selection_Option_bool_wasm( - arg: cstring): cstring {.importc.} -proc setSelection*(self: TextDocumentEditor; selection: Selection; - addToHistory: Option[bool] = bool.none) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selection.toJson() - argsJson.add addToHistory.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setSelection_void_TextDocumentEditor_Selection_Option_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_setSelections_void_TextDocumentEditor_Selections_Option_bool_wasm( - arg: cstring): cstring {.importc.} -proc setSelections*(self: TextDocumentEditor; selections: Selections; - addToHistory: Option[bool] = bool.none) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selections.toJson() - argsJson.add addToHistory.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setSelections_void_TextDocumentEditor_Selections_Option_bool_wasm( - argsJsonString.cstring) - - -proc editor_text_setTargetSelection_void_TextDocumentEditor_Selection_wasm( - arg: cstring): cstring {.importc.} -proc setTargetSelection*(self: TextDocumentEditor; selection: Selection) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selection.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setTargetSelection_void_TextDocumentEditor_Selection_wasm( - argsJsonString.cstring) - - -proc editor_text_enterChooseCursorMode_void_TextDocumentEditor_string_wasm( - arg: cstring): cstring {.importc.} -proc enterChooseCursorMode*(self: TextDocumentEditor; action: string) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add action.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_enterChooseCursorMode_void_TextDocumentEditor_string_wasm( - argsJsonString.cstring) - - -proc editor_text_recordCurrentCommand_void_TextDocumentEditor_seq_string_wasm( - arg: cstring): cstring {.importc.} -proc recordCurrentCommand*(self: TextDocumentEditor; - registers: seq[string] = @[]) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add registers.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_recordCurrentCommand_void_TextDocumentEditor_seq_string_wasm( - argsJsonString.cstring) - - -proc editor_text_runControlClickCommand_void_TextDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc runControlClickCommand*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_runControlClickCommand_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_runSingleClickCommand_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc runSingleClickCommand*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_runSingleClickCommand_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_runDoubleClickCommand_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc runDoubleClickCommand*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_runDoubleClickCommand_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_runTripleClickCommand_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc runTripleClickCommand*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_runTripleClickCommand_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_runDragCommand_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc runDragCommand*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_runDragCommand_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - - -proc editor_text_getCurrentEventHandlers_seq_string_TextDocumentEditor_wasm( - arg: cstring): cstring {.importc.} -proc getCurrentEventHandlers*(self: TextDocumentEditor): seq[string] {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_getCurrentEventHandlers_seq_string_TextDocumentEditor_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_setCustomHeader_void_TextDocumentEditor_string_wasm( - arg: cstring): cstring {.importc.} -proc setCustomHeader*(self: TextDocumentEditor; text: string) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add text.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_setCustomHeader_void_TextDocumentEditor_string_wasm( - argsJsonString.cstring) - - -proc editor_text_cycleCase_string_TextDocumentEditor_Selection_bool_wasm( - arg: cstring): cstring {.importc.} -proc cycleCase*(self: TextDocumentEditor; selection: Selection; - inclusiveEnd: bool = false): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add selection.toJson() - argsJson.add inclusiveEnd.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_cycleCase_string_TextDocumentEditor_Selection_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editor_text_cycleSelectedCase_void_TextDocumentEditor_wasm(arg: cstring): cstring {. - importc.} -proc cycleSelectedCase*(self: TextDocumentEditor) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = editor_text_cycleSelectedCase_void_TextDocumentEditor_wasm( - argsJsonString.cstring) - diff --git a/scripting/editors_api_wasm.nim b/scripting/editors_api_wasm.nim deleted file mode 100644 index 91066362b..000000000 --- a/scripting/editors_api_wasm.nim +++ /dev/null @@ -1,32 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc editors_getAllEditors_seq_EditorId_DocumentEditorService_wasm(arg: cstring): cstring {. - importc.} -proc getAllEditors*(): seq[EditorId] {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = editors_getAllEditors_seq_EditorId_DocumentEditorService_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc editors_getExistingEditor_Option_EditorId_DocumentEditorService_string_wasm( - arg: cstring): cstring {.importc.} -proc getExistingEditor*(path: string): Option[EditorId] {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - let argsJsonString = $argsJson - let res {.used.} = editors_getExistingEditor_Option_EditorId_DocumentEditorService_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - diff --git a/scripting/events_api_wasm.nim b/scripting/events_api_wasm.nim deleted file mode 100644 index 28274464f..000000000 --- a/scripting/events_api_wasm.nim +++ /dev/null @@ -1,101 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc events_addKeyDefinitions_void_EventHandlerService_string_seq_string_wasm( - arg: cstring): cstring {.importc.} -proc addKeyDefinitions*(name: string; keys: seq[string]) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add name.toJson() - argsJson.add keys.toJson() - let argsJsonString = $argsJson - let res {.used.} = events_addKeyDefinitions_void_EventHandlerService_string_seq_string_wasm( - argsJsonString.cstring) - - -proc events_setKeyDefinitions_void_EventHandlerService_string_seq_string_wasm( - arg: cstring): cstring {.importc.} -proc setKeyDefinitions*(name: string; keys: seq[string]) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add name.toJson() - argsJson.add keys.toJson() - let argsJsonString = $argsJson - let res {.used.} = events_setKeyDefinitions_void_EventHandlerService_string_seq_string_wasm( - argsJsonString.cstring) - - -proc events_setLeader_void_EventHandlerService_string_wasm(arg: cstring): cstring {. - importc.} -proc setLeader*(leader: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add leader.toJson() - let argsJsonString = $argsJson - let res {.used.} = events_setLeader_void_EventHandlerService_string_wasm( - argsJsonString.cstring) - - -proc events_setLeaders_void_EventHandlerService_seq_string_wasm(arg: cstring): cstring {. - importc.} -proc setLeaders*(leaders: seq[string]) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add leaders.toJson() - let argsJsonString = $argsJson - let res {.used.} = events_setLeaders_void_EventHandlerService_seq_string_wasm( - argsJsonString.cstring) - - -proc events_addLeader_void_EventHandlerService_string_wasm(arg: cstring): cstring {. - importc.} -proc addLeader*(leader: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add leader.toJson() - let argsJsonString = $argsJson - let res {.used.} = events_addLeader_void_EventHandlerService_string_wasm( - argsJsonString.cstring) - - -proc events_addLeaders_void_EventHandlerService_seq_string_wasm(arg: cstring): cstring {. - importc.} -proc addLeaders*(leaders: seq[string]) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add leaders.toJson() - let argsJsonString = $argsJson - let res {.used.} = events_addLeaders_void_EventHandlerService_seq_string_wasm( - argsJsonString.cstring) - - -proc events_clearCommands_void_EventHandlerService_string_wasm(arg: cstring): cstring {. - importc.} -proc clearCommands*(context: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add context.toJson() - let argsJsonString = $argsJson - let res {.used.} = events_clearCommands_void_EventHandlerService_string_wasm( - argsJsonString.cstring) - - -proc events_removeCommand_void_EventHandlerService_string_string_wasm( - arg: cstring): cstring {.importc.} -proc removeCommand*(context: string; keys: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add context.toJson() - argsJson.add keys.toJson() - let argsJsonString = $argsJson - 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) - diff --git a/scripting/layout_api_wasm.nim b/scripting/layout_api_wasm.nim deleted file mode 100644 index 8951867f9..000000000 --- a/scripting/layout_api_wasm.nim +++ /dev/null @@ -1,347 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc layout_changeSplitSize_void_LayoutService_float_bool_wasm(arg: cstring): cstring {. - importc.} -proc changeSplitSize*(change: float; vertical: bool) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add change.toJson() - argsJson.add vertical.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_changeSplitSize_void_LayoutService_float_bool_wasm( - argsJsonString.cstring) - - -proc layout_toggleMaximizeViewLocal_void_LayoutService_string_wasm(arg: cstring): cstring {. - importc.} -proc toggleMaximizeViewLocal*(slot: string = "**") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add slot.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_toggleMaximizeViewLocal_void_LayoutService_string_wasm( - argsJsonString.cstring) - - -proc layout_toggleMaximizeView_void_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc toggleMaximizeView*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_toggleMaximizeView_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_setMaxViews_void_LayoutService_string_int_wasm(arg: cstring): cstring {. - importc.} -proc setMaxViews*(slot: string; maxViews: int = int.high) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add slot.toJson() - argsJson.add maxViews.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_setMaxViews_void_LayoutService_string_int_wasm( - argsJsonString.cstring) - - -proc layout_getNumVisibleViews_int_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc getNumVisibleViews*(): int {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_getNumVisibleViews_int_LayoutService_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc layout_getNumHiddenViews_int_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc getNumHiddenViews*(): int {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_getNumHiddenViews_int_LayoutService_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc layout_showEditor_void_LayoutService_EditorId_string_bool_wasm(arg: cstring): cstring {. - importc.} -proc showEditor*(editorId: EditorId; slot: string = ""; focus: bool = true) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add editorId.toJson() - argsJson.add slot.toJson() - argsJson.add focus.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_showEditor_void_LayoutService_EditorId_string_bool_wasm( - argsJsonString.cstring) - - -proc layout_getOrOpenEditor_Option_EditorId_LayoutService_string_wasm( - arg: cstring): cstring {.importc.} -proc getOrOpenEditor*(path: string): Option[EditorId] {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_getOrOpenEditor_Option_EditorId_LayoutService_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc layout_hideActiveView_void_LayoutService_bool_wasm(arg: cstring): cstring {. - importc.} -proc hideActiveView*(closeOpenPopup: bool = true) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add closeOpenPopup.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_hideActiveView_void_LayoutService_bool_wasm( - argsJsonString.cstring) - - -proc layout_closeActiveView_void_LayoutService_bool_bool_wasm(arg: cstring): cstring {. - importc.} -proc closeActiveView*(closeOpenPopup: bool = true; restoreHidden: bool = true) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add closeOpenPopup.toJson() - argsJson.add restoreHidden.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_closeActiveView_void_LayoutService_bool_bool_wasm( - argsJsonString.cstring) - - -proc layout_hideOtherViews_void_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc hideOtherViews*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_hideOtherViews_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_closeOtherViews_void_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc closeOtherViews*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_closeOtherViews_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_focusViewLeft_void_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc focusViewLeft*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_focusViewLeft_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_focusViewRight_void_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc focusViewRight*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_focusViewRight_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_focusViewUp_void_LayoutService_wasm(arg: cstring): cstring {.importc.} -proc focusViewUp*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_focusViewUp_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_focusViewDown_void_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc focusViewDown*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_focusViewDown_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_focusView_void_LayoutService_string_wasm(arg: cstring): cstring {. - importc.} -proc focusView*(slot: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add slot.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_focusView_void_LayoutService_string_wasm( - argsJsonString.cstring) - - -proc layout_focusNextView_void_LayoutService_string_wasm(arg: cstring): cstring {. - importc.} -proc focusNextView*(slot: string = "") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add slot.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_focusNextView_void_LayoutService_string_wasm( - argsJsonString.cstring) - - -proc layout_focusPrevView_void_LayoutService_string_wasm(arg: cstring): cstring {. - importc.} -proc focusPrevView*(slot: string = "") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add slot.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_focusPrevView_void_LayoutService_string_wasm( - argsJsonString.cstring) - - -proc layout_openPrevView_void_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc openPrevView*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_openPrevView_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_openNextView_void_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc openNextView*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_openNextView_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_openLastView_void_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc openLastView*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_openLastView_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_setLayout_void_LayoutService_string_wasm(arg: cstring): cstring {. - importc.} -proc setLayout*(layout: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add layout.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_setLayout_void_LayoutService_string_wasm( - argsJsonString.cstring) - - -proc layout_setActiveViewIndex_void_LayoutService_string_int_wasm(arg: cstring): cstring {. - importc.} -proc setActiveViewIndex*(slot: string; index: int) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add slot.toJson() - argsJson.add index.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_setActiveViewIndex_void_LayoutService_string_int_wasm( - argsJsonString.cstring) - - -proc layout_moveActiveViewFirst_void_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc moveActiveViewFirst*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_moveActiveViewFirst_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_moveActiveViewPrev_void_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc moveActiveViewPrev*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_moveActiveViewPrev_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_moveActiveViewNext_void_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc moveActiveViewNext*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_moveActiveViewNext_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_moveActiveViewNextAndGoBack_void_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc moveActiveViewNextAndGoBack*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_moveActiveViewNextAndGoBack_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_splitView_void_LayoutService_string_wasm(arg: cstring): cstring {. - importc.} -proc splitView*(slot: string = "") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add slot.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_splitView_void_LayoutService_string_wasm( - argsJsonString.cstring) - - -proc layout_moveView_void_LayoutService_string_wasm(arg: cstring): cstring {. - importc.} -proc moveView*(slot: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add slot.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_moveView_void_LayoutService_string_wasm( - argsJsonString.cstring) - - -proc layout_wrapLayout_void_LayoutService_JsonNode_string_wasm(arg: cstring): cstring {. - importc.} -proc wrapLayout*(layout: JsonNode; slot: string = "**") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add layout.toJson() - argsJson.add slot.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_wrapLayout_void_LayoutService_JsonNode_string_wasm( - argsJsonString.cstring) - - -proc layout_chooseLayout_void_LayoutService_wasm(arg: cstring): cstring {. - importc.} -proc chooseLayout*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_chooseLayout_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_logLayout_void_LayoutService_wasm(arg: cstring): cstring {.importc.} -proc logLayout*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = layout_logLayout_void_LayoutService_wasm( - argsJsonString.cstring) - - -proc layout_open_void_LayoutService_string_string_wasm(arg: cstring): cstring {. - importc.} -proc open*(path: string; slot: string = "") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - argsJson.add slot.toJson() - let argsJsonString = $argsJson - let res {.used.} = layout_open_void_LayoutService_string_string_wasm( - argsJsonString.cstring) - diff --git a/scripting/lsp_api_wasm.nim b/scripting/lsp_api_wasm.nim deleted file mode 100644 index de0bda16c..000000000 --- a/scripting/lsp_api_wasm.nim +++ /dev/null @@ -1,29 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc lsp_lspLogVerbose_void_bool_wasm(arg: cstring): cstring {.importc.} -proc lspLogVerbose*(val: bool) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add val.toJson() - let argsJsonString = $argsJson - let res {.used.} = lsp_lspLogVerbose_void_bool_wasm(argsJsonString.cstring) - - -proc lsp_lspToggleLogServerDebug_void_wasm(arg: cstring): cstring {.importc.} -proc lspToggleLogServerDebug*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = lsp_lspToggleLogServerDebug_void_wasm( - argsJsonString.cstring) - - -proc lsp_lspLogServerDebug_void_bool_wasm(arg: cstring): cstring {.importc.} -proc lspLogServerDebug*(val: bool) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add val.toJson() - let argsJsonString = $argsJson - let res {.used.} = lsp_lspLogServerDebug_void_bool_wasm(argsJsonString.cstring) - diff --git a/scripting/plugin_api.nim b/scripting/plugin_api.nim deleted file mode 100644 index f10469e5e..000000000 --- a/scripting/plugin_api.nim +++ /dev/null @@ -1,45 +0,0 @@ -import "../src/scripting_api" -export scripting_api - -## This file is auto generated, don't modify. - -import collab_api_wasm -export collab_api_wasm -import commands_api_wasm -export commands_api_wasm -import config_api_wasm -export config_api_wasm -import debugger_api_wasm -export debugger_api_wasm -import editor_api_wasm -export editor_api_wasm -import editor_text_api_wasm -export editor_text_api_wasm -import editors_api_wasm -export editors_api_wasm -import events_api_wasm -export events_api_wasm -import layout_api_wasm -export layout_api_wasm -import lsp_api_wasm -export lsp_api_wasm -import plugins_api_wasm -export plugins_api_wasm -import popup_selector_api_wasm -export popup_selector_api_wasm -import process_api_wasm -export process_api_wasm -import registers_api_wasm -export registers_api_wasm -import session_api_wasm -export session_api_wasm -import terminal_api_wasm -export terminal_api_wasm -import toast_api_wasm -export toast_api_wasm -import vcs_api_wasm -export vcs_api_wasm -import vfs_api_wasm -export vfs_api_wasm - -const enableAst* = false diff --git a/scripting/plugin_api_internal.nim b/scripting/plugin_api_internal.nim deleted file mode 100644 index 115acf373..000000000 --- a/scripting/plugin_api_internal.nim +++ /dev/null @@ -1,1005 +0,0 @@ -## This file is auto generated, don't modify. - -import std/[json, options] -import scripting_api - -template varargs*() {.pragma.} - -proc editor_text_enableAutoReload_void_TextDocumentEditor_bool_impl*( - self: TextDocumentEditor; enabled: bool) = - discard -proc editor_text_getFileName_string_TextDocumentEditor_impl*( - self: TextDocumentEditor): string = - discard -proc editor_text_lineCount_int_TextDocumentEditor_impl*(self: TextDocumentEditor): int = - discard -proc editor_text_lineLength_int_TextDocumentEditor_int_impl*( - self: TextDocumentEditor; line: int): int = - discard -proc editor_text_screenLineCount_int_TextDocumentEditor_impl*( - self: TextDocumentEditor): int = - discard -proc editor_text_doMoveCursorLine_Cursor_TextDocumentEditor_Cursor_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; offset: int; wrap: bool = false; - includeAfter: bool = false): Cursor = - discard -proc editor_text_doMoveCursorVisualLine_Cursor_TextDocumentEditor_Cursor_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; offset: int; wrap: bool = false; - includeAfter: bool = false): Cursor = - discard -proc editor_text_doMoveCursorHome_Cursor_TextDocumentEditor_Cursor_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; offset: int; wrap: bool; - includeAfter: bool): Cursor = - discard -proc editor_text_doMoveCursorEnd_Cursor_TextDocumentEditor_Cursor_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; offset: int; wrap: bool; - includeAfter: bool): Cursor = - discard -proc editor_text_doMoveCursorVisualHome_Cursor_TextDocumentEditor_Cursor_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; offset: int; wrap: bool; - includeAfter: bool): Cursor = - discard -proc editor_text_doMoveCursorVisualEnd_Cursor_TextDocumentEditor_Cursor_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; offset: int; wrap: bool; - includeAfter: bool): Cursor = - discard -proc editor_text_doMoveCursorPrevFindResult_Cursor_TextDocumentEditor_Cursor_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; offset: int; wrap: bool; - includeAfter: bool): Cursor = - discard -proc editor_text_doMoveCursorNextFindResult_Cursor_TextDocumentEditor_Cursor_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; offset: int; wrap: bool; - includeAfter: bool): Cursor = - discard -proc editor_text_doMoveCursorLineCenter_Cursor_TextDocumentEditor_Cursor_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; offset: int; wrap: bool; - includeAfter: bool): Cursor = - discard -proc editor_text_doMoveCursorCenter_Cursor_TextDocumentEditor_Cursor_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; offset: int; wrap: bool; - includeAfter: bool): Cursor = - discard -proc editor_text_doMoveCursorColumn_Cursor_TextDocumentEditor_Cursor_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; offset: int; wrap: bool = true; - includeAfter: bool = true): Cursor = - discard -proc editor_text_includeSelectionEnd_Selection_TextDocumentEditor_Selection_bool_impl*( - self: TextDocumentEditor; res: Selection; includeAfter: bool = true): Selection = - discard -proc editor_text_findSurroundStart_Option_Cursor_TextDocumentEditor_Cursor_int_char_char_int_impl*( - editor: TextDocumentEditor; cursor: Cursor; count: int; c0: char; c1: char; - depth: int = 1): Option[Cursor] = - discard -proc editor_text_findSurroundEnd_Option_Cursor_TextDocumentEditor_Cursor_int_char_char_int_impl*( - editor: TextDocumentEditor; cursor: Cursor; count: int; c0: char; c1: char; - depth: int = 1): Option[Cursor] = - discard -proc editor_text_setMode_void_TextDocumentEditor_string_impl*( - self: TextDocumentEditor; mode: string) = - discard -proc editor_text_mode_string_TextDocumentEditor_impl*(self: TextDocumentEditor): string = - discard -proc editor_text_getContextWithMode_string_TextDocumentEditor_string_impl*( - self: TextDocumentEditor; context: string): string = - discard -proc editor_text_updateTargetColumn_void_TextDocumentEditor_SelectionCursor_impl*( - self: TextDocumentEditor; cursor: SelectionCursor = Last) = - discard -proc editor_text_invertSelection_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_getRevision_int_TextDocumentEditor_impl*( - self: TextDocumentEditor): int = - discard -proc editor_text_getUsage_string_TextDocumentEditor_impl*( - self: TextDocumentEditor): string = - discard -proc editor_text_getText_string_TextDocumentEditor_Selection_bool_impl*( - self: TextDocumentEditor; selection: Selection; inclusiveEnd: bool = false): string = - discard -proc editor_text_insert_seq_Selection_TextDocumentEditor_seq_Selection_string_bool_bool_impl*( - self: TextDocumentEditor; selections: seq[Selection]; text: string; - notify: bool = true; record: bool = true): seq[Selection] = - discard -proc editor_text_insertMulti_seq_Selection_TextDocumentEditor_seq_Selection_seq_string_bool_bool_impl*( - self: TextDocumentEditor; selections: seq[Selection]; texts: seq[string]; - notify: bool = true; record: bool = true): seq[Selection] = - discard -proc editor_text_delete_seq_Selection_TextDocumentEditor_seq_Selection_bool_bool_bool_impl*( - self: TextDocumentEditor; selections: seq[Selection]; notify: bool = true; - record: bool = true; inclusiveEnd: bool = false): seq[Selection] = - discard -proc editor_text_edit_seq_Selection_TextDocumentEditor_seq_Selection_seq_string_bool_bool_bool_impl*( - self: TextDocumentEditor; selections: seq[Selection]; texts: seq[string]; - notify: bool = true; record: bool = true; inclusiveEnd: bool = false): seq[ - Selection] = - discard -proc editor_text_deleteLines_void_TextDocumentEditor_Slice_int_Selections_impl*( - self: TextDocumentEditor; slice: Slice[int]; oldSelections: Selections) = - discard -proc editor_text_selectPrev_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_selectNext_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_selectInside_void_TextDocumentEditor_Cursor_impl*( - self: TextDocumentEditor; cursor: Cursor) = - discard -proc editor_text_selectInsideCurrent_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_selectLine_void_TextDocumentEditor_int_impl*( - self: TextDocumentEditor; line: int) = - discard -proc editor_text_selectLineCurrent_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_getParentNodeSelection_Selection_TextDocumentEditor_Selection_bool_impl*( - self: TextDocumentEditor; selection: Selection; includeAfter: bool = true): Selection = - discard -proc editor_text_getNextNamedSiblingNodeSelection_Option_Selection_TextDocumentEditor_Selection_bool_impl*( - self: TextDocumentEditor; selection: Selection; includeAfter: bool = true): Option[ - Selection] = - discard -proc editor_text_getNextSiblingNodeSelection_Option_Selection_TextDocumentEditor_Selection_bool_impl*( - self: TextDocumentEditor; selection: Selection; includeAfter: bool = true): Option[ - Selection] = - discard -proc editor_text_getParentNodeSelections_Selections_TextDocumentEditor_Selections_bool_impl*( - self: TextDocumentEditor; selections: Selections; includeAfter: bool = true): Selections = - discard -proc editor_text_selectParentTs_void_TextDocumentEditor_Selection_bool_impl*( - self: TextDocumentEditor; selection: Selection; includeAfter: bool = true) = - discard -proc editor_text_printTreesitterTree_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_printTreesitterTreeUnderCursor_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_selectParentCurrentTs_void_TextDocumentEditor_bool_impl*( - self: TextDocumentEditor; includeAfter: bool = true) = - discard -proc editor_text_getNextNodeWithSameType_Option_Selection_TextDocumentEditor_Selection_int_bool_bool_bool_bool_impl*( - self: TextDocumentEditor; selection: Selection; offset: int = 0; - includeAfter: bool = true; wrap: bool = true; stepIn: bool = true; - stepOut: bool = true): Option[Selection] = - discard -proc editor_text_shouldShowCompletionsAt_bool_TextDocumentEditor_Cursor_impl*( - self: TextDocumentEditor; cursor: Cursor): bool = - discard -proc editor_text_autoShowCompletions_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_insertText_void_TextDocumentEditor_string_bool_impl*( - self: TextDocumentEditor; text: string; autoIndent: bool = true) = - discard -proc editor_text_indent_void_TextDocumentEditor_impl*(self: TextDocumentEditor) = - discard -proc editor_text_unindent_void_TextDocumentEditor_impl*(self: TextDocumentEditor) = - discard -proc editor_text_insertIndent_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_undo_void_TextDocumentEditor_string_impl*( - self: TextDocumentEditor; checkpoint: string = "word") = - discard -proc editor_text_redo_void_TextDocumentEditor_string_impl*( - self: TextDocumentEditor; checkpoint: string = "word") = - discard -proc editor_text_addNextCheckpoint_void_TextDocumentEditor_string_impl*( - self: TextDocumentEditor; checkpoint: string) = - discard -proc editor_text_printUndoHistory_void_TextDocumentEditor_int_impl*( - self: TextDocumentEditor; max: int = 50) = - discard -proc editor_text_copy_void_TextDocumentEditor_string_bool_impl*( - self: TextDocumentEditor; register: string = ""; inclusiveEnd: bool = false) = - discard -proc editor_text_paste_void_TextDocumentEditor_string_bool_impl*( - self: TextDocumentEditor; register: string = ""; inclusiveEnd: bool = false) = - discard -proc editor_text_scrollText_void_TextDocumentEditor_float32_impl*( - self: TextDocumentEditor; amount: float32) = - discard -proc editor_text_scrollLines_void_TextDocumentEditor_int_impl*( - self: TextDocumentEditor; amount: int) = - discard -proc editor_text_duplicateLastSelection_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_addCursorBelow_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_addCursorAbove_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_getPrevFindResult_Selection_TextDocumentEditor_Cursor_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; offset: int = 0; - includeAfter: bool = true; wrap: bool = true): Selection = - discard -proc editor_text_getNextFindResult_Selection_TextDocumentEditor_Cursor_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; offset: int = 0; - includeAfter: bool = true; wrap: bool = true): Selection = - discard -proc editor_text_getPrevDiagnostic_Selection_TextDocumentEditor_Cursor_int_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; severity: int = 0; - offset: int = 0; includeAfter: bool = true; wrap: bool = true): Selection = - discard -proc editor_text_getNextDiagnostic_Selection_TextDocumentEditor_Cursor_int_int_bool_bool_impl*( - self: TextDocumentEditor; cursor: Cursor; severity: int = 0; - offset: int = 0; includeAfter: bool = true; wrap: bool = true): Selection = - discard -proc editor_text_closeDiff_void_TextDocumentEditor_impl*(self: TextDocumentEditor) = - discard -proc editor_text_getPrevChange_Selection_TextDocumentEditor_Cursor_impl*( - self: TextDocumentEditor; cursor: Cursor): Selection = - discard -proc editor_text_getNextChange_Selection_TextDocumentEditor_Cursor_impl*( - self: TextDocumentEditor; cursor: Cursor): Selection = - discard -proc editor_text_updateDiff_void_TextDocumentEditor_bool_impl*( - self: TextDocumentEditor; gotoFirstDiff: bool = false) = - discard -proc editor_text_checkoutFile_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_addNextFindResultToSelection_void_TextDocumentEditor_bool_bool_impl*( - self: TextDocumentEditor; includeAfter: bool = true; wrap: bool = true) = - discard -proc editor_text_addPrevFindResultToSelection_void_TextDocumentEditor_bool_bool_impl*( - self: TextDocumentEditor; includeAfter: bool = true; wrap: bool = true) = - discard -proc editor_text_setAllFindResultToSelection_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_clearSelections_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_moveCursorColumn_void_TextDocumentEditor_int_SelectionCursor_bool_bool_bool_impl*( - self: TextDocumentEditor; distance: int; - cursor: SelectionCursor = SelectionCursor.Config; all: bool = true; - wrap: bool = true; includeAfter: bool = true) = - discard -proc editor_text_moveCursorLine_void_TextDocumentEditor_int_SelectionCursor_bool_bool_bool_impl*( - self: TextDocumentEditor; distance: int; - cursor: SelectionCursor = SelectionCursor.Config; all: bool = true; - wrap: bool = true; includeAfter: bool = true) = - discard -proc editor_text_moveCursorVisualLine_void_TextDocumentEditor_int_SelectionCursor_bool_bool_bool_impl*( - self: TextDocumentEditor; distance: int; - cursor: SelectionCursor = SelectionCursor.Config; all: bool = true; - wrap: bool = true; includeAfter: bool = true) = - discard -proc editor_text_moveCursorHome_void_TextDocumentEditor_SelectionCursor_bool_impl*( - self: TextDocumentEditor; cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true) = - discard -proc editor_text_moveCursorEnd_void_TextDocumentEditor_SelectionCursor_bool_bool_impl*( - self: TextDocumentEditor; cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true; includeAfter: bool = true) = - discard -proc editor_text_moveCursorVisualHome_void_TextDocumentEditor_SelectionCursor_bool_impl*( - self: TextDocumentEditor; cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true) = - discard -proc editor_text_moveCursorVisualEnd_void_TextDocumentEditor_SelectionCursor_bool_bool_impl*( - self: TextDocumentEditor; cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true; includeAfter: bool = true) = - discard -proc editor_text_moveCursorTo_void_TextDocumentEditor_string_SelectionCursor_bool_impl*( - self: TextDocumentEditor; str: string; - cursor: SelectionCursor = SelectionCursor.Config; all: bool = true) = - discard -proc editor_text_moveCursorBefore_void_TextDocumentEditor_string_SelectionCursor_bool_impl*( - self: TextDocumentEditor; str: string; - cursor: SelectionCursor = SelectionCursor.Config; all: bool = true) = - discard -proc editor_text_moveCursorNextFindResult_void_TextDocumentEditor_SelectionCursor_bool_bool_impl*( - self: TextDocumentEditor; cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true; wrap: bool = true) = - discard -proc editor_text_moveCursorPrevFindResult_void_TextDocumentEditor_SelectionCursor_bool_bool_impl*( - self: TextDocumentEditor; cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true; wrap: bool = true) = - discard -proc editor_text_moveCursorLineCenter_void_TextDocumentEditor_SelectionCursor_bool_impl*( - self: TextDocumentEditor; cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true) = - discard -proc editor_text_moveCursorCenter_void_TextDocumentEditor_SelectionCursor_bool_impl*( - self: TextDocumentEditor; cursor: SelectionCursor = SelectionCursor.Config; - all: bool = true) = - discard -proc editor_text_scrollToCursor_void_TextDocumentEditor_SelectionCursor_impl*( - self: TextDocumentEditor; cursor: SelectionCursor = SelectionCursor.Config) = - discard -proc editor_text_setNextScrollBehaviour_void_TextDocumentEditor_ScrollBehaviour_impl*( - self: TextDocumentEditor; scrollBehaviour: ScrollBehaviour) = - discard -proc editor_text_setCursorScrollOffset_void_TextDocumentEditor_float_SelectionCursor_impl*( - self: TextDocumentEditor; offset: float; - cursor: SelectionCursor = SelectionCursor.Config) = - discard -proc editor_text_getContentBounds_Vec2_TextDocumentEditor_impl*( - self: TextDocumentEditor): Vec2 = - discard -proc editor_text_centerCursor_void_TextDocumentEditor_SelectionCursor_impl*( - self: TextDocumentEditor; cursor: SelectionCursor = SelectionCursor.Config) = - discard -proc editor_text_reloadTreesitter_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_deleteLeft_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_deleteRight_void_TextDocumentEditor_bool_impl*( - self: TextDocumentEditor; includeAfter: bool = true) = - discard -proc editor_text_getCommandCount_int_TextDocumentEditor_impl*( - self: TextDocumentEditor): int = - discard -proc editor_text_setCommandCount_void_TextDocumentEditor_int_impl*( - self: TextDocumentEditor; count: int) = - discard -proc editor_text_setCommandCountRestore_void_TextDocumentEditor_int_impl*( - self: TextDocumentEditor; count: int) = - discard -proc editor_text_updateCommandCount_void_TextDocumentEditor_int_impl*( - self: TextDocumentEditor; digit: int) = - discard -proc editor_text_setFlag_void_TextDocumentEditor_string_bool_impl*( - self: TextDocumentEditor; name: string; value: bool) = - discard -proc editor_text_getFlag_bool_TextDocumentEditor_string_impl*( - self: TextDocumentEditor; name: string): bool = - discard -proc editor_text_runAction_Option_JsonNode_TextDocumentEditor_string_JsonNode_impl*( - self: TextDocumentEditor; action: string; args: JsonNode): Option[JsonNode] = - discard -proc editor_text_findWordBoundary_Selection_TextDocumentEditor_Cursor_impl*( - self: TextDocumentEditor; cursor: Cursor): Selection = - discard -proc editor_text_getSelectionInPair_Selection_TextDocumentEditor_Cursor_char_impl*( - self: TextDocumentEditor; cursor: Cursor; delimiter: char): Selection = - discard -proc editor_text_getSelectionInPairNested_Selection_TextDocumentEditor_Cursor_char_char_impl*( - self: TextDocumentEditor; cursor: Cursor; open: char; close: char): Selection = - discard -proc editor_text_extendSelectionWithMove_Selection_TextDocumentEditor_Selection_string_int_impl*( - self: TextDocumentEditor; selection: Selection; move: string; count: int = 0): Selection = - discard -proc editor_text_getSelectionForMove_Selection_TextDocumentEditor_Cursor_string_int_impl*( - self: TextDocumentEditor; cursor: Cursor; move: string; count: int = 0): Selection = - discard -proc editor_text_applyMove_void_TextDocumentEditor_JsonNode_impl*( - self: TextDocumentEditor; args: JsonNode) = - discard -proc editor_text_deleteMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_impl*( - self: TextDocumentEditor; move: string; inside: bool = false; - which: SelectionCursor = SelectionCursor.Config; all: bool = true) = - discard -proc editor_text_selectMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_impl*( - self: TextDocumentEditor; move: string; inside: bool = false; - which: SelectionCursor = SelectionCursor.Config; all: bool = true) = - discard -proc editor_text_extendSelectMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_impl*( - self: TextDocumentEditor; move: string; inside: bool = false; - which: SelectionCursor = SelectionCursor.Config; all: bool = true) = - discard -proc editor_text_copyMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_impl*( - self: TextDocumentEditor; move: string; inside: bool = false; - which: SelectionCursor = SelectionCursor.Config; all: bool = true) = - discard -proc editor_text_changeMove_void_TextDocumentEditor_string_bool_SelectionCursor_bool_impl*( - self: TextDocumentEditor; move: string; inside: bool = false; - which: SelectionCursor = SelectionCursor.Config; all: bool = true) = - discard -proc editor_text_moveLast_void_TextDocumentEditor_string_SelectionCursor_bool_int_impl*( - self: TextDocumentEditor; move: string; - which: SelectionCursor = SelectionCursor.Config; all: bool = true; - count: int = 0) = - discard -proc editor_text_moveFirst_void_TextDocumentEditor_string_SelectionCursor_bool_int_impl*( - self: TextDocumentEditor; move: string; - which: SelectionCursor = SelectionCursor.Config; all: bool = true; - count: int = 0) = - discard -proc editor_text_setSearchQuery_void_TextDocumentEditor_string_bool_string_string_impl*( - self: TextDocumentEditor; query: string; escapeRegex: bool = false; - prefix: string = ""; suffix: string = "") = - discard -proc editor_text_setSearchQueryFromMove_Selection_TextDocumentEditor_string_int_string_string_impl*( - self: TextDocumentEditor; move: string; count: int = 0; prefix: string = ""; - suffix: string = ""): Selection = - discard -proc editor_text_toggleLineComment_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_gotoDefinition_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_gotoDeclaration_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_gotoTypeDefinition_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_gotoImplementation_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_gotoReferences_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_switchSourceHeader_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_getCompletions_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_gotoSymbol_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_fuzzySearchLines_void_TextDocumentEditor_float_bool_impl*( - self: TextDocumentEditor; minScore: float = 0.2; sort: bool = true) = - discard -proc editor_text_gotoWorkspaceSymbol_void_TextDocumentEditor_string_impl*( - self: TextDocumentEditor; query: string = "") = - discard -proc editor_text_hideCompletions_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_selectPrevCompletion_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_selectNextCompletion_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_hasTabStops_bool_TextDocumentEditor_impl*( - self: TextDocumentEditor): bool = - discard -proc editor_text_clearTabStops_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_selectNextTabStop_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_selectPrevTabStop_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_applyCompletion_void_TextDocumentEditor_JsonNode_impl*( - self: TextDocumentEditor; completion: JsonNode) = - discard -proc editor_text_applySelectedCompletion_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_showHoverFor_void_TextDocumentEditor_Cursor_impl*( - self: TextDocumentEditor; cursor: Cursor) = - discard -proc editor_text_showHoverForCurrent_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_hideHover_void_TextDocumentEditor_impl*(self: TextDocumentEditor) = - discard -proc editor_text_cancelDelayedHideHover_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_hideHoverDelayed_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_clearDiagnostics_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_updateDiagnosticsForCurrent_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_showDiagnosticsForCurrent_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_setReadOnly_void_TextDocumentEditor_bool_impl*( - self: TextDocumentEditor; readOnly: bool) = - discard -proc editor_text_setFileReadOnly_void_TextDocumentEditor_bool_impl*( - self: TextDocumentEditor; readOnly: bool) = - discard -proc editor_text_isRunningSavedCommands_bool_TextDocumentEditor_impl*( - self: TextDocumentEditor): bool = - discard -proc editor_text_runSavedCommands_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_clearCurrentCommandHistory_void_TextDocumentEditor_bool_impl*( - self: TextDocumentEditor; retainLast: bool = false) = - discard -proc editor_text_saveCurrentCommandHistory_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_getSelection_Selection_TextDocumentEditor_impl*( - self: TextDocumentEditor): Selection = - discard -proc editor_text_setSelection_void_TextDocumentEditor_Selection_impl*( - self: TextDocumentEditor; selection: Selection) = - discard -proc editor_text_setTargetSelection_void_TextDocumentEditor_Selection_impl*( - self: TextDocumentEditor; selection: Selection) = - discard -proc editor_text_enterChooseCursorMode_void_TextDocumentEditor_string_impl*( - self: TextDocumentEditor; action: string) = - discard -proc editor_text_recordCurrentCommand_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_runSingleClickCommand_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_runDoubleClickCommand_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_runTripleClickCommand_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc editor_text_runDragCommand_void_TextDocumentEditor_impl*( - self: TextDocumentEditor) = - discard -proc debugger_prevDebuggerView_void_Debugger_impl*() = - discard -proc debugger_nextDebuggerView_void_Debugger_impl*() = - discard -proc debugger_setDebuggerView_void_Debugger_string_impl*(view: string) = - discard -proc debugger_selectFirstVariable_void_Debugger_impl*() = - discard -proc debugger_selectLastVariable_void_Debugger_impl*() = - discard -proc debugger_prevThread_void_Debugger_impl*() = - discard -proc debugger_nextThread_void_Debugger_impl*() = - discard -proc debugger_prevStackFrame_void_Debugger_impl*() = - discard -proc debugger_nextStackFrame_void_Debugger_impl*() = - discard -proc debugger_openFileForCurrentFrame_void_Debugger_impl*() = - discard -proc debugger_prevVariable_void_Debugger_impl*() = - discard -proc debugger_nextVariable_void_Debugger_impl*() = - discard -proc debugger_expandVariable_void_Debugger_impl*() = - discard -proc debugger_collapseVariable_void_Debugger_impl*() = - discard -proc debugger_stopDebugSession_void_Debugger_impl*() = - discard -proc debugger_stopDebugSessionDelayed_void_Debugger_impl*() = - discard -proc debugger_runConfiguration_void_Debugger_string_impl*(name: string) = - discard -proc debugger_chooseRunConfiguration_void_Debugger_impl*() = - discard -proc debugger_runLastConfiguration_void_Debugger_impl*() = - discard -proc debugger_addBreakpoint_void_Debugger_EditorId_int_impl*(editorId: EditorId; - line: int) = - discard -proc debugger_removeBreakpoint_void_Debugger_string_int_impl*(path: string; - line: int) = - discard -proc debugger_toggleBreakpointEnabled_void_Debugger_string_int_impl*( - path: string; line: int) = - discard -proc debugger_toggleAllBreakpointsEnabled_void_Debugger_impl*() = - discard -proc debugger_toggleBreakpointsEnabled_void_Debugger_impl*() = - discard -proc debugger_editBreakpoints_void_Debugger_impl*() = - discard -proc debugger_continueExecution_void_Debugger_impl*() = - discard -proc debugger_stepOver_void_Debugger_impl*() = - discard -proc debugger_stepIn_void_Debugger_impl*() = - discard -proc debugger_stepOut_void_Debugger_impl*() = - discard -proc lsp_lspLogVerbose_void_bool_impl*(val: bool) = - discard -proc lsp_lspToggleLogServerDebug_void_impl*() = - discard -proc lsp_lspLogServerDebug_void_bool_impl*(val: bool) = - discard -proc popup_selector_setPreviewVisible_void_SelectorPopup_bool_impl*( - self: SelectorPopup; visible: bool) = - discard -proc popup_selector_togglePreview_void_SelectorPopup_impl*(self: SelectorPopup) = - discard -proc popup_selector_getSelectedItemJson_JsonNode_SelectorPopup_impl*( - self: SelectorPopup): JsonNode = - discard -proc popup_selector_accept_void_SelectorPopup_impl*(self: SelectorPopup) = - discard -proc popup_selector_cancel_void_SelectorPopup_impl*(self: SelectorPopup) = - discard -proc popup_selector_sort_void_SelectorPopup_ToggleBool_impl*(self: SelectorPopup; - sort: ToggleBool) = - discard -proc popup_selector_setMinScore_void_SelectorPopup_float_bool_impl*( - self: SelectorPopup; value: float; add: bool = false) = - discard -proc popup_selector_prev_void_SelectorPopup_int_impl*(self: SelectorPopup; - count: int = 1) = - discard -proc popup_selector_next_void_SelectorPopup_int_impl*(self: SelectorPopup; - count: int = 1) = - discard -proc popup_selector_toggleFocusPreview_void_SelectorPopup_impl*( - self: SelectorPopup) = - discard -proc popup_selector_setFocusPreview_void_SelectorPopup_bool_impl*( - self: SelectorPopup; focus: bool) = - discard -proc editor_reapplyConfigKeybindings_void_App_bool_bool_bool_impl*( - app: bool = false; home: bool = false; workspace: bool = false) = - discard -proc editor_splitView_void_App_impl*() = - discard -proc editor_runExternalCommand_void_App_string_seq_string_string_impl*( - command: string; args: seq[string] = @[]; workingDir: string = "") = - discard -proc editor_disableLogFrameTime_void_App_bool_impl*(disable: bool) = - discard -proc editor_enableDebugPrintAsyncAwaitStackTrace_void_App_bool_impl*(enable: bool) = - discard -proc editor_showDebuggerView_void_App_impl*() = - discard -proc editor_setLocationListFromCurrentPopup_void_App_impl*() = - discard -proc editor_getBackend_Backend_App_impl*(): Backend = - discard -proc editor_getHostOs_string_App_impl*(): string = - discard -proc editor_loadApplicationFile_Option_string_App_string_impl*(path: string): Option[ - string] = - discard -proc editor_toggleShowDrawnNodes_void_App_impl*() = - discard -proc editor_setMaxViews_void_App_int_bool_impl*(maxViews: int; - openExisting: bool = false) = - discard -proc editor_saveAppState_void_App_impl*() = - discard -proc editor_requestRender_void_App_bool_impl*(redrawEverything: bool = false) = - discard -proc editor_setHandleInputs_void_App_string_bool_impl*(context: string; - value: bool) = - discard -proc editor_setHandleActions_void_App_string_bool_impl*(context: string; - value: bool) = - discard -proc editor_setConsumeAllActions_void_App_string_bool_impl*(context: string; - value: bool) = - discard -proc editor_setConsumeAllInput_void_App_string_bool_impl*(context: string; - value: bool) = - discard -proc editor_clearWorkspaceCaches_void_App_impl*() = - discard -proc editor_openGithubWorkspace_void_App_string_string_string_impl*(user: string; - repository: string; branchOrHash: string) = - discard -proc editor_openRemoteServerWorkspace_void_App_string_impl*(url: string) = - discard -proc editor_callScriptAction_JsonNode_App_string_JsonNode_impl*(context: string; - args: JsonNode): JsonNode = - discard -proc editor_addScriptAction_void_App_string_string_seq_tuple_name_string_typ_string_string_bool_string_impl*( - name: string; docs: string = ""; - params: seq[tuple[name: string, typ: string]] = @[]; - returnType: string = ""; active: bool = false; context: string = "script") = - discard -proc editor_openLocalWorkspace_void_App_string_impl*(path: string) = - discard -proc editor_getFlag_bool_App_string_bool_impl*(flag: string; - default: bool = false): bool = - discard -proc editor_setFlag_void_App_string_bool_impl*(flag: string; value: bool) = - discard -proc editor_toggleFlag_void_App_string_impl*(flag: string) = - discard -proc editor_setOption_void_App_string_JsonNode_bool_impl*(option: string; - value: JsonNode; override: bool = true) = - discard -proc editor_quit_void_App_impl*() = - discard -proc editor_quitImmediately_void_App_int_impl*(exitCode: int = 0) = - discard -proc editor_help_void_App_string_impl*(about: string = "") = - discard -proc editor_loadWorkspaceFile_void_App_string_string_impl*(path: string; - callback: string) = - discard -proc editor_writeWorkspaceFile_void_App_string_string_impl*(path: string; - content: string) = - discard -proc editor_changeFontSize_void_App_float32_impl*(amount: float32) = - discard -proc editor_changeLineDistance_void_App_float32_impl*(amount: float32) = - discard -proc editor_platformTotalLineHeight_float32_App_impl*(): float32 = - discard -proc editor_platformLineHeight_float32_App_impl*(): float32 = - discard -proc editor_platformLineDistance_float32_App_impl*(): float32 = - discard -proc editor_changeLayoutProp_void_App_string_float32_impl*(prop: string; - change: float32) = - discard -proc editor_toggleStatusBarLocation_void_App_impl*() = - discard -proc editor_logs_void_App_bool_impl*(scrollToBottom: bool = false) = - discard -proc editor_toggleConsoleLogger_void_App_impl*() = - discard -proc editor_showEditor_void_App_EditorId_Option_int_impl*(editorId: EditorId; - viewIndex: Option[int] = int.none) = - discard -proc editor_getVisibleEditors_seq_EditorId_App_impl*(): seq[EditorId] = - discard -proc editor_getHiddenEditors_seq_EditorId_App_impl*(): seq[EditorId] = - discard -proc editor_getExistingEditor_Option_EditorId_App_string_impl*(path: string): Option[ - EditorId] = - discard -proc editor_getOrOpenEditor_Option_EditorId_App_string_impl*(path: string): Option[ - EditorId] = - discard -proc editor_closeView_void_App_int_bool_bool_impl*(index: int; - keepHidden: bool = true; restoreHidden: bool = true) = - discard -proc editor_closeCurrentView_void_App_bool_bool_impl*(keepHidden: bool = true; - restoreHidden: bool = true) = - discard -proc editor_closeOtherViews_void_App_bool_impl*(keepHidden: bool = true) = - discard -proc editor_moveCurrentViewToTop_void_App_impl*() = - discard -proc editor_nextView_void_App_impl*() = - discard -proc editor_prevView_void_App_impl*() = - discard -proc editor_toggleMaximizeView_void_App_impl*() = - discard -proc editor_moveCurrentViewPrev_void_App_impl*() = - discard -proc editor_moveCurrentViewNext_void_App_impl*() = - discard -proc editor_setLayout_void_App_string_impl*(layout: string) = - discard -proc editor_commandLine_void_App_string_impl*(initialValue: string = "") = - discard -proc editor_exitCommandLine_void_App_impl*() = - discard -proc editor_selectPreviousCommandInHistory_void_App_impl*() = - discard -proc editor_selectNextCommandInHistory_void_App_impl*() = - discard -proc editor_executeCommandLine_bool_App_impl*(): bool = - discard -proc editor_writeFile_void_App_string_bool_impl*(path: string = ""; - appFile: bool = false) = - discard -proc editor_loadFile_void_App_string_impl*(path: string = "") = - discard -proc editor_removeFromLocalStorage_void_App_impl*() = - discard -proc editor_loadTheme_void_App_string_impl*(name: string) = - discard -proc editor_chooseTheme_void_App_impl*() = - discard -proc editor_createFile_void_App_string_impl*(path: string) = - discard -proc editor_mountVfs_void_App_string_string_JsonNode_impl*(parentPath: string; - prefix: string; config: JsonNode) = - discard -proc editor_browseKeybinds_void_App_bool_float_float_float_impl*( - preview: bool = true; scaleX: float = 0.9; scaleY: float = 0.8; - previewScale: float = 0.4) = - discard -proc editor_connectCollaborator_void_App_int_impl*(port: int = 6969) = - discard -proc editor_hostCollaborator_void_App_int_impl*(port: int = 6969) = - discard -proc editor_chooseFile_void_App_bool_float_float_float_impl*( - preview: bool = true; scaleX: float = 0.8; scaleY: float = 0.8; - previewScale: float = 0.5) = - discard -proc editor_openLastEditor_void_App_impl*() = - discard -proc editor_chooseOpen_void_App_bool_float_float_float_impl*( - preview: bool = true; scaleX: float = 0.8; scaleY: float = 0.8; - previewScale: float = 0.6) = - discard -proc editor_chooseOpenDocument_void_App_impl*() = - discard -proc editor_gotoNextLocation_void_App_impl*() = - discard -proc editor_gotoPrevLocation_void_App_impl*() = - discard -proc editor_chooseLocation_void_App_impl*() = - discard -proc editor_searchGlobalInteractive_void_App_impl*() = - discard -proc editor_searchGlobal_void_App_string_impl*(query: string) = - discard -proc editor_installTreesitterParser_void_App_string_string_impl*( - language: string; host: string = "github.com") = - discard -proc editor_chooseGitActiveFiles_void_App_bool_impl*(all: bool = false) = - discard -proc editor_exploreFiles_void_App_string_impl*(root: string = "") = - discard -proc editor_exploreUserConfigDir_void_App_impl*() = - discard -proc editor_exploreAppConfigDir_void_App_impl*() = - discard -proc editor_exploreHelp_void_App_impl*() = - discard -proc editor_exploreWorkspacePrimary_void_App_impl*() = - discard -proc editor_exploreCurrentFileDirectory_void_App_impl*() = - discard -proc editor_openPreviousEditor_void_App_impl*() = - discard -proc editor_openNextEditor_void_App_impl*() = - discard -proc editor_setGithubAccessToken_void_App_string_impl*(token: string) = - discard -proc editor_reloadConfig_void_App_bool_impl*(clearOptions: bool = false) = - discard -proc editor_reloadPlugin_void_App_impl*() = - discard -proc editor_reloadState_void_App_impl*() = - discard -proc editor_saveSession_void_App_string_impl*(sessionFile: string = "") = - discard -proc editor_logOptions_void_App_impl*() = - discard -proc editor_dumpKeymapGraphViz_void_App_string_impl*(context: string = "") = - discard -proc editor_clearCommands_void_App_string_impl*(context: string) = - discard -proc editor_getAllEditors_seq_EditorId_App_impl*(): seq[EditorId] = - discard -proc editor_setMode_void_App_string_impl*(mode: string) = - discard -proc editor_mode_string_App_impl*(): string = - discard -proc editor_getContextWithMode_string_App_string_impl*(context: string): string = - discard -proc editor_scriptRunAction_void_string_string_impl*(action: string; arg: string) = - discard -proc editor_scriptLog_void_string_impl*(message: string) = - discard -proc editor_changeAnimationSpeed_void_App_float_impl*(factor: float) = - discard -proc editor_setLeader_void_App_string_impl*(leader: string) = - discard -proc editor_setLeaders_void_App_seq_string_impl*(leaders: seq[string]) = - discard -proc editor_addLeader_void_App_string_impl*(leader: string) = - discard -proc editor_registerPluginSourceCode_void_App_string_string_impl*(path: string; - content: string) = - discard -proc editor_addCommandScript_void_App_string_string_string_string_string_string_tuple_filename_string_line_int_column_int_impl*( - context: string; subContext: string; keys: string; action: string; - arg: string = ""; description: string = ""; - source: tuple[filename: string, line: int, column: int] = ("", 0, 0)) = - discard -proc editor_removeCommand_void_App_string_string_impl*(context: string; - keys: string) = - discard -proc editor_getActivePopup_EditorId_impl*(): EditorId = - discard -proc editor_getActiveEditor_EditorId_impl*(): EditorId = - discard -proc editor_getActiveEditor2_EditorId_App_impl*(): EditorId = - discard -proc editor_logRootNode_void_App_impl*() = - discard -proc editor_sourceCurrentDocument_void_App_impl*() = - discard -proc editor_getEditorInView_EditorId_int_impl*(index: int): EditorId = - discard -proc editor_scriptIsSelectorPopup_bool_EditorId_impl*(editorId: EditorId): bool = - discard -proc editor_scriptIsTextEditor_bool_EditorId_impl*(editorId: EditorId): bool = - discard -proc editor_scriptIsAstEditor_bool_EditorId_impl*(editorId: EditorId): bool = - discard -proc editor_scriptIsModelEditor_bool_EditorId_impl*(editorId: EditorId): bool = - discard -proc editor_scriptRunActionFor_void_EditorId_string_string_impl*( - editorId: EditorId; action: string; arg: string) = - discard -proc editor_scriptInsertTextInto_void_EditorId_string_impl*(editorId: EditorId; - text: string) = - discard -proc editor_scriptTextEditorSelection_Selection_EditorId_impl*(editorId: EditorId): Selection = - discard -proc editor_scriptSetTextEditorSelection_void_EditorId_Selection_impl*( - editorId: EditorId; selection: Selection) = - discard -proc editor_scriptTextEditorSelections_seq_Selection_EditorId_impl*( - editorId: EditorId): seq[Selection] = - discard -proc editor_scriptSetTextEditorSelections_void_EditorId_seq_Selection_impl*( - editorId: EditorId; selections: seq[Selection]) = - discard -proc editor_scriptGetTextEditorLine_string_EditorId_int_impl*(editorId: EditorId; - line: int): string = - discard -proc editor_scriptGetTextEditorLineCount_int_EditorId_impl*(editorId: EditorId): int = - discard -proc editor_setSessionDataJson_void_App_string_JsonNode_bool_impl*(path: string; - value: JsonNode; override: bool = true) = - discard -proc editor_getSessionDataJson_JsonNode_App_string_JsonNode_impl*(path: string; - default: JsonNode): JsonNode = - discard -proc editor_scriptGetOptionJson_JsonNode_string_JsonNode_impl*(path: string; - default: JsonNode): JsonNode = - discard -proc editor_scriptGetOptionInt_int_string_int_impl*(path: string; default: int): int = - discard -proc editor_scriptGetOptionFloat_float_string_float_impl*(path: string; - default: float): float = - discard -proc editor_scriptGetOptionBool_bool_string_bool_impl*(path: string; - default: bool): bool = - discard -proc editor_scriptGetOptionString_string_string_string_impl*(path: string; - default: string): string = - discard -proc editor_scriptSetOptionInt_void_string_int_impl*(path: string; value: int) = - discard -proc editor_scriptSetOptionFloat_void_string_float_impl*(path: string; - value: float) = - discard -proc editor_scriptSetOptionBool_void_string_bool_impl*(path: string; value: bool) = - discard -proc editor_scriptSetOptionString_void_string_string_impl*(path: string; - value: string) = - discard -proc editor_scriptSetCallback_void_string_int_impl*(path: string; id: int) = - discard -proc editor_setRegisterText_void_App_string_string_impl*(text: string; - register: string = "") = - discard -proc editor_getRegisterText_string_App_string_impl*(register: string): string = - discard -proc editor_startRecordingKeys_void_App_string_impl*(register: string) = - discard -proc editor_stopRecordingKeys_void_App_string_impl*(register: string) = - discard -proc editor_startRecordingCommands_void_App_string_impl*(register: string) = - discard -proc editor_stopRecordingCommands_void_App_string_impl*(register: string) = - discard -proc editor_isReplayingCommands_bool_App_impl*(): bool = - discard -proc editor_isReplayingKeys_bool_App_impl*(): bool = - discard -proc editor_isRecordingCommands_bool_App_string_impl*(registry: string): bool = - discard -proc editor_replayCommands_void_App_string_impl*(register: string) = - discard -proc editor_replayKeys_void_App_string_impl*(register: string) = - discard -proc editor_inputKeys_void_App_string_impl*(input: string) = - discard -proc editor_collectGarbage_void_App_impl*() = - discard -proc editor_printStatistics_void_App_impl*() = - discard diff --git a/scripting/plugin_runtime.nim b/scripting/plugin_runtime.nim deleted file mode 100644 index 6286a4175..000000000 --- a/scripting/plugin_runtime.nim +++ /dev/null @@ -1,438 +0,0 @@ -import std/[strformat, tables, macros, json, strutils, sugar, sequtils, genasts, options, compilesettings] -import misc/[event, util, myjsonutils, macro_utils, embed_source] -import plugin_api, script_expose - -export plugin_api, util, strformat, tables, json, strutils, sugar, sequtils, scripting_api, script_expose -export embed_source.embedSource, embed_source.currentSourceLocation - -embedSource() - -type AnyDocumentEditor = TextDocumentEditor | ModelDocumentEditor - -var voidCallbacks = initTable[int, proc(args: JsonNode): void]() -var boolCallbacks = initTable[int, proc(args: JsonNode): bool]() -var anyCallbacks = initTable[int, proc(args: JsonNode): JsonNode]() -var onEditorModeChanged* = initEvent[tuple[editor: EditorId, oldMode: string, newMode: string]]() -var callbackId = 0 - -proc emscripten_notify_memory_growth*(a: int32) {.exportc.} = discard -const env* = "wasm" - -proc info*(args: varargs[string, `$`]) = - var msgLen = 0 - for arg in args: - msgLen += arg.len - var result = newStringOfCap(msgLen + 12) - result.add "[" - result.add env - result.add "] " - - for arg in args: - result.add(arg) - scriptLog(result) - -template infof*(x: static string) = - scriptLog("[" & env & "] " & fmt(x)) - -proc addCallback*(action: proc(args: JsonNode): void): int = - result = callbackId - voidCallbacks[result] = action - callbackId += 1 - -proc addCallback*(action: proc(args: JsonNode): bool): int = - result = callbackId - boolCallbacks[result] = action - callbackId += 1 - -proc addCallback*(action: proc(args: JsonNode): JsonNode): int = - result = callbackId - anyCallbacks[result] = action - callbackId += 1 - -func toJsonString[T: string](value: T): string = escapeJson(value) -func toJsonString[T: char](value: T): string = escapeJson($value) -func toJsonString[T](value: T): string = $value - -proc removeCommand*(context: string, keys: string) = - removeCommand(context, keys) - -macro runAction*(action: string, args: varargs[untyped]): untyped = - var stmts = nnkStmtList.newTree() - let str = nskVar.genSym "str" - stmts.add quote do: - var `str` = "" - for arg in args: - stmts.add quote do: - `str`.add " " - `str`.add `arg`.toJsonString - - return quote do: - `stmts` - discard scriptRunAction(`action`, `str`) - -template isTextEditor*(editorId: EditorId, injected: untyped): bool = - (scriptIsTextEditor(editorId) and ((let injected {.inject.} = TextDocumentEditor(id: editorId); true))) - -template isModelEditor*(editorId: EditorId, injected: untyped): bool = - (scriptIsModelEditor(editorId) and ((let injected {.inject.} = ModelDocumentEditor(id: editorId); true))) - -template isSelectorPopup*(editorId: EditorId, injected: untyped): bool = - (scriptIsSelectorPopup(editorId) and ((let injected {.inject.} = SelectorPopup(id: editorId); true))) - -proc handleCallbackImpl*(id: int, args: JsonNode): bool = - # infof"handleCallbackImpl {id}, {args}" - if voidCallbacks.contains(id): - voidCallbacks[id](args) - return true - elif boolCallbacks.contains(id): - return boolCallbacks[id](args) - elif anyCallbacks.contains(id): - return anyCallbacks[id](args).isNotNil - return false - -proc handleAnyCallbackImpl*(id: int, args: JsonNode): JsonNode = - # infof"handleAnyCallbackImpl {id}, {args}" - if voidCallbacks.contains(id): - voidCallbacks[id](args) - return newJNull() - elif boolCallbacks.contains(id): - return newJBool(boolCallbacks[id](args)) - elif anyCallbacks.contains(id): - return anyCallbacks[id](args) - return nil - -proc handleScriptActionImpl*(name: string, args: JsonNode): JsonNode = - # infof"handleScriptActionImpl {name}, {args}" - if scriptActions.contains(name): - return scriptActions[name](args) - return nil - -proc runAction*(id: EditorId, action: string, arg: string = "") = - scriptRunActionFor(id, action, arg) - -proc runAction*(editor: TextDocumentEditor, action: string, arg: string = "") = - scriptRunActionFor(editor.id, action, arg) - -proc selection*(editor: TextDocumentEditor): Selection = - return editor.getSelection() - -proc selections*(editor: TextDocumentEditor): seq[Selection] = - return editor.getSelections() - -proc `selection=`*(editor: TextDocumentEditor, selection: Selection) = - editor.setSelection(selection) - -proc `selections=`*(editor: TextDocumentEditor, selections: seq[Selection]) = - editor.setSelections(selections) - -proc `targetSelection=`*(editor: TextDocumentEditor, selection: Selection) = - editor.setTargetSelection(selection) - -proc getOption*[T](path: string, default: T = T.default): T = - try: - let json: JsonNode = plugin_api.getOptionJson(path, newJNull()) - return json.jsonTo T - except: - return default - -proc setOption*[T](path: string, value: T) = - plugin_api.setOption(path, value.toJson) - -proc setConfig*[T](editor: TextDocumentEditor, path: string, value: T) = - plugin_api.setConfig(editor, path, value.toJson) - -proc getSessionData*[T](path: string, default: T = T.default): T = - return getSessionDataJson(path, default.toJson).jsonTo(T) - -proc setSessionData*[T](path: string, value: T) = - setSessionDataJson(path, value.toJson) - -proc bindArgs*(args: NimNode): tuple[stmts: NimNode, arg: NimNode] = - var stmts = nnkStmtList.newTree() - let str = nskVar.genSym "str" - stmts.addAst(str): - var str = "" - for arg in args: - if arg.kind == nnkExprEqExpr: - # todo: check name - stmts.addAst(str, arg = arg[1]): - str.add " " - str.add arg.toJsonString - else: - stmts.addAst(str, arg): - str.add " " - str.add arg.toJsonString - - return (stmts, str) - -var keysPrefix*: string = "" - -template withKeys*(keys: varargs[string], body: untyped): untyped = - for key in keys: - let oldValue = keysPrefix - keysPrefix = keysPrefix & key - defer: - keysPrefix = oldValue - body - -proc loadWorkspaceFile*(path: string, action: proc(content: Option[string]): void {.closure, gcsafe.}) = - let key = "$loadWorkspaceFile" & $callbackId - callbackId += 1 - scriptActions[key] = proc(args: JsonNode): JsonNode = - action(args.jsonTo(Option[string])) - scriptActions.del(key) - return newJNull() - addScriptAction(key, "", @[("path", "string"), ("callback", "proc(content: Option[string])")], "", false) - loadWorkspaceFile(path, key) - -proc runProcess*(process: string, args: seq[string], workingDir: Option[string] = string.none, eval: bool = false, callback: proc(output: string, err: string): void {.closure, gcsafe.} = nil) = - let key = "$runProcess" & $callbackId - callbackId += 1 - scriptActions[key] = proc(args: JsonNode): JsonNode = - let args = args.jsonTo(Option[tuple[output: string, err: string]]) - if callback != nil and args.isSome: - callback(args.get.output, args.get.err) - scriptActions.del(key) - return newJNull() - addScriptAction(key, "", @[("process", "string"), ("args", "seq[string]"), ("workingDir", "Option[string]"), ("callback", "proc(content: Option[string])")], "", false) - runProcess(process, args, key.some, workingDir, eval) - -macro addCommand*(context: string, keys: string, action: string, args: varargs[untyped]): untyped = - let (stmts, str) = bindArgs(args) - return genAst(stmts, context, keys, action, str): - stmts - addCommandScript(context, "", keysPrefix & keys, action, str, source = currentSourceLocation(-2)) - -proc addCommand*(context: string, keys: string, action: proc(): void, source = currentSourceLocation()) = - let key = "$" & context & keys - scriptActions[key] = proc(args: JsonNode): JsonNode = - action() - return newJNull() - - # addCommandScript(context, "", keysPrefix & keys, "lambda-action", key.toJsonString) - addCommandScript(context, "", keysPrefix & keys, key, "", source = source) - -proc addCommandDesc(context: string, keys: string, desc: string, action: proc(): void, source = currentSourceLocation()) = - let key = "$" & context & keys - scriptActions[key] = proc(args: JsonNode): JsonNode = - action() - return newJNull() - - addCommandScript(context, "", keysPrefix & keys, key, "", desc, source) - -proc addCommand*[T: proc](context: string, keys: string, args: string, action: T, source = currentSourceLocation()) = - let key = "$" & context & keys - scriptActions[key] = proc(args: JsonNode): JsonNode = - return callJson(action, args) - - addCommandScript(context, "", keysPrefix & keys, key, args, source = source) - -proc addCommandDesc[T: proc](context: string, keys: string, args: string, desc: string, action: T, source = currentSourceLocation()) = - let key = "$" & context & keys - scriptActions[key] = proc(args: JsonNode): JsonNode = - return callJson(action, args) - - addCommandScript(context, "", keysPrefix & keys, key, args, desc, source) - -template addCommandBlock*(context: string, keys: string, body: untyped): untyped = - block: - let p = proc() = - body - addCommand context, keys, p, currentSourceLocation(-2) - -template addCommandBlockDesc*(context: string, keys: string, desc: string, body: untyped): untyped = - block: - let p = proc() = - body - addCommandDesc context, keys, desc, p, currentSourceLocation(-2) - -func getContextWithMode*(context: string, mode: string): string = - if mode.contains("."): - return mode - if mode.len == 0 or mode[0] == '#': - return context & mode - else: - return context & "." & mode - -# App commands -template addEditorCommandBlock*(mode: string, keys: string, body: untyped): untyped = - addCommand getContextWithMode("editor", mode), keys, proc() = - body - -proc addEditorCommand*(mode: string, keys: string, action: proc(): void, source = currentSourceLocation()) = - block: - let p = proc() = - action() - addCommand getContextWithMode("editor", mode), keys, p, source = source - -macro addEditorCommand*(mode: string, keys: string, action: string, args: varargs[untyped]): untyped = - var stmts = nnkStmtList.newTree() - let str = nskVar.genSym "str" - stmts.add quote do: - var `str` = "" - for arg in args: - stmts.add quote do: - `str`.add " " - `str`.add `arg`.toJsonString - - return genAst(stmts, mode, keys, action, str): - stmts - addCommandScript(getContextWithMode("editor", mode), "", keysPrefix & keys, action, str, source = currentSourceLocation(-2)) - -# Text commands -template addTextCommandBlock*(mode: string, keys: string, body: untyped): untyped = - block: - let p = proc() = - try: - let editor {.inject, used.} = TextDocumentEditor(id: getActiveEditor()) - body - except: - let m {.inject.} = mode - let k {.inject.} = keys - infof"TextCommandBlock {m} {k}: {getCurrentExceptionMsg()}" - addCommand getContextWithMode("editor.text", mode), keys, p, currentSourceLocation(-2) - -template addTextCommandBlockDesc*(mode: string, keys: string, desc: string, body: untyped): untyped = - block: - let p = proc() = - try: - let editor {.inject, used.} = TextDocumentEditor(id: getActiveEditor()) - body - except: - let m {.inject.} = mode - let k {.inject.} = keys - infof"TextCommandBlock {m} {k}: {getCurrentExceptionMsg()}" - addCommandDesc getContextWithMode("editor.text", mode), keys, desc, p, currentSourceLocation(-2) - - -proc addTextCommand*(mode: string, keys: string, action: proc(editor: TextDocumentEditor): void, source = currentSourceLocation()) = - block: - let context = getContextWithMode("editor.text", mode) - let p = proc() = - try: - action(TextDocumentEditor(id: getActiveEditor())) - except: - let m {.inject.} = mode - let k {.inject.} = keys - infof"TextCommand {m} {k}: {getCurrentExceptionMsg()}" - addCommand context, keys, p, source = source - -macro addTextCommand*(mode: string, keys: string, action: string, args: varargs[untyped]): untyped = - let (stmts, str) = bindArgs(args) - return genAst(stmts, mode, keys, action, str): - stmts - addCommandScript(getContextWithMode("editor.text", mode), "", keysPrefix & keys, action, str, source = currentSourceLocation(-2)) - -var customMoves = initTable[string, proc(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection]() -proc handleCustomTextMove*(editor: TextDocumentEditor, move: string, cursor: Cursor, count: int): Option[Selection] = - if customMoves.contains(move): - return customMoves[move](editor, cursor, count).some - return Selection.none - -block: # Custom moves - let id = addCallback proc(args: JsonNode): JsonNode = - type Payload = object - editor: EditorId - move: string - cursor: Cursor - count: int - - let input = args.jsonTo(Payload) - if input.editor.EditorId.isTextEditor editor: - let selection = handleCustomTextMove(editor, input.move, input.cursor, input.count) - if selection.isSome: - return selection.toJson - return nil - else: - infof"Custom move: editor {input.editor} is not a text editor" - return nil - - scriptSetCallback("editor.text.custom-move", id) - -proc addCustomTextMove*(name: string, action: proc(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection) = - customMoves[name] = action - -# Model commands -template addModelCommandBlock*(mode: static[string], keys: string, body: untyped): untyped = - let context = getContextWithMode("editor.model", mode) - addCommand context, keys, proc() = - try: - let editor {.inject, used.} = ModelDocumentEditor(id: getActiveEditor()) - body - except: - let m {.inject.} = mode - let k {.inject.} = keys - infof"ModelCommandBlock {m} {k}: {getCurrentExceptionMsg()}" - -proc addModelCommand*(mode: string, keys: string, action: proc(editor: ModelDocumentEditor): void) = - let context = getContextWithMode("editor.model", mode) - addCommand context, keys, proc() = - action(ModelDocumentEditor(id: getActiveEditor())) - -macro addModelCommand*(mode: static[string], keys: string, action: string, args: varargs[untyped]): untyped = - let context = getContextWithMode("editor.model", mode) - var stmts = nnkStmtList.newTree() - let str = nskVar.genSym "str" - stmts.add quote do: - var `str` = "" - for arg in args: - stmts.add quote do: - `str`.add " " - `str`.add `arg`.toJsonString - - return genAst(stmts, context, keys, action, str): - stmts - addCommandScript(context, "", keysPrefix & keys, action, str, source = currentSourceLocation(-2)) - -proc setModelInputHandler*(context: string, action: proc(editor: ModelDocumentEditor, input: string): bool) = - let id = addCallback proc(args: JsonNode): bool = - let input = args.str - action(ModelDocumentEditor(id: getActiveEditor()), input) - scriptSetCallback("editor.model.input-handler." & context, id) - # setHandleInputs("editor.model." & context, true) - -when defined(wasm): - macro wasmexport*(t: typed): untyped = - if t.kind notin {nnkProcDef, nnkFuncDef}: - error("Can only export procedures", t) - let - newProc = copyNimTree(t) - codeGen = nnkExprColonExpr.newTree(ident"codegendecl", newLit"EMSCRIPTEN_KEEPALIVE $# $#$#") - if newProc[4].kind == nnkEmpty: - newProc[4] = nnkPragma.newTree(codeGen) - else: - newProc[4].add codeGen - newProc[4].add ident"exportC" - result = newStmtList() - result.add: - quote do: - {.emit: "/*INCLUDESECTION*/\n#include ".} - result.add: - newProc - - proc NimMain() {.importc.} - proc emscripten_stack_init() {.importc.} - # proc init_pthread_self() {.importc.} # todo - - proc stackSave*(): uint32 {.wasmexport.} = - assert false, "stackSave not supported" - - proc stackRestore*(n: uint32) {.wasmexport.} = - assert false, "stackRestore not supported" - - proc stackAlloc*(n: uint32): pointer {.wasmexport.} = - assert false, "stackAlloc not supported" - - proc plugin_main*() {.wasmexport.} = - emscripten_stack_init() - # init_pthread_self() - NimMain() - - proc my_alloc*(len: uint32): pointer {.wasmexport.} = - return alloc0(len) - - proc my_dealloc*(p: pointer) {.wasmexport.} = - dealloc(p) - -else: - template wasmexport*(t: typed): untyped = t diff --git a/scripting/plugin_runtime_impl.nim b/scripting/plugin_runtime_impl.nim deleted file mode 100644 index 3abd67256..000000000 --- a/scripting/plugin_runtime_impl.nim +++ /dev/null @@ -1,62 +0,0 @@ -import std/[compilesettings] -import plugin_runtime -import misc/[event, embed_source] - -generateEmbeddedSourceMap() - -const projPath = querySetting(SingleValueSetting.projectPath).normalizeSourcePath & "/" - -for path, content in embeddedSourceFiles(): - let relPath = if path.startsWith(projPath): - path[projPath.len..^1] - else: - path - registerPluginSourceCode(relPath, content) - -proc handleCallback*(id: int, args: JsonNode): bool = handleCallbackImpl(id, args) -proc handleAnyCallback*(id: int, args: JsonNode): JsonNode = handleAnyCallbackImpl(id, args) -proc handleScriptAction*(name: string, args: JsonNode): JsonNode = handleScriptActionImpl(name, args) - -proc postInitializeWasm(): bool {.wasmexport.} = - when compiles(postInitialize()): - try: - return postInitialize() - except: - info &"postInitializeWasm failed: {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - return false - else: - true - -proc handleCallbackWasm(id: int32, args: cstring): bool {.wasmexport.} = - try: - return handleCallback(id.int, ($args).parseJson) - except: - info &"handleCallbackWasm failed: {id} {args}: {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - return false - -var handleAnyCallbackWasmResult = "" -proc handleAnyCallbackWasm(id: int32, args: cstring): cstring {.wasmexport.} = - # infof"handleAnyCallbackWasm {id.int} {args}" - try: - let res = handleAnyCallback(id.int, ($args).parseJson) - if res.isNil: - return "" - handleAnyCallbackWasmResult = $res - return handleAnyCallbackWasmResult.cstring - except: - info &"handleAnyCallbackWasm failed: {id.int} {args}: {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - return cstring ("error: " & getCurrentExceptionMsg()) - -var handleScriptActionWasmResult = "" -proc handleScriptActionWasm(name: cstring, args: cstring): cstring {.wasmexport.} = - # infof"handleScriptActionWasm {name} {args}" - try: - let res = handleScriptAction($name, ($args).parseJson) - if res.isNil: - return "" - handleScriptActionWasmResult = $res - return handleScriptActionWasmResult.cstring - except: - info &"handleScriptActionWasm failed: {name} '{args}': {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - handleScriptActionWasmResult = "error: " & getCurrentExceptionMsg() - return handleScriptActionWasmResult.cstring diff --git a/scripting/plugins_api_wasm.nim b/scripting/plugins_api_wasm.nim deleted file mode 100644 index e7da5b98c..000000000 --- a/scripting/plugins_api_wasm.nim +++ /dev/null @@ -1,60 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc plugins_bindKeys_void_PluginService_string_string_string_string_string_string_tuple_filename_string_line_int_column_int_wasm( - arg: cstring): cstring {.importc.} -proc bindKeys*(context: string; subContext: string; keys: string; - action: string; arg: string = ""; description: string = ""; - source: tuple[filename: string, line: int, column: int] = ("", 0, 0)) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add context.toJson() - argsJson.add subContext.toJson() - argsJson.add keys.toJson() - argsJson.add action.toJson() - argsJson.add arg.toJson() - argsJson.add description.toJson() - argsJson.add source.toJson() - let argsJsonString = $argsJson - let res {.used.} = plugins_bindKeys_void_PluginService_string_string_string_string_string_string_tuple_filename_string_line_int_column_int_wasm( - argsJsonString.cstring) - - -proc plugins_callScriptAction_JsonNode_PluginService_string_JsonNode_wasm( - arg: cstring): cstring {.importc.} -proc callScriptAction*(context: string; args: JsonNode): JsonNode {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add context.toJson() - argsJson.add args.toJson() - let argsJsonString = $argsJson - let res {.used.} = plugins_callScriptAction_JsonNode_PluginService_string_JsonNode_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc plugins_addScriptAction_void_PluginService_string_string_seq_tuple_name_string_typ_string_string_bool_string_bool_wasm( - arg: cstring): cstring {.importc.} -proc addScriptAction*(name: string; docs: string = ""; - params: seq[tuple[name: string, typ: string]] = @[]; - returnType: string = ""; active: bool = false; - context: string = "script"; override: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add name.toJson() - argsJson.add docs.toJson() - argsJson.add params.toJson() - argsJson.add returnType.toJson() - argsJson.add active.toJson() - argsJson.add context.toJson() - argsJson.add override.toJson() - let argsJsonString = $argsJson - let res {.used.} = plugins_addScriptAction_void_PluginService_string_string_seq_tuple_name_string_typ_string_string_bool_string_bool_wasm( - argsJsonString.cstring) - diff --git a/scripting/popup_selector_api_wasm.nim b/scripting/popup_selector_api_wasm.nim deleted file mode 100644 index 1b863631f..000000000 --- a/scripting/popup_selector_api_wasm.nim +++ /dev/null @@ -1,136 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc popup_selector_setPreviewVisible_void_SelectorPopup_bool_wasm(arg: cstring): cstring {. - importc.} -proc setPreviewVisible*(self: SelectorPopup; visible: bool) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add visible.toJson() - let argsJsonString = $argsJson - let res {.used.} = popup_selector_setPreviewVisible_void_SelectorPopup_bool_wasm( - argsJsonString.cstring) - - -proc popup_selector_togglePreview_void_SelectorPopup_wasm(arg: cstring): cstring {. - importc.} -proc togglePreview*(self: SelectorPopup) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = popup_selector_togglePreview_void_SelectorPopup_wasm( - argsJsonString.cstring) - - -proc popup_selector_getSelectedItemJson_JsonNode_SelectorPopup_wasm(arg: cstring): cstring {. - importc.} -proc getSelectedItemJson*(self: SelectorPopup): JsonNode {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = popup_selector_getSelectedItemJson_JsonNode_SelectorPopup_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc popup_selector_pop_void_SelectorPopup_wasm(arg: cstring): cstring {.importc.} -proc pop*(self: SelectorPopup) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = popup_selector_pop_void_SelectorPopup_wasm( - argsJsonString.cstring) - - -proc popup_selector_accept_void_SelectorPopup_wasm(arg: cstring): cstring {. - importc.} -proc accept*(self: SelectorPopup) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = popup_selector_accept_void_SelectorPopup_wasm( - argsJsonString.cstring) - - -proc popup_selector_cancel_void_SelectorPopup_wasm(arg: cstring): cstring {. - importc.} -proc cancel*(self: SelectorPopup) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = popup_selector_cancel_void_SelectorPopup_wasm( - argsJsonString.cstring) - - -proc popup_selector_sort_void_SelectorPopup_ToggleBool_wasm(arg: cstring): cstring {. - importc.} -proc sort*(self: SelectorPopup; sort: ToggleBool) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add sort.toJson() - let argsJsonString = $argsJson - let res {.used.} = popup_selector_sort_void_SelectorPopup_ToggleBool_wasm( - argsJsonString.cstring) - - -proc popup_selector_setMinScore_void_SelectorPopup_float_bool_wasm(arg: cstring): cstring {. - importc.} -proc setMinScore*(self: SelectorPopup; value: float; add: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add value.toJson() - argsJson.add add.toJson() - let argsJsonString = $argsJson - let res {.used.} = popup_selector_setMinScore_void_SelectorPopup_float_bool_wasm( - argsJsonString.cstring) - - -proc popup_selector_prev_void_SelectorPopup_int_wasm(arg: cstring): cstring {. - importc.} -proc prev*(self: SelectorPopup; count: int = 1) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add count.toJson() - let argsJsonString = $argsJson - let res {.used.} = popup_selector_prev_void_SelectorPopup_int_wasm( - argsJsonString.cstring) - - -proc popup_selector_next_void_SelectorPopup_int_wasm(arg: cstring): cstring {. - importc.} -proc next*(self: SelectorPopup; count: int = 1) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add count.toJson() - let argsJsonString = $argsJson - let res {.used.} = popup_selector_next_void_SelectorPopup_int_wasm( - argsJsonString.cstring) - - -proc popup_selector_setFocusPreview_void_SelectorPopup_bool_wasm(arg: cstring): cstring {. - importc.} -proc setFocusPreview*(self: SelectorPopup; focus: bool) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - argsJson.add focus.toJson() - let argsJsonString = $argsJson - let res {.used.} = popup_selector_setFocusPreview_void_SelectorPopup_bool_wasm( - argsJsonString.cstring) - - -proc popup_selector_toggleFocusPreview_void_SelectorPopup_wasm(arg: cstring): cstring {. - importc.} -proc toggleFocusPreview*(self: SelectorPopup) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add self.toJson() - let argsJsonString = $argsJson - let res {.used.} = popup_selector_toggleFocusPreview_void_SelectorPopup_wasm( - argsJsonString.cstring) - diff --git a/scripting/process_api_wasm.nim b/scripting/process_api_wasm.nim deleted file mode 100644 index 73fe1be9e..000000000 --- a/scripting/process_api_wasm.nim +++ /dev/null @@ -1,22 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc process_runProcess_void_PluginService_string_seq_string_Option_string_Option_string_bool_wasm( - arg: cstring): cstring {.importc.} -proc runProcess*(process: string; args: seq[string]; - callback: Option[string] = string.none; - workingDir: Option[string] = string.none; eval: bool = false) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add process.toJson() - argsJson.add args.toJson() - argsJson.add callback.toJson() - argsJson.add workingDir.toJson() - argsJson.add eval.toJson() - let argsJsonString = $argsJson - let res {.used.} = process_runProcess_void_PluginService_string_seq_string_Option_string_Option_string_bool_wasm( - argsJsonString.cstring) - diff --git a/scripting/registers_api_wasm.nim b/scripting/registers_api_wasm.nim deleted file mode 100644 index 2dae62744..000000000 --- a/scripting/registers_api_wasm.nim +++ /dev/null @@ -1,120 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc registers_setRegisterText_void_Registers_string_string_wasm(arg: cstring): cstring {. - importc.} -proc setRegisterText*(text: string; register: string = "") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add text.toJson() - argsJson.add register.toJson() - let argsJsonString = $argsJson - let res {.used.} = registers_setRegisterText_void_Registers_string_string_wasm( - argsJsonString.cstring) - - -proc registers_getRegisterText_string_Registers_string_wasm(arg: cstring): cstring {. - importc.} -proc getRegisterText*(register: string): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add register.toJson() - let argsJsonString = $argsJson - let res {.used.} = registers_getRegisterText_string_Registers_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc registers_startRecordingKeys_void_Registers_string_wasm(arg: cstring): cstring {. - importc.} -proc startRecordingKeys*(register: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add register.toJson() - let argsJsonString = $argsJson - let res {.used.} = registers_startRecordingKeys_void_Registers_string_wasm( - argsJsonString.cstring) - - -proc registers_stopRecordingKeys_void_Registers_string_wasm(arg: cstring): cstring {. - importc.} -proc stopRecordingKeys*(register: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add register.toJson() - let argsJsonString = $argsJson - let res {.used.} = registers_stopRecordingKeys_void_Registers_string_wasm( - argsJsonString.cstring) - - -proc registers_startRecordingCommands_void_Registers_string_wasm(arg: cstring): cstring {. - importc.} -proc startRecordingCommands*(register: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add register.toJson() - let argsJsonString = $argsJson - let res {.used.} = registers_startRecordingCommands_void_Registers_string_wasm( - argsJsonString.cstring) - - -proc registers_stopRecordingCommands_void_Registers_string_wasm(arg: cstring): cstring {. - importc.} -proc stopRecordingCommands*(register: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add register.toJson() - let argsJsonString = $argsJson - let res {.used.} = registers_stopRecordingCommands_void_Registers_string_wasm( - argsJsonString.cstring) - - -proc registers_isReplayingCommands_bool_Registers_wasm(arg: cstring): cstring {. - importc.} -proc isReplayingCommands*(): bool {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = registers_isReplayingCommands_bool_Registers_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc registers_isReplayingKeys_bool_Registers_wasm(arg: cstring): cstring {. - importc.} -proc isReplayingKeys*(): bool {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = registers_isReplayingKeys_bool_Registers_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc registers_isRecordingCommands_bool_Registers_string_wasm(arg: cstring): cstring {. - importc.} -proc isRecordingCommands*(registry: string): bool {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add registry.toJson() - let argsJsonString = $argsJson - let res {.used.} = registers_isRecordingCommands_bool_Registers_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc registers_replayCommands_void_Registers_string_wasm(arg: cstring): cstring {. - importc.} -proc replayCommands*(register: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add register.toJson() - let argsJsonString = $argsJson - let res {.used.} = registers_replayCommands_void_Registers_string_wasm( - argsJsonString.cstring) - diff --git a/scripting/script_expose.nim b/scripting/script_expose.nim deleted file mode 100644 index 94f38cd8f..000000000 --- a/scripting/script_expose.nim +++ /dev/null @@ -1,62 +0,0 @@ -import std/[strformat, tables, macros, json, strutils, sugar, sequtils, genasts] -import misc/[util, wrap, myjsonutils] -import plugin_api - -export plugin_api, util, strformat, tables, json, strutils, sugar, sequtils, scripting_api - -var scriptActions* = initTable[string, proc(args: JsonNode): JsonNode]() - -proc exposeImpl*(context: NimNode, name: NimNode, fun: NimNode, active: bool): NimNode = - # defer: - # echo result.repr - - let def = if fun.kind == nnkProcDef: fun else: fun.getImpl - if def.kind != nnkProcDef: - error("expose can only be used on proc definitions", fun) - - let signature = def.copy - signature[6] = newEmptyNode() - - let signatureUntyped = parseExpr(signature.repr) - let jsonWrapperName = (def.name.repr & "Json").ident - let jsonWrapper = createJsonWrapper(signatureUntyped, jsonWrapperName) - - let documentation = def.getDocumentation() - let documentationStr = documentation.map((it) => it.strVal).get("").newLit - - let returnType = if def[3][0].kind == nnkEmpty: "" else: def[3][0].repr - var params: seq[(string, string)] = @[] - for param in def[3][1..^1]: - params.add (param[0].repr, param[1].repr) - - if def == fun: - return genAst(name, def, jsonWrapper, jsonWrapperName, documentationStr, params, returnType, active, context): - def - jsonWrapper - scriptActions[name] = jsonWrapperName - addScriptAction(name, documentationStr, params, returnType, active, context) - - else: - return genAst(name, jsonWrapper, jsonWrapperName, documentationStr, params, returnType, active, context): - jsonWrapper - scriptActions[name] = jsonWrapperName - addScriptAction(name, documentationStr, params, returnType, active, context) - -macro expose*(name: string, fun: typed): untyped = - return exposeImpl(newLit"script", name, fun, active=false) - -macro expose*(context, string, name: string, fun: typed): untyped = - return exposeImpl(context, name, fun, active=false) - -macro exposeActive*(context: string, name: string, fun: typed): untyped = - return exposeImpl(context, name, fun, active=true) - -macro callJson*(fun: typed, args: JsonNode): JsonNode = - ## Calls a function with a json object as argument, converting the json object to nim types - let jsonWrapperName = genSym(nskProc, "jsonWrapper") - let jsonWrapper = createJsonWrapper(fun, fun.getType, jsonWrapperName) - jsonWrapper.addPragma("closure".ident) - return genAst(jsonWrapper, jsonWrapperName, args): - block: - jsonWrapper - jsonWrapperName(args) diff --git a/scripting/session_api_wasm.nim b/scripting/session_api_wasm.nim deleted file mode 100644 index 5272ecb6c..000000000 --- a/scripting/session_api_wasm.nim +++ /dev/null @@ -1,34 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc session_setSessionDataJson_void_SessionService_string_JsonNode_bool_wasm( - arg: cstring): cstring {.importc.} -proc setSessionDataJson*(path: string; value: JsonNode; override: bool = true) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - argsJson.add value.toJson() - argsJson.add override.toJson() - let argsJsonString = $argsJson - let res {.used.} = session_setSessionDataJson_void_SessionService_string_JsonNode_bool_wasm( - argsJsonString.cstring) - - -proc session_getSessionDataJson_JsonNode_SessionService_string_JsonNode_wasm( - arg: cstring): cstring {.importc.} -proc getSessionDataJson*(path: string; default: JsonNode): JsonNode {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - argsJson.add default.toJson() - let argsJsonString = $argsJson - let res {.used.} = session_getSessionDataJson_JsonNode_SessionService_string_JsonNode_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - diff --git a/scripting/terminal_api_wasm.nim b/scripting/terminal_api_wasm.nim deleted file mode 100644 index 1e0759fb4..000000000 --- a/scripting/terminal_api_wasm.nim +++ /dev/null @@ -1,128 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc terminal_setTerminalMode_void_TerminalService_string_wasm(arg: cstring): cstring {. - importc.} -proc setTerminalMode*(mode: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add mode.toJson() - let argsJsonString = $argsJson - let res {.used.} = terminal_setTerminalMode_void_TerminalService_string_wasm( - argsJsonString.cstring) - - -proc terminal_escape_void_TerminalService_wasm(arg: cstring): cstring {.importc.} -proc escape*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = terminal_escape_void_TerminalService_wasm( - argsJsonString.cstring) - - -proc terminal_createTerminal_void_TerminalService_string_CreateTerminalOptions_wasm( - arg: cstring): cstring {.importc.} -proc createTerminal*(command: string = ""; - options: CreateTerminalOptions = CreateTerminalOptions()) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add command.toJson() - argsJson.add options.toJson() - let argsJsonString = $argsJson - let res {.used.} = terminal_createTerminal_void_TerminalService_string_CreateTerminalOptions_wasm( - argsJsonString.cstring) - - -proc terminal_runInTerminal_void_TerminalService_string_string_RunInTerminalOptions_wasm( - arg: cstring): cstring {.importc.} -proc runInTerminal*(shell: string; command: string; - options: RunInTerminalOptions = RunInTerminalOptions()) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add shell.toJson() - argsJson.add command.toJson() - argsJson.add options.toJson() - let argsJsonString = $argsJson - let res {.used.} = terminal_runInTerminal_void_TerminalService_string_string_RunInTerminalOptions_wasm( - argsJsonString.cstring) - - -proc terminal_scrollTerminal_void_TerminalService_int_wasm(arg: cstring): cstring {. - importc.} -proc scrollTerminal*(amount: int) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add amount.toJson() - let argsJsonString = $argsJson - let res {.used.} = terminal_scrollTerminal_void_TerminalService_int_wasm( - argsJsonString.cstring) - - -proc terminal_pasteTerminal_void_TerminalService_string_wasm(arg: cstring): cstring {. - importc.} -proc pasteTerminal*(register: string = "") {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add register.toJson() - let argsJsonString = $argsJson - let res {.used.} = terminal_pasteTerminal_void_TerminalService_string_wasm( - argsJsonString.cstring) - - -proc terminal_enableTerminalDebugLog_void_TerminalService_bool_wasm(arg: cstring): cstring {. - importc.} -proc enableTerminalDebugLog*(enable: bool) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add enable.toJson() - let argsJsonString = $argsJson - let res {.used.} = terminal_enableTerminalDebugLog_void_TerminalService_bool_wasm( - argsJsonString.cstring) - - -proc terminal_sendTerminalInput_void_TerminalService_string_bool_wasm( - arg: cstring): cstring {.importc.} -proc sendTerminalInput*(input: string; noKitty: bool = false) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add input.toJson() - argsJson.add noKitty.toJson() - let argsJsonString = $argsJson - let res {.used.} = terminal_sendTerminalInput_void_TerminalService_string_bool_wasm( - argsJsonString.cstring) - - -proc terminal_sendTerminalInputAndSetMode_void_TerminalService_string_string_wasm( - arg: cstring): cstring {.importc.} -proc sendTerminalInputAndSetMode*(input: string; mode: string) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add input.toJson() - argsJson.add mode.toJson() - let argsJsonString = $argsJson - let res {.used.} = terminal_sendTerminalInputAndSetMode_void_TerminalService_string_string_wasm( - argsJsonString.cstring) - - -proc terminal_editTerminalBuffer_void_TerminalService_wasm(arg: cstring): cstring {. - importc.} -proc editTerminalBuffer*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = terminal_editTerminalBuffer_void_TerminalService_wasm( - argsJsonString.cstring) - - -proc terminal_selectTerminal_void_TerminalService_bool_float_float_float_wasm( - arg: cstring): cstring {.importc.} -proc selectTerminal*(preview: bool = true; scaleX: float = 0.9; - scaleY: float = 0.9; previewScale: float = 0.6) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add preview.toJson() - argsJson.add scaleX.toJson() - argsJson.add scaleY.toJson() - argsJson.add previewScale.toJson() - let argsJsonString = $argsJson - let res {.used.} = terminal_selectTerminal_void_TerminalService_bool_float_float_float_wasm( - argsJsonString.cstring) - diff --git a/scripting/toast_api_wasm.nim b/scripting/toast_api_wasm.nim deleted file mode 100644 index a520fb556..000000000 --- a/scripting/toast_api_wasm.nim +++ /dev/null @@ -1,18 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc toast_showToast_void_ToastService_string_string_string_wasm(arg: cstring): cstring {. - importc.} -proc showToast*(title: string; message: string; color: string) {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add title.toJson() - argsJson.add message.toJson() - argsJson.add color.toJson() - let argsJsonString = $argsJson - let res {.used.} = toast_showToast_void_ToastService_string_string_string_wasm( - argsJsonString.cstring) - diff --git a/scripting/vcs_api_wasm.nim b/scripting/vcs_api_wasm.nim deleted file mode 100644 index f90882fe1..000000000 --- a/scripting/vcs_api_wasm.nim +++ /dev/null @@ -1,15 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc vcs_chooseGitActiveFiles_void_VCSService_bool_wasm(arg: cstring): cstring {. - importc.} -proc chooseGitActiveFiles*(all: bool = false) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add all.toJson() - let argsJsonString = $argsJson - let res {.used.} = vcs_chooseGitActiveFiles_void_VCSService_bool_wasm( - argsJsonString.cstring) - diff --git a/scripting/vfs_api_wasm.nim b/scripting/vfs_api_wasm.nim deleted file mode 100644 index 254882ffc..000000000 --- a/scripting/vfs_api_wasm.nim +++ /dev/null @@ -1,109 +0,0 @@ -import std/[json, options] -import scripting_api, misc/myjsonutils - -## This file is auto generated, don't modify. - - -proc vfs_mountVfs_void_VFSService_Option_string_string_JsonNode_wasm( - arg: cstring): cstring {.importc.} -proc mountVfs*(parentPath: Option[string]; prefix: string; config: JsonNode) {. - gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add parentPath.toJson() - argsJson.add prefix.toJson() - argsJson.add config.toJson() - let argsJsonString = $argsJson - let res {.used.} = vfs_mountVfs_void_VFSService_Option_string_string_JsonNode_wasm( - argsJsonString.cstring) - - -proc vfs_normalizePath_string_VFSService_string_wasm(arg: cstring): cstring {. - importc.} -proc normalizePath*(path: string): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - let argsJsonString = $argsJson - let res {.used.} = vfs_normalizePath_string_VFSService_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc vfs_localizePath_string_VFSService_string_wasm(arg: cstring): cstring {. - importc.} -proc localizePath*(path: string): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - let argsJsonString = $argsJson - let res {.used.} = vfs_localizePath_string_VFSService_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc vfs_writeFileSync_void_VFSService_string_string_wasm(arg: cstring): cstring {. - importc.} -proc writeFileSync*(path: string; content: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - argsJson.add content.toJson() - let argsJsonString = $argsJson - let res {.used.} = vfs_writeFileSync_void_VFSService_string_string_wasm( - argsJsonString.cstring) - - -proc vfs_readFileSync_string_VFSService_string_wasm(arg: cstring): cstring {. - importc.} -proc readFileSync*(path: string): string {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - let argsJsonString = $argsJson - let res {.used.} = vfs_readFileSync_string_VFSService_string_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc vfs_deleteFileSync_void_VFSService_string_wasm(arg: cstring): cstring {. - importc.} -proc deleteFileSync*(path: string) {.gcsafe, raises: [].} = - var argsJson = newJArray() - argsJson.add path.toJson() - let argsJsonString = $argsJson - let res {.used.} = vfs_deleteFileSync_void_VFSService_string_wasm( - argsJsonString.cstring) - - -proc vfs_genTempPath_string_VFSService_string_string_string_int_bool_wasm( - arg: cstring): cstring {.importc.} -proc genTempPath*(prefix: string; suffix: string; dir: string = "temp://"; - randLen: int = 8; checkExists: bool = true): string {.gcsafe, - raises: [].} = - var argsJson = newJArray() - argsJson.add prefix.toJson() - argsJson.add suffix.toJson() - argsJson.add dir.toJson() - argsJson.add randLen.toJson() - argsJson.add checkExists.toJson() - let argsJsonString = $argsJson - let res {.used.} = vfs_genTempPath_string_VFSService_string_string_string_int_bool_wasm( - argsJsonString.cstring) - try: - result = parseJson($res).jsonTo(typeof(result)) - except CatchableError: - raiseAssert(getCurrentExceptionMsg()) - - -proc vfs_dumpVfsHierarchy_void_VFSService_wasm(arg: cstring): cstring {.importc.} -proc dumpVfsHierarchy*() {.gcsafe, raises: [].} = - var argsJson = newJArray() - let argsJsonString = $argsJson - let res {.used.} = vfs_dumpVfsHierarchy_void_VFSService_wasm( - argsJsonString.cstring) - From cb8c09f69be181c7e8fa19574dbf7878ef2085f1 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 19 Oct 2025 22:25:27 +0200 Subject: [PATCH 32/47] Delete old plugins and unused stuff, refactor moves --- config/keybindings.json | 63 +- config/keybindings_custom.nim | 292 ------ config/keybindings_helix.nim | 97 -- config/keybindings_normal.nim | 205 ---- config/keybindings_plugin.nim | 11 - config/keybindings_vim.nim | 1357 -------------------------- config/keybindings_vim_like.nim | 300 ------ nev.nimble | 3 +- nimble.lock | 22 - plugins/vim/vim.nim | 8 +- src/app.nim | 2 +- src/channel.nim | 2 +- src/command_service_api.nim | 4 +- src/desktop_main.nim | 5 +- src/generated/plugin_api_host.nim | 80 +- src/language_server_command_line.nim | 2 +- src/misc/generational_seq.nim | 2 +- src/misc/macro_utils.nim | 2 +- src/misc/rope_utils.nim | 12 +- src/move_database.nim | 402 ++++++-- src/platform/platform.nim | 2 +- src/plugin_api/plugin_api.nim | 12 - src/scripting/wasm.nim | 364 ------- src/terminal_service.nim | 17 +- src/text/text_document.nim | 54 + src/text/text_editor.nim | 797 ++++----------- wit/v0/api.wit | 4 - 27 files changed, 667 insertions(+), 3454 deletions(-) delete mode 100644 config/keybindings_custom.nim delete mode 100644 config/keybindings_helix.nim delete mode 100644 config/keybindings_normal.nim delete mode 100644 config/keybindings_plugin.nim delete mode 100644 config/keybindings_vim.nim delete mode 100644 config/keybindings_vim_like.nim delete mode 100644 src/scripting/wasm.nim diff --git a/config/keybindings.json b/config/keybindings.json index 29dc99c97..3c7329aa2 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -491,12 +491,12 @@ "M": ["enter-choose-cursor-mode", "vim.handle-select-word"], // movement - "": ["select-prev"], - "": ["select-next"], + "": ["select-prev"], // todo + "": ["select-next"], // todo "": ["vim.select-last "], - "o": ["vim.reverse-selections"], - "": ["select-next-tab-stop"], - "": ["select-prev-tab-stop"], + "o": ["move", "(reverse)"], + "": ["move", "(next-tab-stop)"], + "": ["move", "(prev-tab-stop)"], "gt": ["vim.goto-next-diagnostic"], "gn": ["vim.goto-prev-diagnostic"], @@ -758,36 +758,37 @@ "vscode": { "": ["vscode-escape"], - "": ["move-cursor-column", -1], - "": ["move-cursor-column", 1], - "": ["move-last", "word-line-back"], - "": ["move-last", "word-line"], - "": ["move-first", "line"], - "": ["move-last", "line"], + "": ["move", "(column -1)"], + "": ["move", "(column 1)"], + "": ["move", "(word-line-back) (start)"], + "": ["move", "(word-line) (end)"], + "": ["move", "(line) (start)"], + "": ["move", "(line) (end)"], + "": ["move", "(word-line-back) (join . curr-start)"], + "": ["move", "(word-line) (join)"], + "": ["move", "(visual-line-up)", false], + "": ["move", "(visual-line-down)", false], + "": ["move", "(file) (start)"], + "": ["move", "(file) (end)"], + "": ["move", "(file) (join . curr-start)"], + "": ["move", "(file) (join . curr-end)"], + "": ["move", "(column -1) (join)"], + "": ["move", "(column 1) (join)"], + "": ["move", "(visual-line-up) (join)", false], + "": ["move", "(visual-line-down) (join)", false], + "": ["move", "(line) (start) (join)"], + "": ["move", "(line) (end) (join)"], + "": ["scroll-text", 20], "": ["scroll-text", -20], - "": ["move-last", "word-line-back", "last"], - "": ["move-last", "word-line", "last"], - "": ["move-cursor-line", -1], - "": ["move-cursor-line", 1], - "": ["move-first", "file"], - "": ["move-last", "file"], - "": ["move-first", "file", "last"], - "": ["move-last", "file", "last"], - "": ["move-cursor-column", -1, "last"], - "": ["move-cursor-column", 1, "last"], - "": ["move-cursor-line", -1, "last"], - "": ["move-cursor-line", 1, "last"], - "": ["move-first", "line", "last"], - "": ["move-last", "line", "last"], "": ["undo", ""], "": ["redo", ""], "": ["copy"], "": ["paste"], - "": ["delete-left"], - "": ["delete-move", "word-line-back"], - "": ["delete-right"], - "": ["delete-move", "word-line"], + "": ["delete-move", "(column -1) (join)"], + "": ["delete-move", "(word-line-back) (start) (join)"], + "": ["delete-move", "(column 1) (join)"], + "": ["delete-move", "(word-line) (end) (join)"], "": ["insert-text", "\n"], "": ["insert-text", " "], "": ["insert-indent"], @@ -798,7 +799,7 @@ "": ["move-cursor-visual-page", -1, "last"], "": ["move-cursor-visual-page", 1, "last"], - "": ["vscode-delete-line"], + "": ["all", [".delete-move", "(line) (column) (join last-start curr-end)", false], [".move", "(line) (start) (column target-column)", false, {"wrap": false}]], "": ["vscode-todo", "move-line-up"], "": ["vscode-todo", "move-line-down"], "": ["vscode-todo", "copy-line-up"], @@ -818,7 +819,7 @@ "": ["open-prev-view" ], // todo: jump list "": ["select-next" ], // todo: jump list "": ["select-prev" ], // todo: jump list - "": ["select-line-current"], + "": ["move", "(line)"], "": ["vscode-todo", "move-selection-to-last-find-match"], "": ["add-cursor-above"], "": ["add-cursor-below"], diff --git a/config/keybindings_custom.nim b/config/keybindings_custom.nim deleted file mode 100644 index 0945d6077..000000000 --- a/config/keybindings_custom.nim +++ /dev/null @@ -1,292 +0,0 @@ -import plugin_runtime, keybindings_normal - -proc loadCustomKeybindings*() {.expose("load-custom-keybindings").} = - loadNormalKeybindings() - - info "Applying Custom keybindings" - - # clearCommands("editor.text") - # for id in getAllEditors(): - # if id.isTextEditor(editor): - # editor.setMode("") - - # Normal mode - setHandleInputs "editor.text", false - setOption "editor.text.cursor.movement.", "both" - setOption "editor.text.cursor.wide.", true - - # navigation - addTextCommand "", "", "move-cursor-line", 30 - addTextCommand "", "", "move-cursor-line", -30 - - addTextCommandBlock "", "gg": - let count = editor.getCommandCount - if count == 0: - editor.selection = (0, 0).toSelection - else: - editor.selection = (count, 0).toSelection - editor.setCommandCount 0 - editor.scrollToCursor(Last) - - addTextCommand "", "G", "move-last", "file" - - addTextCommand "", "n", "select-move", "next-find-result", true - addTextCommand "", "N", "select-move", "prev-find-result", true - addTextCommand "", "", "addNextFindResultToSelection" - addTextCommand "", "", "addPrevFindResultToSelection" - addTextCommand "", "", "setAllFindResultToSelection" - addTextCommandBlock "", "*": editor.setSearchQueryFromMove("word") - - addTextCommand "", "", "select-line-current" - addTextCommand "", "miw", "select-inside-current" - - # lsp - addTextCommand "", "gd", "goto-definition" - addTextCommand "", "gs", "goto-symbol" - addTextCommand "", "", "get-completions" - - # editing - addTextCommand "", "x", "delete-right" - addTextCommand "", "u", "undo" - addTextCommand "", "U", "redo" - addTextCommand "", "y", "copy" - addTextCommand "", "p", "paste" - - # mode switches - addTextCommand "", "i", "set-mode", "insert" - addTextCommandBlock "", "a": - editor.setMode("insert") - editor.selections = editor.selections.mapIt((it.first, it.last.move(columns=1))) - - addTextCommand "", "v", "set-mode", "visual" - addTextCommand "", "V", "set-mode", "visual-temp" - - addTextCommandBlock "", "s": - editor.setMode("insert") - editor.selections = editor.delete(editor.selections) - - for i in 0..9: - capture i: - proc updateCommandCountHelper(editor: TextDocumentEditor) = - editor.updateCommandCount i - # echo "updateCommandCount ", editor.getCommandCount - editor.setCommandCountRestore editor.getCommandCount - editor.setCommandCount 0 - - addTextCommand "", $i, updateCommandCountHelper - addTextCommand "delete", $i, updateCommandCountHelper - addTextCommand "move", $i, updateCommandCountHelper - - addTextCommandBlock "", "d": - editor.setMode "move" - setOption "text.move-action", "delete-move" - setOption "text.move-next-mode", "" - setOption "text.move-command-count", editor.getCommandCount() - editor.setCommandCount 0 - - addTextCommand "", "D", "delete-move", "line" - - addTextCommandBlock "", "c": - editor.setMode "move" - setOption "text.move-action", "change-move" - setOption "text.move-next-mode", "insert" - setOption "text.move-command-count", editor.getCommandCount() - editor.setCommandCount 0 - - addTextCommandBlock "", "y": - editor.setMode "move" - setOption "text.move-action", "copy-move" - setOption "text.move-next-mode", "" - setOption "text.move-command-count", editor.getCommandCount() - editor.setCommandCount 0 - - addTextCommandBlock "", "f": - setOption("text.move-next-mode", editor.mode) - editor.setMode "move-to" - setOption "text.move-command-count", editor.getCommandCount() - editor.setCommandCount 0 - - addTextCommandBlock "", "t": - setOption("text.move-next-mode", editor.mode) - editor.setMode "move-before" - setOption "text.move-command-count", editor.getCommandCount() - editor.setCommandCount 0 - - addTextCommandBlock "", "D": - editor.deleteMove("line") - - addTextCommandBlock "", "C": - editor.deleteMove("line") - editor.setMode("insert") - - addTextCommandBlock "", "Y": - editor.selectMove("line") - editor.copy() - - addTextCommand "", "dl", "delete-move", "line-next" - addTextCommand "", "b", "move-last", "word-line-back" - addTextCommand "", "w", "move-last", "word-line" - addTextCommand "", "e", "move-last", "word-line" - addTextCommand "", "", "move-first", "line" - addTextCommand "", "", "move-last", "line" - addTextCommandBlock "", "o": - editor.moveCursorEnd() - editor.insertText("\n") - editor.setMode("insert") - addTextCommandBlock "", "O": - editor.moveCursorEnd() - editor.insertText("\n") - addTextCommandBlock "", "": - editor.setMode("") - editor.selection = editor.selection.last.toSelection - addTextCommandBlock "", "": - editor.setMode("") - editor.selection = editor.selection.last.toSelection - - # move mode - setHandleInputs "editor.text.move", false - setOption "editor.text.cursor.wide.move", true - setOption "editor.text.cursor.movement.move", "both" - addTextCommand "move", "i", "set-mode", "move-inside" - - template addTextMoveCommand(keys: string, move: string): untyped = - addTextCommand "move", keys, "apply-move", move, false - addTextCommand "move-inside", keys, "apply-move", move, true - - addTextMoveCommand "w", "word-line" - addTextMoveCommand "W", "word" - addTextMoveCommand "b", "word-line-back" - addTextMoveCommand "B", "word-back" - addTextMoveCommand "p", "paragraph" - addTextMoveCommand "l", "line-next" - addTextMoveCommand "L", "line" - addTextMoveCommand "d", "line-next" - addTextMoveCommand "F", "file" - addTextMoveCommand "\"", "\"" - addTextMoveCommand "'", "'" - addTextMoveCommand "(", "(" - addTextMoveCommand ")", "(" - addTextMoveCommand "[", "[" - addTextMoveCommand "]", "[" - addTextMoveCommand "}", "}" - - addTextCommandBlock "move", "f": - editor.setMode "move-to" - setOption "text.move-command-count", editor.getCommandCount() - editor.setCommandCount 0 - - addTextCommandBlock "move", "t": - editor.setMode "move-before" - setOption "text.move-command-count", editor.getCommandCount() - editor.setCommandCount 0 - - # move-to mode - setHandleActions "editor.text.move-to", false - setTextInputHandler "move-to", proc(editor: TextDocumentEditor, input: string): bool = - editor.setMode getOption[string]("text.move-next-mode") - if getOption[string]("text.move-action") != "": - editor.setCommandCount getOption[int]("text.move-command-count") - var args = newJArray() - args.add newJString("move-to " & input) - discard editor.runAction(getOption[string]("text.move-action"), args) - setOption[string]("text.move-action", "") - else: - editor.moveCursorTo(input) - return true - setOption "editor.text.cursor.wide.move-to", true - setOption "editor.text.cursor.movement.move-to", "both" - - # move-before mode - setHandleActions "editor.text.move-before", false - setTextInputHandler "move-before", proc(editor: TextDocumentEditor, input: string): bool = - editor.setMode getOption[string]("text.move-next-mode") - if getOption[string]("text.move-action") != "": - editor.setCommandCount getOption[int]("text.move-command-count") - var args = newJArray() - args.add newJString("move-before " & input) - discard editor.runAction(getOption[string]("text.move-action"), args) - setOption[string]("text.move-action", "") - else: - editor.moveCursorBefore(input) - return true - setOption "editor.text.cursor.wide.move-before", true - setOption "editor.text.cursor.movement.move-before", "both" - - # Insert mode - setHandleInputs "editor.text.insert", true - setOption "editor.text.cursor.wide.insert", false - addTextCommand "insert", "", "insert-text", "\n" - addTextCommand "insert", "", "insert-text", " " - - # Visual mode - setHandleInputs "editor.text.visual", false - setOption "editor.text.cursor.wide.visual", true - setOption "editor.text.cursor.movement.visual", "last" - addTextCommandBlock "visual", "i": - editor.setMode("move") - setOption("text.move-action", "select-move") - setOption("text.move-next-mode", "visual") - - addTextCommandBlock "visual", "d": - editor.selections = editor.delete(editor.selections) - editor.setMode("") - editor.scrollToCursor(Last) - editor.updateTargetColumn(Last) - - addTextCommandBlock "visual", "c": - editor.selections = editor.delete(editor.selections) - editor.setMode("insert") - editor.scrollToCursor(Last) - editor.updateTargetColumn(Last) - - # Visual temp mode - setHandleInputs "editor.text.visual-temp", false - setOption "editor.text.cursor.wide.visual-temp", false - setOption "editor.text.cursor.movement.visual-temp", "last-to-first" - addTextCommandBlock "visual-temp", "i": - editor.setMode("move") - setOption("text.move-action", "select-move") - setOption("text.move-next-mode", "visual-temp") - - addTextCommandBlock "visual", "d": - editor.selections = editor.delete(editor.selections) - editor.setMode("") - editor.scrollToCursor(Last) - editor.updateTargetColumn(Last) - - addTextCommandBlock "visual", "c": - editor.selections = editor.delete(editor.selections) - editor.setMode("insert") - editor.scrollToCursor(Last) - editor.updateTargetColumn(Last) - - addTextCommand "", "", "set-mode", "cursor-build" - addTextCommand "cursor-build", "c", "set-mode", "normal" - addTextCommand "cursor-build", "", "move-cursor-column", -1, "config", false - addTextCommand "cursor-build", "", "move-cursor-column", 1, "config", false - addTextCommand "cursor-build", "", "move-first", "word-line", "config", false - addTextCommand "cursor-build", "", "move-last", "word-line", "config", false - addTextCommand "cursor-build", "", "move-first", "line", "config", false - addTextCommand "cursor-build", "", "move-last", "line", "config", false - addTextCommand "cursor-build", "", "move-first", "word-line", "last", false - addTextCommand "cursor-build", "", "move-last", "word-line", "last", false - addTextCommand "cursor-build", "", "move-cursor-line", -1, "config", false - addTextCommand "cursor-build", "", "move-cursor-line", 1, "config", false - addTextCommand "cursor-build", "", "move-first", "file", "config", false - addTextCommand "cursor-build", "", "move-last", "file", "config", false - addTextCommand "cursor-build", "", "move-first", "file", "last", false - addTextCommand "cursor-build", "", "move-last", "file", "last", false - addTextCommand "cursor-build", "", "move-cursor-column", -1, "last", false - addTextCommand "cursor-build", "", "move-cursor-column", 1, "last", false - addTextCommand "cursor-build", "", "move-cursor-line", -1, "last", false - addTextCommand "cursor-build", "", "move-cursor-line", 1, "last", false - addTextCommand "cursor-build", "", "move-first", "line", "last", false - addTextCommand "cursor-build", "", "move-last", "line", "last", false - addTextCommand "cursor-build", "n", "select-move", "next-find-result", false, false - addTextCommand "cursor-build", "N", "select-move", "prev-find-result", false, false - addTextCommandBlock "cursor-build", "y": - editor.runAction("duplicate-last-selection") - editor.runAction("select-move", "\"next-find-result\" false false") - addTextCommandBlock "cursor-build", "Y": - editor.runAction("duplicate-last-selection") - editor.runAction("select-move", "\"prev-find-result\" false false") diff --git a/config/keybindings_helix.nim b/config/keybindings_helix.nim deleted file mode 100644 index 8e13fa126..000000000 --- a/config/keybindings_helix.nim +++ /dev/null @@ -1,97 +0,0 @@ -import plugin_runtime, keybindings_normal - -proc loadHelixKeybindings*() {.expose("load-helix-keybindings").} = - loadNormalKeybindings() - - info "Applying Helix keybindings" - - # clearCommands("editor.text") - # for id in getAllEditors(): - # if id.isTextEditor(editor): - # editor.setMode("") - - # Normal mode - setHandleInputs "editor.text", false - setOption "editor.text.cursor.movement.", "last-to-first" - setOption "editor.text.cursor.wide.", true - - addTextCommand "", "x", "delete-right" - addTextCommand "", "", "select-line-current" - addTextCommand "", "miw", "select-inside-current" - addTextCommand "", "u", "undo" - addTextCommand "", "U", "redo" - addTextCommand "", "i", "set-mode", "insert" - addTextCommand "", "v", "set-mode", "visual" - addTextCommand "", "m", "set-mode", "match" - addTextCommand "", "dl", "delete-move", "line-next" - addTextCommand "", "b", "move-first", "word-line" - addTextCommand "", "w", "move-last", "word-line" - addTextCommand "", "e", "move-last", "word-line" - addTextCommand "", "", "move-first", "line" - addTextCommand "", "", "move-last", "line" - - addTextCommandBlock "", "di": - editor.setMode("move") - setOption("text.move-action", "delete-move") - setOption("text.move-next-mode", "") - addTextCommandBlock "", "c": - editor.setMode("move") - setOption("text.move-action", "change-move") - setOption("text.move-next-mode", "") - addTextCommandBlock "", "o": - editor.moveCursorEnd() - editor.insertText("\n") - editor.setMode("insert") - addTextCommandBlock "", "O": - editor.moveCursorEnd() - editor.insertText("\n") - addTextCommandBlock "", "": - editor.setMode("") - editor.selection = editor.selection.last.toSelection - addTextCommandBlock "", "": - editor.setMode("") - editor.selection = editor.selection.last.toSelection - - # Match mode - setHandleInputs "editor.text.move", false - setOption "editor.text.cursor.movement.match", "both" - setOption "editor.text.cursor.wide.match", true - addTextCommandBlock "match", "i": - editor.setMode("move") - setOption("text.move-action", "select-move") - setOption("text.move-next-mode", "") - - # Move mode - setHandleInputs "editor.text.move", false - setOption "editor.text.cursor.movement.move", "both" - setOption "editor.text.cursor.wide.move", true - addTextCommand "move", "w", "set-move", "word" - addTextCommand "move", "W", "set-move", "word-line" - addTextCommand "move", "p", "set-move", "paragraph" - addTextCommand "move", "l", "set-move", "line" - addTextCommand "move", "L", "set-move", "line-next" - addTextCommand "move", "\"", "set-move", "\"" - addTextCommand "move", "'", "set-move", "'" - addTextCommand "move", "(", "set-move", "(" - addTextCommand "move", ")", "set-move", "(" - addTextCommand "move", "[", "set-move", "[" - addTextCommand "move", "]", "set-move", "[" - addTextCommand "move", "}", "set-move", "}" - addTextCommand "move", "}", "set-move", "}" - - # Insert mode - setHandleInputs "editor.text.insert", true - setOption "editor.text.cursor.wide.insert", false - setOption "editor.text.cursor.movement.visual", "both" - addTextCommand "insert", "", "insert-text", "\n" - addTextCommand "insert", "", "insert-text", " " - - # Visual mode - setHandleInputs "editor.text.visual", false - setOption "editor.text.cursor.wide.insert", true - setOption "editor.text.cursor.movement.visual", "last" - addTextCommand "visual", "v", "set-mode", "" - addTextCommandBlock "visual", "i": - editor.setMode("move") - setOption("text.move-action", "select-move") - setOption("text.move-next-mode", "visual") \ No newline at end of file diff --git a/config/keybindings_normal.nim b/config/keybindings_normal.nim deleted file mode 100644 index 975b273fe..000000000 --- a/config/keybindings_normal.nim +++ /dev/null @@ -1,205 +0,0 @@ -import plugin_runtime -import misc/[event, id, timer] - -{.used.} - -proc setModeChangedHandler*(handler: proc(editor: TextDocumentEditor, oldMode: string, newMode: string) {.gcsafe, raises: [].}) = - let modeChangedHandler = getOption("editor.text.mode-changed-handler", "") - if modeChangedHandler != "": - onEditorModeChanged.unsubscribe(parseId(modeChangedHandler)) - let id = onEditorModeChanged.subscribe proc(arg: auto) {.gcsafe, raises: [].} = - # infof"onEditorModeChanged: {arg.editor}, {arg.oldMode}, {arg.newMode}" - if arg.editor.isTextEditor(editor) and not editor.isRunningSavedCommands: - handler(editor, arg.oldMode, arg.newMode) - setOption("editor.text.mode-changed-handler", $id) - -proc addModeChangedHandler*(id: var Id, handler: proc(editor: TextDocumentEditor, oldMode: string, newMode: string) {.gcsafe, raises: [].}) = - if id != idNone(): - onEditorModeChanged.unsubscribe(id) - id = onEditorModeChanged.subscribe proc(arg: auto) {.gcsafe, raises: [].} = - # infof"onEditorModeChanged: {arg.editor}, {arg.oldMode}, {arg.newMode}" - if arg.editor.isTextEditor(editor) and not editor.isRunningSavedCommands: - handler(editor, arg.oldMode, arg.newMode) - -proc vscodeEscape(editor: TextDocumentEditor) {.exposeActive("editor.text", "vscode-escape").} = - editor.selection = editor.selection.last.toSelection - editor.clearTabStops() - -proc loadModelKeybindings*() {.expose("load-model-keybindings").} = - let t = startTimer() - defer: - infof"loadModelKeybindings: {t.elapsed.ms} ms" - - info "Applying model keybindings" - - # setHandleInputs "editor.model", true - setOption "editor.model.cursor.wide.", false - - addCommand("editor.model", "", "move-cursor-left-line") - addCommand("editor.model", "", "move-cursor-right-line") - addCommand("editor.model", "", "move-cursor-up") - addCommand("editor.model", "", "move-cursor-down") - addCommand("editor.model", "", "select-prev-neighbor") - addCommand("editor.model", "", "select-next-neighbor") - addCommand("editor.model", "", "select-prev-neighbor", true) - addCommand("editor.model", "", "select-next-neighbor", true) - addCommand("editor.model", "", "select-node") - addCommand("editor.model", "", "select-prev") - addCommand("editor.model", "", "move-cursor-left-cell") - addCommand("editor.model", "", "move-cursor-right-cell") - addCommand("editor.model", "", "move-cursor-line-start") - addCommand("editor.model", "", "move-cursor-line-end") - addCommand("editor.model", "", "move-cursor-line-start-inline") - addCommand("editor.model", "", "move-cursor-line-end-inline") - addCommand("editor.model", "", "scroll-lines", 1) - addCommand("editor.model", "", "scroll-lines", -1) - - addCommand("editor.model", "", "move-cursor-left-line", true) - addCommand("editor.model", "", "move-cursor-right-line", true) - addCommand("editor.model", "", "move-cursor-left", true) - addCommand("editor.model", "", "move-cursor-right", true) - addCommand("editor.model", "", "move-cursor-up", true) - addCommand("editor.model", "", "move-cursor-down", true) - addCommand("editor.model", "", "move-cursor-up", true) - addCommand("editor.model", "", "move-cursor-down", true) - addCommand("editor.model", "", "move-cursor-left-cell", true) - addCommand("editor.model", "", "move-cursor-right-cell", true) - addCommand("editor.model", "", "move-cursor-line-start", true) - addCommand("editor.model", "", "move-cursor-line-end", true) - addCommand("editor.model", "", "move-cursor-line-start-inline", true) - addCommand("editor.model", "", "move-cursor-line-end-inline", true) - - addCommand("editor.model", "", "undo") - addCommand("editor.model", "", "redo") - - addCommand("editor.model", "", "copy-node") - addCommand("editor.model", "", "paste-node") - - addCommand("editor.model", "", "invert-selection") - addModelCommand "", "", "select-prev" - addModelCommand "", "", "select-next" - addModelCommand "", "", "select-next" - - addModelCommand "", "g", "find-declaration", false - addModelCommand "", "G", "find-declaration", true - - addModelCommand "", "<*C-g>c", "goto-next-node-of-class", "ConstDecl" - addModelCommand "", "<*C-g>C", "goto-prev-node-of-class", "ConstDecl" - addModelCommand "", "<*C-g>l", "goto-next-node-of-class", "LetDecl" - addModelCommand "", "<*C-g>L", "goto-prev-node-of-class", "LetDecl" - addModelCommand "", "<*C-g>v", "goto-next-node-of-class", "VarDecl" - addModelCommand "", "<*C-g>V", "goto-prev-node-of-class", "VarDecl" - addModelCommand "", "<*C-g>f", "goto-next-node-of-class", "FunctionDefinition" - addModelCommand "", "<*C-g>F", "goto-prev-node-of-class", "FunctionDefinition" - addModelCommand "", "<*C-g>p", "goto-next-node-of-class", "ParameterDecl" - addModelCommand "", "<*C-g>P", "goto-prev-node-of-class", "ParameterDecl" - addModelCommand "", "<*C-g>i", "goto-next-node-of-class", "ThenCase" - addModelCommand "", "<*C-g>I", "goto-prev-node-of-class", "ThenCase" - addModelCommand "", "<*C-g>o", "goto-next-node-of-class", "ForLoop" - addModelCommand "", "<*C-g>O", "goto-prev-node-of-class", "ForLoop" - addModelCommand "", "<*C-g>w", "goto-next-node-of-class", "WhileExpression" - addModelCommand "", "<*C-g>W", "goto-prev-node-of-class", "WhileExpression" - addModelCommand "", "<*C-g>r", "goto-next-reference" - addModelCommand "", "<*C-g>R", "goto-prev-reference" - addModelCommand "", "<*C-g>e", "goto-next-invalid-node" - addModelCommand "", "<*C-g>E", "goto-prev-invalid-node" - - addCommand "editor.model", "", "replace-left" - addCommand "editor.model", "", "replace-right" - addCommand "editor.model", "", "delete-left" - addCommand "editor.model", "", "delete-right" - addCommand "editor.model", "", "create-new-node" - addCommand "editor.model", "", "select-next-placeholder" - addCommand "editor.model", "", "select-prev-placeholder" - - addCommand "editor.model", "", "show-completions" - - addCommand "editor.model", "er", "run-selected-function" - addCommand "editor.model", "ed", "toggle-use-default-cell-builder" - addCommand "editor.model", "ec", "compile-language" - - addCommand "editor.model", "gd", "goto-definition" - addCommand "editor.model", "gp", "goto-prev-reference" - addCommand "editor.model", "gn", "goto-next-reference" - addCommand "editor.model", "tt", "toggle-bool-cell" - - addCommand "editor.model.completion", "", "hide-completions" - addCommand "editor.model.completion", "", "select-prev-completion" - addCommand "editor.model.completion", "", "select-next-completion" - addCommand "editor.model.completion", "", "move-cursor-start" - addCommand "editor.model.completion", "", "apply-selected-completion" - addCommand "editor.model.completion", "", "apply-selected-completion" - - addCommand "editor.model.goto", "", "end" - -proc vscodeSelectLines(editor: TextDocumentEditor, selections: Selections): Selections = - result = selections.mapIt (if it.isBackwards: - ((it.first.line, editor.lineLength(it.first.line)), (it.last.line, 0)) - else: - ((it.first.line, 0), (it.last.line, editor.lineLength(it.last.line)))) - -proc vscodeExtendForLineDeletion(editor: TextDocumentEditor, selections: Selections): Selections = - result = selections.mapIt ( - if it.isBackwards: - if it.last.line > 0: - (it.first, editor.doMoveCursorColumn(it.last, -1)) - elif it.first.line + 1 < editor.lineCount: - (editor.doMoveCursorColumn(it.first, 1), it.last) - else: - it - else: - if it.first.line > 0: - (editor.doMoveCursorColumn(it.first, -1), it.last) - elif it.last.line + 1 < editor.lineCount: - (it.first, editor.doMoveCursorColumn(it.last, 1)) - else: - it - ) - -proc vscodeDeleteLine(editor: TextDocumentEditor) {.expose("vscode-delete-line").} = - editor.addNextCheckpoint("word") - let lineSelections = editor.vscodeExtendForLineDeletion(editor.vscodeSelectLines(editor.selections)) - editor.selections = editor.delete(lineSelections, inclusiveEnd=true).mapIt( - editor.doMoveCursorLine(it.normalized.first, 1).toSelection - ) - -proc vscodeTodo(editor: TextDocumentEditor, command: string) {.expose("vscode-todo").} = - infof"command not implemented: '{command}'. Open an issue." - -proc vscodeSelectNextDiagnostic(editor: TextDocumentEditor) {.exposeActive("editor.text", "vscode-select-next-diagnostic").} = - editor.selection = editor.getNextDiagnostic(editor.selection.last, 1).first.toSelection - editor.scrollToCursor Last - editor.updateTargetColumn() - -proc vscodeSelectPrevDiagnostic(editor: TextDocumentEditor) {.exposeActive("editor.text", "vscode-select-prev-diagnostic").} = - editor.selection = editor.getPrevDiagnostic(editor.selection.last, 1).first.toSelection - editor.scrollToCursor Last - editor.updateTargetColumn() - -proc vscodeSearch(editor: TextDocumentEditor) {.exposeActive("editor.text", "vscode-search").} = - commandLine(".set-search-query \\") - if getActiveEditor().isTextEditor(editor): - var arr = newJArray() - arr.add newJString("file") - discard editor.runAction("move-last", arr) - editor.setMode("insert") - -proc vscodeSelectNextFindResult(editor: TextDocumentEditor) {.exposeActive("editor.text", "vscode-select-next-find-result").} = - editor.selection = editor.getNextFindResult(editor.selection.last).first.toSelection - editor.scrollToCursor Last - editor.updateTargetColumn() - -proc vscodeSelectPrevFindResult(editor: TextDocumentEditor) {.exposeActive("editor.text", "vscode-select-prev-find-result").} = - editor.selection = editor.getPrevFindResult(editor.selection.last).first.toSelection - editor.scrollToCursor Last - editor.updateTargetColumn() - -proc vscodeAddNextFindResultToSelection(editor: TextDocumentEditor) {.exposeActive("editor.text", "vscode-add-next-find-result-to-selection").} = - if editor.selections.len == 1 and editor.selection.isEmpty: - let selection = editor.setSearchQueryFromMove("word", prefix=r"\b", suffix=r"\b") - editor.selection = selection - else: - let next = editor.getNextFindResult(editor.selection.last, includeAfter=true) - editor.selections = editor.selections & next - editor.scrollToCursor Last - editor.updateTargetColumn() diff --git a/config/keybindings_plugin.nim b/config/keybindings_plugin.nim deleted file mode 100644 index 0727dec2c..000000000 --- a/config/keybindings_plugin.nim +++ /dev/null @@ -1,11 +0,0 @@ -import plugin_runtime -import std/[strutils, unicode] - -proc postInitialize*(): bool {.wasmexport.} = - return true - -import keybindings_vim -import keybindings_normal - -when defined(wasm): - include plugin_runtime_impl diff --git a/config/keybindings_vim.nim b/config/keybindings_vim.nim deleted file mode 100644 index c323c3250..000000000 --- a/config/keybindings_vim.nim +++ /dev/null @@ -1,1357 +0,0 @@ -import std/[strutils, macros, genasts, sequtils, sets, algorithm, jsonutils] -import plugin_runtime, keybindings_normal -import misc/[timer, util, myjsonutils, custom_unicode, id, regex] -import input_api - -embedSource() - -infof"import vim keybindings" - -var yankedLines: bool = false ## Whether the last thing we yanked was in a line mode - -type EditorVimState = object - ## Contains state which can vary per editor - selectLines: bool = false ## Whether entire lines should be selected (e.g. in visual-line mode/when using dd) - deleteInclusiveEnd: bool = true ## Whether the next time we delete some the selection end should be inclusive - cursorIncludeEol: bool = false ## Whether the cursor can be after the last character in a line (e.g. in insert mode) - currentUndoCheckpoint: string = "insert" ## Which checkpoint to undo to (depends on mode) - revisionBeforeImplicitInsertMacro: int - marks: Table[string, seq[(Anchor, Anchor)]] - unresolveMarks: Table[string, seq[Selection]] - -var editorStates: Table[EditorId, EditorVimState] - -const editorContext = "editor.text" - -proc vimState(editor: TextDocumentEditor): var EditorVimState = - if not editorStates.contains(editor.id): - editorStates[editor.id] = EditorVimState() - return editorStates[editor.id] - -proc shouldRecortImplicitPeriodMacro(editor: TextDocumentEditor): bool = - case editor.getUsage() - of "command-line", "search-bar": - return false - else: - return true - -proc recordCurrentCommandInPeriodMacro(editor: TextDocumentEditor) = - if not isReplayingCommands() and editor.shouldRecortImplicitPeriodMacro(): - setRegisterText("", ".") - editor.recordCurrentCommand(@["."]) - -proc startRecordingCurrentCommandInPeriodMacro(editor: TextDocumentEditor) = - if not isReplayingCommands() and editor.shouldRecortImplicitPeriodMacro(): - startRecordingCommands(".-temp") - setRegisterText("", ".-temp") - editor.recordCurrentCommand(@[".-temp"]) - editor.vimState.revisionBeforeImplicitInsertMacro = editor.getRevision - -type VimTextObjectRange* = enum Inner, Outer, CurrentToEnd - -proc getVimLineMargin*(): float = getOption[float]("editor.text.vim.line-margin", 5) -proc getVimClipboard*(): string = getOption[string]("editor.text.vim.clipboard", "") -proc getVimDefaultRegister*(): string = - case getVimClipboard(): - of "unnamed": return "*" - of "unnamedplus": return "+" - else: return "\"" - -proc getCurrentMacroRegister*(): string = getOption("editor.current-macro-register", "") - -proc getEnclosing(line: string, column: int, predicate: proc(c: char): bool): (int, int) = - var startColumn = column - var endColumn = column - while endColumn < line.high and predicate(line[endColumn + 1]): - inc endColumn - while startColumn > 0 and predicate(line[startColumn - 1]): - dec startColumn - return (startColumn, endColumn) - -proc vimHandleSelectWord(editor: TextDocumentEditor, cursor: Cursor) {.exposeActive(editorContext, "vim-handle-select-word").} = - editor.setSelection(cursor.toSelection) - -proc vimSelectLine(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-select-line").} = - editor.selections = editor.selections.mapIt (if it.isBackwards: - ((it.first.line, editor.lineLength(it.first.line)), (it.last.line, 0)) - else: - ((it.first.line, 0), (it.last.line, editor.lineLength(it.last.line)))) - -proc vimSelectLastCursor(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-select-last-cursor").} = - # infof"vimSelectLastCursor" - editor.selections = editor.selections.mapIt(it.last.toSelection) - editor.updateTargetColumn() - editor.setNextScrollBehaviour(ScrollToMargin) - editor.setNextSnapBehaviour(MinDistanceOffscreen) - -proc vimSelectLast(editor: TextDocumentEditor, move: string, count: int = 1) {.exposeActive(editorContext, "vim-select-last").} = - # infof"vimSelectLast '{move}' {count}" - let (action, arg) = move.parseAction - for i in 0.. 0: - (it.first, editor.doMoveCursorColumn(it.last, -1)) - elif it.first.line + 1 < editor.lineCount: - (editor.doMoveCursorColumn(it.first, 1), it.last) - else: - it - else: - if it.first.line > 0: - (editor.doMoveCursorColumn(it.first, -1), it.last) - elif it.last.line + 1 < editor.lineCount: - (it.first, editor.doMoveCursorColumn(it.last, 1)) - else: - it - ) - - return selections.mapIt(it.normalized.first.toSelection) - -proc vimUpdateSelections(editor: TextDocumentEditor, selections: Selections) = - if editor.vimState.selectLines: - editor.selections = selections.mapIt editor.doMoveCursorLine(it.normalized.first, 1).toSelection - - else: - editor.selections = selections - editor.updateTargetColumn() - editor.scrollToCursor Last - -proc vimDeleteSelection(editor: TextDocumentEditor, forceInclusiveEnd: bool, oldSelections: Option[Selections] = Selections.none) {.exposeActive(editorContext, "vim-delete-selection").} = - let newSelections = editor.copySelection(getVimDefaultRegister()) - let selectionsToDelete = editor.selections - if oldSelections.isSome: - editor.selections = oldSelections.get - editor.addNextCheckpoint("insert") - let inclusiveEnd = (not editor.vimState.selectLines) and (editor.vimState.deleteInclusiveEnd or forceInclusiveEnd) - editor.vimUpdateSelections editor.delete( - selectionsToDelete, inclusiveEnd=inclusiveEnd) - editor.vimState.deleteInclusiveEnd = true - editor.setMode "vim.normal" - -proc vimChangeSelection*(editor: TextDocumentEditor, forceInclusiveEnd: bool, oldSelections: Option[Selections] = Selections.none) {.exposeActive(editorContext, "vim-change-selection").} = - let newSelections = editor.copySelection(getVimDefaultRegister()) - let selectionsToDelete = editor.selections - if oldSelections.isSome: - editor.selections = oldSelections.get - editor.addNextCheckpoint("insert") - # todo: figure out if we should check for selectLines here as well - editor.vimUpdateSelections editor.delete( - selectionsToDelete, inclusiveEnd=editor.vimState.deleteInclusiveEnd or forceInclusiveEnd) - editor.vimState.deleteInclusiveEnd = true - editor.setMode "vim.insert" - -proc vimYankSelection*(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-yank-selection").} = - let selections = editor.copySelection(getVimDefaultRegister()) - editor.selections = selections - editor.setMode "vim.normal" - -proc vimYankSelectionClipboard*(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-yank-selection-clipboard").} = - let selections = editor.copySelection() - editor.selections = selections - editor.setMode "vim.normal" - -proc vimReplace(editor: TextDocumentEditor, input: string) {.exposeActive(editorContext, "vim-replace").} = - let texts = editor.selections.mapIt(block: - let selection = it - let text = editor.getText(selection, inclusiveEnd=true) - var newText = newStringOfCap(text.runeLen.int * input.runeLen.int) - var lastIndex = 0 - var index = text.find('\n') - if index == -1: - newText.add input.repeat(text.runeLen.int) - else: - while index != -1: - let lineLen = text.toOpenArray(lastIndex, index).runeLen.int - 1 - newText.add input.repeat(lineLen) - newText.add "\n" - lastIndex = index + 1 - index = text.find('\n', index + 1) - - let lineLen = text.toOpenArray(lastIndex, text.high).runeLen.int - newText.add input.repeat(lineLen) - - newText - ) - - # infof"replace {editor.selections} with '{input}' -> {texts}" - - editor.addNextCheckpoint "insert" - editor.selections = editor.edit(editor.selections, texts, inclusiveEnd=true).mapIt(it.first.toSelection) - editor.setMode "vim.normal" - -proc vimSelectMove(editor: TextDocumentEditor, move: string, count: int = 1) {.exposeActive(editorContext, "vim-select-move").} = - # infof"vimSelectMove '{move}' {count}" - let (action, arg) = move.parseAction - for i in 0.. failed to parse key: {target}" - - let key = parseTarget(target) - - for _ in 0.. 0: lineLen.dec - result = (cursor.line, min(cursor.column, lineLen)) - -proc vimMotionLine*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = - var lineLen = editor.lineLength(cursor.line) - if not editor.vimState.cursorIncludeEol and lineLen > 0: lineLen.dec - result = ((cursor.line, 0), (cursor.line, lineLen)) - -proc vimMotionVisualLine*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = - var lineLen = editor.lineLength(cursor.line) - result = editor.getSelectionForMove(cursor, "visual-line", count) - if not editor.vimState.cursorIncludeEol and result.last.column > result.first.column: - result.last.column.dec - elif result.last.column < lineLen: # This is the case if we're not in the last visual sub line - result.last.column.dec - -proc vimMotionParagraphInner*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = - let isEmpty = editor.lineLength(cursor.line) == 0 - - result = ((cursor.line, 0), cursor) - while result.first.line - 1 >= 0 and (editor.lineLength(result.first.line - 1) == 0) == isEmpty: - dec result.first.line - while result.last.line + 1 < editor.lineCount and (editor.lineLength(result.last.line + 1) == 0) == isEmpty: - inc result.last.line - - result.last.column = editor.lineLength(result.last.line) - -proc vimMotionParagraphOuter*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = - result = editor.vimMotionParagraphInner(cursor, count) - if result.last.line + 1 < editor.lineCount: - result = result or editor.vimMotionParagraphInner((result.last.line + 1, 0), 1) - -proc vimMotionWordOuter*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = - result = vimMotionWord(editor, cursor, count) - if result.last.column < editor.lineLength(result.last.line) and editor.getChar(result.last) in Whitespace: - result.last = editor.vimMotionWord(result.last, 1).last - -proc vimMotionWordBigOuter*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = - result = vimMotionWordBig(editor, cursor, count) - if result.last.column < editor.lineLength(result.last.line) and editor.getChar(result.last) in Whitespace: - result.last = editor.vimMotionWordBig(result.last, 1).last - -proc charAt*(editor: TextDocumentEditor, cursor: Cursor): char = - let res = editor.getText(cursor.toSelection, inclusiveEnd=true) - if res.len > 0: - return res[0] - else: - return '\0' - -proc vimMotionSurround*(editor: TextDocumentEditor, cursor: Cursor, count: int, c0: char, c1: char, inside: bool): Selection = - result = cursor.toSelection - # infof"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}" - while true: - let lastChar = editor.charAt(result.last) - let (startDepth, endDepth) = if lastChar == c0: - (1, 0) - elif lastChar == c1: - (0, 1) - else: - (1, 1) - - # infof"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find around: {startDepth}, {endDepth}" - if editor.findSurroundStart(result.first, count, c0, c1, startDepth).getSome(opening) and editor.findSurroundEnd(result.last, count, c0, c1, endDepth).getSome(closing): - result = (opening, closing) - # infof"vimMotionSurround: found inside {result}" - if inside: - result.first = editor.doMoveCursorColumn(result.first, 1) - result.last = editor.doMoveCursorColumn(result.last, -1) - return - - # infof"vimMotionSurround: {cursor}, {count}, {c0}, {c1}, {inside}: try find ahead: {startDepth}, {endDepth}" - if editor.findSurroundEnd(result.first, count, c0, c1, -1).getSome(opening) and editor.findSurroundEnd(opening, count, c0, c1, 0).getSome(closing): - result = (opening, closing) - # infof"vimMotionSurround: found ahead {result}" - if inside: - result.first = editor.doMoveCursorColumn(result.first, 1) - result.last = editor.doMoveCursorColumn(result.last, -1) - return - else: - # infof"vimMotionSurround: found nothing {result}" - return - -proc vimMoveToMatching(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-move-to-matching").} = - # todo: pass as parameter - let which = if editor.mode == "vim.visual" or editor.mode == "vim.visual-line": - SelectionCursor.Last - else: - SelectionCursor.Both - - editor.selections = editor.selections.mapIt(block: - let c = editor.charAt(it.last) - let (open, close, last) = case c - of '(': ('(', ')', true) - of '{': ('{', '}', true) - of '[': ('[', ']', true) - of '<': ('<', '>', true) - of ')': ('(', ')', false) - of '}': ('{', '}', false) - of ']': ('[', ']', false) - of '>': ('<', '>', false) - of '"': ('"', '"', true) - of '\'': ('\'', '\'', true) - else: return - - let selection = editor.vimMotionSurround(it.last, 0, open, close, false) - - if last: - selection.last.toSelection(it, which) - else: - selection.first.toSelection(it, which) - ) - - editor.scrollToCursor Last - editor.updateTargetColumn() - -proc vimMotionSurroundBracesInner*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '{', '}', true) -proc vimMotionSurroundBracesOuter*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '{', '}', false) -proc vimMotionSurroundParensInner*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '(', ')', true) -proc vimMotionSurroundParensOuter*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '(', ')', false) -proc vimMotionSurroundBracketsInner*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '[', ']', true) -proc vimMotionSurroundBracketsOuter*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '[', ']', false) -proc vimMotionSurroundAngleInner*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '<', '>', true) -proc vimMotionSurroundAngleOuter*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '<', '>', false) -proc vimMotionSurroundDoubleQuotesInner*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '"', '"', true) -proc vimMotionSurroundDoubleQuotesOuter*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '"', '"', false) -proc vimMotionSurroundSingleQuotesInner*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '\'', '\'', true) -proc vimMotionSurroundSingleQuotesOuter*(editor: TextDocumentEditor, cursor: Cursor, count: int): Selection = vimMotionSurround(editor, cursor, count, '\'', '\'', false) - -# todo -addCustomTextMove "vim-line", vimMotionLine -addCustomTextMove "vim-visual-line", vimMotionVisualLine -addCustomTextMove "vim-word-outer", vimMotionWordOuter -addCustomTextMove "vim-WORD-outer", vimMotionWordBigOuter -addCustomTextMove "vim-paragraph-inner", vimMotionParagraphInner -addCustomTextMove "vim-paragraph-outer", vimMotionParagraphOuter -addCustomTextMove "vim-surround-{-inner", vimMotionSurroundBracesInner -addCustomTextMove "vim-surround-{-outer", vimMotionSurroundBracesOuter -addCustomTextMove "vim-surround-(-inner", vimMotionSurroundParensInner -addCustomTextMove "vim-surround-(-outer", vimMotionSurroundParensOuter -addCustomTextMove "vim-surround-[-inner", vimMotionSurroundBracketsInner -addCustomTextMove "vim-surround-[-outer", vimMotionSurroundBracketsOuter -addCustomTextMove "vim-surround-angle-inner", vimMotionSurroundAngleInner -addCustomTextMove "vim-surround-angle-outer", vimMotionSurroundAngleOuter -addCustomTextMove "vim-surround-\"-inner", vimMotionSurroundDoubleQuotesInner -addCustomTextMove "vim-surround-\"-outer", vimMotionSurroundDoubleQuotesOuter -addCustomTextMove "vim-surround-'-inner", vimMotionSurroundSingleQuotesInner -addCustomTextMove "vim-surround-'-outer", vimMotionSurroundSingleQuotesOuter - -iterator iterateTextObjects*(editor: TextDocumentEditor, cursor: Cursor, move: string, backwards: bool = false): Selection = - var selection = editor.getSelectionForMove(cursor, move, 0) - # infof"iterateTextObjects({cursor}, {move}, {backwards}), selection: {selection}" - yield selection - while true: - let lastSelection = selection - if not backwards and selection.last.column == editor.lineLength(selection.last.line): - if selection.last.line == editor.lineCount - 1: - break - selection = (selection.last.line + 1, 0).toSelection - elif backwards and selection.first.column == 0: - if selection.first.line == 0: - break - selection = (selection.first.line - 1, editor.lineLength(selection.first.line - 1)).toSelection - if selection.first.column == 0: - yield selection - continue - - let nextCursor = if backwards: (selection.first.line, selection.first.column - 1) else: (selection.last.line, selection.last.column + 1) - let newSelection = editor.getSelectionForMove(nextCursor, move, 0) - # infof"iterateTextObjects({cursor}, {move}, {backwards}) nextCursor: {nextCursor}, newSelection: {newSelection}" - if newSelection == lastSelection: - break - - selection = newSelection - yield selection - -iterator enumerateTextObjects*(editor: TextDocumentEditor, cursor: Cursor, move: string, backwards: bool = false): (int, Selection) = - var i = 0 - for selection in iterateTextObjects(editor, cursor, move, backwards): - yield (i, selection) - inc i - -proc vimSelectTextObject(editor: TextDocumentEditor, textObject: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1, textObjectRange: VimTextObjectRange = Inner) {.exposeActive(editorContext, "vim-select-text-object").} = - # infof"vimSelectTextObject({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count})" - - editor.selections = editor.selections.mapIt(block: - var res = it.last - var resultSelection = it - # infof"-> {resultSelection}" - - for i, selection in enumerateTextObjects(editor, res, textObject, backwards): - # infof"{i}: {res} -> {selection}" - resultSelection = resultSelection or selection - if i == max(count, 1) - 1: - break - - # infof"vimSelectTextObject({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count}): {resultSelection}" - if it.isBackwards: - resultSelection.reverse - else: - resultSelection - ) - - editor.scrollToCursor(Last) - editor.updateTargetColumn() - -proc vimSelectSurrounding(editor: TextDocumentEditor, textObject: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1, textObjectRange: VimTextObjectRange = Inner) {.exposeActive(editorContext, "vim-select-surrounding").} = - # infof"vimSelectSurrounding({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count})" - - editor.selections = editor.selections.mapIt(block: - let resultSelection = editor.getSelectionForMove(it.last, textObject, count) - # infof"vimSelectSurrounding({textObject}, {textObjectRange}, {backwards}, {allowEmpty}, {count}): {resultSelection}" - if it.isBackwards: - resultSelection.reverse - else: - resultSelection - ) - - editor.scrollToCursor(Last) - editor.updateTargetColumn() - -proc moveSelectionNext(editor: TextDocumentEditor, move: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1) {.exposeActive(editorContext, "move-selection-next").} = - # infof"moveSelectionNext '{move}' {count} {backwards} {allowEmpty}" - editor.vimState.deleteInclusiveEnd = false - let which = getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) - editor.selections = editor.selections.mapIt(block: - var res = it.last - for k in 0..= editor.lineLength(selection.first.line) or editor.getChar(selection.first) notin Whitespace: - res = cursor - break - # echo res, ", ", it, ", ", which - res.toSelection(it, which) - ) - - editor.scrollToCursor(Last) - editor.updateTargetColumn() - -proc applyMove(editor: TextDocumentEditor, selections: seq[Selection], move: string, backwards: bool = false, allowEmpty: bool = false, count: int = 1, which: Option[SelectionCursor] = SelectionCursor.none): seq[Selection] = - ## Applies the given move `count` times and returns the resulting selections - ## `allowEmpty` If true then the move can stop on empty lines - ## `backwards` Move backwards - ## `count` How often to apply the move - ## `which` How to assemble the final selection from the input and the move. If not set uses `editor.text.cursor.movement` - - # infof"moveSelectionEnd '{move}' {count} {backwards} {allowEmpty}" - let which = which.get(getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both)) - return selections.mapIt(block: - var res = it.last - for k in 0.. 1: - editor.moveCursorLine(count - 1) - editor.moveLast("vim-line") - editor.scrollToCursor Last - editor.updateTargetColumn() - -proc vimMoveToEndOfVisualLine(editor: TextDocumentEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-end-of-visual-line").} = - # infof"vimMoveToEndOfLine {count}" - let count = max(1, count) - if count > 1: - editor.moveCursorLine(count - 1) - editor.moveLast("vim-visual-line") - editor.scrollToCursor Last - editor.updateTargetColumn() - -proc vimMoveCursorLineFirstChar(editor: TextDocumentEditor, direction: int, count: int = 1) {.exposeActive(editorContext, "vim-move-cursor-line-first-char").} = - editor.moveCursorLine(direction * max(count, 1)) - editor.moveFirst "line-no-indent" - editor.updateTargetColumn() - -proc vimMoveToStartOfLine(editor: TextDocumentEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-start-of-line").} = - # infof"vimMoveToStartOfLine {count}" - let count = max(1, count) - if count > 1: - editor.moveCursorLine(count - 1) - editor.moveFirst "line-no-indent" - editor.scrollToCursor Last - editor.updateTargetColumn() - -proc vimPaste(editor: TextDocumentEditor, pasteRight: bool = false, inclusiveEnd: bool = false, register: string = "") {.exposeActive(editorContext, "vim-paste").} = - # infof"vimPaste {register}, lines: {yankedLines}" - let register = if register == "vim-default-register": - getVimDefaultRegister() - else: - register - - editor.addNextCheckpoint "insert" - - let selectionsToDelete = editor.selections - editor.selections = editor.delete(selectionsToDelete, inclusiveEnd=false) - - if yankedLines: - # todo: pass bool as parameter - if editor.mode != "vim.visual-line": - editor.moveLast "line", Both - editor.insertText "\n", autoIndent=false - - if pasteRight: - editor.selections = editor.selections.mapIt(editor.doMoveCursorColumn(it.last, 1, wrap=false).toSelection) - - editor.setMode "vim.normal" - editor.paste register, inclusiveEnd=inclusiveEnd - -proc vimToggleCase(editor: TextDocumentEditor, moveCursorRight: bool) {.exposeActive(editorContext, "vim-toggle-case").} = - var editTexts: seq[string] - - for s in editor.selections: - let text = editor.getText(s, inclusiveEnd=true) - var newText = "" - for r in text.runes: - if r.isLower: - newText.add $r.toUpper - else: - newText.add $r.toLower - editTexts.add newText - - editor.addNextCheckpoint "insert" - let oldSelections = editor.selections - discard editor.edit(editor.selections, editTexts, inclusiveEnd=true) - editor.selections = oldSelections.mapIt(it.first.toSelection) - - editor.setMode "vim.normal" - - if moveCursorRight: - editor.moveCursorColumn(1, Both, wrap=false, - includeAfter=editor.vimState.cursorIncludeEol) - editor.updateTargetColumn() - -proc vimCloseCurrentViewOrQuit() {.exposeActive(editorContext, "vim-close-current-view-or-quit").} = - let openEditors = getNumVisibleViews() + getNumHiddenViews() - if openEditors == 1: - plugin_runtime.quit() - else: - closeActiveView() - -proc vimIndent(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-indent").} = - editor.addNextCheckpoint "insert" - editor.indent() - -proc vimUnindent(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-unindent").} = - editor.addNextCheckpoint "insert" - editor.unindent() - -proc vimAddCursorAbove(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-add-cursor-above").} = - editor.addCursorAbove() - editor.scrollToCursor(Last) - -proc vimAddCursorBelow(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-add-cursor-below").} = - editor.addCursorBelow() - editor.scrollToCursor(Last) - -proc vimEnter(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-enter").} = - editor.addNextCheckpoint "insert" - editor.insertText("\n") - -proc vimNormalMode(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-normal-mode").} = - ## Exit to normal mode and clear things - if editor.mode == "vim.normal": - # editor.selection = editor.selection.last.toSelection - editor.setSelection(editor.selection.last.toSelection, addToHistory = true.some) - editor.clearTabStops() - editor.setMode("vim.normal") - -proc vimVisualLineMode(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-visual-line-mode").} = - editor.setMode "vim.visual-line" - editor.vimSelectLine() - -proc vimYankLine(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-yank-line").} = - editor.vimState.selectLines = true - editor.vimSelectLine() - editor.vimYankSelection() - editor.vimState.selectLines = false - -proc vimDeleteLine(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-delete-line").} = - editor.vimState.selectLines = true - let oldSelections = editor.selections - editor.vimSelectLine() - editor.vimDeleteSelection(true, oldSelections=oldSelections.some) - editor.vimState.selectLines = false - -proc vimChangeLine(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-change-line").} = - let oldSelections = editor.selections - editor.vimSelectLine() - editor.vimChangeSelection(true, oldSelections=oldSelections.some) - -proc vimDeleteToLineEnd(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-delete-to-line-end").} = - let oldSelections = editor.selections - editor.selections = editor.selections.mapIt (it.last, editor.vimMotionLine(it.last, 0).last) - editor.vimDeleteSelection(true, oldSelections=oldSelections.some) - editor.vimState.selectLines = false - -proc vimChangeToLineEnd(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-change-to-line-end").} = - let oldSelections = editor.selections - editor.selections = editor.selections.mapIt (it.last, editor.vimMotionLine(it.last, 0).last) - editor.vimChangeSelection(true, oldSelections=oldSelections.some) - editor.vimState.selectLines = false - -proc vimYankToLineEnd(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-yank-to-line-end").} = - editor.selections = editor.selections.mapIt (it.last, editor.vimMotionLine(it.last, 0).last) - editor.vimYankSelection() - editor.vimState.selectLines = false - -proc vimMoveFileStart(editor: TextDocumentEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-file-start").} = - let which = getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) - editor.selection = (count - 1, 0).toSelection(editor.selection, which) - editor.moveFirst "line-no-indent" - editor.scrollToCursor Last - editor.setNextSnapBehaviour(MinDistanceOffscreen) - -proc vimMoveFileEnd(editor: TextDocumentEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-file-end").} = - let line = if count == 0: editor.lineCount - 1 else: count - 1 - let which = getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) - var newSelection = (line, 0).toSelection(editor.selection, which) - if newSelection == editor.selection: - let lineLen = editor.lineLength(line) - editor.selection = (line, lineLen).toSelection(editor.selection, which) - else: - editor.selection = newSelection - editor.moveFirst "line-no-indent" - editor.scrollToCursor Last - editor.setNextSnapBehaviour(MinDistanceOffscreen) - -proc vimMoveToMatchingOrFileOffset(editor: TextDocumentEditor, count: int = 1) {.exposeActive(editorContext, "vim-move-to-matching-or-file-offset").} = - if count == 0: - editor.vimMoveToMatching() - else: - let line = clamp((count * editor.lineCount) div 100, 0, editor.lineCount - 1) - let which = getOption[SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) - editor.selection = (line, 0).toSelection(editor.selection, which) - editor.moveFirst "line-no-indent" - editor.scrollToCursor Last - editor.setNextSnapBehaviour(MinDistanceOffscreen) - -proc vimScrollLineToTopAndMoveLineStart(editor: TextDocumentEditor, count: int = 1) {.exposeActive(editorContext, "vim-scroll-line-to-top-and-move-line-start").} = - if editor.getCommandCount != 0: - editor.selection = (editor.getCommandCount, 0).toSelection - editor.moveFirst "line-no-indent" - editor.setCursorScrollOffset getVimLineMargin() * platformTotalLineHeight() - -proc vimScrollLineToTop(editor: TextDocumentEditor, count: int = 1) {.exposeActive(editorContext, "vim-scroll-line-to-top").} = - if editor.getCommandCount != 0: - editor.selection = (editor.getCommandCount, editor.selection.last.column).toSelection - editor.setCursorScrollOffset getVimLineMargin() * platformTotalLineHeight() - -proc vimCenterLineAndMoveLineStart(editor: TextDocumentEditor, count: int = 1) {.exposeActive(editorContext, "vim-center-line-and-move-line-start").} = - if editor.getCommandCount != 0: - editor.selection = (editor.getCommandCount, 0).toSelection - editor.moveFirst "line-no-indent" - editor.centerCursor() - -proc vimCenterLine(editor: TextDocumentEditor, count: int = 1) {.exposeActive(editorContext, "vim-center-line").} = - if editor.getCommandCount != 0: - editor.selection = (editor.getCommandCount, editor.selection.last.column).toSelection - editor.centerCursor() - -proc vimScrollLineToBottomAndMoveLineStart(editor: TextDocumentEditor, count: int = 1) {.exposeActive(editorContext, "vim-scroll-line-to-bottom-and-move-line-start").} = - if editor.getCommandCount != 0: - editor.selection = (editor.getCommandCount, 0).toSelection - editor.moveFirst "line-no-indent" - editor.setCursorScrollOffset (editor.screenLineCount.float - getVimLineMargin()) * platformTotalLineHeight() - -proc vimScrollLineToBottom(editor: TextDocumentEditor, count: int = 1) {.exposeActive(editorContext, "vim-scroll-line-to-bottom").} = - if editor.getCommandCount != 0: - editor.selection = (editor.getCommandCount, editor.selection.last.column).toSelection - editor.setCursorScrollOffset (editor.screenLineCount.float - getVimLineMargin()) * platformTotalLineHeight() - -proc vimInsertMode(editor: TextDocumentEditor, move: string = "") {.exposeActive(editorContext, "vim-insert-mode").} = - case move - of "right": - editor.selections = editor.selections.mapIt(editor.doMoveCursorColumn(it.last, 1, wrap=false).toSelection) - of "line-end": - editor.moveLast "line", Both - of "line-no-indent": - editor.moveFirst "line-no-indent", Both - of "first": - editor.selections = editor.selections.mapIt(it.normalized.first.toSelection) - of "line-start": - editor.moveFirst "line", Both - else: - discard - editor.setMode "vim.insert" - editor.addNextCheckpoint "insert" - -proc vimInsertLineBelow(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-insert-line-below").} = - editor.moveLast "line", Both - editor.addNextCheckpoint "insert" - editor.insertText "\n" - editor.setMode "vim.insert" - -proc vimInsertLineAbove(editor: TextDocumentEditor, move: string = "") {.exposeActive(editorContext, "vim-insert-line-above").} = - editor.moveFirst "line", Both - editor.addNextCheckpoint "insert" - editor.insertText "\n", autoIndent=false - editor.vimMoveCursorLine -1 - editor.setMode "vim.insert" - -proc vimSetSearchQueryFromWord(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-set-search-query-from-word").} = - editor.selection = editor.setSearchQueryFromMove("word", prefix=r"\b", suffix=r"\b").first.toSelection - -proc vimSetSearchQueryFromSelection(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-set-search-query-from-selection").} = - discard editor.setSearchQuery(editor.getText(editor.selection, inclusiveEnd=true), escapeRegex=true) - editor.selection = editor.selection.first.toSelection - editor.setMode("vim.normal") - -proc vimNextSearchResult(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-next-search-result").} = - editor.selection = editor.getNextFindResult(editor.selection.last).first.toSelection - editor.scrollToCursor Last - editor.setNextSnapBehaviour(MinDistanceOffscreen) - editor.updateTargetColumn() - -proc vimPrevSearchResult(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-prev-search-result").} = - editor.selection = editor.getPrevFindResult(editor.selection.last).first.toSelection - editor.scrollToCursor Last - editor.setNextSnapBehaviour(MinDistanceOffscreen) - editor.updateTargetColumn() - -proc vimOpenSearchBar(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-open-search-bar").} = - editor.openSearchBar() - if getActiveEditor().isTextEditor(editor): - editor.setMode("vim.insert") - -proc vimExitCommandLine() {.expose("vim-exit-command-line").} = - if getActiveEditor().isTextEditor(editor): - if editor.mode == "vim.normal": - exitCommandLine() - return - - editor.setMode("vim.normal") - -proc vimExitPopup() {.expose("vim-exit-popup").} = - if getActiveEditor().isTextEditor(editor): - if editor.mode == "vim.normal": - if getActivePopup().isSelectorPopup(popup): - popup.cancel() - return - - editor.setMode("vim.normal") - -proc vimSelectWordOrAddCursor(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-select-word-or-add-cursor").} = - if editor.selections.len == 1: - var selection = editor.setSearchQueryFromMove("word", prefix=r"\b", suffix=r"\b") - selection.last.column -= 1 - editor.selection = selection - else: - let next = editor.getNextFindResult(editor.selection.last, includeAfter=false) - editor.selections = editor.selections & next - editor.scrollToCursor Last - editor.setNextSnapBehaviour(MinDistanceOffscreen) - editor.updateTargetColumn() - - editor.setMode("vim.visual") - -proc vimMoveLastSelectionToNextSearchResult(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-move-last-selection-to-next-search-result").} = - if editor.selections.len == 1: - var selection = editor.setSearchQueryFromMove("word", prefix=r"\b", suffix=r"\b") - selection.last.column -= 1 - editor.selection = selection - else: - let next = editor.getNextFindResult(editor.selection.last, includeAfter=false) - editor.selections = editor.selections[0..^2] & next - editor.scrollToCursor Last - editor.setNextSnapBehaviour(MinDistanceOffscreen) - editor.updateTargetColumn() - - editor.setMode("vim.visual") - -proc vimSetSearchQueryOrAddCursor(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-set-search-query-or-add-cursor").} = - if editor.selections.len == 1: - let text = editor.getText(editor.selection, inclusiveEnd=true) - let textEscaped = text.escapeRegex - let currentSearchQuery = editor.getSearchQuery() - # infof"'{text}' -> '{textEscaped}' -> '{currentSearchQuery}'" - if textEscaped != currentSearchQuery and r"\b" & textEscaped & r"\b" != currentSearchQuery: - if editor.setSearchQuery(text, escapeRegex=true): - return - - let next = editor.getNextFindResult(editor.selection.last, includeAfter=false) - editor.selections = editor.selections & next - editor.scrollToCursor Last - editor.updateTargetColumn() - -proc vimSaveState() {.expose("vim-save-state").} = - try: - var states = initTable[string, JsonNode]() - - for id, state in editorStates: - if id.isTextEditor(editor): - let filename = editor.getFileName() - if filename == "": - continue - - var marks = initTable[string, seq[Selection]]() - for name, anchors in editor.vimState.marks: - let selections = editor.resolveAnchors(anchors) - marks[name] = selections - for name, selections in editor.vimState.unresolveMarks: - marks[name] = selections - - if marks.len > 0: - states[filename] = %*{ - "marks": marks.toJson, - } - - setSessionData("vim.states", states) - except: - infof"Failed to save vim editor states" - -proc resolveMarks(editor: TextDocumentEditor) = - let unresolveMarks = editor.vimState.unresolveMarks - for name, selections in unresolveMarks: - let anchors = editor.createAnchors(selections) - if anchors.len > 0: - editor.vimState.marks[name] = anchors - editor.vimState.unresolveMarks.del(name) - -proc vimAddMark(editor: TextDocumentEditor, name: string) {.exposeActive(editorContext, "vim-add-mark").} = - editor.resolveMarks() - editor.vimState.marks[name] = editor.createAnchors(editor.selections) - -proc vimGotoMark(editor: TextDocumentEditor, name: string) {.exposeActive(editorContext, "vim-goto-mark").} = - editor.resolveMarks() - - if name in editor.vimState.marks: - let newSelections = editor.resolveAnchors(editor.vimState.marks[name]) - if newSelections.len == 0: - return - - case editor.mode - of "vim.visual", "vim.visual-line": - let oldSelections = editor.selections - if newSelections.len == oldSelections.len: - editor.selections = collect: - for i in 0..newSelections.high: - oldSelections[i] or newSelections[i] - else: - editor.selections = newSelections - else: - editor.selections = newSelections - - editor.updateTargetColumn() - editor.scrollToCursor Last - editor.setNextSnapBehaviour(MinDistanceOffscreen) - -proc vimDeleteWordBack(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-delete-word-back").} = - let selections = editor.applyMove(editor.selections, "vim-word", true, true, 1, which = SelectionCursor.Last.some) - editor.selections = editor.delete(selections) - editor.autoShowCompletions() - -proc vimDeleteLineBack(editor: TextDocumentEditor) {.exposeActive(editorContext, "vim-delete-line-back").} = - let selections = editor.applyMove(editor.selections, "vim-line", true, true, 1, which = SelectionCursor.Last.some) - editor.selections = editor.delete(selections) - editor.autoShowCompletions() - -proc vimSurround(editor: TextDocumentEditor, text: string) {.exposeActive(editorContext, "vim-surround").} = - let (left, right) = case text - of "(", ")": ("(", ")") - of "{", "}": ("{", "}") - of "[", "]": ("[", "]") - of "<", ">": ("<", ">") - else: - (text, text) - - var insertSelections: Selections = @[] - var insertTexts: seq[string] = @[] - for s in editor.selections: - let s = s.normalized - insertSelections.add s.first.toSelection - insertSelections.add editor.doMoveCursorColumn(s.last, 1).toSelection - insertTexts.add left - insertTexts.add right - - editor.addNextCheckpoint "insert" - let newSelections = editor.insertMulti(insertSelections, insertTexts) - if newSelections.len mod 2 != 0: - return - - editor.selections = collect: - for i in 0.. {newModes}" - - let oldMode = if oldModes.len > 0: - oldModes[0] - else: - "" - - if newModes.len == 0: - return - - let newMode = newModes[0] - - if not editor.getCurrentEventHandlers().contains("vim"): - return - - if newMode == "": - editor.setMode "vim.normal" - return - - if not newMode.startsWith("vim"): - return - - let recordModes = [ - "vim.visual", - "vim.visual-line", - "vim.insert", - ].toHashSet - - # infof"vim: handle mode change {oldMode} -> {newMode}" - if newMode == "vim.normal": - if not isReplayingCommands() and isRecordingCommands(".-temp"): - stopRecordingCommands(".-temp") - - if editor.getRevision > editor.vimState.revisionBeforeImplicitInsertMacro: - infof"Record implicit macro because document was modified" - let text = getRegisterText(".-temp") - setRegisterText(text, ".") - # else: - # infof"Don't record implicit macro because nothing was modified" - else: - if oldMode == "vim.normal" and newMode in recordModes: - editor.startRecordingCurrentCommandInPeriodMacro() - - editor.clearCurrentCommandHistory(retainLast=true) - - editor.vimState.selectLines = newMode == "vim.visual-line" - editor.vimState.cursorIncludeEol = newMode == "vim.insert" - editor.vimState.currentUndoCheckpoint = if newMode == "vim.insert": "word" else: "insert" - - case newMode - of "vim.normal": - editor.setConfig "text.inclusive-selection", false - editor.selections = editor.selections.mapIt(editor.vimClamp(it.last).toSelection) - editor.saveCurrentCommandHistory() - editor.hideCompletions() - - of "vim.insert": - editor.setConfig "text.inclusive-selection", false - - of "vim.visual": - editor.setConfig "text.inclusive-selection", true - - of "vim.visual-line": - editor.setConfig "text.inclusive-selection", false - - else: - editor.setConfig "text.inclusive-selection", false - -proc loadVimKeybindings*() {.expose("load-vim-keybindings").} = - let afterRestoreSessionHandle = addCallback proc(args: JsonNode): JsonNode = - let states = getSessionData[Table[string, JsonNode]]("vim.states") - for id in getAllEditors(): - if id.isTextEditor(editor): - try: - let filename = editor.getFileName() - if states.hasKey(filename): - let editorState = states[filename] - if editorState.hasKey("marks"): - let marks = editorState["marks"].jsonTo(Table[string, seq[Selection]]) - for name, selections in marks: - editor.vimState.unresolveMarks[name] = selections - except: - infof"Failed to restore marks for {editor}" - scriptSetCallback("after-restore-session", afterRestoreSessionHandle) - - let beforeSaveAppStateHandle = addCallback proc(args: JsonNode): JsonNode = - vimSaveState() - scriptSetCallback("before-save-app-state", beforeSaveAppStateHandle) diff --git a/config/keybindings_vim_like.nim b/config/keybindings_vim_like.nim deleted file mode 100644 index 15a208e05..000000000 --- a/config/keybindings_vim_like.nim +++ /dev/null @@ -1,300 +0,0 @@ -import plugin_runtime, keybindings_normal -import misc/[timer] - -proc loadVimLikeKeybindings*() {.expose("load-vim-like-keybindings").} = - loadNormalKeybindings() - - let t = startTimer() - defer: - infof"loadVimLikeKeybindings: {t.elapsed.ms} ms" - - info "Applying Vim-like keybindings" - - # clearCommands("editor.text") - # for id in getAllEditors(): - # if id.isTextEditor(editor): - # editor.setMode("") - - setModeChangedHandler proc(editor, oldMode, newMode: auto) = - if oldMode == "" and newMode != "": - editor.clearCurrentCommandHistory(retainLast=true) - elif oldMode != "" and newMode == "": - editor.saveCurrentCommandHistory() - - # Normal mode - setHandleInputs "editor.text", false - setOption "editor.text.cursor.movement.", "both" - setOption "editor.text.cursor.wide.", true - - # navigation - addTextCommand "", "", "move-cursor-line", 30 - addTextCommand "", "", "move-cursor-line", -30 - - addTextCommandBlock "", "gg": - let count = editor.getCommandCount - editor.selection = (count, 0).toSelection - editor.setCommandCount 0 - editor.scrollToCursor Last - - addTextCommand "", "G", "move-last", "file" - - addTextCommand "", "n", "select-move", "next-find-result", true - addTextCommand "", "N", "select-move", "prev-find-result", true - - addTextCommandBlock "", "*": editor.selection = editor.setSearchQueryFromMove("word") - - # editing - addTextCommand "", "x", "delete-right" - addTextCommand "", "u", "undo" - addTextCommand "", "U", "redo" - addTextCommand "", "p", "paste" - - addTextCommand "", "\\>", "indent" - addTextCommand "", "\\<", "unindent" - - addTextCommand "", ".", "run-saved-commands" - - # mode switches - addTextCommand "", "i", "set-mode", "insert" - addTextCommandBlock "", "I": - editor.moveFirst("line-no-indent") - editor.setMode("insert") - addTextCommandBlock "", "a": - editor.selections = editor.selections.mapIt(editor.doMoveCursorColumn(it.last, 1).toSelection) - editor.setMode("insert") - addTextCommandBlock "", "A": - editor.moveLast("line") - editor.setMode("insert") - - addTextCommand "", "v", "set-mode", "visual" - - addTextCommandBlock "", "s": - editor.selections = editor.delete(editor.selections) - editor.setMode("insert") - - for i in 0..9: - capture i: - proc updateCommandCountHelper(editor: TextDocumentEditor) = - editor.updateCommandCount i - # echo "updateCommandCount ", editor.getCommandCount - editor.setCommandCountRestore editor.getCommandCount - editor.setCommandCount 0 - - addTextCommand "", $i, updateCommandCountHelper - - addTextCommandBlock "", "d": - editor.setMode "move" - setOption "text.move-action", "delete-move" - setOption "text.move-next-mode", "" - setOption "text.move-command-count", editor.getCommandCount() - editor.setCommandCount 0 - - addTextCommand "", "D", "delete-move", "line" - - addTextCommandBlock "", "c": - editor.setMode "move" - setOption "text.move-action", "change-move" - setOption "text.move-next-mode", "insert" - setOption "text.move-command-count", editor.getCommandCount() - editor.setCommandCount 0 - - addTextCommandBlock "", "y": - editor.setMode "move" - setOption "text.move-action", "copy-move" - setOption "text.move-next-mode", "" - setOption "text.move-command-count", editor.getCommandCount() - editor.setCommandCount 0 - - addTextCommandBlock "", "f": - setOption("text.move-next-mode", editor.mode) - editor.setMode "move-to" - setOption "text.move-command-count", editor.getCommandCount() - editor.setCommandCount 0 - - addTextCommandBlock "", "t": - setOption("text.move-next-mode", editor.mode) - editor.setMode "move-before" - setOption "text.move-command-count", editor.getCommandCount() - editor.setCommandCount 0 - - addTextCommandBlock "", "D": - editor.deleteMove("line") - - addTextCommandBlock "", "C": - editor.deleteMove("line") - editor.setMode("insert") - - addTextCommandBlock "", "Y": - editor.selectMove("line") - editor.copy() - - addTextCommand "", "dl", "delete-move", "line-next" - addTextCommand "", "b", "move-last", "word-line-back" - addTextCommand "", "w", "move-last", "word-line" - addTextCommand "", "e", "move-last", "word-line" - addTextCommand "", "", "move-first", "line" - addTextCommand "", "", "move-last", "line" - addTextCommandBlock "", "o": - editor.moveCursorEnd() - editor.insertText("\n") - editor.setMode("insert") - addTextCommandBlock "", "O": - editor.moveCursorEnd() - editor.insertText("\n") - addTextCommandBlock "", "": - editor.setMode("") - editor.selection = editor.selection.last.toSelection - addTextCommandBlock "", "": - editor.setMode("") - editor.selection = editor.selection.last.toSelection - addTextCommandBlock "", "": - editor.setMode("") - editor.selection = editor.selection.last.toSelection - - # move mode - setHandleInputs "editor.text.move", false - setOption "editor.text.cursor.wide.move", true - setOption "editor.text.cursor.movement.move", "both" - - template addTextMoveCommand(keys: string, move: string): untyped = - addTextCommand "move", keys, "apply-move", move, false - addTextCommand "move-inside", keys, "apply-move", move, true - - addTextMoveCommand "w", "word-line" - addTextMoveCommand "W", "word" - addTextMoveCommand "b", "word-line-back" - addTextMoveCommand "B", "word-back" - addTextMoveCommand "p", "paragraph" - addTextMoveCommand "F", "file" - addTextMoveCommand "\"", "\"" - addTextMoveCommand "'", "'" - addTextMoveCommand "(", "(" - addTextMoveCommand ")", "(" - addTextMoveCommand "[", "[" - addTextMoveCommand "]", "[" - addTextMoveCommand "}", "}" - - addTextCommand "move", "d", "apply-move", "line-next", true - addTextCommand "move-inside", "d", "apply-move", "line-next", true - addTextCommand "move", "y", "apply-move", "line-next", true - addTextCommand "move-inside", "y", "apply-move", "line-next", true - - addTextCommandBlock "move", "f": - editor.setMode "move-to" - setOption "text.move-command-count", editor.getCommandCount() - editor.setCommandCount 0 - - addTextCommandBlock "move", "t": - editor.setMode "move-before" - setOption "text.move-command-count", editor.getCommandCount() - editor.setCommandCount 0 - - # move-to mode - setHandleActions "editor.text.move-to", false - setTextInputHandler "move-to", proc(editor: TextDocumentEditor, input: string): bool = - editor.setMode getOption[string]("text.move-next-mode") - if getOption[string]("text.move-action") != "": - editor.setCommandCount getOption[int]("text.move-command-count") - var args = newJArray() - args.add newJString("move-to " & input) - discard editor.runAction(getOption[string]("text.move-action"), args) - setOption[string]("text.move-action", "") - else: - editor.moveCursorTo(input) - return true - setOption "editor.text.cursor.wide.move-to", true - setOption "editor.text.cursor.movement.move-to", "both" - - # move-before mode - setHandleActions "editor.text.move-before", false - setTextInputHandler "move-before", proc(editor: TextDocumentEditor, input: string): bool = - editor.setMode getOption[string]("text.move-next-mode") - if getOption[string]("text.move-action") != "": - editor.setCommandCount getOption[int]("text.move-command-count") - var args = newJArray() - args.add newJString("move-before " & input) - discard editor.runAction(getOption[string]("text.move-action"), args) - setOption[string]("text.move-action", "") - else: - editor.moveCursorBefore(input) - return true - setOption "editor.text.cursor.wide.move-before", true - setOption "editor.text.cursor.movement.move-before", "both" - - # Insert mode - setHandleInputs "editor.text.insert", true - setOption "editor.text.cursor.wide.insert", false - addTextCommand "insert", "", "insert-text", "\n" - addTextCommand "insert", "", "insert-text", " " - - # Visual mode - setHandleInputs "editor.text.visual", false - setOption "editor.text.cursor.wide.visual", true - setOption "editor.text.cursor.movement.visual", "last" - addTextCommand "visual", "y", "copy" - - addTextCommandBlock "visual", "i": - editor.setMode("move-inside") - setOption("text.move-action", "select-move") - setOption("text.move-next-mode", "visual") - - addTextCommandBlock "visual", "d": - editor.selections = editor.delete(editor.selections) - editor.setMode("") - editor.scrollToCursor(Last) - editor.updateTargetColumn(Last) - - addTextCommandBlock "visual", "c": - editor.selections = editor.delete(editor.selections) - editor.setMode("insert") - editor.scrollToCursor(Last) - editor.updateTargetColumn(Last) - - addTextCommandBlock "visual", "d": - editor.selections = editor.delete(editor.selections) - editor.setMode("") - editor.scrollToCursor(Last) - editor.updateTargetColumn(Last) - - addTextCommandBlock "visual", "c": - editor.selections = editor.delete(editor.selections) - editor.setMode("insert") - editor.scrollToCursor(Last) - editor.updateTargetColumn(Last) - - when enableAst: - setHandleInputs "editor.model", false - setOption "editor.model.cursor.wide.", true - - addCommand("editor.model", "b", "move-cursor-left-cell") - addCommand("editor.model", "w", "move-cursor-right-cell") - addCommand("editor.model", "u", "undo") - addCommand("editor.model", "U", "redo") - addModelCommand "", "}", "goto-next-node-of-class", "EmptyLine" - addModelCommand "", "{", "goto-prev-node-of-class", "EmptyLine" - - addModelCommandBlock "", "": - editor.setMode("") - # editor.selection = editor.selection.last.toSelection - addModelCommandBlock "", "": - editor.setMode("") - # editor.selection = editor.selection.last.toSelection - addModelCommandBlock "", "": - editor.setMode("") - # editor.selection = editor.selection.last.toSelection - - addModelCommand "", "i", "set-mode", "insert" - addModelCommandBlock "", "I": - editor.moveCursorLineStart(false) - editor.setMode("insert") - addModelCommandBlock "", "a": - # editor.selections = editor.selections.mapIt(editor.doMoveCursorColumn(it.last, 1).toSelection) - editor.setMode("insert") - addModelCommandBlock "", "A": - editor.moveCursorLineEnd(false) - editor.setMode("insert") - - # Insert mode - setHandleInputs "editor.model.insert", true - setOption "editor.model.cursor.wide.insert", false - addModelCommand "insert", "", "insert-text-at-cursor", " " diff --git a/nev.nimble b/nev.nimble index cf27899c5..351c5ec7e 100644 --- a/nev.nimble +++ b/nev.nimble @@ -34,12 +34,11 @@ requires "chronos >= 4.0.3" requires "https://github.com/Nimaoth/fsnotify >= 0.1.6" requires "https://github.com/Nimaoth/ws >= 0.5.0" requires "https://github.com/Nimaoth/windy >= 0.0.5" -requires "https://github.com/Nimaoth/wasm3 >= 0.1.17" requires "https://github.com/Nimaoth/lrucache.nim >= 1.1.4" requires "https://github.com/Nimaoth/boxy#bfc8665" requires "https://github.com/Nimaoth/nimtreesitter-api >= 0.1.21" requires "https://github.com/Nimaoth/nimwasmtime#415a252" -requires "https://github.com/Nimaoth/nimsumtree#47f7208" +requires "https://github.com/Nimaoth/nimsumtree#2a6e442" requires "https://github.com/Nimaoth/zippy >= 0.10.17" requires "libssh2 >= 0.1.9" requires "ssh2 >= 0.1.9" diff --git a/nimble.lock b/nimble.lock index 3049dd0fd..b18205672 100644 --- a/nimble.lock +++ b/nimble.lock @@ -310,16 +310,6 @@ "sha1": "8bccb6b02295a85eab30aeb8c411927680ca9192" } }, - "micros": { - "version": "0.1.16", - "vcsRevision": "4dfeac6a47ba8b6287081b048158981a07832063", - "url": "https://github.com/beef331/micros/", - "downloadMethod": "git", - "dependencies": [], - "checksums": { - "sha1": "9452155bea9280923b9a5654037eb92513ec8e7c" - } - }, "nimgen": { "version": "0.5.4", "vcsRevision": "e6697aabbe36e42e4dd261cfd0b8a8be16f6f342", @@ -432,18 +422,6 @@ "sha1": "fa636c9852fbf196df05baa1c9a0c976f533150d" } }, - "wasm3": { - "version": "0.1.17", - "vcsRevision": "e056b725ec1fad4e35e922d1602b41e970a06d3d", - "url": "https://github.com/Nimaoth/wasm3", - "downloadMethod": "git", - "dependencies": [ - "micros" - ], - "checksums": { - "sha1": "fd5cc6b4e8dc6cb1f6f2fe45cbc50cc0adc5363e" - } - }, "ws": { "version": "0.5.0", "vcsRevision": "7f39f987232cd839eb126488855da107c331f140", diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index c66fe64c6..3d55e2f56 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -450,10 +450,12 @@ proc moveTo*(editor: TextEditor, target: string, before: bool, count: int = 1) { let key = parseTarget(target) var s = editor.getSelections - for _ in 0.. 0: cb.seekPrevRune() if not predicate(cb.currentChar()): diff --git a/src/move_database.nim b/src/move_database.nim index d78b384aa..58b239f32 100644 --- a/src/move_database.nim +++ b/src/move_database.nim @@ -1,11 +1,11 @@ -import std/[strutils, sequtils, sugar, options, json, strformat, tables, algorithm] +import std/[strutils, sequtils, sugar, strformat, tables, json] import scripting_api except DocumentEditor, TextDocumentEditor, AstDocumentEditor from scripting_api as sca import nil -import misc/[util, custom_logger, custom_unicode, myjsonutils, regex, rope_utils, rope_regex, custom_async] -import text/custom_treesitter, text/indent -import config_provider, service -import text/[overlay_map, tab_map, wrap_map, diff_map, display_map] +import misc/[util, custom_logger, custom_unicode, rope_utils, custom_async, myjsonutils] +import service +import text/[wrap_map, display_map] import nimsumtree/[rope] +import lisp {.push gcsafe, raises: [].} @@ -15,15 +15,17 @@ type MoveImpl* = proc(rope: Rope, move: string, selections: openArray[Selection], count: int, includeEol: bool): seq[Selection] {.gcsafe, raises: [].} MoveDatabase* = ref object of Service moves: Table[string, MoveImpl] + env: Env func serviceName*(_: typedesc[MoveDatabase]): string = "MoveDatabase" addBuiltinService(MoveDatabase) method init*(self: MoveDatabase): Future[Result[void, ref CatchableError]] {.async: (raises: []).} = + self.env = baseEnv() return ok() -proc vimMotionWord*(text: Rope, cursor: Cursor): Selection = +proc vimMotionWord*(text: Rope, cursor: Cursor, inclusive: bool): Selection = const AlphaNumeric = {'A'..'Z', 'a'..'z', '0'..'9', '_'} var line = text.getLine(cursor.line) @@ -32,15 +34,15 @@ proc vimMotionWord*(text: Rope, cursor: Cursor): Selection = let c = line.charAt(cursor.column.clamp(0, line.len - 1)) if c in Whitespace: - let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c in Whitespace) + let (startColumn, endColumn) = line.getEnclosing(cursor.column, inclusive, (c) => c in Whitespace) return ((cursor.line, startColumn), (cursor.line, endColumn)) elif c in AlphaNumeric: - let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c in AlphaNumeric) + let (startColumn, endColumn) = line.getEnclosing(cursor.column, inclusive, (c) => c in AlphaNumeric) return ((cursor.line, startColumn), (cursor.line, endColumn)) else: - let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c notin Whitespace and c notin AlphaNumeric) + let (startColumn, endColumn) = line.getEnclosing(cursor.column, inclusive, (c) => c notin Whitespace and c notin AlphaNumeric) return ((cursor.line, startColumn), (cursor.line, endColumn)) type VimWordCharCategory {.pure.} = enum Word, Whitespace, Other @@ -59,7 +61,7 @@ proc vimWordCharCategory*(c: Rune): VimWordCharCategory = return VimWordCharCategory.Word return VimWordCharCategory.Other -proc vimMotionWordBack*(text: Rope, cursor: Cursor): Selection = +proc vimMotionWordBack*(text: Rope, cursor: Cursor, inclusive: bool): Selection = if cursor.column == 0: return cursor.toSelection @@ -67,21 +69,21 @@ proc vimMotionWordBack*(text: Rope, cursor: Cursor): Selection = let current = text.runeAt(cursor.toPoint) let prev = text.runeAt(prevCursor.toPoint) if current.vimWordCharCategory == prev.vimWordCharCategory: - return vimMotionWord(text, cursor) - return vimMotionWord(text, prevCursor) + return vimMotionWord(text, cursor, inclusive) + return vimMotionWord(text, prevCursor, inclusive) -proc vimMotionWordBig*(text: Rope, cursor: Cursor): Selection = +proc vimMotionWordBig*(text: Rope, cursor: Cursor, inclusive: bool): Selection = var line = text.getLine(cursor.line) if line.len == 0: return (cursor.line, 0).toSelection let c = line.charAt(cursor.column.clamp(0, line.len - 1)) if c in Whitespace: - let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c in Whitespace) + let (startColumn, endColumn) = line.getEnclosing(cursor.column, inclusive, (c) => c in Whitespace) return ((cursor.line, startColumn), (cursor.line, endColumn)) else: - let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c notin Whitespace) + let (startColumn, endColumn) = line.getEnclosing(cursor.column, inclusive, (c) => c notin Whitespace) return ((cursor.line, startColumn), (cursor.line, endColumn)) proc findNext*(text: Rope, cursor: Cursor, target: Rune): Cursor = @@ -151,8 +153,94 @@ proc moveCursorColumn(text: Rope, cursor: Cursor, offset: int, wrap: bool = true return text.clampCursor(cursor, includeEol) -proc applyMove*(self: MoveDatabase, displayMap: DisplayMap, move: string, selections: openArray[Selection], count: int = 0, includeEol: bool = true): seq[Selection] = - # debugf"applyMove '{move}', {selections}, {count}, {includeEol}" +proc moveCursorVisualLine(displayMap: DisplayMap, cursor: Cursor, offset: int, wrap: bool = false, includeAfter: bool = false, targetColumn: int): Cursor = + let rope {.cursor.} = displayMap.buffer.visibleText + let wrapPointOld = displayMap.toWrapPoint(cursor.toPoint) + let endWrapPoint = displayMap.wrapMap.endWrapPoint + let wrapPoint = wrapPoint(max(wrapPointOld.row.int + offset, 0), targetColumn).clamp(wrapPoint()...endWrapPoint) + let newCursor = displayMap.toPoint(wrapPoint, if offset < 0: Bias.Left else: Bias.Right).toCursor + if offset < 0 and newCursor.line > 0 and newCursor.line == cursor.line and displayMap.toWrapPoint(newCursor.toPoint).row == wrapPointOld.row: + let newCursor2 = point(cursor.line - 1, rope.lineLen(cursor.line - 1)) + let displayPoint = displayMap.toDisplayPoint(newCursor2) + let displayPoint2 = displayPoint(displayPoint.row, targetColumn.uint32) + let point = displayMap.toPoint(displayPoint2) + + # echo &"moveCursorVisualLine {cursor}, {offset} -> {newCursor2} -> {displayPoint} -> {displayPoint2} -> {point}" + return point.toCursor + elif offset > 0: + # go to wrap point and back to point one more time because if we land inside of e.g an overlay then the position will + # be clamped which can screw up the target column we set before, so we need to calculate the target column again. + let wrapPoint2 = wrapPoint(displayMap.toWrapPoint(newCursor.toPoint).row, targetColumn).clamp(wrapPoint()...endWrapPoint) + let newCursor2 = displayMap.toPoint(wrapPoint2, if offset < 0: Bias.Left else: Bias.Right).toCursor + + # echo &"moveCursorVisualLine {cursor}, {offset} -> {newCursor}, wp: {wrapPointOld} -> {wrapPoint} -> {displayMap.toWrapPoint(newCursor.toPoint)}, {wrapPoint2} -> {newCursor2}" + if newCursor2.line >= rope.lines: + return cursor + return rope.clampCursor(newCursor2, includeAfter) + + if newCursor.line >= rope.lines: + return cursor + return rope.clampCursor(newCursor, includeAfter) + +proc moveCursorLine(text: Rope, displayMap: DisplayMap, cursor: Cursor, offset: int, targetColumn: int, wrap: bool, includeEol: bool): Cursor = + var cursor = cursor + let line = cursor.line + offset + if line < 0: + cursor = (0, cursor.column) + elif line >= text.lines: + cursor = (text.lines - 1, cursor.column) + else: + cursor.line = line + let wrapPoint = displayMap.toWrapPoint(Point.init(line, 0)) + cursor.column = displayMap.toPoint(wrapPoint(wrapPoint.row.int, targetColumn)).column.int + return text.clampCursor(cursor, includeEol) + +type MoveFunction* = proc(move: string, selections: openArray[Selection], count: int): seq[Selection] {.gcsafe, raises: [].} + +proc applyMoveImpl(self: MoveDatabase, displayMap: DisplayMap, move: string, selections: openArray[Selection], fallback: MoveFunction, args: openArray[LispVal], env: Env): seq[Selection] = + + template getEnv(name: string, typ: untyped, default: untyped): untyped = + block: + let val = env[name] + if val != nil: + try: + val.toJson().to(typ) + except CatchableError as e: + log lvlError, "In move '" & move & "': Failed to convert env." & name & " (" & $val & ") to typ " & $typ & ": " & e.msg + default + else: + default + + template getArg(index: int, typ: untyped, default: untyped): untyped = + block: + if index < args.len: + try: + args[index].toJson().to(typ) + except CatchableError as e: + log lvlError, "In move '" & move & "': Failed to convert argument " & $index & " to typ " & $typ & ": " & e.msg + default + else: + default + + var count = block: + let val = env["count"] + if val != nil: + case val.kind + of Number: + if val.num.int == 0: + 1 + else: + val.num.int + else: + log lvlError, "In move '" & move & "': Can't convert env.count (" & $val & ") to type int" + 1 + else: + 1 + assert count != 0 + + let targetColumn = getEnv("target-column", int, 0) + let includeEol = getEnv("include-eol", bool, true) + let wrap = getEnv("wrap", bool, true) let rope {.cursor.} = displayMap.buffer.visibleText @@ -161,63 +249,78 @@ proc applyMove*(self: MoveDatabase, displayMap: DisplayMap, move: string, select return impl(rope, move, selections, count, includeEol) case move - # of "word": - # result = self.findWordBoundary(cursor) - # for _ in 1.. 0: - # result.first = (cursor.line - 1, self.document.lineLength(cursor.line - 1)) - # if cursor.column == line.len and cursor.line < self.document.numLines - 1: - # result.last = (cursor.line + 1, 0) - - # for _ in 1.. 0: - # result.first = (result.first.line - 1, self.document.lineLength(result.first.line - 1)) - # if result.last.column == line.len and result.last.line < self.document.numLines - 1: - # result.last = (result.last.line + 1, 0) - - # of "word-back": - # return self.getSelectionForMove((cursor.line, max(0, cursor.column - 1)), "word", count).reverse + s = s or vimMotionWordBig(rope, s.last, true) or vimMotionWordBig(rope, s.first, true) + + of "reverse": + return selections.mapIt(it.reverse) + + of "word-line": + return selections.mapIt: + let cursor = it.last + let line = rope.getLine cursor.line + var res = vimMotionWord(rope, cursor, false) + if cursor.column == 0 and cursor.line > 0: + res.first = (cursor.line - 1, rope.lineLen(cursor.line - 1)) + if cursor.column == line.len and cursor.line < rope.lines - 1: + res.last = (cursor.line + 1, 0) + res - # of "word-line-back": - # return self.getSelectionForMove((cursor.line, max(0, cursor.column - 1)), "word-line", count).reverse + of "word-line-back": + return selections.mapIt: + let cursor = it.last + let line = rope.getLine cursor.line + var res = vimMotionWordBack(rope, cursor, false) + if cursor.column == 0 and cursor.line > 0: + res.first = (cursor.line - 1, rope.lineLen(cursor.line - 1)) + if cursor.column == line.len and cursor.line < rope.lines - 1: + res.last = (cursor.line + 1, 0) + res # of "number": # var r = cursor.toPoint...cursor.toPoint @@ -280,6 +383,28 @@ proc applyMove*(self: MoveDatabase, displayMap: DisplayMap, move: string, select res ) + of "visual-line-up", "visual-line-down", "visual-page": + var minLine = int.high + var maxLine = int.low + for s in selections: + minLine = min(minLine, s.last.line) + maxLine = max(maxLine, s.last.line) + + let direction = if move == "visual-line-down": + count + elif move == "visual-line-up": + -count + else: + ((count * getEnv("screen-lines", int, 55)) div 100) + + proc doMoveCursor(numSelections: int, cursor: Cursor, offset: int, includeAfter: bool): Cursor = + let targetColumn = if maxLine - minLine + 1 < numSelections: + displayMap.toDisplayPoint(cursor.toPoint).column.int + else: + targetColumn + moveCursorVisualLine(displayMap, cursor, offset, false, includeAfter, targetColumn) + return selections.mapIt(doMoveCursor(selections.len, it.last, direction, includeEol).toSelection) + # of "line-next": # result = ((cursor.line, 0), (cursor.line, self.document.lineLength(cursor.line))) # if result.last.line + 1 < self.document.numLines: @@ -304,19 +429,48 @@ proc applyMove*(self: MoveDatabase, displayMap: DisplayMap, move: string, select of "line-no-indent": result = selections.mapIt ((it.last.line, rope.indentBytes(it.last.line)), (it.last.line, rope.lineLen(it.last.line))) - # of "file": - # result.first = (0, 0) - # let line = self.document.numLines - 1 - # result.last = (line, self.document.lineLength(line)) + of "file": + result = @[((0, 0), (rope.lines - 1, rope.lineLen(rope.lines - 1)))] of "column": - return selections.mapIt((it.first, rope.moveCursorColumn(it.last, count, includeEol))) + let dir = getArg(0, int, 1) + return selections.mapIt(rope.moveCursorColumn(it.last, count * dir, wrap, includeEol).toSelection) + + of "line-up", "line-down": + let direction = if move == "line-down": 1 else: -1 + # return selections.mapIt((it.last, moveCursorLine(rope, displayMap, it.last, direction * count, targetColumn, false, includeEol))) + return selections.mapIt(moveCursorLine(rope, displayMap, it.last, direction * count, targetColumn, false, includeEol).toSelection) + + of "grow": + let dir = getArg(0, int, 1) * count + return selections.mapIt: + if dir < 0 and it.first.line == it.last.line and abs(it.first.column - it.last.column) < 2: + it + else: + (rope.moveCursorColumn(it.first, -dir, wrap, includeEol), rope.moveCursorColumn(it.last, dir, wrap, includeEol)) + + of "number": + result = selections.mapIt: + var r = it.last.toPoint...it.last.toPoint + var c = rope.cursorT(it.last.toPoint) + while c.currentChar in {'0'..'9'}: + c.seekNextRune() + r.b = c.position + + if r.a.column > 0: + c = rope.cursorT(it.last.toPoint) + while c.position.column > 0: + c.seekPrevRune() + if c.currentChar == '-': + r.a = c.position + break - # of "prev-find-result": - # result = self.getPrevFindResult(cursor, count) + if c.currentChar notin {'0'..'9'}: + c.seekNextRune() + break + r.a = c.position - # of "next-find-result": - # result = self.getNextFindResult(cursor, count) + r.toSelection # of "\"": # result = self.getSelectionInPair(cursor, '"') @@ -333,16 +487,122 @@ proc applyMove*(self: MoveDatabase, displayMap: DisplayMap, move: string, select # of "[", "]": # result = self.getSelectionInPairNested(cursor, '[', ']') + of "target-column": + return selections.mapIt(rope.clampCursor((it.last.line, targetColumn)).toSelection) + + of "move-to": + let c = getArg(0, string, "") + if c.len > 0: + let r = c.runeAt(0) + return selections.mapIt((it.first, rope.findNext(it.last, r))) + return @selections + else: - if move.startsWith("move-to "): - let c = move["move-to ".len..^1] - if c.len > 0: - let r = c.runeAt(0) - return selections.mapIt((it.first, rope.findNext(it.last, r))) + if fallback != nil: + return fallback(move, selections, count) + log lvlError, &"Unknown move '{move}'" + return @selections + +proc applyMoveLisp(self: MoveDatabase, displayMap: DisplayMap, move: string, originalSelections: openArray[Selection], env: Env, fallback: MoveFunction): seq[Selection] = + try: + var env = env + # debugf"applyMoveLisp '{move}', {env.env}" + let originalSelections = @originalSelections + var lastSelections = originalSelections + var selections = originalSelections + + var expr = move.parseLisp() + # debugf"lisp: {expr}" + + type Selector = enum OriginalStart, OriginalEnd, LastStart, LastEnd, CurrentStart, CurrentEnd + proc parseSelector(val: LispVal, default: Selector): Selector = + if val.kind == Symbol: + case val.sym + of "orig-start": OriginalStart + of "orig-end": OriginalEnd + of "last-start": LastStart + of "last-end": LastEnd + of "curr-start": CurrentStart + of "curr-end": CurrentEnd + of ".": default + else: + log lvlError, &"Unknown cursor selector '{val.sym}'" + default + else: + log lvlError, &"Failed to parse cursor selector '{val}'. Expected Symbol, got {val.kind}" + default + + proc selectCursor(selector: Selector, i: int, default: Cursor): Cursor = + case selector + of OriginalStart: + if i in 0.. 0: + parseSelector(args[0], OriginalStart) + else: + OriginalStart + let endSelector = if args.len > 1: + parseSelector(args[1], CurrentEnd) + else: + CurrentEnd + + # debugf"join {startSelector}, {endSelector}, {originalSelections}, {lastSelections}, {selections}" + + if selections.len > 0: + for i in 0.. ", signature - return newLit(signature) - -template addFunction*(self: var WasmImports, name: string, function: static proc) = - block: - template buildFunction(runtime, outFunction: untyped) = - let module = cast[WasmModule](m3_GetUserData(runtime)) - {.push hint[XCannotRaiseY]:off.} - createHostWrapper(module, function, outFunction) - {.pop.} - self.functions.add toWasmHostProcTemplate(buildFunction, self.namespace, name, getWasmSignature(function)) - -type - WasiFD* = distinct uint32 - -proc newWasmModule*(wasmData: ArrayBuffer, importsOld: seq[WasmImports]): Future[Option[WasmModule]] {.async.} = - try: - var allFunctions: seq[WasmHostProc] = @[] - for imp in importsOld: - allFunctions.add imp.functions - - let res = WasmModule() - - try: - res.env = loadWasmEnv(wasmData.buffer, hostProcs=allFunctions, loadAlloc=true, allocName="my_alloc", deallocName="my_dealloc", userdata=cast[pointer](res)) - except: - log lvlError, &"Failed to create wasm env: {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - return WasmModule.none - - var imports = @importsOld - for imp in imports.mitems: - imp.module = res - return res.some - - except CatchableError: - log lvlError, &"Failed to load wasm binary from array buffer: {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - return WasmModule.none - -proc newWasmModule*(path: string, importsOld: seq[WasmImports], vfs: VFS): Future[Option[WasmModule]] {.async.} = - try: - var allFunctions: seq[WasmHostProc] = @[] - for imp in importsOld: - allFunctions.add imp.functions - - let res = WasmModule(path: path) - - var content: string - try: - content = await vfs.read(path, {Binary}) - - if content.len == 0: - log lvlError, &"Failed to load wasm module file {path}" - return WasmModule.none - except CatchableError: - log lvlError, &"Failed to load wasm module file {path}: {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - return WasmModule.none - - try: - res.env = loadWasmEnv(content, hostProcs=allFunctions, loadAlloc=true, allocName="my_alloc", deallocName="my_dealloc", userdata=cast[pointer](res)) - except: - log lvlError, &"Failed to create wasm env for {path}: {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - return WasmModule.none - - var imports = @importsOld - for imp in imports.mitems: - imp.module = res - return res.some - - except CatchableError: - log lvlError, &"Failed to load wasm binary from file {path}: {getCurrentExceptionMsg()}\n{getCurrentException().getStackTrace()}" - return WasmModule.none - -macro createWasmWrapper(returnType: typedesc, typ: typedesc, body: untyped): untyped = - # echo "createWasmWrapper" - - var params: seq[NimNode] = @[] - var args: seq[NimNode] = @[] - - let returnCString = returnType.getType[1].repr == "cstring" - - var actualReturnType = if returnCString: - var actualReturnType = genAst(): - typedesc[WasmPtr] - actualReturnType - else: - returnType - - params.add returnType.getType[1] - - let moduleSym = genSym(nskParam, "module") - let pfunSym = genSym(nskParam, "pfun") - params.add nnkIdentDefs.newTree(moduleSym, typ.getType[1][2], newEmptyNode()) - params.add nnkIdentDefs.newTree(pfunSym, typ.getType[1][3], newEmptyNode()) - - for i, p in typ.getType[1][4..^1]: - let argSym = genSym(nskParam, "p" & $i) - let repr = p.repr - let isCString = p.repr == "cstring" - let isString = p.repr == "string" - - params.add nnkIdentDefs.newTree(argSym, p, newEmptyNode()) - - var arg = argSym - - if isCString: - arg = genAst(argSym, moduleSym): - block: - let p: WasmPtr = moduleSym.alloc(argSym.len.uint32 + 1) - moduleSym.copyMem(p, cast[pointer](argSym), argSym.len + 1) - p - elif isString: - arg = genAst(argSym, moduleSym): - block: - let p: WasmPtr = moduleSym.alloc(argSym.len.uint32 + 1) - moduleSym.copyMem(p, cast[ptr char](argSym.cstring), argSym.len + 1) - p.uint64 or (argSym.len.uint64 shl 32) - - args.add(arg) - - discard body.replace("returnType", [actualReturnType]) - discard body.replace("parameters", args) - discard body.replace("f", [pfunSym]) - - if returnType.getType[1].repr != "void": - if returnCString: - body[0] = genAst(value = body[0], moduleSym): - let p = value - let res = moduleSym.getString(p) - return res - else: - body[0] = nnkReturnStmt.newTree(body[0]) - - # defer: - # echo result.repr - - result = newProc(params=params, body=body) - result.addPragma(ident"gcsafe") - result.addPragma(nnkExprColonExpr.newTree(ident"raises", nnkBracket.newTree(bindSym"CatchableError"))) - -proc findFunction*(module: WasmModule, name: string, R: typedesc, T: typedesc): Option[tuple[pfun: PFunction, fun: T]] = - try: - let f = module.env.findFunction(name) - if f.isNil: - return - - {.push hint[XCannotRaiseY]:off.} - let wrapper = createWasmWrapper(R, T): - try: - `f`.call(`returnType`, `parameters`) - except WasmError as e: - raise newException(CatchableError, "Failed to call function " & e.msg, e) - {.pop.} - - result = typeof(result.get).default.some - result.get.pfun = f - result.get.fun = wrapper - except CatchableError as e: - log lvlError, &"Failed to find function {name}: {e.msg}" - return diff --git a/src/terminal_service.nim b/src/terminal_service.nim index 4a08a38fc..1e20c70c0 100644 --- a/src/terminal_service.nim +++ b/src/terminal_service.nim @@ -1,15 +1,14 @@ -import std/[os, streams, strutils, sequtils, strformat, typedthreads, tables, json, colors, hashes, base64, algorithm, sets, macros, bitops, deques] +import std/[os, streams, strutils, sequtils, strformat, typedthreads, tables, json, colors, hashes, base64, algorithm, sets, macros, deques] import vmath import chroma, pixie, pixie/fileformats/png import nimsumtree/[rope, arc] -import misc/[custom_logger, util, custom_unicode, custom_async, event, timer, disposable_ref, myjsonutils, render_command, embed_source, async_process] +import misc/[custom_logger, util, custom_unicode, custom_async, event, timer, disposable_ref, myjsonutils, render_command, async_process] import dispatch_tables, config_provider, events, view, layout, service, platform_service, selector_popup, vfs_service, vfs, theme import scripting/expose import platform/[tui, platform] import finder/[finder, previewer] import vterm, input, input_api, register, command_service, channel import scripting_api as api except DocumentEditor, TextDocumentEditor, AstDocumentEditor, ModelDocumentEditor, Popup, SelectorPopup -import compilation_config when defined(enableLibssh): static: @@ -994,7 +993,7 @@ proc handleProcessOutput(state: var TerminalThreadState, buffer: var string) {.r state.screen.flushDamage() state.dirty = true - except IOError as e: + except IOError: discard else: when defined(windows): @@ -1560,8 +1559,8 @@ iterator parseKittyData(s: ptr TerminalThreadState) {.closure, gcsafe, raises: [ # kittyDebugf"Read file '{path}'" try: data = readFile(path) - except IOError as e: - kittyDebugf"Failed to read file '{path}': {e.msg}" + except IOError: + kittyDebugf"Failed to read file '{path}': {getCurrentExceptionMsg()}" sendErrResponse("EBADF"): discard return @@ -1571,8 +1570,8 @@ iterator parseKittyData(s: ptr TerminalThreadState) {.closure, gcsafe, raises: [ try: data = readFile(path) removeFile(path) - except IOError as e: - kittyDebugf"Failed to read file '{path}': {e.msg}" + except IOError: + kittyDebugf"Failed to read file '{path}': {getCurrentExceptionMsg()}" sendErrResponse("EBADF"): discard return @@ -2354,7 +2353,7 @@ when defined(enableLibssh): except CatchableError as e: log lvlError, &"Failed to create ssh connection: {e.msg}" -proc createTerminal*(self: TerminalService, width: int, height: int, command: string, args: seq[string] = @[], autoRunCommand: string = "", createPty: bool = true, kittyPathPrefix: string = "", ssh: Option[SshOptions]): Terminal {.raises: [OSError, IOError, ResourceExhaustedError, TransportError, CancelledError].} = +proc createTerminal*(self: TerminalService, width: int, height: int, command: string, args: seq[string] = @[], autoRunCommand: string = "", createPty: bool = true, kittyPathPrefix: string = "", ssh: Option[SshOptions]): Terminal {.raises: [IOError, OSError, ResourceExhaustedError].} = if ssh.isSome: var stdin = newInMemoryChannel() var stdout = newInMemoryChannel() diff --git a/src/text/text_document.nim b/src/text/text_document.nim index 2899655ef..b4ac81840 100644 --- a/src/text/text_document.nim +++ b/src/text/text_document.nim @@ -1719,3 +1719,57 @@ proc trimTrailingWhitespace*(self: TextDocument) = discard self.edit(selections, selections, [""]) func isInitialized*(self: TextDocument): bool = self.isInitialized + +type IdentifierCase* = enum Camel, Pascal, Kebab, Snake, ScreamingSnake + +proc splitCase*(s: string): tuple[cas: IdentifierCase, parts: seq[string]] = + if s == "": + return (IdentifierCase.Camel, @[]) + + if s.find('_') != -1: + result.cas = IdentifierCase.Snake + result.parts = s.split('_').mapIt(custom_unicode.toLower(it)) + for r in s.runes: + if r != '_'.Rune and not r.isLower: + result.cas = IdentifierCase.ScreamingSnake + break + + elif s.find('-') != -1: + result.cas = IdentifierCase.Kebab + result.parts = s.split('-').mapIt(custom_unicode.toLower(it)) + else: + if s[0].isUpperAscii: + result.cas = IdentifierCase.Pascal + else: + result.cas = IdentifierCase.Camel + + result.parts.add "" + for r in s.runes: + if not r.isLower and result.parts.last.len > 0: + result.parts.add "" + result.parts.last.add(custom_unicode.toLower(r)) + +proc joinCase*(parts: seq[string], cas: IdentifierCase): string = + if parts.len == 0: + return "" + case cas + of IdentifierCase.Camel: + parts[0] & parts[1..^1].mapIt(it.capitalize).join("") + of IdentifierCase.Pascal: + parts.mapIt(it.capitalize).join("") + of IdentifierCase.Kebab: + parts.join("-") + of IdentifierCase.Snake: + parts.join("_") + of IdentifierCase.ScreamingSnake: + parts.mapIt(custom_unicode.toUpper(it)).join("_") + +proc cycleCase*(s: string): string = + if s.len == 0: + return s + let (cas, parts) = s.splitCase() + let nextCase = if cas == IdentifierCase.high: + IdentifierCase.low + else: + cas.succ + return parts.joinCase(nextCase) diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index d8222cc28..8abce3b35 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -18,6 +18,7 @@ import workspaces/workspace import finder/[previewer, finder] import vcs/vcs import overlay_map, tab_map, wrap_map, diff_map, display_map +import lisp from language/lsp_types import CompletionList, CompletionItem, InsertTextFormat, TextEdit, Position, asTextEdit, asInsertReplaceEdit, toJsonHook, CodeAction, CodeActionResponse, CodeActionKind, @@ -382,6 +383,8 @@ type TextDocumentEditor* = ref object of DocumentEditor debugSettings*: DebugSettings settings*: TextEditorSettings + moveFallbacks: MoveFunction + type TextDocumentEditorService* = ref object of Service TextDocumentFactory* = ref object of DocumentFactory @@ -1204,18 +1207,18 @@ proc enableAutoReload(self: TextDocumentEditor, enabled: bool) {.expose: "editor proc setLanguage(self: TextDocumentEditor, language: string) {.expose: "editor.text".} = self.document.languageId = language -proc getFileName*(self: TextDocumentEditor): string {.expose: "editor.text".} = +proc getFileName*(self: TextDocumentEditor): string = if self.document.isNil: return "" return self.document.filename -proc lineCount(self: TextDocumentEditor): int {.expose: "editor.text".} = +proc lineCount(self: TextDocumentEditor): int = return self.document.numLines -proc lineLength*(self: TextDocumentEditor, line: int): int {.expose: "editor.text".} = +proc lineLength*(self: TextDocumentEditor, line: int): int = return self.document.lineLength(line) -proc numDisplayLines*(self: TextDocumentEditor): int {.expose: "editor.text".} = +proc numDisplayLines*(self: TextDocumentEditor): int = return self.displayMap.toDisplayPoint(self.document.rope.summary.lines).row.int + 1 proc displayEndPoint*(self: TextDocumentEditor): DisplayPoint = @@ -1227,13 +1230,13 @@ proc endDisplayPoint*(self: TextDocumentEditor): DisplayPoint = proc endPoint*(self: TextDocumentEditor): Point = return self.displayMap.buffer.visibleText.endPoint -proc numWrapLines*(self: TextDocumentEditor): int {.expose: "editor.text".} = +proc numWrapLines*(self: TextDocumentEditor): int = return self.displayMap.wrapMap.endWrapPoint.row.int + 1 proc wrapEndPoint*(self: TextDocumentEditor): WrapPoint = return self.displayMap.wrapMap.endWrapPoint -proc screenLineCount*(self: TextDocumentEditor): int {.expose: "editor.text".} = +proc screenLineCount*(self: TextDocumentEditor): int = ## Returns the number of lines that can be shown on the screen ## This value depends on the size of the view this editor is in and the font size # todo @@ -1368,8 +1371,8 @@ proc evaluateExpressionAsync(self: TextDocumentEditor, selections: Selections, i proc evaluateExpressions*(self: TextDocumentEditor, selections: Selections, inclusiveEnd: bool = false, prefix: string = "", suffix: string = "", addSelectionIndex: bool = false) {.expose: "editor.text".} = asyncSpawn self.evaluateExpressionAsync(selections, inclusiveEnd, prefix, suffix, addSelectionIndex) -proc doMoveCursorLine(self: TextDocumentEditor, cursor: Cursor, offset: int, - wrap: bool = false, includeAfter: bool = false): Cursor {.expose: "editor.text".} = +# todo: remove +proc doMoveCursorLine(self: TextDocumentEditor, cursor: Cursor, offset: int, wrap: bool = false, includeAfter: bool = false): Cursor = var cursor = cursor let line = cursor.line + offset if line < 0: @@ -1382,13 +1385,11 @@ proc doMoveCursorLine(self: TextDocumentEditor, cursor: Cursor, offset: int, cursor.column = self.displayMap.toPoint(wrapPoint(wrapPoint.row.int, self.targetColumn)).column.int return self.clampCursor(cursor, includeAfter) -proc getDefaultScrollBehaviour(self: TextDocumentEditor): ScrollBehaviour {.expose: "editor.text".} = - self.defaultScrollBehaviour - proc setDefaultScrollBehaviour(self: TextDocumentEditor, scrollBehaviour: ScrollBehaviour) {.expose: "editor.text".} = self.defaultScrollBehaviour = scrollBehaviour -proc doMoveCursorVisualLine(self: TextDocumentEditor, cursor: Cursor, offset: int, wrap: bool = false, includeAfter: bool = false, targetColumn: Option[int] = int.none): Cursor {.expose: "editor.text".} = +# todo: remove +proc doMoveCursorVisualLine(self: TextDocumentEditor, cursor: Cursor, offset: int, wrap: bool = false, includeAfter: bool = false, targetColumn: Option[int] = int.none): Cursor = let targetColumn = targetColumn.get(self.targetColumn) let wrapPointOld = self.displayMap.toWrapPoint(cursor.toPoint) let wrapPoint = wrapPoint(max(wrapPointOld.row.int + offset, 0), targetColumn).clamp(wrapPoint()...self.wrapEndPoint) @@ -1416,34 +1417,26 @@ proc doMoveCursorVisualLine(self: TextDocumentEditor, cursor: Cursor, offset: in return cursor return self.clampCursor(newCursor, includeAfter) -proc doMoveCursorHome(self: TextDocumentEditor, cursor: Cursor, offset: int, wrap: bool, - includeAfter: bool): Cursor {.expose: "editor.text".} = +# todo: remove +proc doMoveCursorHome(self: TextDocumentEditor, cursor: Cursor, offset: int, wrap: bool, includeAfter: bool): Cursor = return (cursor.line, 0) -proc doMoveCursorEnd(self: TextDocumentEditor, cursor: Cursor, offset: int, wrap: bool, - includeAfter: bool): Cursor {.expose: "editor.text".} = +# todo: remove +proc doMoveCursorEnd(self: TextDocumentEditor, cursor: Cursor, offset: int, wrap: bool, includeAfter: bool): Cursor = return (cursor.line, self.document.rope.lastValidIndex cursor.line) -proc doMoveCursorPrevFindResult(self: TextDocumentEditor, cursor: Cursor, offset: int, - wrap: bool, includeAfter: bool): Cursor {.expose: "editor.text".} = - return self.getPrevFindResult(cursor, offset, includeAfter=includeAfter).first - -proc doMoveCursorNextFindResult(self: TextDocumentEditor, cursor: Cursor, offset: int, - wrap: bool, includeAfter: bool): Cursor {.expose: "editor.text".} = - return self.getNextFindResult(cursor, offset, includeAfter=includeAfter).first - -proc doMoveCursorLineCenter(self: TextDocumentEditor, cursor: Cursor, offset: int, wrap: bool, - includeAfter: bool): Cursor {.expose: "editor.text".} = +# todo: remove +proc doMoveCursorLineCenter(self: TextDocumentEditor, cursor: Cursor, offset: int, wrap: bool, includeAfter: bool): Cursor = return (cursor.line, self.document.lineLength(cursor.line) div 2) -proc doMoveCursorCenter(self: TextDocumentEditor, cursor: Cursor, offset: int, wrap: bool, - includeAfter: bool): Cursor {.expose: "editor.text".} = +# todo: remove +proc doMoveCursorCenter(self: TextDocumentEditor, cursor: Cursor, offset: int, wrap: bool, includeAfter: bool): Cursor = let r = self.visibleDisplayRange() let line = clamp((r.a.row.int + r.b.row.int) div 2, 0, self.numDisplayLines - 1) return self.displayMap.toPoint(displayPoint(line, self.targetColumn)).toCursor -proc doMoveCursorColumn(self: TextDocumentEditor, cursor: Cursor, offset: int, - wrap: bool = true, includeAfter: bool = true): Cursor {.expose: "editor.text".} = +# todo: remove +proc doMoveCursorColumn(self: TextDocumentEditor, cursor: Cursor, offset: int, wrap: bool = true, includeAfter: bool = true): Cursor = var cursor = cursor if cursor.line notin 0.. 0: return modes.last return "" -proc modes*(self: TextDocumentEditor): seq[string] {.expose("editor.text").} = +proc modes*(self: TextDocumentEditor): seq[string] = ## Returns the current modes of the text editor return self.settings.modes.get() -proc getContextWithMode(self: TextDocumentEditor, context: string): string {.expose("editor.text").} = +proc getContextWithMode(self: TextDocumentEditor, context: string): string = ## Appends the current mode to context return context & "." & $self.mode -proc updateTargetColumn*(self: TextDocumentEditor, cursor: SelectionCursor = Last) {. - expose("editor.text").} = +proc updateTargetColumn*(self: TextDocumentEditor, cursor: SelectionCursor = Last) = let cursor = self.getCursor(cursor) let wrapPoint = self.displayMap.toWrapPoint(cursor.toPoint) self.targetColumn = wrapPoint.column.int -proc invertSelection(self: TextDocumentEditor) {.expose("editor.text").} = - ## Inverts the current selection. Discards all but the last cursor. - self.selection = (self.selection.last, self.selection.first) - -proc getRevision*(self: TextDocumentEditor): int {.expose("editor.text").} = +proc getRevision*(self: TextDocumentEditor): int = return self.document.revision -proc getUsage*(self: TextDocumentEditor): string {.expose("editor.text").} = +proc getUsage*(self: TextDocumentEditor): string = return self.usage -proc getChar*(self: TextDocumentEditor, cursor: Cursor): char {.expose("editor.text").} = +proc getChar*(self: TextDocumentEditor, cursor: Cursor): char = return self.document.rope.slice(Point).charAt(cursor.toPoint) -proc getText*(self: TextDocumentEditor, selection: Selection, inclusiveEnd: bool = false): - string {.expose("editor.text").} = +proc getText*(self: TextDocumentEditor, selection: Selection, inclusiveEnd: bool = false): string = return self.document.contentString(selection, inclusiveEnd) -proc getLine*(self: TextDocumentEditor, line: int): string {.expose("editor.text").} = +proc getLine*(self: TextDocumentEditor, line: int): string = return $self.document.getLine(line) -proc insert*(self: TextDocumentEditor, selections: seq[Selection], text: string, notify: bool = true, - record: bool = true): seq[Selection] {.expose("editor.text").} = +proc insert*(self: TextDocumentEditor, selections: seq[Selection], text: string, notify: bool = true, record: bool = true): seq[Selection] = return self.document.edit(selections, self.selections, [text], notify, record) -proc insertMulti*(self: TextDocumentEditor, selections: seq[Selection], texts: seq[string], notify: bool = true, - record: bool = true): seq[Selection] {.expose("editor.text").} = - return self.document.edit(selections, self.selections, texts, notify, record) - -proc delete*(self: TextDocumentEditor, selections: seq[Selection], notify: bool = true, - record: bool = true, inclusiveEnd: bool = false): seq[Selection] {.expose("editor.text").} = +proc delete*(self: TextDocumentEditor, selections: seq[Selection], notify: bool = true, record: bool = true, inclusiveEnd: bool = false): seq[Selection] = return self.document.edit(selections, self.selections, [""], notify, record, inclusiveEnd=inclusiveEnd) proc edit*(self: TextDocumentEditor, selections: seq[Selection], texts: seq[string], - notify: bool = true, record: bool = true, inclusiveEnd: bool = false): seq[Selection] {. - expose("editor.text").} = + notify: bool = true, record: bool = true, inclusiveEnd: bool = false): seq[Selection] = return self.document.edit(selections, self.selections, texts, notify, record, inclusiveEnd=inclusiveEnd) -proc deleteLines(self: TextDocumentEditor, slice: Slice[int], oldSelections: Selections) {. - expose("editor.text").} = - var selection: Selection = ( - (slice.a.clamp(0, self.lineCount - 1), 0), - (slice.b.clamp(0, self.lineCount - 1), 0) - ).normalized - selection.last.column = self.document.rope.lastValidIndex(selection.last.line) - if selection.last.line < self.document.numLines - 1: - selection.last = (selection.last.line + 1, 0) - elif selection.first.line > 0: - selection.first = (selection.first.line - 1, self.document.rope.lastValidIndex(selection.first.line - 1)) - discard self.document.edit([selection], oldSelections, [""]) - proc selectPrev(self: TextDocumentEditor) {.expose("editor.text").} = if self.selectionHistory.len > 0: let selection = self.selectionHistory.popLast @@ -1739,42 +1706,7 @@ proc selectNext(self: TextDocumentEditor) {.expose("editor.text").} = self.scrollToCursor(self.selection.last) self.setNextSnapBehaviour(ScrollSnapBehaviour.MinDistanceOffscreen) -proc selectInside*(self: TextDocumentEditor, cursor: Cursor) {.expose("editor.text").} = - self.selection = self.getSelectionForMove(cursor, "word") - # todo - # let regex = re("[a-zA-Z0-9_]") - # var first = cursor.column - # # echo self.document.getLine(cursor.line), ", ", first, ", ", - # self.document.getLine(cursor.line).matchLen(regex, start = first - 1) - # while first > 0 and self.document.getLine(cursor.line).matchLen(regex, start = first - 1) == 1: - # first -= 1 - # var last = cursor.column - # while last < self.document.lineLength(cursor.line) and - # self.document.getLine(cursor.line).matchLen(regex, start = last) == 1: - # last += 1 - # self.selection = ((cursor.line, first), (cursor.line, last)) - -proc selectInsideCurrent(self: TextDocumentEditor) {.expose("editor.text").} = - self.selection = self.extendSelectionWithMove(self.selection, "word") - -proc selectLine*(self: TextDocumentEditor, line: int) {.expose("editor.text").} = - self.selection = ((line, 0), (line, self.document.rope.lastValidIndex(line))) - -proc selectLineCurrent(self: TextDocumentEditor) {.expose("editor.text").} = - let first = ( - (self.selection.first.line, 0), - (self.selection.first.line, self.document.rope.lastValidIndex(self.selection.first.line)) - ) - let last = ( - (self.selection.last.line, 0), - (self.selection.last.line, self.document.rope.lastValidIndex(self.selection.last.line)) - ) - let wasBackwards = self.selection.isBackwards - self.selection = first or last - if wasBackwards: - self.selection = self.selection.reverse - -proc getParentNodeSelection(self: TextDocumentEditor, selection: Selection, includeAfter: bool = true): Selection {.expose("editor.text").} = +proc getParentNodeSelection(self: TextDocumentEditor, selection: Selection, includeAfter: bool = true): Selection = if self.document.tsTree.isNil: return selection @@ -1791,7 +1723,7 @@ proc getParentNodeSelection(self: TextDocumentEditor, selection: Selection, incl result = node.getRange.toSelection result = self.includeSelectionEnd(result, includeAfter) -proc getNextNamedSiblingNodeSelection(self: TextDocumentEditor, selection: Selection, includeAfter: bool = true): Option[Selection] {.expose("editor.text").} = +proc getNextNamedSiblingNodeSelection(self: TextDocumentEditor, selection: Selection, includeAfter: bool = true): Option[Selection] = if self.document.tsTree.isNil: return Selection.none @@ -1808,7 +1740,7 @@ proc getNextNamedSiblingNodeSelection(self: TextDocumentEditor, selection: Selec return Selection.none -proc getNextSiblingNodeSelection(self: TextDocumentEditor, selection: Selection, includeAfter: bool = true): Option[Selection] {.expose("editor.text").} = +proc getNextSiblingNodeSelection(self: TextDocumentEditor, selection: Selection, includeAfter: bool = true): Option[Selection] = if self.document.tsTree.isNil: return Selection.none @@ -1825,14 +1757,9 @@ proc getNextSiblingNodeSelection(self: TextDocumentEditor, selection: Selection, return Selection.none -proc getParentNodeSelections(self: TextDocumentEditor, selections: Selections, includeAfter: bool = true): Selections {.expose("editor.text").} = +proc getParentNodeSelections(self: TextDocumentEditor, selections: Selections, includeAfter: bool = true): Selections = return selections.mapIt(self.getParentNodeSelection(it, includeAfter)) -proc selectParentTs(self: TextDocumentEditor, selection: Selection, includeAfter: bool = true) {.expose("editor.text").} = - if self.document.tsTree.isNil: - return - self.selection = self.getParentNodeSelection(selection, includeAfter) - proc printTreesitterMemoryUsage*(self: TextDocumentEditor) {.expose("editor.text").} = let allocated = custom_treesitter.tsAllocated let freed = custom_treesitter.tsFreed @@ -1859,11 +1786,12 @@ proc printTreesitterTreeUnderCursor*(self: TextDocumentEditor) {.expose("editor. toasts.showToast("Treesitter tree", $node, "info") log lvlInfo, $node +# todo proc selectParentCurrentTs*(self: TextDocumentEditor, includeAfter: bool = true) {.expose("editor.text").} = self.`selections=`(self.getParentNodeSelections(self.selections, includeAfter), addToHistory = true.some) proc getNextNodeWithSameType*(self: TextDocumentEditor, selection: Selection, offset: int = 0, - includeAfter: bool = true, wrap: bool = true, stepIn: bool = true, stepOut: bool = true): Option[Selection] {.expose("editor.text").} = + includeAfter: bool = true, wrap: bool = true, stepIn: bool = true, stepOut: bool = true): Option[Selection] = if self.document.tsTree.isNil: return Selection.none @@ -1920,7 +1848,7 @@ proc getNextNodeWithSameType*(self: TextDocumentEditor, selection: Selection, of return Selection.none -proc shouldShowCompletionsAt*(self: TextDocumentEditor, cursor: Cursor): bool {.expose("editor.text").} = +proc shouldShowCompletionsAt*(self: TextDocumentEditor, cursor: Cursor): bool = ## Returns true if the completion window should automatically open at the given position # todo: use RopeCursor if cursor.column <= 0 or cursor.column > self.document.rope.lineLen(cursor.line): @@ -1940,7 +1868,7 @@ proc shouldShowCompletionsAt*(self: TextDocumentEditor, cursor: Cursor): bool {. return false -proc autoShowCompletions*(self: TextDocumentEditor) {.expose("editor.text").} = +proc autoShowCompletions*(self: TextDocumentEditor) = if self.disableCompletions: return if self.shouldShowCompletionsAt(self.selection.last): @@ -2171,8 +2099,7 @@ proc copyAsync*(self: TextDocumentEditor, register: string, inclusiveEnd: bool): self.registers.setRegisterAsync(register, Register(kind: Rope, rope: text.move)).await -proc copy*(self: TextDocumentEditor, register: string = "", inclusiveEnd: bool = false) {. - expose("editor.text").} = +proc copy*(self: TextDocumentEditor, register: string = "", inclusiveEnd: bool = false) {.expose("editor.text").} = asyncSpawn self.copyAsync(register, inclusiveEnd) proc pasteAsync*(self: TextDocumentEditor, selections: seq[Selection], registerName: string, inclusiveEnd: bool = false): @@ -2215,8 +2142,7 @@ proc pasteAsync*(self: TextDocumentEditor, selections: seq[Selection], registerN self.setNextSnapBehaviour(ScrollSnapBehaviour.Always) self.markDirty() -proc paste*(self: TextDocumentEditor, registerName: string = "", inclusiveEnd: bool = false) {. - expose("editor.text").} = +proc paste*(self: TextDocumentEditor, registerName: string = "", inclusiveEnd: bool = false) {.expose("editor.text").} = asyncSpawn self.pasteAsync(self.selections, registerName, inclusiveEnd) proc scrollText*(self: TextDocumentEditor, amount: float32) {.expose("editor.text").} = @@ -2241,22 +2167,19 @@ proc scrollLines(self: TextDocumentEditor, amount: int) {.expose("editor.text"). self.markDirty() -proc duplicateLastSelection*(self: TextDocumentEditor) {.expose("editor.text").} = - let newSelection = self.doMoveCursorColumn(self.selections[self.selections.high].last, 1).toSelection - self.selections = self.selections & @[newSelection] - +# todo proc addCursorBelow*(self: TextDocumentEditor) {.expose("editor.text").} = let newCursor = self.doMoveCursorLine(self.selections[self.selections.high].last, 1).toSelection if not self.selections.contains(newCursor): self.selections = self.selections & @[newCursor] +# todo proc addCursorAbove*(self: TextDocumentEditor) {.expose("editor.text").} = let newCursor = self.doMoveCursorLine(self.selections[self.selections.high].last, -1).toSelection if not self.selections.contains(newCursor): self.selections = self.selections & @[newCursor] -proc getPrevFindResult*(self: TextDocumentEditor, cursor: Cursor, offset: int = 0, - includeAfter: bool = true, wrap: bool = true): Selection {.expose("editor.text").} = +proc getPrevFindResult*(self: TextDocumentEditor, cursor: Cursor, offset: int = 0, includeAfter: bool = true, wrap: bool = true): Selection = self.updateSearchResults() if self.searchResults.len == 0: @@ -2283,8 +2206,7 @@ proc getPrevFindResult*(self: TextDocumentEditor, cursor: Cursor, offset: int = if not includeAfter: result.last = self.doMoveCursorColumn(result.last, -1, wrap = false) -proc getNextFindResult*(self: TextDocumentEditor, cursor: Cursor, offset: int = 0, - includeAfter: bool = true, wrap: bool = true): Selection {.expose("editor.text").} = +proc getNextFindResult*(self: TextDocumentEditor, cursor: Cursor, offset: int = 0, includeAfter: bool = true, wrap: bool = true): Selection = self.updateSearchResults() if self.searchResults.len == 0: @@ -2311,19 +2233,18 @@ proc getNextFindResult*(self: TextDocumentEditor, cursor: Cursor, offset: int = if not includeAfter: result.last = self.doMoveCursorColumn(result.last, -1, wrap = false) -proc createAnchors*(self: TextDocumentEditor, selections: Selections): seq[(Anchor, Anchor)] {.expose("editor.text").} = +proc createAnchors*(self: TextDocumentEditor, selections: Selections): seq[(Anchor, Anchor)] = if self.document.requiresLoad: return @[] let snapshot {.cursor.} = self.document.buffer.snapshot return selections.mapIt (snapshot.anchorAfter(it.first.toPoint), snapshot.anchorBefore(it.last.toPoint)) -proc resolveAnchors*(self: TextDocumentEditor, anchors: seq[(Anchor, Anchor)]): Selections {.expose("editor.text").} = +proc resolveAnchors*(self: TextDocumentEditor, anchors: seq[(Anchor, Anchor)]): Selections = if self.document.requiresLoad: return @[] return anchors.mapIt (it[0].summaryOpt(Point, self.snapshot).get(Point()), it[1].summaryOpt(Point, self.snapshot).get(Point())).toSelection -proc getPrevDiagnostic*(self: TextDocumentEditor, cursor: Cursor, severity: int = 0, - offset: int = 0, includeAfter: bool = true, wrap: bool = true): Selection {.expose("editor.text").} = +proc getPrevDiagnostic*(self: TextDocumentEditor, cursor: Cursor, severity: int = 0, offset: int = 0, includeAfter: bool = true, wrap: bool = true): Selection = self.document.resolveDiagnosticAnchors() @@ -2362,8 +2283,7 @@ proc getPrevDiagnostic*(self: TextDocumentEditor, cursor: Cursor, severity: int return wrapped return cursor.toSelection -proc getNextDiagnostic*(self: TextDocumentEditor, cursor: Cursor, severity: int = 0, - offset: int = 0, includeAfter: bool = true, wrap: bool = true): Selection {.expose("editor.text").} = +proc getNextDiagnostic*(self: TextDocumentEditor, cursor: Cursor, severity: int = 0, offset: int = 0, includeAfter: bool = true, wrap: bool = true): Selection = self.document.resolveDiagnosticAnchors() @@ -2414,7 +2334,7 @@ proc closeDiff*(self: TextDocumentEditor) {.expose("editor.text").} = self.diffDocument = nil self.markDirty() -proc getPrevChange*(self: TextDocumentEditor, cursor: Cursor): Selection {.expose("editor.text").} = +proc getPrevChange*(self: TextDocumentEditor, cursor: Cursor): Selection = if self.diffChanges.isNone: return cursor.toSelection @@ -2424,7 +2344,7 @@ proc getPrevChange*(self: TextDocumentEditor, cursor: Cursor): Selection {.expos return cursor.toSelection -proc getNextChange*(self: TextDocumentEditor, cursor: Cursor): Selection {.expose("editor.text").} = +proc getNextChange*(self: TextDocumentEditor, cursor: Cursor): Selection = if self.diffChanges.isNone: return cursor.toSelection @@ -2750,16 +2670,19 @@ proc checkoutFileAsync*(self: TextDocumentEditor) {.async.} = proc checkoutFile*(self: TextDocumentEditor) {.expose("editor.text").} = asyncSpawn self.checkoutFileAsync() +# todo proc addNextFindResultToSelection*(self: TextDocumentEditor, includeAfter: bool = true, wrap: bool = true) {.expose("editor.text").} = self.selections = self.selections & @[self.getNextFindResult(self.selection.last, includeAfter=includeAfter)] +# todo proc addPrevFindResultToSelection*(self: TextDocumentEditor, includeAfter: bool = true, wrap: bool = true) {.expose("editor.text").} = self.selections = self.selections & @[self.getPrevFindResult(self.selection.first, includeAfter=includeAfter)] +# todo proc setAllFindResultToSelection*(self: TextDocumentEditor) {.expose("editor.text").} = self.updateSearchResults() @@ -2769,23 +2692,7 @@ proc setAllFindResultToSelection*(self: TextDocumentEditor) {.expose("editor.tex if selections.len > 0: self.selections = selections -proc clearSelections*(self: TextDocumentEditor) {.expose("editor.text").} = - if self.selections.len > 1: - self.selection = self.selection - else: - self.selection = self.selection.last.toSelection - -proc moveCursorColumn*(self: TextDocumentEditor, distance: int, - cursor: SelectionCursor = SelectionCursor.Config, all: bool = true, wrap: bool = true, - includeAfter: bool = true) {.expose("editor.text").} = - self.moveCursor(cursor, doMoveCursorColumn, distance, all, wrap, includeAfter) - self.updateTargetColumn(cursor) - -proc moveCursorLine*(self: TextDocumentEditor, distance: int, - cursor: SelectionCursor = SelectionCursor.Config, all: bool = true, wrap: bool = true, - includeAfter: bool = true) {.expose("editor.text").} = - self.moveCursor(cursor, doMoveCursorLine, distance, all, wrap, includeAfter) - +# todo proc moveCursorVisualLine*(self: TextDocumentEditor, distance: int, cursor: SelectionCursor = SelectionCursor.Config, all: bool = true, wrap: bool = true, includeAfter: bool = true) {.expose("editor.text").} = @@ -2805,14 +2712,7 @@ proc moveCursorVisualLine*(self: TextDocumentEditor, distance: int, self.doMoveCursorVisualLine(cursor, offset, wrap, includeAfter, targetColumn.some) self.moveCursor(cursor, doMoveCursor, distance, all, wrap, includeAfter) -proc moveCursorPage*(self: TextDocumentEditor, distance: float, - cursor: SelectionCursor = SelectionCursor.Config, all: bool = true, wrap: bool = true, - includeAfter: bool = true) {.expose("editor.text").} = - - let visibleLines = self.screenLineCount() - let linesToMove = int(visibleLines.float * distance) - self.moveCursorLine(linesToMove, cursor, all, wrap, includeAfter) - +# todo proc moveCursorVisualPage*(self: TextDocumentEditor, distance: float, cursor: SelectionCursor = SelectionCursor.Config, all: bool = true, wrap: bool = true, includeAfter: bool = true) {.expose("editor.text").} = @@ -2821,75 +2721,28 @@ proc moveCursorVisualPage*(self: TextDocumentEditor, distance: float, let linesToMove = int(visibleLines.float * distance) self.moveCursorVisualLine(linesToMove, cursor, all, wrap, includeAfter) -proc moveCursorHome*(self: TextDocumentEditor, cursor: SelectionCursor = SelectionCursor.Config, - all: bool = true) {.expose("editor.text").} = - self.moveCursor(cursor, doMoveCursorHome, 0, all) - self.updateTargetColumn(cursor) - -proc moveCursorEnd*(self: TextDocumentEditor, cursor: SelectionCursor = SelectionCursor.Config, - all: bool = true, includeAfter: bool = true) {.expose("editor.text").} = - self.moveCursor(cursor, doMoveCursorEnd, 0, all, includeAfter=includeAfter) - self.updateTargetColumn(cursor) - -proc moveCursorTo*(self: TextDocumentEditor, str: string, - cursor: SelectionCursor = SelectionCursor.Config, all: bool = true) {.expose("editor.text").} = - proc doMoveCursorTo(self: TextDocumentEditor, cursor: Cursor, offset: int, - wrap: bool = true, includeAfter: bool = true): Cursor = - # todo: use RopeCursor - let line = self.document.getLine cursor.line - result = cursor - let index = line.suffix(cursor.column + 1).find(str) - if index >= 0: - result = (cursor.line, index + cursor.column + 1) - self.moveCursor(cursor, doMoveCursorTo, 0, all) - self.updateTargetColumn(cursor) - -proc moveCursorBefore*(self: TextDocumentEditor, str: string, - cursor: SelectionCursor = SelectionCursor.Config, all: bool = true) {.expose("editor.text").} = - proc doMoveCursorBefore(self: TextDocumentEditor, cursor: Cursor, offset: int, - wrap: bool = true, includeAfter: bool = true): Cursor = - # todo: use RopeCursor - let line = self.document.getLine cursor.line - result = cursor - let index = line.suffix(cursor.column).find(str) - if index > 0: - result = (cursor.line, index - 1 + cursor.column) - - self.moveCursor(cursor, doMoveCursorBefore, 0, all) - self.updateTargetColumn(cursor) - -proc moveCursorNextFindResult*(self: TextDocumentEditor, - cursor: SelectionCursor = SelectionCursor.Config, all: bool = true, wrap: bool = true) {. - expose("editor.text").} = - self.moveCursor(cursor, doMoveCursorNextFindResult, 0, all, wrap) - self.updateTargetColumn(cursor) - -proc moveCursorPrevFindResult*(self: TextDocumentEditor, - cursor: SelectionCursor = SelectionCursor.Config, all: bool = true, wrap: bool = true) {. - expose("editor.text").} = - self.moveCursor(cursor, doMoveCursorPrevFindResult, 0, all, wrap) - self.updateTargetColumn(cursor) - +# todo proc moveCursorLineCenter*(self: TextDocumentEditor, cursor: SelectionCursor = SelectionCursor.Config, all: bool = true) {.expose("editor.text").} = self.moveCursor(cursor, doMoveCursorLineCenter, 0, all) self.updateTargetColumn(cursor) +# todo proc moveCursorCenter*(self: TextDocumentEditor, cursor: SelectionCursor = SelectionCursor.Config, all: bool = true) {.expose("editor.text").} = self.moveCursor(cursor, doMoveCursorCenter, 0, all) proc scrollToCursor*(self: TextDocumentEditor, cursor: SelectionCursor = SelectionCursor.Config, - margin: Option[float] = float.none, scrollBehaviour: Option[ScrollBehaviour] = ScrollBehaviour.none, relativePosition: float = 0.5) {.expose("editor.text").} = + margin: Option[float] = float.none, scrollBehaviour: Option[ScrollBehaviour] = ScrollBehaviour.none, relativePosition: float = 0.5) = self.scrollToCursor(self.getCursor(cursor), margin, scrollBehaviour, relativePosition) -proc setNextScrollBehaviour*(self: TextDocumentEditor, scrollBehaviour: ScrollBehaviour) {.expose("editor.text").} = +proc setNextScrollBehaviour*(self: TextDocumentEditor, scrollBehaviour: ScrollBehaviour) = self.nextScrollBehaviour = scrollBehaviour.some proc setDefaultSnapBehaviour*(self: TextDocumentEditor, snapBehaviour: ScrollSnapBehaviour) {.expose("editor.text").} = self.defaultSnapBehaviour = snapBehaviour -proc setNextSnapBehaviour*(self: TextDocumentEditor, snapBehaviour: ScrollSnapBehaviour) {.expose("editor.text").} = +proc setNextSnapBehaviour*(self: TextDocumentEditor, snapBehaviour: ScrollSnapBehaviour) = self.nextSnapBehaviour = snapBehaviour.some if snapBehaviour == Always: self.interpolatedScrollOffset = self.scrollOffset @@ -2905,12 +2758,11 @@ proc setCursorScrollOffset*(self: TextDocumentEditor, cursor: Cursor, offset: fl self.scrollOffset.y = offset * self.platform.totalLineHeight - displayPoint.row.float * self.platform.totalLineHeight self.markDirty() -proc getContentBounds*(self: TextDocumentEditor): Vec2 {.expose("editor.text").} = +proc getContentBounds*(self: TextDocumentEditor): Vec2 = # todo return self.lastContentBounds.wh -proc centerCursor*(self: TextDocumentEditor, cursor: SelectionCursor = SelectionCursor.Config) {. - expose("editor.text").} = +proc centerCursor*(self: TextDocumentEditor, cursor: SelectionCursor = SelectionCursor.Config) {.expose("editor.text").} = self.centerCursor(self.getCursor(cursor)) proc reloadTreesitter*(self: TextDocumentEditor) {.expose("editor.text").} = @@ -2924,101 +2776,40 @@ proc reloadTreesitter*(self: TextDocumentEditor) {.expose("editor.text").} = if doc.languageId == self.document.languageId: doc.reloadTreesitterLanguage() -proc deleteLeft*(self: TextDocumentEditor) {.expose("editor.text").} = - var selections = self.selections - for i, selection in selections: - if selection.isEmpty: - selections[i] = (self.doMoveCursorColumn(selection.first, -1), selection.first) - - # echo &"delete left {self.selections} -> {selections}" - self.selections = self.document.edit(selections, self.selections, [""], - inclusiveEnd=self.useInclusiveSelections) - - self.autoShowCompletions() - -proc deleteRight*(self: TextDocumentEditor, includeAfter: bool = true) {.expose("editor.text").} = - var selections = self.selections - for i, selection in selections: - if selection.isEmpty: - selections[i] = (selection.first, self.doMoveCursorColumn(selection.first, 1)) - - self.selections = self.document.edit(selections, self.selections, [""], - inclusiveEnd=self.useInclusiveSelections).mapIt(self.clampSelection(it, includeAfter)) - - self.autoShowCompletions() - -proc getCommandCount*(self: TextDocumentEditor): int {.expose("editor.text").} = +proc getCommandCount*(self: TextDocumentEditor): int = return self.commandCount -proc setCommandCount*(self: TextDocumentEditor, count: int) {.expose("editor.text").} = +proc setCommandCount*(self: TextDocumentEditor, count: int) = self.commandCount = count -proc setCommandCountRestore*(self: TextDocumentEditor, count: int) {.expose("editor.text").} = +proc setCommandCountRestore*(self: TextDocumentEditor, count: int) = self.commandCountRestore = count -proc updateCommandCount*(self: TextDocumentEditor, digit: int) {.expose("editor.text").} = +proc updateCommandCount*(self: TextDocumentEditor, digit: int) = self.commandCount = self.commandCount * 10 + digit -proc runAction*(self: TextDocumentEditor, action: string, args: JsonNode): Option[JsonNode] {. - expose("editor.text").} = +proc runAction*(self: TextDocumentEditor, action: string, args: JsonNode): Option[JsonNode] {.expose("editor.text").} = # echo "runAction ", action, ", ", $args return self.handleActionInternal(action, args) -proc findWordBoundary*(self: TextDocumentEditor, cursor: Cursor): Selection {.expose("editor.text").} = +proc findWordBoundary*(self: TextDocumentEditor, cursor: Cursor): Selection = self.document.findWordBoundary(cursor) -proc getSelectionInPair*(self: TextDocumentEditor, cursor: Cursor, delimiter: char): Selection {. - expose("editor.text").} = +proc getSelectionInPair*(self: TextDocumentEditor, cursor: Cursor, delimiter: char): Selection = result = cursor.toSelection # todo -proc getSelectionInPairNested*(self: TextDocumentEditor, cursor: Cursor, open: char, - close: char): Selection {.expose("editor.text").} = +proc getSelectionInPairNested*(self: TextDocumentEditor, cursor: Cursor, open: char, close: char): Selection = result = cursor.toSelection # todo -proc extendSelectionWithMove*(self: TextDocumentEditor, selection: Selection, move: string, - count: int = 0): Selection {.expose("editor.text").} = +proc extendSelectionWithMove*(self: TextDocumentEditor, selection: Selection, move: string, count: int = 0): Selection = result = self.getSelectionForMove(selection.first, move, count) or self.getSelectionForMove(selection.last, move, count) if selection.isBackwards: result = result.reverse -proc vimMotionWord*(self: TextDocumentEditor, cursor: Cursor, count: int = 0): Selection {.expose("editor.text").} = - const AlphaNumeric = {'A'..'Z', 'a'..'z', '0'..'9', '_'} - - var line = self.document.getLine(cursor.line) - if line.len == 0: - return (cursor.line, 0).toSelection - - let c = line.charAt(cursor.column.clamp(0, line.len - 1)) - if c in Whitespace: - let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c in Whitespace) - return ((cursor.line, startColumn), (cursor.line, endColumn)) - - elif c in AlphaNumeric: - let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c in AlphaNumeric) - return ((cursor.line, startColumn), (cursor.line, endColumn)) - - else: - let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c notin Whitespace and c notin AlphaNumeric) - return ((cursor.line, startColumn), (cursor.line, endColumn)) - -proc vimMotionWordBig*(self: TextDocumentEditor, cursor: Cursor, count: int = 0): Selection {.expose("editor.text").} = - var line = self.document.getLine(cursor.line) - if line.len == 0: - return (cursor.line, 0).toSelection - - let c = line.charAt(cursor.column.clamp(0, line.len - 1)) - if c in Whitespace: - let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c in Whitespace) - return ((cursor.line, startColumn), (cursor.line, endColumn)) - - else: - let (startColumn, endColumn) = line.getEnclosing(cursor.column, (c) => c notin Whitespace) - return ((cursor.line, startColumn), (cursor.line, endColumn)) - -proc getSurrounding*(self: TextDocumentEditor, selection: Selection, count: int, includeEol: bool, c0: char, c1: char, inside: bool): Selection = +proc getSurrounding*(self: TextDocumentEditor, selection: Selection, count: int, c0: char, c1: char, inside: bool): Selection = result = selection while true: let lastChar = self.getChar(result.last) @@ -3045,8 +2836,10 @@ proc getSurrounding*(self: TextDocumentEditor, selection: Selection, count: int, else: return -proc getSelectionsForMove*(self: TextDocumentEditor, selections: openArray[Selection], move: string, - count: int = 0, includeEol: bool = true, wrap: bool = true): seq[Selection] = +proc applyMoveFallback(self: TextDocumentEditor, move: string, selections: openArray[Selection], count: int): seq[Selection] = + debugf"applyMoveFallback {move}" + let includeEol = true + let wrap = false let cursorSelector = self.config.get(self.getContextWithMode("editor.text.cursor.movement"), SelectionCursor.Both) @@ -3092,44 +2885,12 @@ proc getSelectionsForMove*(self: TextDocumentEditor, selections: openArray[Selec let c1 = getArg(1, string, ")") let inside = getArg(2, bool, true) if c0.len > 0 and c1.len > 0: - result = selections.mapIt(self.getSurrounding(it, count, includeEol, c0[0], c1[0], inside)) + result = selections.mapIt(self.getSurrounding(it, count, c0[0], c1[0], inside)) of "page": let linesToMove = int(self.screenLineCount() * count div 100) return selections.mapIt(self.doMoveCursorLine(it.last, linesToMove, false, includeEol).toSelection(it, cursorSelector)) - of "visual-page": - let linesToMove = int(self.screenLineCount() * count div 100) - return selections.mapIt(self.doMoveCursorVisualLine(it.last, linesToMove, false, includeEol).toSelection(it, cursorSelector)) - # let visibleLines = self.screenLineCount() - # let linesToMove = int(visibleLines.float * distance) - # self.moveCursorVisualLine(linesToMove, cursor, all, wrap, includeAfter) - - of "visual-line-up", "visual-line-down": - var minLine = int.high - var maxLine = int.low - for s in self.selections: - minLine = min(minLine, s.last.line) - maxLine = max(maxLine, s.last.line) - - let direction = if move == "visual-line-down": 1 else: -1 - - proc doMoveCursor(self: TextDocumentEditor, cursor: Cursor, offset: int, includeAfter: bool): Cursor = - let targetColumn = if maxLine - minLine + 1 < self.selections.len: - self.displayMap.toDisplayPoint(cursor.toPoint).column.int - else: - self.targetColumn - self.doMoveCursorVisualLine(cursor, offset, false, includeAfter, targetColumn.some) - return selections.mapIt(doMoveCursor(self, it.last, direction * max(count, 1), includeEol).toSelection(it, cursorSelector)) - - of "line-up", "line-down": - let direction = if move == "line-down": 1 else: -1 - let count = if count == 0: 1 else: count - return selections.mapIt(self.doMoveCursorLine(it.last, direction * count, false, includeEol).toSelection(it, cursorSelector)) - - of "column": - result = selections.mapIt(self.doMoveCursorColumn(it.last, count, wrap = wrap, includeAfter = includeEol).toSelection(it, cursorSelector)) - of "prev-search-result": let count = getArg(0, int, 0) let wrap = getArg(1, bool, true) @@ -3158,89 +2919,82 @@ proc getSelectionsForMove*(self: TextDocumentEditor, selections: openArray[Selec let wrap = getArg(2, bool, true) result = selections.mapIt(self.getNextDiagnostic(it.last, severity, count, includeEol, wrap)) - of "number": - result = selections.mapIt: - var r = it.last.toPoint...it.last.toPoint - var c = self.document.rope.cursorT(it.last.toPoint) - while c.currentChar in {'0'..'9'}: - c.seekNextRune() - r.b = c.position - - if r.a.column > 0: - c = self.document.rope.cursorT(it.last.toPoint) - while c.position.column > 0: - c.seekPrevRune() - if c.currentChar == '-': - r.a = c.position - break + of "next-tab-stop": + result = @selections + if self.currentSnippetData.isNone: + return - if c.currentChar notin {'0'..'9'}: - c.seekNextRune() - break - r.a = c.position + var foundTabStop = false + while self.currentSnippetData.get.currentTabStop < self.currentSnippetData.get.highestTabStop: + self.currentSnippetData.get.currentTabStop.inc + self.currentSnippetData.get.tabStops.withValue(self.currentSnippetData.get.currentTabStop, val): + result = val[] + foundTabStop = true + break - r.toSelection + if not foundTabStop: + self.currentSnippetData.get.currentTabStop = 0 + result = self.currentSnippetData.get.tabStops[0] - of "grow": - return selections.mapIt: - if count < 0 and it.first.line == it.last.line and abs(it.first.column - it.last.column) < 2: - it - else: - (self.doMoveCursorColumn(it.first, -count, wrap, includeEol), self.doMoveCursorColumn(it.last, count, wrap, includeEol)) + of "prev-tab-stop": + result = @selections + if self.currentSnippetData.isNone: + return - else: - return self.moveDatabase.applyMove(self.displayMap, moveOriginal, selections, count, includeEol) + if self.currentSnippetData.get.currentTabStop == 0: + self.currentSnippetData.get.currentTabStop = self.currentSnippetData.get.highestTabStop + self.currentSnippetData.get.tabStops.withValue(self.currentSnippetData.get.currentTabStop, val): + result = val[] + return -proc getSelectionForMove*(self: TextDocumentEditor, cursor: Cursor, move: string, - count: int = 0, includeEol: bool = true): Selection {.expose("editor.text").} = - case move - of "word": - result = self.findWordBoundary(cursor) - for _ in 1.. 1: + self.currentSnippetData.get.currentTabStop.dec + self.currentSnippetData.get.tabStops.withValue(self.currentSnippetData.get.currentTabStop, val): + result = val[] + break - of "vim-word": - result = self.vimMotionWord(cursor) - for _ in 1.. {result}" + case move + of "word": result = self.findWordBoundary(cursor) - if cursor.column == 0 and cursor.line > 0: - result.first = (cursor.line - 1, self.document.lineLength(cursor.line - 1)) - if cursor.column == line.len and cursor.line < self.document.numLines - 1: - result.last = (cursor.line + 1, 0) - for _ in 1.. 0: - result.first = (result.first.line - 1, self.document.lineLength(result.first.line - 1)) - if result.last.column == line.len and result.last.line < self.document.numLines - 1: - result.last = (result.last.line + 1, 0) - - of "word-back": - return self.getSelectionForMove((cursor.line, max(0, cursor.column - 1)), "word", count).reverse - - of "word-line-back": - return self.getSelectionForMove((cursor.line, max(0, cursor.column - 1)), "word-line", count).reverse of "number": var r = cursor.toPoint...cursor.toPoint @@ -3415,52 +3169,18 @@ proc cursor(self: TextDocumentEditor, selection: Selection, which: SelectionCurs of Last, LastToFirst: return selection.last -proc applyMove*(self: TextDocumentEditor, args {.varargs.}: JsonNode) {.expose("editor.text").} = - self.config.set("text.move-count", self.getCommandCount) - self.setMode self.config.get("text.move-next-mode", "") - self.setCommandCount self.config.get("text.move-command-count", 0) - let command = self.config.get("text.move-action", "") - discard self.runAction(command, args) - self.config.set("text.move-action", "") - -proc deleteMove*(self: TextDocumentEditor, move: string, inside: bool = false, - which: SelectionCursor = SelectionCursor.Config, all: bool = true) {.expose("editor.text").} = +proc deleteMove*(self: TextDocumentEditor, move: string, updateTargetColumn: bool = true, options {.varargs.}: JsonNode = newJObject()) {.expose("editor.text").} = ## Deletes text based on the current selections. ## ## `move` specifies which move should be applied to each selection. let count = self.config.get("text.move-count", 0) - # echo fmt"delete-move {move}, {which}, {count}, {inside}" - - let selections = if inside: - self.selections.mapAllOrLast(all, (s) {.gcsafe, raises: [].} => self.getSelectionForMove(s.last, move, count)) - else: - self.selections.mapAllOrLast(all, (s) {.gcsafe, raises: [].} => ( - self.getCursor(s, which), - self.getCursor(self.getSelectionForMove(s.last, move, count), which) - )) - - self.selections = self.document.edit(selections, self.selections, [""], - inclusiveEnd=self.useInclusiveSelections) - self.scrollToCursor(Last) - self.setNextSnapBehaviour(ScrollSnapBehaviour.Always) - self.updateTargetColumn(Last) - -proc selectMove*(self: TextDocumentEditor, move: string, inside: bool = false, - which: SelectionCursor = SelectionCursor.Config, all: bool = true) {.expose("editor.text").} = - let count = self.config.get("text.move-count", 0) - - self.selections = if inside: - self.selections.mapAllOrLast(all, (s) {.gcsafe, raises: [].} => self.getSelectionForMove(s.last, move, count)) - else: - self.selections.mapAllOrLast(all, (s) {.gcsafe, raises: [].} => ( - self.getCursor(s, which), - self.getCursor(self.getSelectionForMove(s.last, move, count), which) - )) - + let selections = self.getSelectionsForMove(self.selections, move, 1, true, true, options) + self.selections = self.document.edit(selections, self.selections, [""]) self.scrollToCursor(Last) self.setNextSnapBehaviour(ScrollSnapBehaviour.Always) - self.updateTargetColumn(Last) + if updateTargetColumn: + self.updateTargetColumn(Last) proc extendSelectMove*(self: TextDocumentEditor, move: string, inside: bool = false, which: SelectionCursor = SelectionCursor.Config, all: bool = true) {.expose("editor.text").} = @@ -3477,69 +3197,16 @@ proc extendSelectMove*(self: TextDocumentEditor, move: string, inside: bool = fa self.scrollToCursor(Last) self.updateTargetColumn(Last) -proc copyMove*(self: TextDocumentEditor, move: string, inside: bool = false, - which: SelectionCursor = SelectionCursor.Config, all: bool = true) {.expose("editor.text").} = - self.selectMove(move, inside, which, all) - self.copy() - self.selections = self.selections.mapIt(it.first.toSelection) - -proc changeMove*(self: TextDocumentEditor, move: string, inside: bool = false, - which: SelectionCursor = SelectionCursor.Config, all: bool = true) {.expose("editor.text").} = - let count = self.config.get("text.move-count", 0) - - let selections = if inside: - self.selections.mapAllOrLast(all, (s) {.gcsafe, raises: [].} => self.getSelectionForMove(s.last, move, count)) - else: - self.selections.mapAllOrLast(all, (s) {.gcsafe, raises: [].} => ( - self.getCursor(s, which), - self.getCursor(self.getSelectionForMove(s.last, move, count), which) - )) - - self.selections = self.document.edit(selections, self.selections, [""], - inclusiveEnd=self.useInclusiveSelections) +proc move*(self: TextDocumentEditor, move: string, updateTargetColumn: bool = true, options {.varargs.}: JsonNode = newJObject()) {.expose("editor.text").} = + self.selections = self.getSelectionsForMove(self.selections, move, 1, true, true, options) self.scrollToCursor(Last) - self.setNextSnapBehaviour(ScrollSnapBehaviour.Always) - self.updateTargetColumn(Last) - -proc moveLast*(self: TextDocumentEditor, move: string, which: SelectionCursor = SelectionCursor.Config, - all: bool = true, count: int = 0) {.expose("editor.text").} = - case which - of Config: - let cursorSelector = self.config.get( - self.getContextWithMode("editor.text.cursor.movement"), - SelectionCursor.Both - ) - self.selections = self.selections.mapAllOrLast(all, (s) {.gcsafe, raises: [].} => - self.getSelectionForMove(self.cursor(s, which), move, count).last.toSelection(s, cursorSelector) - ) - else: - self.selections = self.selections.mapAllOrLast(all, (s) {.gcsafe, raises: [].} => - self.getSelectionForMove(self.cursor(s, which), move, count).last.toSelection(s, which)) - self.scrollToCursor(which) - self.updateTargetColumn(which) - -proc moveFirst*(self: TextDocumentEditor, move: string, which: SelectionCursor = SelectionCursor.Config, - all: bool = true, count: int = 0) {.expose("editor.text").} = - case which - of Config: - let cursorSelector = self.config.get( - self.getContextWithMode("editor.text.cursor.movement"), - SelectionCursor.Both - ) - self.selections = self.selections.mapAllOrLast(all, (s) {.gcsafe, raises: [].} => - self.getSelectionForMove(self.cursor(s, which), move, count).first.toSelection(s, cursorSelector) - ) - else: - self.selections = self.selections.mapAllOrLast(all, (s) {.gcsafe, raises: [].} => - self.getSelectionForMove(self.cursor(s, which), move, count).first.toSelection(s, which)) - self.scrollToCursor(which) - self.updateTargetColumn(which) + if updateTargetColumn: + self.updateTargetColumn(Last) -proc getSearchQuery*(self: TextDocumentEditor): string {.expose("editor.text").} = +proc getSearchQuery*(self: TextDocumentEditor): string = return self.searchQuery -proc setSearchQuery*(self: TextDocumentEditor, query: string, escapeRegex: bool = false, - prefix: string = "", suffix: string = ""): bool {.expose("editor.text").} = +proc setSearchQuery*(self: TextDocumentEditor, query: string, escapeRegex: bool = false, prefix: string = "", suffix: string = ""): bool {.expose("editor.text").} = let query = if escapeRegex: query.escapeRegex @@ -3574,7 +3241,7 @@ proc openSearchBar*(self: TextDocumentEditor, query: string = "", scrollToPrevie let document = commandLineEditor.document commandLineEditor.disableCompletions = true - commandLineEditor.moveLast("file") + commandLineEditor.move("(file) (end)") commandLineEditor.updateTargetColumn() var onEditHandle = Id.new @@ -3599,7 +3266,7 @@ proc openSearchBar*(self: TextDocumentEditor, query: string = "", scrollToPrevie self.scrollToCursor(s.last) proc setSearchQueryFromMove*(self: TextDocumentEditor, move: string, - count: int = 0, prefix: string = "", suffix: string = ""): Selection {.expose("editor.text").} = + count: int = 0, prefix: string = "", suffix: string = ""): Selection = let selection = self.getSelectionForMove(self.selection.last, move, count) let searchText = self.document.contentString(selection) discard self.setSearchQuery(searchText, escapeRegex=true, prefix, suffix) @@ -4028,8 +3695,7 @@ proc renameAsync(self: TextDocumentEditor) {.async.} = log lvlError, &"Failed to rename to '{name}': {it.error}" commandLineEditor.disableCompletions = true - commandLineEditor.moveLast("file") - commandLineEditor.updateTargetColumn() + commandLineEditor.move("(file) (end)") proc rename*(self: TextDocumentEditor) {.expose("editor.text").} = asyncSpawn self.renameAsync() @@ -4068,44 +3734,12 @@ proc selectNextCompletionVisual*(self: TextDocumentEditor) {.expose("editor.text else: self.selectNextCompletion() -proc hasTabStops*(self: TextDocumentEditor): bool {.expose("editor.text").} = +proc hasTabStops*(self: TextDocumentEditor): bool = return self.currentSnippetData.isSome proc clearTabStops*(self: TextDocumentEditor) {.expose("editor.text").} = self.currentSnippetData = SnippetData.none -proc selectNextTabStop*(self: TextDocumentEditor) {.expose("editor.text").} = - if self.currentSnippetData.isNone: - return - - var foundTabStop = false - while self.currentSnippetData.get.currentTabStop < self.currentSnippetData.get.highestTabStop: - self.currentSnippetData.get.currentTabStop.inc - self.currentSnippetData.get.tabStops.withValue(self.currentSnippetData.get.currentTabStop, val): - self.selections = val[] - foundTabStop = true - break - - if not foundTabStop: - self.currentSnippetData.get.currentTabStop = 0 - self.selections = self.currentSnippetData.get.tabStops[0] - -proc selectPrevTabStop*(self: TextDocumentEditor) {.expose("editor.text").} = - if self.currentSnippetData.isNone: - return - - if self.currentSnippetData.get.currentTabStop == 0: - self.currentSnippetData.get.currentTabStop = self.currentSnippetData.get.highestTabStop - self.currentSnippetData.get.tabStops.withValue(self.currentSnippetData.get.currentTabStop, val): - self.selections = val[] - return - - while self.currentSnippetData.get.currentTabStop > 1: - self.currentSnippetData.get.currentTabStop.dec - self.currentSnippetData.get.tabStops.withValue(self.currentSnippetData.get.currentTabStop, val): - self.selections = val[] - break - proc applyCompletion*(self: TextDocumentEditor, completion: Completion) = let completion = completion log(lvlInfo, fmt"Applying completion {completion.item.label}") @@ -4216,7 +3850,7 @@ proc applyCompletion*(self: TextDocumentEditor, completion: Completion) = self.selections = newSelections[numAdditionalEdits..^1].mapIt(it.last.toSelection) self.currentSnippetData = snippetData - self.selectNextTabStop() + self.move("(next-tab-stop)") if completion.item.showCompletionsAgain.get(false): self.autoShowCompletions() @@ -4228,7 +3862,7 @@ proc applyCompletion*(self: TextDocumentEditor, completion: JsonNode) {.expose(" except: log lvlError, &"[applyCompletion] Failed to parse completion {completion}" -proc isShowingCompletions*(self: TextDocumentEditor): bool {.expose("editor.text").} = +proc isShowingCompletions*(self: TextDocumentEditor): bool = return self.showCompletions and self.completions.len > 0 proc applySelectedCompletion*(self: TextDocumentEditor) {.expose("editor.text").} = @@ -4273,7 +3907,7 @@ proc showHoverForAsync(self: TextDocumentEditor, cursor: Cursor): Future[void] { self.markDirty() -proc showHoverFor*(self: TextDocumentEditor, cursor: Cursor) {.expose("editor.text").} = +proc showHoverFor*(self: TextDocumentEditor, cursor: Cursor) = ## Shows lsp hover information for the given cursor. ## Does nothing if no language server is available or the language server doesn't return any info. asyncSpawn self.showHoverForAsync(cursor) @@ -4288,11 +3922,11 @@ proc hideHover*(self: TextDocumentEditor) {.expose("editor.text").} = self.showHover = false self.markDirty() -proc cancelDelayedHideHover*(self: TextDocumentEditor) {.expose("editor.text").} = +proc cancelDelayedHideHover*(self: TextDocumentEditor) = if self.hideHoverTask.isNotNil: self.hideHoverTask.pause() -proc hideHoverDelayed*(self: TextDocumentEditor) {.expose("editor.text").} = +proc hideHoverDelayed*(self: TextDocumentEditor) = ## Hides the hover information after a delay. if self.showHoverTask.isNotNil: self.showHoverTask.pause() @@ -4602,7 +4236,7 @@ proc setFileReadOnlyAsync*(self: TextDocumentEditor, readOnly: bool) {.async.} = proc setFileReadOnly*(self: TextDocumentEditor, readOnly: bool) {.expose("editor.text").} = asyncSpawn self.setFileReadOnlyAsync(readOnly) -proc isRunningSavedCommands*(self: TextDocumentEditor): bool {.expose("editor.text").} = +proc isRunningSavedCommands*(self: TextDocumentEditor): bool = self.bIsRunningSavedCommands proc runSavedCommands*(self: TextDocumentEditor) {.expose("editor.text").} = @@ -4625,8 +4259,7 @@ proc runSavedCommands*(self: TextDocumentEditor) {.expose("editor.text").} = self.savedCommandHistory = commandHistory -proc clearCurrentCommandHistory*(self: TextDocumentEditor, retainLast: bool = false) {. - expose("editor.text").} = +proc clearCurrentCommandHistory*(self: TextDocumentEditor, retainLast: bool = false) = if retainLast and self.currentCommandHistory.commands.len > 0: let last = self.currentCommandHistory.commands[self.currentCommandHistory.commands.high] self.currentCommandHistory.commands.setLen 0 @@ -4634,7 +4267,7 @@ proc clearCurrentCommandHistory*(self: TextDocumentEditor, retainLast: bool = fa else: self.currentCommandHistory.commands.setLen 0 -proc saveCurrentCommandHistory*(self: TextDocumentEditor) {.expose("editor.text").} = +proc saveCurrentCommandHistory*(self: TextDocumentEditor) = self.savedCommandHistory = self.currentCommandHistory self.currentCommandHistory.commands.setLen 0 @@ -4697,22 +4330,22 @@ proc assignKeys*(self: TextDocumentEditor, cursors: openArray[Cursor]): seq[stri if result.len == cursors.len: return -proc getSelection*(self: TextDocumentEditor): Selection {.expose("editor.text").} = +proc getSelection*(self: TextDocumentEditor): Selection = self.selection -proc getSelections*(self: TextDocumentEditor): Selections {.expose("editor.text").} = +proc getSelections*(self: TextDocumentEditor): Selections = self.selections -proc setSelection*(self: TextDocumentEditor, selection: Selection, addToHistory: Option[bool] = bool.none) {.expose("editor.text").} = +proc setSelection*(self: TextDocumentEditor, selection: Selection, addToHistory: Option[bool] = bool.none) = self.`selections=`(@[selection], addToHistory) -proc setSelections*(self: TextDocumentEditor, selections: Selections, addToHistory: Option[bool] = bool.none) {.expose("editor.text").} = +proc setSelections*(self: TextDocumentEditor, selections: Selections, addToHistory: Option[bool] = bool.none) = if selections.len == 0: log lvlError, &"Failed to set selections for '{self.getFileName()}': no selections provided" return self.`selections=`(selections, addToHistory) -proc setTargetSelection*(self: TextDocumentEditor, selection: Selection) {.expose("editor.text").} = +proc setTargetSelection*(self: TextDocumentEditor, selection: Selection) = self.targetSelection = selection proc enterChooseCursorMode*(self: TextDocumentEditor, action: string) {.expose("editor.text").} = @@ -4791,41 +4424,41 @@ proc enterChooseCursorMode*(self: TextDocumentEditor, action: string) {.expose(" self.markDirty() -proc recordCurrentCommand*(self: TextDocumentEditor, registers: seq[string] = @[]) {.expose("editor.text").} = +proc recordCurrentCommand*(self: TextDocumentEditor, registers: seq[string] = @[]) = self.recordCurrentCommandRegisters = if registers.len > 0: registers else: self.registers.recordingCommands -proc runControlClickCommand*(self: TextDocumentEditor) {.expose("editor.text").} = +proc runControlClickCommand*(self: TextDocumentEditor) = let commandName = self.settings.controlClickCommand.get() let args = self.settings.controlClickCommandArgs.get() if commandName.len == 0: return discard self.runAction(commandName, args) -proc runSingleClickCommand*(self: TextDocumentEditor) {.expose("editor.text").} = +proc runSingleClickCommand*(self: TextDocumentEditor) = let commandName = self.settings.singleClickCommand.get() let args = self.settings.singleClickCommandArgs.get() if commandName.len == 0: return discard self.runAction(commandName, args) -proc runDoubleClickCommand*(self: TextDocumentEditor) {.expose("editor.text").} = +proc runDoubleClickCommand*(self: TextDocumentEditor) = let commandName = self.settings.doubleClickCommand.get() let args = self.settings.doubleClickCommandArgs.get() if commandName.len == 0: return discard self.runAction(commandName, args) -proc runTripleClickCommand*(self: TextDocumentEditor) {.expose("editor.text").} = +proc runTripleClickCommand*(self: TextDocumentEditor) = let commandName = self.settings.tripleClickCommand.get() let args = self.settings.tripleClickCommandArgs.get() if commandName.len == 0: return discard self.runAction(commandName, args) -proc runDragCommand*(self: TextDocumentEditor) {.expose("editor.text").} = +proc runDragCommand*(self: TextDocumentEditor) = if self.lastPressedMouseButton == MouseButton.Left: self.runSingleClickCommand() elif self.lastPressedMouseButton == MouseButton.DoubleClick: @@ -4833,67 +4466,14 @@ proc runDragCommand*(self: TextDocumentEditor) {.expose("editor.text").} = elif self.lastPressedMouseButton == MouseButton.TripleClick: self.runTripleClickCommand() -proc getCurrentEventHandlers*(self: TextDocumentEditor): seq[string] {.expose("editor.text").} = +proc getCurrentEventHandlers*(self: TextDocumentEditor): seq[string] = return self.settings.modes.get() proc setCustomHeader*(self: TextDocumentEditor, text: string) {.expose("editor.text").} = self.customHeader = text self.markDirty() -type IdentifierCase = enum Camel, Pascal, Kebab, Snake, ScreamingSnake - -proc splitCase(s: string): tuple[cas: IdentifierCase, parts: seq[string]] = - if s == "": - return (IdentifierCase.Camel, @[]) - - if s.find('_') != -1: - result.cas = IdentifierCase.Snake - result.parts = s.split('_').mapIt(custom_unicode.toLower(it)) - for r in s.runes: - if r != '_'.Rune and not r.isLower: - result.cas = IdentifierCase.ScreamingSnake - break - - elif s.find('-') != -1: - result.cas = IdentifierCase.Kebab - result.parts = s.split('-').mapIt(custom_unicode.toLower(it)) - else: - if s[0].isUpperAscii: - result.cas = IdentifierCase.Pascal - else: - result.cas = IdentifierCase.Camel - - result.parts.add "" - for r in s.runes: - if not r.isLower and result.parts.last.len > 0: - result.parts.add "" - result.parts.last.add(custom_unicode.toLower(r)) - -proc joinCase(parts: seq[string], cas: IdentifierCase): string = - assert parts.len > 0 - case cas - of IdentifierCase.Camel: - parts[0] & parts[1..^1].mapIt(it.capitalize).join("") - of IdentifierCase.Pascal: - parts.mapIt(it.capitalize).join("") - of IdentifierCase.Kebab: - parts.join("-") - of IdentifierCase.Snake: - parts.join("_") - of IdentifierCase.ScreamingSnake: - parts.mapIt(custom_unicode.toUpper(it)).join("_") - -proc cycleCase(s: string): string = - if s.len == 0: - return s - let (cas, parts) = s.splitCase() - let nextCase = if cas == IdentifierCase.high: - IdentifierCase.low - else: - cas.succ - return parts.joinCase(nextCase) - -proc cycleCase*(self: TextDocumentEditor, selection: Selection, inclusiveEnd: bool = false): string {.expose("editor.text").} = +proc cycleCase*(self: TextDocumentEditor, selection: Selection, inclusiveEnd: bool = false): string = return self.document.contentString(selection, inclusiveEnd).cycleCase() proc cycleSelectedCase*(self: TextDocumentEditor) {.expose("editor.text").} = @@ -5075,19 +4655,19 @@ proc updateMatchingWordHighlightAsync(self: TextDocumentEditor) {.async.} = return let (selection, inclusive, addWordBoundary) = if oldSelection.isEmpty: - var s = self.vimMotionWord(oldSelection.last).normalized + var s = self.document.rope.vimMotionWord(oldSelection.last, false).normalized const AlphaNumeric = {'A'..'Z', 'a'..'z', '0'..'9', '_'} if self.document.rope.charAt(s.first.toPoint) notin AlphaNumeric: let prev = (oldSelection.last.line, oldSelection.last.column - 1) if s.first.column > 0 and self.document.rope.charAt(prev.toPoint) in AlphaNumeric: - s = self.vimMotionWord(prev).normalized + s = self.document.rope.vimMotionWord(prev, false).normalized else: self.clearCustomHighlights(wordHighlightId) return if self.document.rope.charAt(s.first.toPoint) notin AlphaNumeric: self.clearCustomHighlights(wordHighlightId) return - (s, true, true) + (s, false, true) else: (oldSelection.normalized, self.useInclusiveSelections, false) @@ -5282,7 +4862,6 @@ proc handleDisplayMapUpdated(self: TextDocumentEditor, displayMap: DisplayMap) = if displayMap == self.displayMap: if displayMap.endDisplayPoint.row != self.lastEndDisplayPoint.row: self.lastEndDisplayPoint = displayMap.endDisplayPoint - self.updateTargetColumn() # let oldScrollOffset = self.scrollOffset.y # debugf"handleDisplayMapUpdated '{self.getFileName()}': target {self.targetPoint}, {displayMap.endDisplayPoint.row} -> {self.lastEndDisplayPoint.row}, {self.lastContentBounds}" @@ -5345,6 +4924,8 @@ proc newTextEditor*(document: TextDocument, services: Services): TextDocumentEdi self.debugSettings = DebugSettings.new(self.config) self.settings = TextEditorSettings.new(self.config) + self.moveFallbacks = proc(move: string, selections: openArray[Selection], count: int): seq[Selection] = self.applyMoveFallback(move, selections, count) + self.setDocument(document) self.startBlinkCursorTask() diff --git a/wit/v0/api.wit b/wit/v0/api.wit index 5db7a12a8..8a6844062 100644 --- a/wit/v0/api.wit +++ b/wit/v0/api.wit @@ -284,10 +284,6 @@ interface text-editor { /// todo clear-tab-stops: func(editor: text-editor); /// todo - select-next-tab-stop: func(editor: text-editor); - /// todo - select-prev-tab-stop: func(editor: text-editor); - /// todo undo: func(editor: text-editor, checkpoint: string); /// todo redo: func(editor: text-editor, checkpoint: string); From 40a3b3b2004f28f4e1630957779f20c4a638878e Mon Sep 17 00:00:00 2001 From: Nico Date: Sun, 19 Oct 2025 22:42:43 +0200 Subject: [PATCH 33/47] Remove nimble buildNimConfigWasmAll commands for old plugin system --- .github/workflows/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1350740e5..762e29d7d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -93,7 +93,6 @@ jobs: run: PATH=/github/home/.nimble/bin:$PATH nimble setup # linux builds - - run: nimble buildNimConfigWasmAll - run: nimble buildWasmModule - run: nim c --out:nev -D:enableGui=false -D:enableTerminal=true --app:console -D:forceLogToFile --passC:-std=gnu11 -d:exposeScriptingApi -D:isCI -D:isCINimbleCached=${{steps.cache-nimble.outputs.cache-hit}} src/desktop_main.nim # - run: nim c --out:nevc -D:enableGui=false -D:enableTerminal=true --app:console -D:forceLogToFile --passC:-std=gnu11 -d:exposeScriptingApi -D:isCI -D:isCINimbleCached=${{steps.cache-nimble.outputs.cache-hit}} --cc:clang --passC:-Wno-incompatible-function-pointer-types --passL:-ladvapi32.lib -d:enableSystemClipboard=false src/desktop_main.nim @@ -171,7 +170,6 @@ jobs: - run: Invoke-WebRequest -Uri "https://github.com/electron/rcedit/releases/download/v2.0.0/rcedit-x64.exe" -OutFile "./rcedit-x64.exe" # windows builds - - run: ./emsdk/emsdk.ps1 activate 4.0.10; nimble buildNimConfigWasmAll - run: ./emsdk/emsdk.ps1 activate 4.0.10; nimble buildWasmModule - run: nim c --out:nev.exe -D:enableGui=false -D:enableTerminal=true --app:console -D:forceLogToFile --passC:-std=gnu11 -d:exposeScriptingApi -D:isCI -D:isCINimbleCached=${{steps.cache-nimble.outputs.cache-hit}} src/desktop_main.nim - run: nim c --out:nevg.exe -D:enableGui=true -D:enableTerminal=false --app:gui -D:forceLogToFile --passC:-std=gnu11 -d:exposeScriptingApi -D:isCI -D:isCINimbleCached=${{steps.cache-nimble.outputs.cache-hit}} src/desktop_main.nim From f3b33c733ba12263736e724a32d4e09858ac4f53 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 19 Oct 2025 22:49:43 +0200 Subject: [PATCH 34/47] Move lisp interpreter to core --- src/lisp.nim | 1091 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1091 insertions(+) create mode 100644 src/lisp.nim diff --git a/src/lisp.nim b/src/lisp.nim new file mode 100644 index 000000000..2ed067870 --- /dev/null +++ b/src/lisp.nim @@ -0,0 +1,1091 @@ + +import std/[json, sequtils, strutils, strformat, lexbase, unicode, streams, tables, math] +import misc/[util, myjsonutils, custom_logger] + +{.push gcsafe.} + +logCategory "lisp" + +type + LispValKind* = enum + Nil, Number, Bool, Symbol, String, List, Array, Map, Func, Lambda, Macro + + Env* = ref object + parent*: Env + env*: Table[string, LispVal] + onUndefinedSymbol*: proc(env: Env, name: string): LispVal {.gcsafe, raises: [].} + + LispError* = object of CatchableError + + LispVal* = ref object + case kind*: LispValKind + of Nil: + discard + of Number: + num*: float + of Bool: + bol*: bool + of Symbol: + sym*: string + of String: + str*: string + of List, Array: + elems*: seq[LispVal] + of Map: + fields*: OrderedTable[string, LispVal] + of Func: + name*: string + evalArgs*: bool = true + fn*: proc (args: seq[LispVal]): LispVal {.gcsafe, raises: [LispError].} + of Lambda, Macro: + params*: seq[string] + body*: LispVal + env*: Env + +proc `$`*(val: LispVal): string {.raises: [].} = + if val == nil: + return "" + case val.kind + of Nil: $"nil" + of Number: + if val.num.int.float == val.num: + $val.num.int + else: + $val.num + of Bool: $val.bol + of Symbol: val.sym + of String: val.str + of List: "(" & val.elems.mapIt($it).join(" ") & ")" + of Array: "[" & val.elems.mapIt($it).join(" ") & "]" + of Map: + var res = "{" + var i = 0 + for key, val in val.fields.pairs: + if i > 0: + res.add ", " + inc i + res.add key + res.add ": " + res.add $val + res.add "} " + res + of Func: &"" + of Lambda: &"({val.params}) -> {val.body})" + of Macro: &"!({val.params}) -> {val.body})" + +proc newNil*(): LispVal = + LispVal(kind: Nil) + +proc newNumber*(n: float): LispVal = + LispVal(kind: Number, num: n) + +proc newNumber*(n: int): LispVal = + LispVal(kind: Number, num: n.float) + +proc newBool*(n: bool): LispVal = + LispVal(kind: Bool, bol: n) + +proc newSymbol*(s: string): LispVal = + LispVal(kind: Symbol, sym: s) + +proc newString*(s: string): LispVal = + LispVal(kind: String, str: s) + +proc newList*(elems: seq[LispVal] = @[]): LispVal = + LispVal(kind: List, elems: elems) + +proc newArray*(elems: seq[LispVal] = @[]): LispVal = + LispVal(kind: Array, elems: elems) + +proc newMap*(fields: OrderedTable[string, LispVal] = initOrderedTable[string, LispVal]()): LispVal = + LispVal(kind: Map, fields: fields) + +proc newFunc*(name: string, evalArgs: bool, fn: proc(args: seq[LispVal]): LispVal {.gcsafe, raises: [LispError].}): LispVal = + LispVal(kind: Func, name: name, evalArgs: evalArgs, fn: fn) + +proc newFunc*(name: string, fn: proc(args: seq[LispVal]): LispVal {.gcsafe, raises: [LispError].}): LispVal = + LispVal(kind: Func, name: name, evalArgs: true, fn: fn) + +proc newFunc*(fn: proc(args: seq[LispVal]): LispVal {.gcsafe, raises: [LispError].}): LispVal = + LispVal(kind: Func, evalArgs: true, fn: fn) + +proc newLambda*(params: seq[string], body: LispVal, env: Env): LispVal = + LispVal(kind: Lambda, params: params, body: body, env: env) + +proc newMacro*(params: seq[string], body: LispVal, env: Env): LispVal = + LispVal(kind: Macro, params: params, body: body, env: env) + +proc createChild*(env: Env): Env = + result = Env(parent: env) + +proc `[]`*(env: Env, key: string): LispVal = + env.env.withValue(key, value): + return value[] + + if env.parent != nil: + result = env.parent[key] + if result != nil: + return + + if env.onUndefinedSymbol != nil: + result = env.onUndefinedSymbol(env, key) + if result != nil: + return + + return nil + +proc `[]=`*(env: Env, key: string, val: LispVal) = + env.env[key] = val + +proc set*(env: Env, key: string, val: LispVal): bool = + env.env.withValue(key, value): + value[] = val + return true + + if env.parent != nil: + return env.parent.set(key, val) + + return false + +proc toJsonHook*(val: LispVal, opt = initToJsonOptions()): JsonNode {.raises: [].} = + case val.kind + of Nil: newJNull() + of Number: + if val.num.int.float == val.num: + newJInt(val.num.int) + else: + newJFloat(val.num) + of Bool: newJBool(val.bol) + of Symbol: newJString(val.sym) + of String: newJString(val.str) + of List, Array: val.elems.toJson + of Map: + var res = newJObject() + for key, value in val.fields.pairs: + res[key] = value.toJson + res + of Func: newJString(&"") + of Lambda: newJString(&"({val.params}) -> {val.body})") + of Macro: newJString(&"!({val.params}) -> {val.body})") + +proc fromJsonHook*(val: var LispVal, jsonNode: JsonNode, opt = Joptions()) {.raises: [ValueError].} = + case jsonNode.kind + of JNull: + val = newNil() + of JBool: + val = newBool(jsonNode.getBool) + of JInt: + val = newNumber(jsonNode.getInt.float) + of JFloat: + val = newNumber(jsonNode.getFloat) + of JString: + val = newString(jsonNode.getStr) + of JObject: + val = newMap() + for key, value in jsonNode.fields.pairs: + val.fields[key] = value.jsonTo(LispVal) + of JArray: + val = newArray(jsonNode.elems.mapIt(it.jsonTo(LispVal))) + +type + LispEventKind* = enum ## enumeration of all events that may occur when parsing + lispError, ## an error occurred during parsing + lispEof, ## end of file reached + lispString, ## a string literal + lispSymbol, ## a symbol + lispInt, ## an integer literal + lispFloat, ## a float literal + lispTrue, ## the value `true` + lispFalse, ## the value `false` + lispNull, ## the value `null` + lispObjectStart, ## start of an object: the `{` token + lispObjectEnd, ## end of an object: the `}` token + lispArrayStart, ## start of an array: the `[` token + lispArrayEnd ## end of an array: the `]` token + lispListStart, ## start of an array: the `(` token + lispListEnd, ## end of an array: the `)` token + lispListColon, ## : in list + + TokKind* = enum # must be synchronized with TLispEventKind! + tkError, + tkEof, + tkString, + tkSymbol, + tkInt, + tkFloat, + tkTrue, + tkFalse, + tkNull, + tkParenLe, + tkParenRi, + tkCurlyLe, + tkCurlyRi, + tkBracketLe, + tkBracketRi, + tkColon, + tkComma, + tkCommaAt, + tkBacktick, # {()}@*789/?[<>]=+456-!#~|&.0123%; + + ParserState = enum + stateEof, stateStart, stateObject, stateArray, stateList, stateExpectArrayComma, + stateExpectObjectComma, stateExpectColon, stateExpectValue + + LispParserError* = enum ## enumeration that lists all errors that can occur + errNone, ## no error + errInvalidToken, ## invalid token + errStringExpected, ## string expected + errColonExpected, ## `:` expected + errCommaExpected, ## `,` expected + errBracketRiExpected, ## `]` expected + errParenRiExpected, ## `)` expected + errCurlyRiExpected, ## `}` expected + errQuoteExpected, ## `"` or `'` expected + errEOC_Expected, ## `*/` expected + errEofExpected, ## EOF expected + errExprExpected ## expr expected + + LispParser* = object of BaseLexer ## the parser object. + a*: string + tok*: TokKind + kind: LispEventKind + err: LispParserError + state: seq[ParserState] + filename: string + rawStringLiterals: bool + + LispKindError* = object of ValueError ## raised by the `to` macro if the + ## Lisp kind is incorrect. + LispParsingError* = object of ValueError ## is raised for a Lisp error + +const + errorMessages*: array[LispParserError, string] = [ + "no error", + "invalid token", + "string expected", + "':' expected", + "',' expected", + "']' expected", + "')' expected", + "'}' expected", + "'\"' or \"'\" expected", + "'*/' expected", + "EOF expected", + "expression expected" + ] + tokToStr: array[TokKind, string] = [ + "invalid token", + "EOF", + "string literal", + "symbol", + "int literal", + "float literal", + "true", + "false", + "nil", + "(", ")", "{", "}", "[", "]", ":", ",", ",@", "`" + ] + +proc open*(my: var LispParser, input: Stream, filename: string; rawStringLiterals = false) = + ## initializes the parser with an input stream. `Filename` is only used + ## for nice error messages. If `rawStringLiterals` is true, string literals + ## are kept with their surrounding quotes and escape sequences in them are + ## left untouched too. + lexbase.open(my, input) + my.filename = filename + my.state = @[stateStart] + my.kind = lispError + my.a = "" + my.rawStringLiterals = rawStringLiterals + +proc close*(my: var LispParser) {.inline.} = + ## closes the parser `my` and its associated input stream. + lexbase.close(my) + +proc str*(my: LispParser): string {.inline.} = + ## returns the character data for the events: `lispInt`, `lispFloat`, + ## `lispString` + assert(my.kind in {lispInt, lispFloat, lispString, lispSymbol}) + return my.a + +proc getInt*(my: LispParser): BiggestInt {.inline.} = + ## returns the number for the event: `lispInt` + assert(my.kind == lispInt) + return parseBiggestInt(my.a) + +proc getFloat*(my: LispParser): float {.inline.} = + ## returns the number for the event: `lispFloat` + assert(my.kind == lispFloat) + return parseFloat(my.a) + +proc kind*(my: LispParser): LispEventKind {.inline.} = + ## returns the current event type for the Lisp parser + return my.kind + +proc getColumn*(my: LispParser): int {.inline.} = + ## get the current column the parser has arrived at. + result = getColNumber(my, my.bufpos) + +proc getLine*(my: LispParser): int {.inline.} = + ## get the current line the parser has arrived at. + result = my.lineNumber + +proc getFilename*(my: LispParser): string {.inline.} = + ## get the filename of the file that the parser processes. + result = my.filename + +proc errorMsg*(my: LispParser): string = + ## returns a helpful error message for the event `lispError` + assert(my.kind == lispError) + result = "$1($2, $3) Error: $4" % [ + my.filename, $getLine(my), $getColumn(my), errorMessages[my.err]] + +proc errorMsgExpected*(my: LispParser, e: string): string = + ## returns an error message "`e` expected" in the same format as the + ## other error messages + result = "$1($2, $3) Error: $4" % [ + my.filename, $getLine(my), $getColumn(my), e & " expected"] + +proc handleHexChar*(c: char, x: var int): bool {.inline.} = + ## Converts `%xx` hexadecimal to the ordinal number and adds the result to `x`. + ## Returns `true` if `c` is hexadecimal. + ## + ## When `c` is hexadecimal, the proc is equal to `x = x shl 4 + hex2Int(c)`. + result = true + case c + of '0'..'9': x = (x shl 4) or (ord(c) - ord('0')) + of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10) + of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10) + else: + result = false + +proc parseEscapedUTF16*(buf: cstring, pos: var int): int = + result = 0 + #UTF-16 escape is always 4 bytes. + for _ in 0..3: + # if char in '0' .. '9', 'a' .. 'f', 'A' .. 'F' + if handleHexChar(buf[pos], result): + inc(pos) + else: + return -1 + +proc parseSymbol(my: var LispParser): TokKind = + result = tkSymbol + var pos = my.bufpos + while true: + case my.buf[pos] + of ')', '}', ']', ',', ' ', '\n', ':': + break + of '\c': + break + # of '\L': + # break + else: + add(my.a, my.buf[pos]) + inc(pos) + my.bufpos = pos # store back + +proc parseString(my: var LispParser, endd: char): TokKind = + result = tkString + var pos = my.bufpos + 1 + if my.rawStringLiterals: + add(my.a, endd) + while true: + let c = my.buf[pos] + case c + of '\0': + my.err = errQuoteExpected + result = tkError + break + of '\\': + if my.rawStringLiterals: + add(my.a, '\\') + case my.buf[pos+1] + of '\\', '"', '\'', '/': + add(my.a, my.buf[pos+1]) + inc(pos, 2) + of 'b': + add(my.a, '\b') + inc(pos, 2) + of 'f': + add(my.a, '\f') + inc(pos, 2) + of 'n': + add(my.a, '\L') + inc(pos, 2) + of 'r': + add(my.a, '\C') + inc(pos, 2) + of 't': + add(my.a, '\t') + inc(pos, 2) + of 'v': + add(my.a, '\v') + inc(pos, 2) + of 'u': + if my.rawStringLiterals: + add(my.a, 'u') + inc(pos, 2) + var pos2 = pos + var r = parseEscapedUTF16(cstring(my.buf), pos) + if r < 0: + my.err = errInvalidToken + break + # Deal with surrogates + if (r and 0xfc00) == 0xd800: + if my.buf[pos] != '\\' or my.buf[pos+1] != 'u': + my.err = errInvalidToken + break + inc(pos, 2) + var s = parseEscapedUTF16(cstring(my.buf), pos) + if (s and 0xfc00) == 0xdc00 and s > 0: + r = 0x10000 + (((r - 0xd800) shl 10) or (s - 0xdc00)) + else: + my.err = errInvalidToken + break + if my.rawStringLiterals: + let length = pos - pos2 + for i in 1 .. length: + if my.buf[pos2] in {'0'..'9', 'A'..'F', 'a'..'f'}: + add(my.a, my.buf[pos2]) + inc pos2 + else: + break + else: + add(my.a, toUTF8(Rune(r))) + else: + # don't bother with the error + add(my.a, my.buf[pos]) + inc(pos) + of '\c': + pos = lexbase.handleCR(my, pos) + add(my.a, '\c') + of '\L': + pos = lexbase.handleLF(my, pos) + add(my.a, '\L') + else: + if c == endd: + if my.rawStringLiterals: + add(my.a, endd) + inc(pos) + break + add(my.a, my.buf[pos]) + inc(pos) + my.bufpos = pos # store back + +proc skip(my: var LispParser) = + var pos = my.bufpos + while true: + case my.buf[pos] + of ';': + if true or my.buf[pos+1] == '/': + # skip line comment: + inc(pos, 2) + while true: + case my.buf[pos] + of '\0': + break + of '\c': + pos = lexbase.handleCR(my, pos) + break + of '\L': + pos = lexbase.handleLF(my, pos) + break + else: + inc(pos) + elif my.buf[pos+1] == '*': + # skip long comment: + inc(pos, 2) + while true: + case my.buf[pos] + of '\0': + my.err = errEOC_Expected + break + of '\c': + pos = lexbase.handleCR(my, pos) + of '\L': + pos = lexbase.handleLF(my, pos) + of '*': + inc(pos) + if my.buf[pos] == '/': + inc(pos) + break + else: + inc(pos) + else: + break + of ' ', '\t': + inc(pos) + of '\c': + pos = lexbase.handleCR(my, pos) + of '\L': + pos = lexbase.handleLF(my, pos) + else: + break + my.bufpos = pos + +proc parseNumber(my: var LispParser) = + var pos = my.bufpos + if my.buf[pos] == '-': + add(my.a, '-') + inc(pos) + if my.buf[pos] == '.': + add(my.a, "0.") + inc(pos) + else: + while my.buf[pos] in Digits: + add(my.a, my.buf[pos]) + inc(pos) + if my.buf[pos] == '.': + add(my.a, '.') + inc(pos) + # digits after the dot: + while my.buf[pos] in Digits: + add(my.a, my.buf[pos]) + inc(pos) + if my.buf[pos] in {'E', 'e'}: + add(my.a, my.buf[pos]) + inc(pos) + if my.buf[pos] in {'+', '-'}: + add(my.a, my.buf[pos]) + inc(pos) + while my.buf[pos] in Digits: + add(my.a, my.buf[pos]) + inc(pos) + my.bufpos = pos + +proc parseName(my: var LispParser) = + var pos = my.bufpos + if my.buf[pos] in IdentStartChars: + while my.buf[pos] in IdentChars: + add(my.a, my.buf[pos]) + inc(pos) + my.bufpos = pos + +proc getTok(my: var LispParser): TokKind = + setLen(my.a, 0) + skip(my) # skip whitespace, comments + case my.buf[my.bufpos] + of '-': + if my.buf[my.bufpos + 1] != ' ': + parseNumber(my) + if {'.', 'e', 'E'} in my.a: + result = tkFloat + else: + result = tkInt + else: + result = parseSymbol(my) + of '0'..'9': + parseNumber(my) + if {'.', 'e', 'E'} in my.a: + result = tkFloat + else: + result = tkInt + of '"': + result = parseString(my, '"') + of '\'': + result = parseString(my, '\'') + of '[': + inc(my.bufpos) + result = tkBracketLe + of '(': + inc(my.bufpos) + result = tkParenLe + of '{': + inc(my.bufpos) + result = tkCurlyLe + of ']': + inc(my.bufpos) + result = tkBracketRi + of ')': + inc(my.bufpos) + result = tkParenRi + of '}': + inc(my.bufpos) + result = tkCurlyRi + of ',': + inc(my.bufpos) + if my.buf[my.bufpos] == '@': + inc(my.bufpos) + result = tkCommaAt + else: + result = tkComma + of '`': + inc(my.bufpos) + result = tkBacktick + of ':': + inc(my.bufpos) + result = tkColon + of '\0': + result = tkEof + else: + result = parseSymbol(my) + case my.a + of "nil": result = tkNull + of "true": result = tkTrue + of "false": result = tkFalse + + my.tok = result + +proc raiseParseErr(p: LispParser, msg: string) {.noinline, noreturn.} = + ## raises an `ELispParsingError` exception. + raise newException(LispParsingError, errorMsgExpected(p, msg)) + +proc eat(p: var LispParser, tok: TokKind) = + if p.tok == tok: discard getTok(p) + else: raiseParseErr(p, tokToStr[tok]) + +proc parseLisp*(p: var LispParser; depth = 0, depthLimit = 1024): LispVal = + ## Parses JSON from a JSON Parser `p`. + case p.tok + of tkString: + result = newString(move p.a) + discard getTok(p) + of tkSymbol: + result = newSymbol(move p.a) + discard getTok(p) + of tkBacktick: + discard getTok(p) + result = newList(@[newSymbol("quasiquote"), parseLisp(p, depth + 1)]) + of tkColon: + discard getTok(p) + result = newSymbol(":") + of tkComma: + discard getTok(p) + result = newList(@[newSymbol("unquote"), parseLisp(p, depth + 1)]) + of tkCommaAt: + discard getTok(p) + result = newList(@[newSymbol("unquote-splicing"), parseLisp(p, depth + 1)]) + of tkInt: + try: + result = newNumber(parseBiggestInt(p.a).float) + except ValueError: + raiseParseErr(p, "number too big") + discard getTok(p) + of tkFloat: + try: + result = newNumber(parseFloat(p.a)) + except ValueError: + raiseParseErr(p, "number too big") + discard getTok(p) + of tkTrue: + result = newNumber(1) + discard getTok(p) + of tkFalse: + result = newNumber(0) + discard getTok(p) + of tkNull: + result = newNil() + discard getTok(p) + of tkCurlyLe: + if depth > depthLimit: + raiseParseErr(p, "}") + result = newMap() + discard getTok(p) + while p.tok != tkCurlyRi: + if p.tok notin {tkString, tkSymbol}: + raiseParseErr(p, "string literal as key") + var key = p.a + discard getTok(p) + eat(p, tkColon) + var val = parseLisp(p, depth+1) + result.fields[key] = val + if p.tok != tkComma: break + discard getTok(p) + eat(p, tkCurlyRi) + of tkBracketLe: + if depth > depthLimit: + raiseParseErr(p, "]") + result = newArray() + discard getTok(p) + while p.tok != tkBracketRi: + result.elems.add(parseLisp(p, depth+1)) + if p.tok != tkComma: break + discard getTok(p) + eat(p, tkBracketRi) + of tkParenLe: + if depth > depthLimit: + raiseParseErr(p, ")") + result = newList() + discard getTok(p) + while p.tok != tkParenRi: + result.elems.add(parseLisp(p, depth+1)) + if p.tok == tkParenRi: break + eat(p, tkParenRi) + else: + raiseParseErr(p, "{") + +iterator parseLispFragments*(s: Stream, filename: string = ""): LispVal = + var p: LispParser + p.open(s, filename) + try: + discard getTok(p) # read first token + while p.tok != tkEof: + yield p.parseLisp() + finally: + p.close() + +proc parseLispSingle*(str: string): LispVal = + var p: LispParser + p.open(newStringStream(str), "repl") + try: + discard getTok(p) # read first token + while p.tok != tkEof: + result = p.parseLisp() + return + finally: + p.close() + +proc parseLisp*(str: string): LispVal = + var p: LispParser + p.open(newStringStream(str), "repl") + result = newList(@[newSymbol("list")]) + try: + discard getTok(p) # read first token + while p.tok != tkEof: + result.elems.add p.parseLisp() + finally: + p.close() + +proc parseLispValues*(str: string): seq[LispVal] = + var p: LispParser + p.open(newStringStream(str), "repl") + try: + discard getTok(p) # read first token + while p.tok != tkEof: + result.add p.parseLisp() + finally: + p.close() + +proc eval*(expr: LispVal, env: var Env): LispVal {.raises: [LispError].} + +proc evalQuasiquote(expr: LispVal, env: var Env): LispVal = + if expr.kind == List and expr.elems.len > 0: + let head = expr.elems[0] + if head.kind == Symbol: + case head.sym + of "unquote": + return eval(expr.elems[1], env) + of "unquote-splicing": + raise newException(LispError, "unquote-splicing not allowed here") # must be handled inside list + if expr.kind in {List, Array}: + var resultElems: seq[LispVal] = @[] + for el in expr.elems: + if el.kind == List and el.elems.len > 0 and + el.elems[0].kind == Symbol and el.elems[0].sym == "unquote-splicing": + let spliceVal = eval(el.elems[1], env) + if spliceVal.kind != expr.kind: + raise newException(LispError, "unquote-splicing must return a list") + resultElems.add(spliceVal.elems) + else: + resultElems.add(evalQuasiquote(el, env)) + return newList(resultElems) + elif expr.kind == Map: + result = newMap() + for key, el in expr.fields.pairs: + if el.kind == List and el.elems.len > 0 and + el.elems[0].kind == Symbol and el.elems[0].sym == "unquote-splicing": + let spliceVal = eval(el.elems[1], env) + if spliceVal.kind != Map: + raise newException(LispError, "unquote-splicing must return a map") + for key2, val in spliceVal.fields.pairs: + result.fields[key2] = val + else: + result.fields[key] = evalQuasiquote(el, env) + return result + else: + return expr + +proc eval(expr: LispVal, env: var Env): LispVal {.raises: [LispError].} = + + case expr.kind + of Nil, Number, Bool, String, Func, Lambda, Macro: + return expr + of Symbol: + if expr.sym.startsWith("'"): + return newSymbol(expr.sym[1..^1]) + result = env[expr.sym] + if result == nil: + raise newException(LispError, "undefined symbol: '" & expr.sym & "'") + + of Array: + return newArray(expr.elems.mapIt(eval(it, env))) + + of Map: + result = newMap() + for key, value in expr.fields.pairs: + result.fields[key] = eval(value, env) + + of List: + if expr.elems.len == 0: + return expr + + let first = expr.elems[0] + if first == nil: + raise newException(LispError, "Failed to evaluate first element of list: nil") + + if first.kind == Symbol: + case first.sym + of "quote": + return expr.elems[1] + of "let": + let sym = expr.elems[1] + let value = eval(expr.elems[2], env) + env[sym.sym] = value + return value + of "set": + let sym = expr.elems[1] + let value = eval(expr.elems[2], env) + if not env.set(sym.sym, value): + raise newException(LispError, "undefined symbol: '" & expr.sym & "'") + + return value + of "eval": + let sub = expr.elems[1] + let value = eval(sub, env) + return eval(value, env) + of "lambda": + let params = expr.elems[1].elems.mapIt(it.sym) + let body = expr.elems[2] + return newLambda(params, body, env) + of "defmacro": + let name = expr.elems[1].sym + let params = expr.elems[2].elems.mapIt(it.sym) + let body = expr.elems[3] + env[name] = newMacro(params, body, env) + return env[name] + of "quasiquote": + return evalQuasiquote(expr.elems[1], env) + of "if": + let cond = eval(expr.elems[1], env) + if cond.kind != Bool: + raise newException(LispError, "Condition must be a number") + if cond.bol: + return eval(expr.elems[2], env) + else: + return eval(expr.elems[3], env) + of "floor": + let val = eval(expr.elems[1], env) + if val.kind != Number: + raise newException(LispError, "floor takes a number") + return newNumber(val.num.floor) + of "repeat": + let name = expr.elems[1].sym + let count = eval(expr.elems[2], env) + if count.kind != Number: + raise newException(LispError, "Count must be a number") + + var res = newList() + for i in 0.."] = newFunc(">", proc(args: seq[LispVal]): LispVal = + newBool(args[0].num > args[1].num)) + result["<"] = newFunc("<", proc(args: seq[LispVal]): LispVal = + newBool(args[0].num < args[1].num)) + result["list"] = newFunc("list", proc(args: seq[LispVal]): LispVal = + newList(args)) + result["do"] = newFunc("do", proc(args: seq[LispVal]): LispVal = + if args.len > 0: + args.last + else: + newNil()) + result["head"] = newFunc("head", proc(args: seq[LispVal]): LispVal = + if args[0].kind != List: raise newException(LispError, "head needs list") + else: args[0].elems[0]) + result["tail"] = newFunc("tail", proc(args: seq[LispVal]): LispVal = + if args[0].kind != List: raise newException(LispError, "tail needs list") + else: newList(args[0].elems[1..^1])) + result["cons"] = newFunc("cons", proc(args: seq[LispVal]): LispVal = + if args[1].kind != List: raise newException(LispError, "cons needs list") + else: newList(@[args[0]] & args[1].elems)) + + result["add"] = newFunc("add", proc(args: seq[LispVal]): LispVal = + if args[0].kind notin {List, Array}: raise newException(LispError, "add needs list or array") + for i in 1..args.high: + args[0].elems.add args[i] + ) + + # result["append"] = newFunc("append", proc(args: seq[LispVal]): LispVal = + # if args[0].kind notin {List, Array}: raise newException(LispError, "append needs list or array") + # for i in 1..args.high: + # if args[i].kind notin {List, Array}: + # raise newException(LispError, "append needs list or array") + # args[0].elems.add args[i].elems + # ) + + # result["info"] = newFunc("info", proc(args: seq[LispVal]): LispVal = + # var str = "" + # for i, arg in args: + # if i > 0: + # str.add " " + # str.add $arg + # debugf"{str}" + # ) + + # result["echo"] = newFunc("echo", proc(args: seq[LispVal]): LispVal = + # var str = "" + # for i, arg in args: + # if i > 0: + # str.add " " + # str.add $arg + # debugf"lisp: {str}" + # ) + + # result["build-str"] = newFunc("build-str", proc(args: seq[LispVal]): LispVal = + # var str = "" + # for i, arg in args: + # str.add $arg + # return newString(str) + # ) + + # result["join-str"] = newFunc("join-str", proc(args: seq[LispVal]): LispVal = + # if args.len < 1: raise newException(LispError, "join-str needs a seperator") + # if args[0].kind notin {String, Symbol}: raise newException(LispError, "join-str needs a seperator") + # let sep = $args[0] + # var str = "" + # for i in 1..args.high: + # if i > 1: + # str.add sep + # str.add $args[i] + # return newString(str) + # ) + +globalEnv = baseEnv() From 547e4229c4766148da96069a61b31b9fb7805a8a Mon Sep 17 00:00:00 2001 From: Nico Date: Sun, 19 Oct 2025 23:11:23 +0200 Subject: [PATCH 35/47] Remove nimcache cleanup from release workflow Removed redundant command to delete nimcache directory before creating release packages. --- .github/workflows/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 762e29d7d..8dcb94062 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -102,7 +102,6 @@ jobs: #- run: PATH=/github/home/.nimble/bin:$PATH nimble buildLspWs # create release packages - - run: rm -r ./config/nimcache - run: PATH=/github/home/.nimble/bin:$PATH nim ./tools/package_release.nims -l # make zips @@ -181,7 +180,6 @@ jobs: - run: ./rcedit-x64.exe nevc.exe --set-icon ./res/icon.ico # create release packages - - run: rm -r ./config/nimcache - run: nim ./tools/package_release.nims -w # make zips From 6d6a7db2a642f2af48909d5aaca18f0b25118886 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 19 Oct 2025 23:36:50 +0200 Subject: [PATCH 36/47] Remove old plugin dir from release package --- tools/package_release.nims | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/package_release.nims b/tools/package_release.nims index 2f27f25f9..34f3b33c0 100644 --- a/tools/package_release.nims +++ b/tools/package_release.nims @@ -41,7 +41,6 @@ proc copySharedFilesTo(dir: string) = cpDir2 "fonts", dir cpDir2 "languages", dir cpDir2 "themes", dir - cpDir2 "scripting", dir cpDir2 "docs", dir cpDir2 "res", dir mkDir dir / "src" From 11b9bb9f70281ab0d5c0edd953c8dd6e29dcd697 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 26 Oct 2025 17:02:21 +0100 Subject: [PATCH 37/47] Convert some vim motions to new move system --- config/keybindings.json | 73 +++++---- plugin_api/v0/api.nim | 8 + plugin_api/v0/text_editor.nim | 16 -- plugins/vim/vim.nim | 260 +++++++++--------------------- src/lisp.nim | 51 ++++-- src/move_database.nim | 192 +++++++++++++++++----- src/text/text_editor.nim | 295 +++++----------------------------- src/vcs/vcs_api.nim | 6 +- 8 files changed, 354 insertions(+), 547 deletions(-) diff --git a/config/keybindings.json b/config/keybindings.json index 3c7329aa2..1c6069c1e 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -355,32 +355,36 @@ }, "vim#move": { - "": ["vim.move-direction", "column", -1, "<#move.count>"], - "": ["vim.move-direction", "column", 1, "<#move.count>"], - "": ["vim.move-direction", "visual-line-up", 1, "<#move.count>", false, false], - "": ["vim.move-direction", "visual-line-down", 1, "<#move.count>", false, false], - "h": ["vim.move-direction", "column", -1, "<#move.count>"], - "l": ["vim.move-direction", "column", 1, "<#move.count>"], - "k": ["vim.move-direction", "visual-line-up", 1, "<#move.count>", false, false], - "j": ["vim.move-direction", "visual-line-down", 1, "<#move.count>", false, false], - "gm": ["move-cursor-line-center"], - "gM": ["move-cursor-center"], - "n": ["vim.next-search-result", "<#move.count>"], - "N": ["vim.prev-search-result", "<#move.count>"], - "gf": ["vim.goto-next-change", "<#move.count>"], - "gh": ["vim.goto-prev-change", "<#move.count>"], - - "0": ["vim.move-first", "line"], - "": ["vim.move-first", "visual-line"], - "^": ["vim.move-first", "line-no-indent"], - "$": ["vim.move-last", "line", "<#move.count>"], - "": ["vim.move-last", "visual-line", "<#move.count>"], - "g0": ["vim.move-first", "line"], - "g^": ["vim.move-first", "line-no-indent"], - "g$": ["vim.move-last", "line", "<#move.count>"], - "gg": ["vim.move-file-start", "<#move.count>"], - "G": ["vim.move-file-end", "<#move.count>"], - "%": ["vim.move-to-matching-or-file-offset", "<#move.count>"], + "": ["(column (* -1 (or <#move.count> 1)))"], + "": ["(column (or <#move.count> 1))"], + "": ["(visual-line-up (or <#move.count> 1)) (dont-update-target-column)"], + "": ["(visual-line-down (or <#move.count> 1)) (dont-update-target-column)"], + "h": ["(column (* -1 (or <#move.count> 1)))"], + "l": ["(column (or <#move.count> 1))"], + "k": ["(visual-line-up (or <#move.count> 1))"], + "j": ["(visual-line-down (or <#move.count> 1))"], + "gm": ["move-cursor-line-center"], // todo + "gM": ["move-cursor-center"], // todo + "n": ["(next-search-result (or <#move.count> 1)) (start)"], + "N": ["(prev-search-result (or <#move.count> 1)) (start)"], + "gf": ["(next-change (or <#move.count> 1)) (start)"], + "gh": ["(prev-change (or <#move.count> 1)) (start)"], + "gt": ["(next-diagnostic (or <#move.count> 1)) (start)"], + "gn": ["(prev-diagnostic (or <#move.count> 1)) (start)"], + // "gt": ["(next-tab-stop (or <#move.count> 1)) (start)"], + // "gn": ["(prev-tab-stop (or <#move.count> 1)) (start)"], + + "0": ["(line) (start)"], + "": ["(visual-line) (start)"], + "^": ["(line-no-indent) (start)"], + "$": ["(line (or <#move.count> 1)) (end)"], + "": ["(visual-line (or <#move.count> 1)) (end)"], + "g0": ["(line) (start)"], + "g^": ["(line-no-indent) (start)"], + "g$": ["(visual-line (or <#move.count> 1)) (end)"], + "gg": ["(let c <#move.count>) (if (eq c 0) (start (file)) (start (line-no-indent (line-num (- c 1)))))"], + "G": ["(file) (end) (line) (start) (if same? (end (line)))"], + "%": ["(let c <#move.count>) (if (eq c 0) (surround) (start (line-no-indent (line-num (floor (* num-lines (/ c 100)))))))"], "w": ["vim.move-selection-next", "vim.word", false, true, "<#move.count>"], "": ["vim.move-selection-next", "vim.word", false, true, "<#move.count>"], @@ -396,8 +400,10 @@ "gE": ["vim.move-selection-next", "vim.WORD", true, false, "<#move.count>"], "}": ["vim.move-paragraph", false, "<#move.count>"], "{": ["vim.move-paragraph", true, "<#move.count>"], - "f": ["vim.move-to ", false, "<#move.count>"], - "t": ["vim.move-to ", true, "<#move.count>"], + "f": ["(move-to )"], + "t": ["(column) (move-to ) (column -1)"], + // "f": ["vim.move-to ", false, "<#move.count>"], + // "t": ["vim.move-to ", true, "<#move.count>"], "": ["vim.move-cursor-visual-page", 50, "<#move.count>", true], "": ["vim.move-cursor-visual-page", 100, "<#move.count>", true], @@ -491,14 +497,12 @@ "M": ["enter-choose-cursor-mode", "vim.handle-select-word"], // movement - "": ["select-prev"], // todo - "": ["select-next"], // todo + "": ["select-prev"], + "": ["select-next"], "": ["vim.select-last "], "o": ["move", "(reverse)"], "": ["move", "(next-tab-stop)"], "": ["move", "(prev-tab-stop)"], - "gt": ["vim.goto-next-diagnostic"], - "gn": ["vim.goto-prev-diagnostic"], // copy/paste "p": ["vim.paste", true, false, "vim.default-register"], @@ -509,7 +513,6 @@ "": ["vim.delete-right"], "": ["vim.delete-left"], "": ["vim.delete-left"], - "": ["vim.delete-left"], "r": ["set-mode", "vim.replace"], // undo/redo @@ -764,7 +767,7 @@ "": ["move", "(word-line) (end)"], "": ["move", "(line) (start)"], "": ["move", "(line) (end)"], - "": ["move", "(word-line-back) (join . curr-start)"], + "": ["move", "(word-line-back) (join orig-start curr-start)"], "": ["move", "(word-line) (join)"], "": ["move", "(visual-line-up)", false], "": ["move", "(visual-line-down)", false], @@ -957,4 +960,4 @@ "": "toggle-all-breakpoints-enabled", }, -} \ No newline at end of file +} diff --git a/plugin_api/v0/api.nim b/plugin_api/v0/api.nim index 931b2cb3d..8e2cdbb53 100644 --- a/plugin_api/v0/api.nim +++ b/plugin_api/v0/api.nim @@ -38,6 +38,8 @@ proc `==`*(a, b: WitString): bool = return false return a.toOpenArray() == b.toOpenArray() +template ws*(s: WitString): WitString = s + ############################ exported functions ############################ type CommandHandler = proc(data: uint32, args: WitString): WitString {.cdecl.} @@ -221,6 +223,12 @@ when pluginWorld == "plugin": proc addCustomTextMove*(name: string, move: MoveHandler) = defineMove(name.ws, cast[uint32](move), 0) + proc multiMove*(editor: TextEditor, selections: WitList[Selection], move: WitString|string): WitList[Selection] = + editor.multiMove(selections, ws(move), 0, false, true) + + proc multiMove*(editor: TextEditor, selections: openArray[Selection], move: WitString|string): WitList[Selection] = + editor.multiMove(@@selections, ws(move), 0, false, true) + func toSelection*(cursor: Cursor, default: Selection, which: sca.SelectionCursor): Selection = case which of sca.SelectionCursor.Config: return default diff --git a/plugin_api/v0/text_editor.nim b/plugin_api/v0/text_editor.nim index 2adfaff7e..e92b15986 100644 --- a/plugin_api/v0/text_editor.nim +++ b/plugin_api/v0/text_editor.nim @@ -272,22 +272,6 @@ proc clearTabStops*(editor: TextEditor): void {.nodestroy.} = arg0 = editor.id textEditorClearTabStopsImported(arg0) -proc textEditorSelectNextTabStopImported(a0: uint64): void {. - wasmimport("select-next-tab-stop", "nev:plugins/text-editor").} -proc selectNextTabStop*(editor: TextEditor): void {.nodestroy.} = - ## todo - var arg0: uint64 - arg0 = editor.id - textEditorSelectNextTabStopImported(arg0) - -proc textEditorSelectPrevTabStopImported(a0: uint64): void {. - wasmimport("select-prev-tab-stop", "nev:plugins/text-editor").} -proc selectPrevTabStop*(editor: TextEditor): void {.nodestroy.} = - ## todo - var arg0: uint64 - arg0 = editor.id - textEditorSelectPrevTabStopImported(arg0) - proc textEditorUndoImported(a0: uint64; a1: int32; a2: int32): void {. wasmimport("undo", "nev:plugins/text-editor").} proc undo*(editor: TextEditor; checkpoint: WitString): void {.nodestroy.} = diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 3d55e2f56..36163a41a 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -256,17 +256,44 @@ proc visualLineMode(editor: TextEditor) {.exposeActive(editorContext).} = editor.setMode "vim.visual-line" editor.selectLine() -proc selectLast(editor: TextEditor, move: string, count: int = 1) {.exposeActive(editorContext).} = - let (action, arg) = move.parseAction - for i in 0.. '{adjustedMove}'" + for i in 0.. failed to parse key: {target}" - - let key = parseTarget(target) - var s = editor.getSelections - - if before: - for _ in 0.. 0: lineLen.dec @@ -654,8 +646,8 @@ proc deleteLeft*(editor: TextEditor) {.exposeActive(editorContext).} = yankedLines = editor.vimState.selectLines editor.copy(ws"", inclusiveEnd = false) editor.addNextCheckpoint ws"insert" - let selections = editor.multiMove(editor.getSelections, ws"column", -1, wrap = false, includeEol = true) - editor.setSelections editor.edit(selections, @@[ws""], inclusive=editor.vimState.cursorIncludeEol) + let selections = editor.multiMove(editor.getSelections, "(column -1) (join)") + editor.setSelections editor.edit(selections, @@[ws""], inclusive=false) proc deleteRight*(editor: TextEditor) {.exposeActive(editorContext).} = yankedLines = editor.vimState.selectLines @@ -720,8 +712,27 @@ proc moveLast(editor: TextEditor, move: string, count: int = 1, wrap: bool = fal editor.scrollToCursor() editor.updateTargetColumn() +proc move(editor: TextEditor, move: string, direction: int = 1, wrap: bool = false) {.exposeActive(editorContext).} = + var move = move + if not move.startsWith("("): + move = "(" & move & ")" + let cursorSelector = editor.getSetting(editor.getContextWithMode("editor.text.cursor.movement"), sca.SelectionCursor.Both) + if cursorSelector == sca.Last: + move.add " (join)" + editor.setSelections editor.multiMove(editor.selections, move.ws, direction, wrap, includeEol = editor.vimState.cursorIncludeEol) + + if editor.vimState.selectLines: + editor.selectLine() + editor.scrollToCursor() + editor.updateTargetColumn() + proc moveDirection(editor: TextEditor, move: string, direction: int, count: int = 1, wrap: bool = false, updateTargetColumn: bool = true) {.exposeActive(editorContext).} = + var move = move + if not move.startsWith("("): + move = "(" & move & ")" let cursorSelector = editor.getSetting(editor.getContextWithMode("editor.text.cursor.movement"), sca.SelectionCursor.Both) + if cursorSelector == sca.Last: + move.add " (join)" editor.setSelections editor.multiMove(editor.selections, move.ws, direction * max(count, 1), wrap, includeEol = editor.vimState.cursorIncludeEol) if editor.vimState.selectLines: @@ -884,56 +895,6 @@ proc moveFileEnd(editor: TextEditor, count: int = 1) {.exposeActive(editorContex editor.scrollToCursor() editor.setNextSnapBehaviour(MinDistanceOffscreen) -proc moveToMatching(editor: TextEditor) {.exposeActive(editorContext).} = - # todo: pass as parameter - let mode = $editor.mode - let which = if mode == "vim.visual" or mode == "vim.visual-line": - sca.SelectionCursor.Last - else: - sca.SelectionCursor.Both - - let content = editor.content - - editor.setSelections editor.selections.mapIt(block: - let c = content.charAt(it.last) - let (open, close, last) = case c - of '(': ('(', ')', true) - of '{': ('{', '}', true) - of '[': ('[', ']', true) - of '<': ('<', '>', true) - of ')': ('(', ')', false) - of '}': ('{', '}', false) - of ']': ('[', ']', false) - of '>': ('<', '>', false) - of '"': ('"', '"', true) - of '\'': ('\'', '\'', true) - else: return - - let selection = editor.applyMove(it, stackWitString(&"surround \"{open}\" \"{close}\" false"), 0, wrap=false, includeEol=true)[0] - # let selection = editor.vimMotionSurround(it.last, 0, open, close, false) - - if last: - selection.last.toSelection(it, which) - else: - selection.first.toSelection(it, which) - ).stackWitList() - - editor.scrollToCursor() - editor.updateTargetColumn() - -proc moveToMatchingOrFileOffset(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = - if count == 0: - editor.moveToMatching() - else: - debugf"not implemented: moveToMatchingOrFileOffset {count}" - # todo -# let line = clamp((count * editor.lineCount) div 100, 0, editor.lineCount - 1) -# let which = getSetting[sca.SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), sca.SelectionCursor.Both) -# editor.setSelection (line, 0).toSelection(editor.selection, which) -# editor.moveFirst "line-no-indent" -# editor.scrollToCursor() -# editor.setNextSnapBehaviour(MinDistanceOffscreen) - proc scrollLineToTopAndMoveLineStart(editor: TextEditor, count: int = 1) {.exposeActive(editorContext).} = if editor.getCommandCount != 0: editor.setSelection (editor.getCommandCount.int, 0).toCursor.toSelection @@ -1005,72 +966,6 @@ proc setSearchQueryFromSelection(editor: TextEditor) {.exposeActive(editorContex editor.setSelection editor.getSelection.first.toSelection editor.normalMode() -proc nextSearchResult(editor: TextEditor, count: int = 0) {.exposeActive(editorContext).} = - let selections = editor.getSelections - let next = editor.multiMove(selections, ws"next-search-result", count, wrap = true, includeEol=false) - let newSelections = mergeSelections(selections, next): - (it1.first, it2.first).toSelection - - editor.setSelections @@newSelections - editor.scrollToCursor() - editor.setNextSnapBehaviour(MinDistanceOffscreen) - editor.updateTargetColumn() - -proc prevSearchResult(editor: TextEditor, count: int = 0) {.exposeActive(editorContext).} = - let selections = editor.getSelections - let prev = editor.multiMove(selections, ws"prev-search-result", count, wrap = true, includeEol=false) - let newSelections = mergeSelections(selections, prev): - (it1.first, it2.first).toSelection - - editor.setSelections @@newSelections - editor.scrollToCursor() - editor.setNextSnapBehaviour(MinDistanceOffscreen) - editor.updateTargetColumn() - -proc gotoNextChange(editor: TextEditor, count: int = 0) {.exposeActive(editorContext).} = - let selections = editor.getSelections - let next = editor.multiMove(selections, ws"next-change", count, wrap = true, includeEol=false) - let newSelections = mergeSelections(selections, next): - (it1.first, it2.first).toSelection - - editor.setSelections @@newSelections - editor.scrollToCursor() - editor.setNextSnapBehaviour(MinDistanceOffscreen) - editor.updateTargetColumn() - -proc gotoPrevChange(editor: TextEditor, count: int = 0) {.exposeActive(editorContext).} = - let selections = editor.getSelections - let prev = editor.multiMove(selections, ws"prev-change", count, wrap = true, includeEol=false) - let newSelections = mergeSelections(selections, prev): - (it1.first, it2.first).toSelection - - editor.setSelections @@newSelections - editor.scrollToCursor() - editor.setNextSnapBehaviour(MinDistanceOffscreen) - editor.updateTargetColumn() - -proc gotoNextDiagnostic(editor: TextEditor, count: int = 0) {.exposeActive(editorContext).} = - let selections = editor.getSelections - let next = editor.multiMove(selections, ws"next-diagnostic", count, wrap = true, includeEol=false) - let newSelections = mergeSelections(selections, next): - (it1.first, it2.first).toSelection - - editor.setSelections @@newSelections - editor.scrollToCursor() - editor.setNextSnapBehaviour(MinDistanceOffscreen) - editor.updateTargetColumn() - -proc gotoPrevDiagnostic(editor: TextEditor, count: int = 0) {.exposeActive(editorContext).} = - let selections = editor.getSelections - let prev = editor.multiMove(selections, ws"prev-diagnostic", count, wrap = true, includeEol=false) - let newSelections = mergeSelections(selections, prev): - (it1.first, it2.first).toSelection - - editor.setSelections @@newSelections - editor.scrollToCursor() - editor.setNextSnapBehaviour(MinDistanceOffscreen) - editor.updateTargetColumn() - proc openSearchBar(editor: TextEditor) {.exposeActive(editorContext).} = editor.openSearchBar(ws"", scrollToPreview=true, selectResult=true) @@ -1093,11 +988,10 @@ proc exitPopup() {.command.} = proc selectWordOrAddCursor(editor: TextEditor) {.exposeActive(editorContext).} = let selections = editor.selections if selections.len == 1: - var selection = editor.setSearchQueryFromMove(ws"word", 1, prefix=ws(r"\b"), suffix=ws(r"\b")) - selection.last.column -= 1 - editor.setSelection selection + var selection = editor.setSearchQueryFromMove(ws"(word)", 1, prefix=ws(r"\b"), suffix=ws(r"\b")) + editor.setSelection editor.multiMove([selection], ws"(inclusive)").last else: - let next = editor.multiMove(@@[selections.last], ws"next-search-result", 0, wrap = true, includeEol=false).last + let next = editor.multiMove(@@[selections.last], ws"(next-search-result) (inclusive)", 0, wrap = true, includeEol=false).last let newSelections = @selections & next editor.setSelections @@newSelections editor.scrollToCursor() @@ -1109,11 +1003,11 @@ proc selectWordOrAddCursor(editor: TextEditor) {.exposeActive(editorContext).} = proc moveLastSelectionToNextSearchResult(editor: TextEditor) {.exposeActive(editorContext).} = let selections = editor.selections if selections.len == 1: - var selection = editor.setSearchQueryFromMove(ws"word", 1, prefix=ws(r"\b"), suffix=ws(r"\b")) - selection.last.column -= 1 + var selection = editor.setSearchQueryFromMove(ws"(word)", 1, prefix=ws(r"\b"), suffix=ws(r"\b")) + editor.setSelection editor.multiMove([selection], "(inclusive)").last editor.setSelection selection else: - let next = editor.multiMove(@@[selections.last], ws"next-search-result", 0, wrap = true, includeEol=false).last + let next = editor.multiMove(@@[selections.last], ws"(next-search-result) (inclusive)", 0, wrap = true, includeEol=false).last let newSelections = selections.toOpenArray[0..^2] & next editor.setSelections @@newSelections editor.scrollToCursor() @@ -1134,7 +1028,7 @@ proc setSearchQueryOrAddCursor(editor: TextEditor) {.exposeActive(editorContext) if editor.setSearchQuery(selectedText.text, escapeRegex=true, prefix=ws"", suffix=ws""): return - let next = editor.multiMove(@@[selections.last], ws"next-search-result", 0, wrap = true, includeEol=false).last + let next = editor.multiMove(@@[selections.last], ws"(next-search-result) (inclusive)", 0, wrap = true, includeEol=false).last let newSelections = @selections & next editor.setSelections @@newSelections editor.scrollToCursor() @@ -1489,4 +1383,4 @@ proc modeChangedHandler(editor: TextEditor, oldModes: seq[string], newModes: seq # let beforeSaveAppStateHandle = addCallback proc(args: JsonNode): JsonNode = # vimSaveState() -# scriptSetCallback("before-save-app-state", beforeSaveAppStateHandle) +# scriptSetCallback("before-save-app-state", beforeSaveAppStateHandle) \ No newline at end of file diff --git a/src/lisp.nim b/src/lisp.nim index 2ed067870..f30e66ef0 100644 --- a/src/lisp.nim +++ b/src/lisp.nim @@ -669,10 +669,10 @@ proc parseLisp*(p: var LispParser; depth = 0, depthLimit = 1024): LispVal = raiseParseErr(p, "number too big") discard getTok(p) of tkTrue: - result = newNumber(1) + result = newBool(true) discard getTok(p) of tkFalse: - result = newNumber(0) + result = newBool(false) discard getTok(p) of tkNull: result = newNil() @@ -757,6 +757,20 @@ proc parseLispValues*(str: string): seq[LispVal] = finally: p.close() +proc isTruthy*(val: LispVal): bool = + case val.kind + of Nil: false + of Number: val.num != 0 + of Bool: val.bol + of Symbol: true + of String: true + of List: true + of Array: true + of Map: true + of Func: true + of Lambda: true + of Macro: true + proc eval*(expr: LispVal, env: var Env): LispVal {.raises: [LispError].} proc evalQuasiquote(expr: LispVal, env: var Env): LispVal = @@ -835,9 +849,12 @@ proc eval(expr: LispVal, env: var Env): LispVal {.raises: [LispError].} = return value of "set": let sym = expr.elems[1] + if sym.kind != Symbol: + raise newException(LispError, "not a symbol: '" & $sym & "'") + let value = eval(expr.elems[2], env) if not env.set(sym.sym, value): - raise newException(LispError, "undefined symbol: '" & expr.sym & "'") + raise newException(LispError, "undefined symbol: '" & sym.sym & "'") return value of "eval": @@ -857,23 +874,25 @@ proc eval(expr: LispVal, env: var Env): LispVal {.raises: [LispError].} = of "quasiquote": return evalQuasiquote(expr.elems[1], env) of "if": - let cond = eval(expr.elems[1], env) - if cond.kind != Bool: - raise newException(LispError, "Condition must be a number") - if cond.bol: - return eval(expr.elems[2], env) - else: - return eval(expr.elems[3], env) + var i = 1 + while i + 1 < expr.elems.len: + let cond = eval(expr.elems[i], env) + if cond.isTruthy: + return eval(expr.elems[i + 1], env) + i = i + 2 + if i < expr.elems.len: + return eval(expr.elems[i], env) + return newNil() of "floor": let val = eval(expr.elems[1], env) if val.kind != Number: - raise newException(LispError, "floor takes a number") + raise newException(LispError, "floor takes a number, got " & $val) return newNumber(val.num.floor) of "repeat": let name = expr.elems[1].sym let count = eval(expr.elems[2], env) if count.kind != Number: - raise newException(LispError, "Count must be a number") + raise newException(LispError, "Count must be a number, got " & $count) var res = newList() for i in 0.. 0: + return args.last + return newNil() + ) result["eq"] = newFunc("eq", proc(args: seq[LispVal]): LispVal = newBool(args[0].kind == args[1].kind and $args[0] == $args[1])) # todo: don't use $ result[">"] = newFunc(">", proc(args: seq[LispVal]): LispVal = diff --git a/src/move_database.nim b/src/move_database.nim index 58b239f32..d0ad6924d 100644 --- a/src/move_database.nim +++ b/src/move_database.nim @@ -16,6 +16,7 @@ type MoveDatabase* = ref object of Service moves: Table[string, MoveImpl] env: Env + debugMoves: bool func serviceName*(_: typedesc[MoveDatabase]): string = "MoveDatabase" @@ -25,6 +26,9 @@ method init*(self: MoveDatabase): Future[Result[void, ref CatchableError]] {.asy self.env = baseEnv() return ok() +proc toggleDebugMoves*(self: MoveDatabase) = + self.debugMoves = not self.debugMoves + proc vimMotionWord*(text: Rope, cursor: Cursor, inclusive: bool): Selection = const AlphaNumeric = {'A'..'Z', 'a'..'z', '0'..'9', '_'} @@ -153,6 +157,90 @@ proc moveCursorColumn(text: Rope, cursor: Cursor, offset: int, wrap: bool = true return text.clampCursor(cursor, includeEol) +proc findSurroundStart*(rope: Rope, cursor: Cursor, count: int, c0: char, c1: char, depth: int = 1): Option[Cursor] = + var depth = depth + var res = cursor + + # todo: use RopeCursor + while res.line >= 0: + let line = rope.getLine(res.line) + res.column = min(res.column, line.len - 1) + while line.len > 0 and res.column >= 0: + let c = line.charAt(res.column) + # debugf"findSurroundStart: {res} -> {depth}, '{c}'" + if c == c1 and (depth < 1 or c0 != c1): + inc depth + if depth == 0: + return res.some + elif c == c0: + dec depth + if depth == 0: + return res.some + dec res.column + + if res.line == 0: + return Cursor.none + + res = (res.line - 1, rope.lineLen(res.line - 1) - 1) + + return Cursor.none + +proc findSurroundEnd*(rope: Rope, cursor: Cursor, count: int, c0: char, c1: char, depth: int = 1): Option[Cursor] = + let lineCount = rope.lines + var depth = depth + var res = cursor + + # todo: use RopeCursor + while res.line < lineCount: + let line = rope.getLine(res.line) + res.column = min(res.column, line.len - 1) + while line.len > 0 and res.column < line.len: + let c = line.charAt(res.column) + # echo &"findSurroundEnd: {res} -> {depth}, '{c}'" + if c == c0 and (depth < 1 or c0 != c1): + inc depth + if depth == 0: + return res.some + elif c == c1: + dec depth + if depth == 0: + return res.some + inc res.column + + if res.line == lineCount - 1: + return Cursor.none + + res = (res.line + 1, 0) + + return Cursor.none + +proc getSurrounding*(rope: Rope, selection: Selection, count: int, c0: char, c1: char, inside: bool): Selection = + result = selection + while true: + let lastChar = rope.charAt(result.last.toPoint) + let (startDepth, endDepth) = if lastChar == c0: + (1, 0) + elif lastChar == c1: + (0, 1) + else: + (1, 1) + + if rope.findSurroundStart(result.first, count, c0, c1, startDepth).getSome(opening) and rope.findSurroundEnd(result.last, count, c0, c1, endDepth).getSome(closing): + result = (opening, closing) + if inside: + result.first = rope.moveCursorColumn(result.first, 1) + result.last = rope.moveCursorColumn(result.last, -1) + return + + if rope.findSurroundEnd(result.first, count, c0, c1, -1).getSome(opening) and rope.findSurroundEnd(opening, count, c0, c1, 0).getSome(closing): + result = (opening, closing) + if inside: + result.first = rope.moveCursorColumn(result.first, 1) + result.last = rope.moveCursorColumn(result.last, -1) + return + else: + return + proc moveCursorVisualLine(displayMap: DisplayMap, cursor: Cursor, offset: int, wrap: bool = false, includeAfter: bool = false, targetColumn: int): Cursor = let rope {.cursor.} = displayMap.buffer.visibleText let wrapPointOld = displayMap.toWrapPoint(cursor.toPoint) @@ -198,6 +286,8 @@ proc moveCursorLine(text: Rope, displayMap: DisplayMap, cursor: Cursor, offset: type MoveFunction* = proc(move: string, selections: openArray[Selection], count: int): seq[Selection] {.gcsafe, raises: [].} proc applyMoveImpl(self: MoveDatabase, displayMap: DisplayMap, move: string, selections: openArray[Selection], fallback: MoveFunction, args: openArray[LispVal], env: Env): seq[Selection] = + if self.debugMoves: + debugf"applyMoveImpl '{move}' {args}, {env.env}" template getEnv(name: string, typ: untyped, default: untyped): untyped = block: @@ -300,6 +390,9 @@ proc applyMoveImpl(self: MoveDatabase, displayMap: DisplayMap, move: string, sel of "reverse": return selections.mapIt(it.reverse) + of "norm": + return selections.mapIt(it.normalized) + of "word-line": return selections.mapIt: let cursor = it.last @@ -322,28 +415,6 @@ proc applyMoveImpl(self: MoveDatabase, displayMap: DisplayMap, move: string, sel res.last = (cursor.line + 1, 0) res - # of "number": - # var r = cursor.toPoint...cursor.toPoint - # var c = self.document.rope.cursorT(cursor.toPoint) - # while c.currentChar in {'0'..'9'}: - # c.seekNextRune() - # r.b = c.position - - # if r.a.column > 0: - # c = self.document.rope.cursorT(cursor.toPoint) - # while c.position.column > 0: - # c.seekPrevRune() - # if c.currentChar == '-': - # r.a = c.position - # break - - # if c.currentChar notin {'0'..'9'}: - # c.seekNextRune() - # break - # r.a = c.position - - # return r.toSelection - # of "line-back": # let first = if cursor.line > 0 and cursor.column == 0: # (cursor.line - 1, self.document.lineLength(cursor.line - 1)) @@ -351,6 +422,9 @@ proc applyMoveImpl(self: MoveDatabase, displayMap: DisplayMap, move: string, sel # (cursor.line, 0) # result = (first, (cursor.line, self.document.lineLength(cursor.line))) + of "line-num": + return @[(getArg(0, int, 0), 0).toSelection] + of "line": return selections.mapIt(block: let lineLen = rope.lineLen(it.last.line) @@ -436,6 +510,9 @@ proc applyMoveImpl(self: MoveDatabase, displayMap: DisplayMap, move: string, sel let dir = getArg(0, int, 1) return selections.mapIt(rope.moveCursorColumn(it.last, count * dir, wrap, includeEol).toSelection) + of "inclusive": + return selections.mapIt(if it.isEmpty: it else: (it.first, rope.moveCursorColumn(it.last, -1, false, true))) + of "line-up", "line-down": let direction = if move == "line-down": 1 else: -1 # return selections.mapIt((it.last, moveCursorLine(rope, displayMap, it.last, direction * count, targetColumn, false, includeEol))) @@ -472,24 +549,38 @@ proc applyMoveImpl(self: MoveDatabase, displayMap: DisplayMap, move: string, sel r.toSelection - # of "\"": - # result = self.getSelectionInPair(cursor, '"') - - # of "'": - # result = self.getSelectionInPair(cursor, '\'') - - # of "(", ")": - # result = self.getSelectionInPairNested(cursor, '(', ')') - - # of "{", "}": - # result = self.getSelectionInPairNested(cursor, '{', '}') - - # of "[", "]": - # result = self.getSelectionInPairNested(cursor, '[', ']') - of "target-column": return selections.mapIt(rope.clampCursor((it.last.line, targetColumn)).toSelection) + of "surround": + var c0 = getArg(0, string, "") + var c1 = getArg(1, string, "") + let inside = getArg(2, bool, false) + if c0.len > 0 and c1.len > 0: + result = selections.mapIt(rope.getSurrounding(it, count, c0[0], c1[0], inside)) + elif c0.len > 0: + result = selections.mapIt(rope.getSurrounding(it, count, c0[0], c0[0], inside)) + else: + result = selections.mapIt: + let c = rope.charAt(it.last.toPoint) + let (open, close, isOpen) = case c + of '(': ('(', ')', true) + of '{': ('{', '}', true) + of '[': ('[', ']', true) + of '<': ('<', '>', true) + of ')': ('(', ')', false) + of '}': ('{', '}', false) + of ']': ('[', ']', false) + of '>': ('<', '>', false) + of '"': ('"', '"', true) + of '\'': ('\'', '\'', true) + else: return + let selection = rope.getSurrounding(it, count, open, close, inside) + if isOpen: + selection + else: + selection.reverse + of "move-to": let c = getArg(0, string, "") if c.len > 0: @@ -506,13 +597,14 @@ proc applyMoveImpl(self: MoveDatabase, displayMap: DisplayMap, move: string, sel proc applyMoveLisp(self: MoveDatabase, displayMap: DisplayMap, move: string, originalSelections: openArray[Selection], env: Env, fallback: MoveFunction): seq[Selection] = try: var env = env - # debugf"applyMoveLisp '{move}', {env.env}" + if self.debugMoves: + log lvlDebug, &"applyMoveLisp '{move}', {env.env}, {originalSelections}" + let originalSelections = @originalSelections var lastSelections = originalSelections var selections = originalSelections var expr = move.parseLisp() - # debugf"lisp: {expr}" type Selector = enum OriginalStart, OriginalEnd, LastStart, LastEnd, CurrentStart, CurrentEnd proc parseSelector(val: LispVal, default: Selector): Selector = @@ -549,15 +641,35 @@ proc applyMoveLisp(self: MoveDatabase, displayMap: DisplayMap, move: string, ori var baseEnv = self.env.createChild() env.parent = baseEnv + var stack = newSeq[seq[Selection]]() baseEnv.onUndefinedSymbol = proc(_: Env, name: string): LispVal = template impl(body: untyped): untyped = newFunc(name, proc(args {.inject.}: seq[LispVal]): LispVal = lastSelections = selections body + if self.debugMoves: + log lvlDebug, "move '", name, "' ", $lastSelections, " -> ", selections ) case name + of "num-lines": + newNumber(displayMap.buffer.visibleText.lines) + of "num-bytes": + newNumber(displayMap.buffer.visibleText.bytes) + of "same?": + newBool(selections == originalSelections) + of "original": + impl: + selections = originalSelections + of "push": + newFunc(name, proc(args {.inject.}: seq[LispVal]): LispVal = + stack.add selections + ) + of "pop": + impl: + if stack.len > 0: + selections = stack.pop() of "start", "first": impl: selections = selections.mapIt(it.first.toSelection) @@ -575,8 +687,6 @@ proc applyMoveLisp(self: MoveDatabase, displayMap: DisplayMap, move: string, ori else: CurrentEnd - # debugf"join {startSelector}, {endSelector}, {originalSelections}, {lastSelections}, {selections}" - if selections.len > 0: for i in 0..= 0: - let line = editor.document.getLine(res.line) - res.column = min(res.column, line.len - 1) - while line.len > 0 and res.column >= 0: - let c = line.charAt(res.column) - # debugf"findSurroundStart: {res} -> {depth}, '{c}'" - if c == c1 and (depth < 1 or c0 != c1): - inc depth - if depth == 0: - return res.some - elif c == c0: - dec depth - if depth == 0: - return res.some - dec res.column - - if res.line == 0: - return Cursor.none - - res = (res.line - 1, editor.lineLength(res.line - 1) - 1) - - return Cursor.none - -proc findSurroundEnd*(editor: TextDocumentEditor, cursor: Cursor, count: int, c0: char, c1: char, - depth: int = 1): Option[Cursor] = - let lineCount = editor.lineCount - var depth = depth - var res = cursor - - # todo: use RopeCursor - while res.line < lineCount: - let line = editor.document.getLine(res.line) - res.column = min(res.column, line.len - 1) - while line.len > 0 and res.column < line.len: - let c = line.charAt(res.column) - # echo &"findSurroundEnd: {res} -> {depth}, '{c}'" - if c == c0 and (depth < 1 or c0 != c1): - inc depth - if depth == 0: - return res.some - elif c == c1: - dec depth - if depth == 0: - return res.some - inc res.column - - if res.line == lineCount - 1: - return Cursor.none - - res = (res.line + 1, 0) - - return Cursor.none - proc toggleFlag*(self: TextDocumentEditor, key: string) {.expose("editor.text").} = try: let value = self.config.get(key, false) @@ -1925,7 +1866,7 @@ proc insertText*(self: TextDocumentEditor, text: string, autoIndent: bool = true texts.setLen(0) for s in selections.mitems: - let openLocation = self.findSurroundStart((s.first.line, s.first.column - 1), + let openLocation = self.document.rope.findSurroundStart((s.first.line, s.first.column - 1), 0, open, close, 1).getOr: texts.add text continue @@ -2350,7 +2291,7 @@ proc getNextChange*(self: TextDocumentEditor, cursor: Cursor): Selection = for mapping in self.diffChanges.get: if mapping.target.first > cursor.line: - return (mapping.target.first, 0).toSelection + return ((mapping.target.first, 0), (mapping.target.last, 0)) return cursor.toSelection @@ -2795,47 +2736,12 @@ proc runAction*(self: TextDocumentEditor, action: string, args: JsonNode): Optio proc findWordBoundary*(self: TextDocumentEditor, cursor: Cursor): Selection = self.document.findWordBoundary(cursor) -proc getSelectionInPair*(self: TextDocumentEditor, cursor: Cursor, delimiter: char): Selection = - result = cursor.toSelection - # todo - -proc getSelectionInPairNested*(self: TextDocumentEditor, cursor: Cursor, open: char, close: char): Selection = - result = cursor.toSelection - # todo - proc extendSelectionWithMove*(self: TextDocumentEditor, selection: Selection, move: string, count: int = 0): Selection = result = self.getSelectionForMove(selection.first, move, count) or self.getSelectionForMove(selection.last, move, count) if selection.isBackwards: result = result.reverse -proc getSurrounding*(self: TextDocumentEditor, selection: Selection, count: int, c0: char, c1: char, inside: bool): Selection = - result = selection - while true: - let lastChar = self.getChar(result.last) - let (startDepth, endDepth) = if lastChar == c0: - (1, 0) - elif lastChar == c1: - (0, 1) - else: - (1, 1) - - if self.findSurroundStart(result.first, count, c0, c1, startDepth).getSome(opening) and self.findSurroundEnd(result.last, count, c0, c1, endDepth).getSome(closing): - result = (opening, closing) - if inside: - result.first = self.doMoveCursorColumn(result.first, 1) - result.last = self.doMoveCursorColumn(result.last, -1) - return - - if self.findSurroundEnd(result.first, count, c0, c1, -1).getSome(opening) and self.findSurroundEnd(opening, count, c0, c1, 0).getSome(closing): - result = (opening, closing) - if inside: - result.first = self.doMoveCursorColumn(result.first, 1) - result.last = self.doMoveCursorColumn(result.last, -1) - return - else: - return - proc applyMoveFallback(self: TextDocumentEditor, move: string, selections: openArray[Selection], count: int): seq[Selection] = debugf"applyMoveFallback {move}" let includeEol = true @@ -2880,12 +2786,8 @@ proc applyMoveFallback(self: TextDocumentEditor, move: string, selections: openA default case move - of "surround": - let c0 = getArg(0, string, "(") - let c1 = getArg(1, string, ")") - let inside = getArg(2, bool, true) - if c0.len > 0 and c1.len > 0: - result = selections.mapIt(self.getSurrounding(it, count, c0[0], c1[0], inside)) + of "word": + return selections.mapIt(self.findWordBoundary(it.last)) of "page": let linesToMove = int(self.screenLineCount() * count div 100) @@ -2988,165 +2890,41 @@ proc getSelectionsForMove*(self: TextDocumentEditor, selections: openArray[Selec proc getSelectionForMove*(self: TextDocumentEditor, cursor: Cursor, move: string, count: int = 0, includeEol: bool = true): Selection = - defer: - debugf"getSelectionForMove '{move}', {cursor} -> {result}" - case move - of "word": - result = self.findWordBoundary(cursor) - for _ in 1.. 0: - c = self.document.rope.cursorT(cursor.toPoint) - while c.position.column > 0: - c.seekPrevRune() - if c.currentChar == '-': - r.a = c.position - break - - if c.currentChar notin {'0'..'9'}: - c.seekNextRune() - break - r.a = c.position - - return r.toSelection - - of "line-back": - let first = if cursor.line > 0 and cursor.column == 0: - (cursor.line - 1, self.document.lineLength(cursor.line - 1)) - else: - (cursor.line, 0) - result = (first, (cursor.line, self.document.lineLength(cursor.line))) - - of "line": - let lineLen = self.document.lineLength(cursor.line) - result = ((cursor.line, 0), (cursor.line, lineLen)) - if not includeEol and result.last.column == lineLen: - result.last = self.doMoveCursorColumn(result.last, -1, wrap = false) - - of "visual-line": - let lineLen = self.document.lineLength(cursor.line) - let wrapPoint = self.displayMap.toWrapPoint(cursor.toPoint) - let displayLineStart = wrapPoint(wrapPoint.row) - let displayLineEnd = wrapPoint(wrapPoint.row + 1) - result[0] = self.displayMap.toPoint(displayLineStart, Right).toCursor - result[1] = self.displayMap.toPoint(displayLineEnd, Right).toCursor - if result[1].column == 0: - result[1].line -= 1 - result[1].column = self.document.lineLength(result[1].line) - - if not includeEol: - if result.last.column == lineLen: - result.last = self.doMoveCursorColumn(result.last, -1, wrap = false) - elif result.last.column < self.document.lineLength(cursor.line): # This is the case if we're not in the last visual sub line - result.last = self.doMoveCursorColumn(result.last, -1, wrap = false) - - of "line-next": - result = ((cursor.line, 0), (cursor.line, self.document.lineLength(cursor.line))) - if result.last.line + 1 < self.document.numLines: - result.last = (result.last.line + 1, 0) - for _ in 1.. 0: - result.first = (result.first.line - 1, self.document.lineLength(result.first.line - 1)) - for _ in 1.. 0: - result.first = (result.first.line - 1, self.document.lineLength(result.first.line - 1)) - - of "line-no-indent": - let indent = self.document.rope.indentBytes(cursor.line) - result = ((cursor.line, indent), (cursor.line, self.document.lineLength(cursor.line))) - - of "file": - result.first = (0, 0) - let line = self.document.numLines - 1 - result.last = (line, self.document.lineLength(line)) - - of "column": - result = self.doMoveCursorColumn(cursor, count, includeAfter = includeEol).toSelection - - of "prev-find-result": - result = self.getPrevFindResult(cursor, count) - - of "next-find-result": - result = self.getNextFindResult(cursor, count) - - of "\"": - result = self.getSelectionInPair(cursor, '"') - - of "'": - result = self.getSelectionInPair(cursor, '\'') - - of "(", ")": - result = self.getSelectionInPairNested(cursor, '(', ')') - - of "{", "}": - result = self.getSelectionInPairNested(cursor, '{', '}') + let selections = self.getSelectionsForMove([cursor.toSelection], move, count, includeEol) + if selections.len > 0: + return selections[0] + else: + return cursor.toSelection + # defer: + # debugf"getSelectionForMove '{move}', {cursor} -> {result}" + # case move - of "[", "]": - result = self.getSelectionInPairNested(cursor, '[', ']') + # of "line-back": + # let first = if cursor.line > 0 and cursor.column == 0: + # (cursor.line - 1, self.document.lineLength(cursor.line - 1)) + # else: + # (cursor.line, 0) + # result = (first, (cursor.line, self.document.lineLength(cursor.line))) - else: - if move.startsWith("move-to "): - # todo: use RopeCursor - let str = move[8..^1] - let line = self.document.getLine cursor.line - result = cursor.toSelection - let index = line.suffix(cursor.column).find(str) - if index >= 0: - result.last = (cursor.line, index + 1 + cursor.column) - for _ in 1..= 0: - result.last = (result.last.line, index + 1 + result.last.column) - - elif move.startsWith("move-before "): - # todo: use RopeCursor - let str = move[12..^1] - let line = self.document.getLine cursor.line - result = cursor.toSelection - let index = line.suffix(cursor.column + 1).find(str) - if index >= 0: - result.last = (cursor.line, index + cursor.column + 1) - for _ in 1..= 0: - result.last = (result.last.line, index + result.last.column + 1) - else: - result = cursor.toSelection + # else: + # result = cursor.toSelection - let cursorJson = self.plugins.invokeAnyCallback("editor.text.custom-move", %*{ - "editor": self.id.EditorId, - "move": move, - "cursor": cursor.toJson, - "count": count, - }) + # let cursorJson = self.plugins.invokeAnyCallback("editor.text.custom-move", %*{ + # "editor": self.id.EditorId, + # "move": move, + # "cursor": cursor.toJson, + # "count": count, + # }) - if cursorJson.isNil: - log(lvlError, fmt"editor.text.custom-move returned nil") - return result + # if cursorJson.isNil: + # log(lvlError, fmt"editor.text.custom-move returned nil") + # return result - result = cursorJson.jsonTo(Selection).catch: - log(lvlError, fmt"Failed to parse selection from custom move '{move}': {cursorJson}") - return cursor.toSelection + # result = cursorJson.jsonTo(Selection).catch: + # log(lvlError, fmt"Failed to parse selection from custom move '{move}': {cursorJson}") + # return cursor.toSelection - return result + # return result proc mapAllOrLast[T](self: openArray[T], all: bool, p: proc(v: T): T {.gcsafe, raises: [].}): seq[T] = if all: @@ -3207,6 +2985,7 @@ proc getSearchQuery*(self: TextDocumentEditor): string = return self.searchQuery proc setSearchQuery*(self: TextDocumentEditor, query: string, escapeRegex: bool = false, prefix: string = "", suffix: string = ""): bool {.expose("editor.text").} = + debugf"setSearchQuery '{query}'" let query = if escapeRegex: query.escapeRegex @@ -3265,13 +3044,15 @@ proc openSearchBar*(self: TextDocumentEditor, query: string = "", scrollToPrevie if scrollToPreview: self.scrollToCursor(s.last) -proc setSearchQueryFromMove*(self: TextDocumentEditor, move: string, - count: int = 0, prefix: string = "", suffix: string = ""): Selection = +proc setSearchQueryFromMove*(self: TextDocumentEditor, move: string, count: int = 0, prefix: string = "", suffix: string = ""): Selection = let selection = self.getSelectionForMove(self.selection.last, move, count) let searchText = self.document.contentString(selection) discard self.setSearchQuery(searchText, escapeRegex=true, prefix, suffix) return selection +proc toggleDebugMoves*(self: TextDocumentEditor) {.expose("editor.text").} = + self.moveDatabase.toggleDebugMoves() + proc toggleLineComment*(self: TextDocumentEditor) {.expose("editor.text").} = self.selections = self.document.toggleLineComment(self.selections) diff --git a/src/vcs/vcs_api.nim b/src/vcs/vcs_api.nim index fec00f6d2..da2c2e8b4 100644 --- a/src/vcs/vcs_api.nim +++ b/src/vcs/vcs_api.nim @@ -8,7 +8,7 @@ import finder/[finder, previewer, file_previewer] import platform/[platform] import service, dispatch_tables, platform_service import selector_popup, vcs, layout, vfs, config_provider -from scripting_api import SelectionCursor, ScrollSnapBehaviour +from scripting_api import SelectionCursor, ScrollSnapBehaviour, toSelection logCategory "vcs_api" @@ -230,7 +230,7 @@ proc chooseGitActiveFiles*(self: VCSService, all: bool = false) {.expose("vcs"). popup.addCustomCommand "prev-change", proc(popup: SelectorPopup, args: JsonNode): bool = if popup.textEditor.isNil: return false - popup.previewEditor.selection = popup.previewEditor.getPrevChange(popup.previewEditor.selection.last) + popup.previewEditor.selection = popup.previewEditor.getPrevChange(popup.previewEditor.selection.first).last.toSelection popup.previewEditor.scrollToCursor(SelectionCursor.Last) popup.previewEditor.centerCursor() popup.previewEditor.setNextSnapBehaviour(ScrollSnapBehaviour.MinDistanceOffscreen) @@ -239,7 +239,7 @@ proc chooseGitActiveFiles*(self: VCSService, all: bool = false) {.expose("vcs"). popup.addCustomCommand "next-change", proc(popup: SelectorPopup, args: JsonNode): bool = if popup.textEditor.isNil: return false - popup.previewEditor.selection = popup.previewEditor.getNextChange(popup.previewEditor.selection.last) + popup.previewEditor.selection = popup.previewEditor.getNextChange(popup.previewEditor.selection.first).last.toSelection popup.previewEditor.scrollToCursor(SelectionCursor.Last) popup.previewEditor.centerCursor() popup.previewEditor.setNextSnapBehaviour(ScrollSnapBehaviour.MinDistanceOffscreen) From 9718515837d73a5c713199684216570013f1162c Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 26 Oct 2025 18:36:47 +0100 Subject: [PATCH 38/47] Convert vim text objects to new move system --- config/keybindings.json | 56 ++++++++++++++++++++--------------------- plugins/vim/vim.nim | 16 ++++++------ src/move_database.nim | 26 +++++++++++++++++++ 3 files changed, 61 insertions(+), 37 deletions(-) diff --git a/config/keybindings.json b/config/keybindings.json index 1c6069c1e..77ad137b8 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -318,33 +318,33 @@ }, "vim#text_object": { - "iw": ["vim.select-text-object", "vim.word-inner", "<#text_object.count>"], - "aw": ["vim.select-text-object", "vim.word-inner", "<#text_object.count>"], - "iW": ["vim.select-text-object", "vim.WORD-inner", "<#text_object.count>"], - "aW": ["vim.select-text-object", "vim.WORD-inner", "<#text_object.count>"], - "ip": ["vim.select-text-object", "vim.paragraph-inner", "<#text_object.count>"], - "ap": ["vim.select-text-object", "vim.paragraph-outer", "<#text_object.count>"], - - "i{": ["vim.select-surrounding", "surround \"{\" \"}\" true", "<#text_object.count>"], - "a{": ["vim.select-surrounding", "surround \"{\" \"}\" false", "<#text_object.count>"], - "i}": ["vim.select-surrounding", "surround \"{\" \"}\" true", "<#text_object.count>"], - "a}": ["vim.select-surrounding", "surround \"{\" \"}\" false", "<#text_object.count>"], - "i(": ["vim.select-surrounding", "surround \"(\" \")\" true", "<#text_object.count>"], - "a(": ["vim.select-surrounding", "surround \"(\" \")\" false", "<#text_object.count>"], - "i)": ["vim.select-surrounding", "surround \"(\" \")\" true", "<#text_object.count>"], - "a)": ["vim.select-surrounding", "surround \"(\" \")\" false", "<#text_object.count>"], - "i[": ["vim.select-surrounding", "surround \"[\" \"]\" true", "<#text_object.count>"], - "a[": ["vim.select-surrounding", "surround \"[\" \"]\" false", "<#text_object.count>"], - "i]": ["vim.select-surrounding", "surround \"[\" \"]\" true", "<#text_object.count>"], - "a]": ["vim.select-surrounding", "surround \"[\" \"]\" false", "<#text_object.count>"], - "i\\<": ["vim.select-surrounding", "surround \"<\" \">\" true", "<#text_object.count>"], - "a\\<": ["vim.select-surrounding", "surround \"<\" \">\" false", "<#text_object.count>"], - "i\\>": ["vim.select-surrounding", "surround \"<\" \">\" true", "<#text_object.count>"], - "a\\>": ["vim.select-surrounding", "surround \"<\" \">\" false", "<#text_object.count>"], - "i\"": ["vim.select-surrounding", "surround \"\\\"\" \"\\\"\" true", "<#text_object.count>"], - "a\"": ["vim.select-surrounding", "surround \"\\\"\" \"\\\"\" false", "<#text_object.count>"], - "i'": ["vim.select-surrounding", "surround \"'\" \"'\" true", "<#text_object.count>"], - "a'": ["vim.select-surrounding", "surround \"'\" \"'\" false", "<#text_object.count>"], + "iw": ["(vim.word-inner)"], + "aw": ["(vim.word-inner)"], + "iW": ["(vim.WORD-inner)"], + "aW": ["(vim.WORD-inner)"], + "ip": ["(vim.paragraph-inner) (inclusive)"], + "ap": ["(vim.paragraph-outer) (inclusive)"], + + "i{": ["(surround \"{\" \"}\" true)"], + "a{": ["(surround \"{\" \"}\" false)"], + "i}": ["(surround \"{\" \"}\" true)"], + "a}": ["(surround \"{\" \"}\" false)"], + "i(": ["(surround \"(\" \")\" true)"], + "a(": ["(surround \"(\" \")\" false)"], + "i)": ["(surround \"(\" \")\" true)"], + "a)": ["(surround \"(\" \")\" false)"], + "i[": ["(surround \"[\" \"]\" true)"], + "a[": ["(surround \"[\" \"]\" false)"], + "i]": ["(surround \"[\" \"]\" true)"], + "a]": ["(surround \"[\" \"]\" false)"], + "i\\<": ["(surround \"<\" \">\" true)"], // , "<#text_object.count>"], + "a\\<": ["(surround \"<\" \">\" false)"], // , "<#text_object.count>"], + "i\\>": ["(surround \"<\" \">\" true)"], // , "<#text_object.count>"], + "a\\>": ["(surround \"<\" \">\" false)"], // , "<#text_object.count>"], + "i\"": ["(surround \"\\\"\" \"\\\"\" true)"], // , "<#text_object.count>"], + "a\"": ["(surround \"\\\"\" \"\\\"\" false)"], // , "<#text_object.count>"], + "i'": ["(surround \"'\" \"'\" true)"], // , "<#text_object.count>"], + "a'": ["(surround \"'\" \"'\" false)"], // , "<#text_object.count>"], }, "vim.visual-line#move": { @@ -402,8 +402,6 @@ "{": ["vim.move-paragraph", true, "<#move.count>"], "f": ["(move-to )"], "t": ["(column) (move-to ) (column -1)"], - // "f": ["vim.move-to ", false, "<#move.count>"], - // "t": ["vim.move-to ", true, "<#move.count>"], "": ["vim.move-cursor-visual-page", 50, "<#move.count>", true], "": ["vim.move-cursor-visual-page", 100, "<#move.count>", true], diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 36163a41a..2d84da27b 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -265,7 +265,7 @@ proc applyVimMove(editor: TextEditor, move: string, suffix: string, count: int = adjustedMove.removeSuffix(targetColumnSuffix) result.updateTargetColumn = false adjustedMove = adjustedMove & " " & suffix - debugf"applyVimMove '{move}' -> '{adjustedMove}'" + # debugf"applyVimMove '{move}' -> '{adjustedMove}'" for i in 0.. 0: + for i in 0.. 0: @@ -715,4 +740,5 @@ proc applyMove*(self: MoveDatabase, displayMap: DisplayMap, move: string, select return self.applyMoveLisp(displayMap, "(" & move & ")", selections, env, fallback) proc registerMove*(self: MoveDatabase, move: string, impl: MoveImpl) = + log lvlInfo, &"Register custom move '{move}'" self.moves[move] = impl From d08c0b90617eedce6e3838b961cb4a4c372f303d Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 26 Oct 2025 19:24:18 +0100 Subject: [PATCH 39/47] Try to fix windows build error --- src/text/custom_treesitter.nim | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/text/custom_treesitter.nim b/src/text/custom_treesitter.nim index 462cd8be2..90ef510f6 100644 --- a/src/text/custom_treesitter.nim +++ b/src/text/custom_treesitter.nim @@ -205,10 +205,14 @@ proc getTextRangeTreesitter(payload: pointer; byteIndex: uint32; position: ts.TS proc parseCallback*(self: TSParser, oldTree: TSTree, text: GetTextCallback): TSTree = let input = TSInput( payload: text.addr, - read: cast[typeof(TSInput().read)](getTextRangeTreesitter), + # read: cast[typeof(TSInput().read)](getTextRangeTreesitter), encoding: TSInputEncoding.TSInputEncodingUTF8 ) + # Ugly hack, but on windows it complains about incompatible function pointer type because nim maps cstring to "char*" + # instead of "const char*" + {.emit: [input, ".read = (void*)(", getTextRangeTreesitter, ");"].} + let oldTreeImpl = if oldTree.isNotNil: oldTree.impl else: From fa8be395fd8c18b707fbfa5fe4c7200d19d573f3 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 26 Oct 2025 20:46:04 +0100 Subject: [PATCH 40/47] Add option to use windy clipboard api and enable it by default --- src/clipboard.nim | 19 ++++++++++++++++--- src/compilation_config.nim | 3 ++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/clipboard.nim b/src/clipboard.nim index c5d42dde2..a575fb2f2 100644 --- a/src/clipboard.nim +++ b/src/clipboard.nim @@ -1,13 +1,26 @@ -import std/[options] +import std/[options, macros] import misc/[util, custom_async, custom_logger] logCategory "clipboard" import compilation_config -when enableSystemClipboard: +when enableWindyClipboard: + import windy static: - echo "Building with system clipboard" + hint("Building with windy clipboard") + + proc setSystemClipboardText*(str: string) = + {.gcsafe.}: + setClipboardString(str) + + proc getSystemClipboardText*(): Future[Option[string]] {.async.} = + {.gcsafe.}: + return getClipboardString().some + +elif enableSystemClipboard: + static: + hint("Building with system clipboard") import nimclipboard/libclipboard import system/ansi_c diff --git a/src/compilation_config.nim b/src/compilation_config.nim index fc26a8747..08fa525a3 100644 --- a/src/compilation_config.nim +++ b/src/compilation_config.nim @@ -4,7 +4,8 @@ const exposeScriptingApi* {.booldefine.}: bool = false const enableGui* {.booldefine.}: bool = false const enableTerminal* {.booldefine.}: bool = false const enableTableIdCacheChecking* {.booldefine.}: bool = false -const enableSystemClipboard* {.booldefine.}: bool = true +const enableWindyClipboard* {.booldefine.}: bool = true +const enableSystemClipboard* {.booldefine.}: bool = false const enableAst* {.booldefine.}: bool = false const enableOldPluginVersions* {.booldefine.}: bool = false const enableLibssh* {.booldefine.}: bool = false From 92aa891fcf102a4e0aa6be1c2ea773fa7c182550 Mon Sep 17 00:00:00 2001 From: Nico Date: Sun, 26 Oct 2025 21:18:53 +0100 Subject: [PATCH 41/47] Update Nim compilation commands with new flags Added compiler flags to suppress warnings for incompatible function pointer types. --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8dcb94062..1a65b9b00 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -170,8 +170,8 @@ jobs: # windows builds - run: ./emsdk/emsdk.ps1 activate 4.0.10; nimble buildWasmModule - - run: nim c --out:nev.exe -D:enableGui=false -D:enableTerminal=true --app:console -D:forceLogToFile --passC:-std=gnu11 -d:exposeScriptingApi -D:isCI -D:isCINimbleCached=${{steps.cache-nimble.outputs.cache-hit}} src/desktop_main.nim - - run: nim c --out:nevg.exe -D:enableGui=true -D:enableTerminal=false --app:gui -D:forceLogToFile --passC:-std=gnu11 -d:exposeScriptingApi -D:isCI -D:isCINimbleCached=${{steps.cache-nimble.outputs.cache-hit}} src/desktop_main.nim + - run: nim c --out:nev.exe -D:enableGui=false -D:enableTerminal=true --app:console -D:forceLogToFile --passC:-std=gnu11 -d:exposeScriptingApi -D:isCI -D:isCINimbleCached=${{steps.cache-nimble.outputs.cache-hit}} --passC:-Wno-incompatible-function-pointer-types src/desktop_main.nim + - run: nim c --out:nevg.exe -D:enableGui=true -D:enableTerminal=false --app:gui -D:forceLogToFile --passC:-std=gnu11 -d:exposeScriptingApi -D:isCI -D:isCINimbleCached=${{steps.cache-nimble.outputs.cache-hit}} --passC:-Wno-incompatible-function-pointer-types src/desktop_main.nim - run: nim c --out:nevc.exe -D:enableGui=true -D:enableTerminal=false --app:gui -D:forceLogToFile --passC:-std=gnu11 -d:exposeScriptingApi -D:isCI -D:isCINimbleCached=${{steps.cache-nimble.outputs.cache-hit}} --cc:clang --passC:-Wno-incompatible-function-pointer-types "--passL:-ladvapi32.lib -luser32.lib" -d:enableSystemClipboard=false src/desktop_main.nim #- run: nimble buildRemoteWorkspaceHostWindows #- run: nimble buildLspWsWindows From 0769c4ff199d2ec7c54b70596349600104b93d44 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 26 Oct 2025 21:39:06 +0100 Subject: [PATCH 42/47] Try to fix windows build errors --- src/libvterm/vterm.h | 16 ++++++++-------- src/vterm.nim | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/libvterm/vterm.h b/src/libvterm/vterm.h index 264ab027f..b89bda152 100644 --- a/src/libvterm/vterm.h +++ b/src/libvterm/vterm.h @@ -402,12 +402,12 @@ int vterm_get_mouse_flags(VTerm *vt); #define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a)) typedef struct { - int (*text)(const char *bytes, size_t len, void *user); + int (*text)(char *bytes, size_t len, void *user); int (*control)(unsigned char control, void *user); - int (*escape)(const char *bytes, size_t len, void *user); - int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); + int (*escape)(char *bytes, size_t len, void *user); + int (*csi)(char *leader, long args[], int argcount, char *intermed, char command, void *user); int (*osc)(int command, VTermStringFragment frag, void *user); - int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); + int (*dcs)(char *command, size_t commandlen, VTermStringFragment frag, void *user); int (*apc)(VTermStringFragment frag, void *user); int (*pm)(VTermStringFragment frag, void *user); int (*sos)(VTermStringFragment frag, void *user); @@ -437,15 +437,15 @@ typedef struct { int (*settermprop)(VTermProp prop, VTermValue *val, void *user); int (*bell)(void *user); int (*resize)(int rows, int cols, VTermStateFields *fields, void *user); - int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user); + int (*setlineinfo)(int row, VTermLineInfo *newinfo, VTermLineInfo *oldinfo, void *user); int (*sb_clear)(void *user); } VTermStateCallbacks; typedef struct { int (*control)(unsigned char control, void *user); - int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); + int (*csi)(char *leader, long args[], int argcount, char *intermed, char command, void *user); int (*osc)(int command, VTermStringFragment frag, void *user); - int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); + int (*dcs)(char *command, size_t commandlen, VTermStringFragment frag, void *user); int (*apc)(VTermStringFragment frag, void *user); int (*pm)(VTermStringFragment frag, void *user); int (*sos)(VTermStringFragment frag, void *user); @@ -545,7 +545,7 @@ typedef struct { int (*settermprop)(VTermProp prop, VTermValue *val, void *user); int (*bell)(void *user); int (*resize)(int rows, int cols, void *user); - int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); + int (*sb_pushline)(int cols, VTermScreenCell *cells, void *user); int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); int (*sb_clear)(void* user); } VTermScreenCallbacks; diff --git a/src/vterm.nim b/src/vterm.nim index 28ae13939..cc7b34517 100644 --- a/src/vterm.nim +++ b/src/vterm.nim @@ -273,7 +273,7 @@ type VTERM_KEY_KP_EQUAL, VTERM_KEY_MAX - VTermAttrMask* = enum + VTermAttrMask* {.importc.} = enum VTERM_ATTR_BOLD_MASK = 1 shl 0, VTERM_ATTR_UNDERLINE_MASK = 1 shl 1, VTERM_ATTR_ITALIC_MASK = 1 shl 2, VTERM_ATTR_BLINK_MASK = 1 shl 3, VTERM_ATTR_REVERSE_MASK = 1 shl 4, VTERM_ATTR_STRIKE_MASK = 1 shl 5, @@ -283,7 +283,7 @@ type VTERM_ATTR_DIM_MASK = 1 shl 12, VTERM_ALL_ATTRS_MASK = (1 shl 13) - 1 - VTermSelectionMask* = enum + VTermSelectionMask* {.importc.} = enum VTERM_SELECTION_CLIPBOARD = (1 shl 0), VTERM_SELECTION_PRIMARY = (1 shl 1), VTERM_SELECTION_SECONDARY = (1 shl 2), From 2577580ae06b09cc14bdfecd987d1e0ea918bf10 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 26 Oct 2025 22:02:07 +0100 Subject: [PATCH 43/47] Fix build error --- src/libvterm/screen.c | 2 +- src/libvterm/state.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libvterm/screen.c b/src/libvterm/screen.c index 09715a3ef..a22379a94 100644 --- a/src/libvterm/screen.c +++ b/src/libvterm/screen.c @@ -805,7 +805,7 @@ static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *us return 1; } -static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user) +static int setlineinfo(int row, VTermLineInfo *newinfo, VTermLineInfo *oldinfo, void *user) { VTermScreen *screen = user; diff --git a/src/libvterm/state.c b/src/libvterm/state.c index daa88e2b2..8357652b3 100644 --- a/src/libvterm/state.c +++ b/src/libvterm/state.c @@ -270,7 +270,7 @@ static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl state->lineinfo[row] = info; } -static int on_text(const char bytes[], size_t len, void *user) +static int on_text(char bytes[], size_t len, void *user) { VTermState *state = user; @@ -589,7 +589,7 @@ static void savecursor(VTermState *state, int save) } } -static int on_escape(const char *bytes, size_t len, void *user) +static int on_escape(char *bytes, size_t len, void *user) { VTermState *state = user; @@ -928,7 +928,7 @@ static void request_version_string(VTermState *state) VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR); } -static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) +static int on_csi(char *leader, long args[], int argcount, char *intermed, char command, void *user) { VTermState *state = user; int leader_byte = 0; @@ -1913,7 +1913,7 @@ static void request_status_string(VTermState *state, VTermStringFragment frag) vterm_push_output_sprintf_str(state->vt, C1_DCS, true, "0$r"); } -static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user) +static int on_dcs(char *command, size_t commandlen, VTermStringFragment frag, void *user) { VTermState *state = user; From c43ad9cc3fe27a62931ded6471d9cbc4ca273788 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 26 Oct 2025 22:29:31 +0100 Subject: [PATCH 44/47] Fix more build errors --- src/libvterm/vterm.h | 2 +- src/terminal_service.nim | 4 ++-- src/vterm.nim | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libvterm/vterm.h b/src/libvterm/vterm.h index b89bda152..7aa90bec5 100644 --- a/src/libvterm/vterm.h +++ b/src/libvterm/vterm.h @@ -354,7 +354,7 @@ void vterm_set_utf8(VTerm *vt, int is_utf8); size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len); /* Setting output callback will override the buffer logic */ -typedef void VTermOutputCallback(const char *s, size_t len, void *user); +typedef void VTermOutputCallback(char *s, size_t len, void *user); void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user); /* These buffer functions only work if output callback is NOT set diff --git a/src/terminal_service.nim b/src/terminal_service.nim index 1e20c70c0..406b0067c 100644 --- a/src/terminal_service.nim +++ b/src/terminal_service.nim @@ -1923,7 +1923,7 @@ proc terminalThread(s: TerminalThreadState) {.thread, nimcall.} = ) var parserCallbacks = VTermStateFallbacks( - control: (proc(control: char; user: pointer): cint {.cdecl.} = + control: (proc(control: uint8; user: pointer): cint {.cdecl.} = # echo &"control '{control}'" return 1 ), @@ -1998,7 +1998,7 @@ proc terminalThread(s: TerminalThreadState) {.thread, nimcall.} = sos: (proc(frag: VTermStringFragment; user: pointer): cint {.cdecl.} = return 1 ), - reset: (proc (hard: int; user: pointer): cint {.cdecl.} = + reset: (proc (hard: cint; user: pointer): cint {.cdecl.} = let state = cast[ptr TerminalThreadState](user) # echo &"===================== reset: {hard}" if state.alternateScreen: diff --git a/src/vterm.nim b/src/vterm.nim index cc7b34517..0cbd11614 100644 --- a/src/vterm.nim +++ b/src/vterm.nim @@ -165,7 +165,7 @@ type VTermParserCallbacks* {.bycopy, importc.} = object text*: proc (bytes: cstring; len: csize_t; user: pointer): cint {.cdecl.} - control*: proc (control: char; user: pointer): cint {.cdecl.} + control*: proc (control: uint8; user: pointer): cint {.cdecl.} escape*: proc (bytes: cstring; len: csize_t; user: pointer): cint {.cdecl.} csi*: proc (leader: cstring; args: ptr clong; argcount: cint; intermed: cstring; command: char; user: pointer): cint {.cdecl.} osc*: proc (command: cint; frag: VTermStringFragment; user: pointer): cint {.cdecl.} @@ -190,14 +190,14 @@ type sb_clear*: proc (user: pointer): cint {.cdecl.} VTermStateFallbacks* {.bycopy, importc.} = object - control*: proc (control: char; user: pointer): cint {.cdecl.} + control*: proc (control: uint8; user: pointer): cint {.cdecl.} csi*: proc (leader: cstring; args: ptr UncheckedArray[clong]; argcount: cint; intermed: cstring; command: char; user: pointer): cint {.cdecl.} osc*: proc (command: cint; frag: VTermStringFragment; user: pointer): cint {.cdecl.} dcs*: proc (command: cstring; commandlen: csize_t; frag: VTermStringFragment; user: pointer): cint {.cdecl.} apc*: proc (frag: VTermStringFragment; user: pointer): cint {.cdecl.} pm*: proc (frag: VTermStringFragment; user: pointer): cint {.cdecl.} sos*: proc (frag: VTermStringFragment; user: pointer): cint {.cdecl.} - reset*: proc (hard: int; user: pointer): cint {.cdecl.} + reset*: proc (hard: cint; user: pointer): cint {.cdecl.} VTermScreenCallbacks* {.bycopy, importc.} = object damage*: proc (rect: VTermRect; user: pointer): cint {.cdecl.} From 76342c94b7343ac6266bcf27592008c83d287067 Mon Sep 17 00:00:00 2001 From: Nico Date: Sun, 26 Oct 2025 22:51:32 +0100 Subject: [PATCH 45/47] Refactor nim build commands in main.yml --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1a65b9b00..8dcb94062 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -170,8 +170,8 @@ jobs: # windows builds - run: ./emsdk/emsdk.ps1 activate 4.0.10; nimble buildWasmModule - - run: nim c --out:nev.exe -D:enableGui=false -D:enableTerminal=true --app:console -D:forceLogToFile --passC:-std=gnu11 -d:exposeScriptingApi -D:isCI -D:isCINimbleCached=${{steps.cache-nimble.outputs.cache-hit}} --passC:-Wno-incompatible-function-pointer-types src/desktop_main.nim - - run: nim c --out:nevg.exe -D:enableGui=true -D:enableTerminal=false --app:gui -D:forceLogToFile --passC:-std=gnu11 -d:exposeScriptingApi -D:isCI -D:isCINimbleCached=${{steps.cache-nimble.outputs.cache-hit}} --passC:-Wno-incompatible-function-pointer-types src/desktop_main.nim + - run: nim c --out:nev.exe -D:enableGui=false -D:enableTerminal=true --app:console -D:forceLogToFile --passC:-std=gnu11 -d:exposeScriptingApi -D:isCI -D:isCINimbleCached=${{steps.cache-nimble.outputs.cache-hit}} src/desktop_main.nim + - run: nim c --out:nevg.exe -D:enableGui=true -D:enableTerminal=false --app:gui -D:forceLogToFile --passC:-std=gnu11 -d:exposeScriptingApi -D:isCI -D:isCINimbleCached=${{steps.cache-nimble.outputs.cache-hit}} src/desktop_main.nim - run: nim c --out:nevc.exe -D:enableGui=true -D:enableTerminal=false --app:gui -D:forceLogToFile --passC:-std=gnu11 -d:exposeScriptingApi -D:isCI -D:isCINimbleCached=${{steps.cache-nimble.outputs.cache-hit}} --cc:clang --passC:-Wno-incompatible-function-pointer-types "--passL:-ladvapi32.lib -luser32.lib" -d:enableSystemClipboard=false src/desktop_main.nim #- run: nimble buildRemoteWorkspaceHostWindows #- run: nimble buildLspWsWindows From 627fa0e8bcba3e7adbe6b555abd8359f49011f21 Mon Sep 17 00:00:00 2001 From: Nimaoth Date: Sun, 26 Oct 2025 23:37:59 +0100 Subject: [PATCH 46/47] Fixed some vim motions --- config/keybindings.json | 8 +++- plugins/vim/vim.nim | 6 +-- src/input.nim | 90 ++++++++++++++++++++++++++++------------- 3 files changed, 72 insertions(+), 32 deletions(-) diff --git a/config/keybindings.json b/config/keybindings.json index 77ad137b8..93ddd1925 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -400,8 +400,12 @@ "gE": ["vim.move-selection-next", "vim.WORD", true, false, "<#move.count>"], "}": ["vim.move-paragraph", false, "<#move.count>"], "{": ["vim.move-paragraph", true, "<#move.count>"], - "f": ["(move-to )"], - "t": ["(column) (move-to ) (column -1)"], + "f": ["(move-to )"], + "t": ["(column) (move-to ) (column -1)"], + "f": ["(move-to \" \")"], + "t": ["(column) (move-to \" \") (column -1)"], + "f": ["(move-to \"\n\")"], + "t": ["(column) (move-to \"\n\") (column -1)"], "": ["vim.move-cursor-visual-page", 50, "<#move.count>", true], "": ["vim.move-cursor-visual-page", 100, "<#move.count>", true], diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 2d84da27b..2467b8266 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -435,7 +435,7 @@ proc selectMove(editor: TextEditor, move: string, count: int = 0) {.exposeActive proc deleteMove(editor: TextEditor, move: string, count: int = 0) {.exposeActive(editorContext).} = editor.recordCurrentCommandInPeriodMacro() let oldSelections = @(editor.selections) - let res = editor.applyVimMove(move, "(join)", count) + let res = editor.applyVimMove(move, "(merge)", count) editor.deleteSelection(false, oldSelections=oldSelections.some) if res.updateTargetColumn: editor.updateTargetColumn() @@ -443,13 +443,13 @@ proc deleteMove(editor: TextEditor, move: string, count: int = 0) {.exposeActive proc changeMove(editor: TextEditor, move: string, count: int = 0) {.exposeActive(editorContext).} = editor.recordCurrentCommandInPeriodMacro() let oldSelections = @(editor.selections) - let res = editor.applyVimMove(move, "(join)", count) + let res = editor.applyVimMove(move, "(merge)", count) editor.changeSelection(false, oldSelections=oldSelections.some) if res.updateTargetColumn: editor.updateTargetColumn() proc yankMove(editor: TextEditor, move: string, count: int = 0) {.exposeActive(editorContext).} = - discard editor.applyVimMove(move, "(join)", count) + discard editor.applyVimMove(move, "(merge)", count) editor.yankSelection() proc vimClamp*(editor: TextEditor, cursor: Cursor): Cursor = diff --git a/src/input.nim b/src/input.nim index 7025b81e9..04f36061b 100644 --- a/src/input.nim +++ b/src/input.nim @@ -10,9 +10,11 @@ export input_api logCategory "input" type + CaptureFlags = enum None, Char, Any Transition = object state: int capture: string + captureFlags: CaptureFlags functionIndices: BitSet function: string @@ -48,6 +50,7 @@ type Left, Middle, Right, DoubleClick, TripleClick, Unknown proc inputToString*(input: int64, modifiers: Modifiers = {}): string +proc inputToCharString*(input: int64, modifiers: Modifiers = {}): string let capturePattern = re"<(.*?)>" proc fillCaptures(dfa: CommandDFA, state: CommandState, args: string): string = @@ -122,7 +125,13 @@ proc stepInput(dfa: CommandDFA, state: CommandState, currentInput: int64, mods: var newState = state newState.current = transition.state newState.functionIndices.bitSetIntersect transition.functionIndices - newState.captures.mgetOrPut(transition.capture, "").add(inputToString(currentInput, mods)) + let captureText = case transition.captureFlags + of None, Any: + inputToString(currentInput, mods) + of Char: + inputToCharString(currentInput, mods) + + newState.captures.mgetOrPut(transition.capture, "").add(captureText) if dfa.states[transition.state].transitions.len > 0 or dfa.states[transition.state].epsilonTransitions.len == 0: result.add newState @@ -292,7 +301,25 @@ proc inputToString*(inputs: Slice[int64], modifiers: Modifiers = {}): string = if special: result.add ">" -proc linkStates(dfa: var CommandDFA, currentState: int, nextState: int, inputCodes: Slice[int64], mods: Modifiers, functionIndex: int, capture: string): bool = +proc inputToCharString*(input: int64, modifiers: Modifiers = {}): string = + if modifiers != {} and modifiers != {Shift}: + return inputToString(input, modifiers) + case input + of INPUT_SPACE: + return " " + of INPUT_ENTER: + return "\n" + of INPUT_TAB: + return "\t" + else: + if input <= 0: + return inputToString(input, modifiers) + else: + result = $input.Rune + if Shift in modifiers: + result = result.toUpper + +proc linkStates(dfa: var CommandDFA, currentState: int, nextState: int, inputCodes: Slice[int64], mods: Modifiers, functionIndex: int, capture: string, captureFlags: CaptureFlags): bool = if not (inputCodes in dfa.states[currentState].transitions): dfa.states[currentState].transitions[inputCodes] = DFAInput() if mods in dfa.states[currentState].transitions[inputCodes].next: @@ -304,14 +331,15 @@ proc linkStates(dfa: var CommandDFA, currentState: int, nextState: int, inputCod assert current.state == nextState assert current.capture == capture + assert current.captureFlags == captureFlags dfa.states[currentState].transitions[inputCodes].next[mods].functionIndices.bitSetIncl functionIndex return true else: # echo &"link states {currentState} -> {nextState}, {inputToString(inputCodes, mods)}, {functionIndex}, {capture}" - dfa.states[currentState].transitions[inputCodes].next[mods] = Transition(state: nextState, functionIndices: [functionIndex].toBitSet, capture: capture) + dfa.states[currentState].transitions[inputCodes].next[mods] = Transition(state: nextState, functionIndices: [functionIndex].toBitSet, capture: capture, captureFlags: captureFlags) return true -proc linkStates(dfa: var CommandDFA, currentState: int, nextState: int, functionIndex: int, capture: string, function: string) = +proc linkStates(dfa: var CommandDFA, currentState: int, nextState: int, functionIndex: int, capture: string, captureFlags: CaptureFlags, function: string) = for transition in dfa.states[currentState].epsilonTransitions.mitems: if transition.state == nextState: # echo &"link states {currentState} -> {nextState}, {functionIndex}, {capture}, {transition.function} -> {function}" @@ -320,9 +348,9 @@ proc linkStates(dfa: var CommandDFA, currentState: int, nextState: int, function transition.functionIndices.bitSetIncl functionIndex return - dfa.states[currentState].epsilonTransitions.add Transition(state: nextState, functionIndices: [functionIndex].toBitSet, capture: capture, function: function) + dfa.states[currentState].epsilonTransitions.add Transition(state: nextState, functionIndices: [functionIndex].toBitSet, capture: capture, captureFlags: captureFlags, function: function) -proc createOrUpdateState(dfa: var CommandDFA, currentState: int, inputCodes: Slice[int64], mods: Modifiers, persistent: bool, loop: bool, capture: string, functionIndex: int): Option[int] = +proc createOrUpdateState(dfa: var CommandDFA, currentState: int, inputCodes: Slice[int64], mods: Modifiers, persistent: bool, loop: bool, capture: string, captureFlags: CaptureFlags, functionIndex: int): Option[int] = let nextState = if loop: currentState elif inputCodes in dfa.states[currentState].transitions: @@ -331,7 +359,7 @@ proc createOrUpdateState(dfa: var CommandDFA, currentState: int, inputCodes: Sli else: dfa.states.add DFAState() # echo &"init states {currentState} -> {dfa.states.len-1}, {inputToString(inputCodes, mods)}, {functionIndex}, {capture}" - dfa.states[currentState].transitions[inputCodes].next[mods] = Transition(state: dfa.states.len - 1, functionIndices: [functionIndex].toBitSet, capture: capture) + dfa.states[currentState].transitions[inputCodes].next[mods] = Transition(state: dfa.states.len - 1, functionIndices: [functionIndex].toBitSet, capture: capture, captureFlags: captureFlags) dfa.states.len - 1 else: dfa.states.add DFAState() @@ -340,7 +368,7 @@ proc createOrUpdateState(dfa: var CommandDFA, currentState: int, inputCodes: Sli dfa.states[nextState].persistent = persistent # echo &"update state {nextState}, {dfa.states[nextState].capture} -> {capture}" dfa.states[nextState].capture = capture - if not linkStates(dfa, currentState, nextState, inputCodes, mods, functionIndex, capture): + if not linkStates(dfa, currentState, nextState, inputCodes, mods, functionIndex, capture, captureFlags): return int.none return nextState.some @@ -376,6 +404,7 @@ proc handleNextInput( leaders: Table[string, seq[(int64, Modifiers)]], functionIndex: int, capture = "", + captureFlags = CaptureFlags.None, depth = 0, subGraphCounts = newTable[string, int](), ): seq[int] = @@ -408,37 +437,38 @@ proc handleNextInput( subGraphCounts[inputName] = subGraphCount + 1 let subCapture = if capture.len > 0: capture & "." & inputName else: inputName + let subCaptureFlags = CaptureFlags.None # echo "| ".repeat(depth) & &"sub graph {inputName}, {subGraphCounts[]}" let subGraphKey = (inputName, capture, subGraphCount) if dfa.subGraphStates.contains(subGraphKey): # echo "| ".repeat(depth) & &"sub graph {inputName} found -> {dfa.subGraphStates[subGraphKey]}" let (nextStartState, nextEndState) = dfa.subGraphStates[subGraphKey] - dfa.linkStates(currentState, nextStartState, functionIndex, capture, "") + dfa.linkStates(currentState, nextStartState, functionIndex, capture, captureFlags, "") fillTransitionFunctionIndicesRec(dfa, nextStartState, [functionIndex].toBitSet, nextEndState) - result = handleNextInput(dfa, commands, input, function, nextIndex, nextEndState, defaultState, leaders, functionIndex, capture, depth + 1, subGraphCounts) + result = handleNextInput(dfa, commands, input, function, nextIndex, nextEndState, defaultState, leaders, functionIndex, capture, captureFlags, depth + 1, subGraphCounts) if Optional in flags: - dfa.linkStates(currentState, nextEndState, functionIndex, capture, "") + dfa.linkStates(currentState, nextEndState, functionIndex, capture, captureFlags, "") return dfa.states.add DFAState() let subState = dfa.states.len - 1 dfa.states.add DFAState() let endState = dfa.states.len - 1 - dfa.linkStates(currentState, subState, functionIndex, capture, "") + dfa.linkStates(currentState, subState, functionIndex, capture, captureFlags, "") for command in commands[inputName].pairs: let (subInput, function) = command # echo "| ".repeat(depth) & &"subInput: {subInput}, function: {function}" - let nextEndStates = handleNextInput(dfa, commands, subInput.toRunes, function, index = 0, currentState = subState, defaultState = defaultState, leaders = leaders, functionIndex, subCapture, depth + 1, subGraphCounts[].neww) + let nextEndStates = handleNextInput(dfa, commands, subInput.toRunes, function, index = 0, currentState = subState, defaultState = defaultState, leaders = leaders, functionIndex, subCapture, subCaptureFlags, depth + 1, subGraphCounts[].neww) # echo "| ".repeat(depth) & &"-> endStates: {nextEndStates}" for es in nextEndStates: - dfa.linkStates(es, endState, functionIndex, subCapture, function) + dfa.linkStates(es, endState, functionIndex, subCapture, subCaptureFlags, function) - result = handleNextInput(dfa, commands, input, function, nextIndex, endState, defaultState, leaders, functionIndex, capture, depth + 1, subGraphCounts) + result = handleNextInput(dfa, commands, input, function, nextIndex, endState, defaultState, leaders, functionIndex, capture, captureFlags, depth + 1, subGraphCounts) if Optional in flags: - dfa.linkStates(currentState, endState, functionIndex, capture, "") + dfa.linkStates(currentState, endState, functionIndex, capture, captureFlags, "") dfa.subGraphStates[subGraphKey] = (subState, endState) return @@ -461,27 +491,33 @@ proc handleNextInput( for key in keys: let nextState = if inputName == "CHAR" or inputName == "ANY": let subCapture = if capture.len > 0: capture & "." & inputName else: inputName - let nextState = createOrUpdateState(dfa, currentState, key.inputCodes, key.mods, persistent, Loop in flags, subCapture, functionIndex).getOr: + var subCaptureFlags: CaptureFlags = None + if inputName == "CHAR": + subCaptureFlags = Char + elif inputName == "ANY": + subCaptureFlags = Any + + let nextState = createOrUpdateState(dfa, currentState, key.inputCodes, key.mods, persistent, Loop in flags, subCapture, subCaptureFlags, functionIndex).getOr: log lvlError, fmt"""Ambigious keybinding '{input.join("")}' at {index} ({inputToString(key.inputCodes, key.mods)})""" continue # echo "| ".repeat(depth) & &"create next state {nextState}, {key.inputCodes}, {key.mods}, current state {currentState}" if inputName == "CHAR": - discard linkStates(dfa, currentState, nextState, key.inputCodes, {Shift}, functionIndex, subCapture) - discard linkStates(dfa, currentState, nextState, key.inputCodes, {}, functionIndex, subCapture) + discard linkStates(dfa, currentState, nextState, key.inputCodes, {Shift}, functionIndex, subCapture, subCaptureFlags) + discard linkStates(dfa, currentState, nextState, key.inputCodes, {}, functionIndex, subCapture, subCaptureFlags) - if inputName == "ANY": + elif inputName == "ANY": for modsInt in 0..pow(2.float32, Modifier.high.ord.float32).int: var mods: Modifiers = {} for i in 0.. Date: Mon, 27 Oct 2025 23:24:47 +0100 Subject: [PATCH 47/47] Refactor more vim motions --- config/keybindings.json | 110 +++++++++++++++++++-------------------- plugins/vim/vim.nim | 11 ++-- src/move_database.nim | 82 +++++++++++++---------------- src/text/text_editor.nim | 3 +- 4 files changed, 98 insertions(+), 108 deletions(-) diff --git a/config/keybindings.json b/config/keybindings.json index 93ddd1925..f5374d3d8 100644 --- a/config/keybindings.json +++ b/config/keybindings.json @@ -318,61 +318,59 @@ }, "vim#text_object": { - "iw": ["(vim.word-inner)"], - "aw": ["(vim.word-inner)"], - "iW": ["(vim.WORD-inner)"], - "aW": ["(vim.WORD-inner)"], - "ip": ["(vim.paragraph-inner) (inclusive)"], - "ap": ["(vim.paragraph-outer) (inclusive)"], - - "i{": ["(surround \"{\" \"}\" true)"], - "a{": ["(surround \"{\" \"}\" false)"], - "i}": ["(surround \"{\" \"}\" true)"], - "a}": ["(surround \"{\" \"}\" false)"], - "i(": ["(surround \"(\" \")\" true)"], - "a(": ["(surround \"(\" \")\" false)"], - "i)": ["(surround \"(\" \")\" true)"], - "a)": ["(surround \"(\" \")\" false)"], - "i[": ["(surround \"[\" \"]\" true)"], - "a[": ["(surround \"[\" \"]\" false)"], - "i]": ["(surround \"[\" \"]\" true)"], - "a]": ["(surround \"[\" \"]\" false)"], - "i\\<": ["(surround \"<\" \">\" true)"], // , "<#text_object.count>"], - "a\\<": ["(surround \"<\" \">\" false)"], // , "<#text_object.count>"], - "i\\>": ["(surround \"<\" \">\" true)"], // , "<#text_object.count>"], - "a\\>": ["(surround \"<\" \">\" false)"], // , "<#text_object.count>"], - "i\"": ["(surround \"\\\"\" \"\\\"\" true)"], // , "<#text_object.count>"], - "a\"": ["(surround \"\\\"\" \"\\\"\" false)"], // , "<#text_object.count>"], - "i'": ["(surround \"'\" \"'\" true)"], // , "<#text_object.count>"], - "a'": ["(surround \"'\" \"'\" false)"], // , "<#text_object.count>"], + "iw": ["(count* <#text_object.count>) (vim.word-inner) (inclusive)"], + "aw": ["(count* <#text_object.count>) (vim.word-inner) (inclusive)"], + "iW": ["(count* <#text_object.count>) (vim.WORD-inner) (inclusive)"], + "aW": ["(count* <#text_object.count>) (vim.WORD-inner) (inclusive)"], + "ip": ["(vim.paragraph-inner) (inclusive)"], + "ap": ["(vim.paragraph-outer) (inclusive)"], + + "i{": ["(surround \"{\" \"}\" true)"], + "a{": ["(surround \"{\" \"}\" false)"], + "i}": ["(surround \"{\" \"}\" true)"], + "a}": ["(surround \"{\" \"}\" false)"], + "i(": ["(surround \"(\" \")\" true)"], + "a(": ["(surround \"(\" \")\" false)"], + "i)": ["(surround \"(\" \")\" true)"], + "a)": ["(surround \"(\" \")\" false)"], + "i[": ["(surround \"[\" \"]\" true)"], + "a[": ["(surround \"[\" \"]\" false)"], + "i]": ["(surround \"[\" \"]\" true)"], + "a]": ["(surround \"[\" \"]\" false)"], + "i\\<": ["(surround \"<\" \">\" true)"], + "a\\<": ["(surround \"<\" \">\" false)"], + "i\\>": ["(surround \"<\" \">\" true)"], + "a\\>": ["(surround \"<\" \">\" false)"], + "i\"": ["(surround \"\\\"\" \"\\\"\" true)"], + "a\"": ["(surround \"\\\"\" \"\\\"\" false)"], + "i'": ["(surround \"'\" \"'\" true)"], + "a'": ["(surround \"'\" \"'\" false)"], }, "vim.visual-line#move": { - "": ["vim.move-direction", "line-up", 1, "<#move.count>", false, false], - "": ["vim.move-direction", "line-down", 1, "<#move.count>", false, false], - "k": ["vim.move-direction", "line-up", 1, "<#move.count>", false, false], - "j": ["vim.move-direction", "line-down", 1, "<#move.count>", false, false], + "": ["vim.move-direction", "line-up", "<#move.count>"], + "": ["vim.move-direction", "line-down", "<#move.count>"], + "k": ["vim.move-direction", "line-up", "<#move.count>"], + "j": ["vim.move-direction", "line-down", "<#move.count>"], }, "vim#move": { - "": ["(column (* -1 (or <#move.count> 1)))"], - "": ["(column (or <#move.count> 1))"], - "": ["(visual-line-up (or <#move.count> 1)) (dont-update-target-column)"], - "": ["(visual-line-down (or <#move.count> 1)) (dont-update-target-column)"], - "h": ["(column (* -1 (or <#move.count> 1)))"], - "l": ["(column (or <#move.count> 1))"], - "k": ["(visual-line-up (or <#move.count> 1))"], - "j": ["(visual-line-down (or <#move.count> 1))"], - "gm": ["move-cursor-line-center"], // todo - "gM": ["move-cursor-center"], // todo + "": ["(count* <#move.count>) (column -1)"], + "": ["(count* <#move.count>) (column 1)"], + "": ["(count* <#move.count>) (visual-line-up) (dont-update-target-column)"], + "": ["(count* <#move.count>) (visual-line-down) (dont-update-target-column)"], + "h": ["(count* <#move.count>) (column -1)"], + "l": ["(count* <#move.count>) (column 1)"], + "k": ["(count* <#move.count>) (visual-line-up) (dont-update-target-column)"], + "j": ["(count* <#move.count>) (visual-line-down) (dont-update-target-column)"], + "gM": ["move-cursor-line-center"], // todo + "gm": ["move-cursor-center"], // todo "n": ["(next-search-result (or <#move.count> 1)) (start)"], "N": ["(prev-search-result (or <#move.count> 1)) (start)"], "gf": ["(next-change (or <#move.count> 1)) (start)"], "gh": ["(prev-change (or <#move.count> 1)) (start)"], "gt": ["(next-diagnostic (or <#move.count> 1)) (start)"], "gn": ["(prev-diagnostic (or <#move.count> 1)) (start)"], - // "gt": ["(next-tab-stop (or <#move.count> 1)) (start)"], - // "gn": ["(prev-tab-stop (or <#move.count> 1)) (start)"], "0": ["(line) (start)"], "": ["(visual-line) (start)"], @@ -386,18 +384,18 @@ "G": ["(file) (end) (line) (start) (if same? (end (line)))"], "%": ["(let c <#move.count>) (if (eq c 0) (surround) (start (line-no-indent (line-num (floor (* num-lines (/ c 100)))))))"], - "w": ["vim.move-selection-next", "vim.word", false, true, "<#move.count>"], - "": ["vim.move-selection-next", "vim.word", false, true, "<#move.count>"], - "W": ["vim.move-selection-next", "vim.WORD", false, true, "<#move.count>"], - "": ["vim.move-selection-next", "vim.WORD", false, true, "<#move.count>"], - "e": ["vim.move-selection-end", "vim.word", false, false, "<#move.count>"], - "E": ["vim.move-selection-end", "vim.WORD", false, false, "<#move.count>"], - "b": ["vim.move-selection-end", "vim.word", true, true, "<#move.count>"], - "": ["vim.move-selection-end", "vim.word", true, true, "<#move.count>"], - "B": ["vim.move-selection-end", "vim.WORD", true, true, "<#move.count>"], - "": ["vim.move-selection-end", "vim.WORD", true, true, "<#move.count>"], - "ge": ["vim.move-selection-next", "vim.word", true, false, "<#move.count>"], - "gE": ["vim.move-selection-next", "vim.WORD", true, false, "<#move.count>"], + "w": ["vim.move-selection-next", "(vim.word) (inclusive)", false, true, "<#move.count>"], + "": ["vim.move-selection-next", "(vim.word) (inclusive)", false, true, "<#move.count>"], + "W": ["vim.move-selection-next", "(vim.WORD) (inclusive)", false, true, "<#move.count>"], + "": ["vim.move-selection-next", "(vim.WORD) (inclusive)", false, true, "<#move.count>"], + "e": ["vim.move-selection-end", "(vim.word) (inclusive)", false, false, "<#move.count>"], + "E": ["vim.move-selection-end", "(vim.WORD) (inclusive)", false, false, "<#move.count>"], + "b": ["vim.move-selection-end", "(vim.word) (inclusive)", true, true, "<#move.count>"], + "": ["vim.move-selection-end", "(vim.word) (inclusive)", true, true, "<#move.count>"], + "B": ["vim.move-selection-end", "(vim.WORD) (inclusive)", true, true, "<#move.count>"], + "": ["vim.move-selection-end", "(vim.WORD) (inclusive)", true, true, "<#move.count>"], + "ge": ["vim.move-selection-next", "(vim.word) (inclusive)", true, false, "<#move.count>"], + "gE": ["vim.move-selection-next", "(vim.WORD) (inclusive)", true, false, "<#move.count>"], "}": ["vim.move-paragraph", false, "<#move.count>"], "{": ["vim.move-paragraph", true, "<#move.count>"], "f": ["(move-to )"], @@ -604,7 +602,7 @@ "A": ["vim.insert-mode", "right"], "I": ["vim.insert-mode", "first"], "": ["vim.select "], - "": ["vim.select-move <#count>"], + "": ["vim.select-move "], "y": ["vim.yank-selection"], "gy": ["vim.yank-selection-clipboard"], diff --git a/plugins/vim/vim.nim b/plugins/vim/vim.nim index 2467b8266..a18beb9e5 100644 --- a/plugins/vim/vim.nim +++ b/plugins/vim/vim.nim @@ -270,7 +270,7 @@ proc applyVimMove(editor: TextEditor, move: string, suffix: string, count: int = editor.setSelections editor.multiMove(editor.selections, adjustedMove.ws, count, wrap=false, includeEol=editor.vimState.cursorIncludeEol) else: - log lvlWarn, &"applyVimMove old '{move}'" + # log lvlWarn, &"applyVimMove old '{move}'" let (action, arg) = move.parseAction for i in 0..= editor.lineLength(selection.last.line) - 1: if selection.last.line == editor.lineCount - 1: break selection = (selection.last.line.int + 1, 0).toSelection @@ -589,7 +590,7 @@ proc applyMove(editor: TextEditor, selections: openArray[Selection], move: strin ## `count` How often to apply the move ## `which` How to assemble the final selection from the input and the move. If not set uses `editor.text.cursor.movement` - debugf"moveSelectionEnd '{move}' {count} {backwards} {allowEmpty}" + # debugf"applyMove '{move}' {count} {backwards} {allowEmpty}" let text = editor.content let which = which.get(getSetting[sca.SelectionCursor](editor.getContextWithMode("editor.text.cursor.movement"), sca.SelectionCursor.Both)) return selections.mapIt(block: @@ -726,14 +727,14 @@ proc move(editor: TextEditor, move: string, direction: int = 1, wrap: bool = fal editor.scrollToCursor() editor.updateTargetColumn() -proc moveDirection(editor: TextEditor, move: string, direction: int, count: int = 1, wrap: bool = false, updateTargetColumn: bool = true) {.exposeActive(editorContext).} = +proc moveDirection(editor: TextEditor, move: string, direction: int) {.exposeActive(editorContext).} = var move = move if not move.startsWith("("): move = "(" & move & ")" let cursorSelector = editor.getSetting(editor.getContextWithMode("editor.text.cursor.movement"), sca.SelectionCursor.Both) if cursorSelector == sca.Last: move.add " (join)" - editor.setSelections editor.multiMove(editor.selections, move.ws, direction * max(count, 1), wrap, includeEol = editor.vimState.cursorIncludeEol) + editor.setSelections editor.multiMove(editor.selections, move.ws, direction, wrap, includeEol = editor.vimState.cursorIncludeEol) if editor.vimState.selectLines: editor.selectLine() diff --git a/src/move_database.nim b/src/move_database.nim index 257530535..ff7d28b32 100644 --- a/src/move_database.nim +++ b/src/move_database.nim @@ -157,7 +157,7 @@ proc moveCursorColumn(text: Rope, cursor: Cursor, offset: int, wrap: bool = true return text.clampCursor(cursor, includeEol) -proc findSurroundStart*(rope: Rope, cursor: Cursor, count: int, c0: char, c1: char, depth: int = 1): Option[Cursor] = +proc findSurroundStart*(rope: Rope, cursor: Cursor, c0: char, c1: char, depth: int = 1): Option[Cursor] = var depth = depth var res = cursor @@ -185,7 +185,7 @@ proc findSurroundStart*(rope: Rope, cursor: Cursor, count: int, c0: char, c1: ch return Cursor.none -proc findSurroundEnd*(rope: Rope, cursor: Cursor, count: int, c0: char, c1: char, depth: int = 1): Option[Cursor] = +proc findSurroundEnd*(rope: Rope, cursor: Cursor, c0: char, c1: char, depth: int = 1): Option[Cursor] = let lineCount = rope.lines var depth = depth var res = cursor @@ -214,9 +214,9 @@ proc findSurroundEnd*(rope: Rope, cursor: Cursor, count: int, c0: char, c1: char return Cursor.none -proc getSurrounding*(rope: Rope, selection: Selection, count: int, c0: char, c1: char, inside: bool): Selection = +proc getSurrounding*(rope: Rope, selection: Selection, c0: char, c1: char, inside: bool): Selection = if selection.isBackwards: - return rope.getSurrounding(selection.reverse, count, c0, c1, inside).reverse + return rope.getSurrounding(selection.reverse, c0, c1, inside).reverse result = selection while true: let lastChar = rope.charAt(result.last.toPoint) @@ -227,14 +227,14 @@ proc getSurrounding*(rope: Rope, selection: Selection, count: int, c0: char, c1: else: (1, 1) - if rope.findSurroundStart(result.first, count, c0, c1, startDepth).getSome(opening) and rope.findSurroundEnd(result.last, count, c0, c1, endDepth).getSome(closing): + if rope.findSurroundStart(result.first, c0, c1, startDepth).getSome(opening) and rope.findSurroundEnd(result.last, c0, c1, endDepth).getSome(closing): result = (opening, closing) if inside: result.first = rope.moveCursorColumn(result.first, 1) result.last = rope.moveCursorColumn(result.last, -1) return - if rope.findSurroundEnd(result.first, count, c0, c1, -1).getSome(opening) and rope.findSurroundEnd(opening, count, c0, c1, 0).getSome(closing): + if rope.findSurroundEnd(result.first, c0, c1, -1).getSome(opening) and rope.findSurroundEnd(opening, c0, c1, 0).getSome(closing): result = (opening, closing) if inside: result.first = rope.moveCursorColumn(result.first, 1) @@ -287,6 +287,20 @@ proc moveCursorLine(text: Rope, displayMap: DisplayMap, cursor: Cursor, offset: type MoveFunction* = proc(move: string, selections: openArray[Selection], count: int): seq[Selection] {.gcsafe, raises: [].} +proc getCount(env: Env): int = + let val = env["count"] + if val != nil: + case val.kind + of Number: + if val.num.int == 0: + return 1 + else: + return val.num.int + else: + log lvlError, "Can't convert env.count (" & $val & ") to type int" + + return 1 + proc applyMoveImpl(self: MoveDatabase, displayMap: DisplayMap, move: string, selections: openArray[Selection], fallback: MoveFunction, args: openArray[LispVal], env: Env): seq[Selection] = if self.debugMoves: debugf"applyMoveImpl '{move}' {args}, {env.env}" @@ -314,20 +328,7 @@ proc applyMoveImpl(self: MoveDatabase, displayMap: DisplayMap, move: string, sel else: default - var count = block: - let val = env["count"] - if val != nil: - case val.kind - of Number: - if val.num.int == 0: - 1 - else: - val.num.int - else: - log lvlError, "In move '" & move & "': Can't convert env.count (" & $val & ") to type int" - 1 - else: - 1 + var count = env.getCount() assert count != 0 let targetColumn = getEnv("target-column", int, 0) @@ -342,52 +343,34 @@ proc applyMoveImpl(self: MoveDatabase, displayMap: DisplayMap, move: string, sel case move of "vim.word": - result = selections.mapIt(vimMotionWord(rope, it.last, true)) - for _ in 1.. 0 and c1.len > 0: - result = selections.mapIt(rope.getSurrounding(it, count, c0[0], c1[0], inside)) + result = selections.mapIt(rope.getSurrounding(it, c0[0], c1[0], inside)) elif c0.len > 0: - result = selections.mapIt(rope.getSurrounding(it, count, c0[0], c0[0], inside)) + result = selections.mapIt(rope.getSurrounding(it, c0[0], c0[0], inside)) else: result = selections.mapIt: let c = rope.charAt(it.last.toPoint) @@ -577,7 +560,7 @@ proc applyMoveImpl(self: MoveDatabase, displayMap: DisplayMap, move: string, sel of '"': ('"', '"', true) of '\'': ('\'', '\'', true) else: return - let selection = rope.getSurrounding(it, count, open, close, inside) + let selection = rope.getSurrounding(it, open, close, inside) if isOpen: selection else: @@ -678,6 +661,15 @@ proc applyMoveLisp(self: MoveDatabase, displayMap: DisplayMap, move: string, ori of "end", "last": impl: selections = selections.mapIt(it.last.toSelection) + of "count*": + newFunc(name, false, proc(args {.inject.}: seq[LispVal]): LispVal = + if args.len == 0 or args[0].kind != Number: + return newNil() + var count = env.getCount() + assert count != 0 + env["count"] = newNumber(count.float * args[0].num) + return newNil() + ) of "merge": newFunc(name, false, proc(args {.inject.}: seq[LispVal]): LispVal = if selections.len > 0: diff --git a/src/text/text_editor.nim b/src/text/text_editor.nim index 74b7bd3d2..2c78a1ccc 100644 --- a/src/text/text_editor.nim +++ b/src/text/text_editor.nim @@ -1866,8 +1866,7 @@ proc insertText*(self: TextDocumentEditor, text: string, autoIndent: bool = true texts.setLen(0) for s in selections.mitems: - let openLocation = self.document.rope.findSurroundStart((s.first.line, s.first.column - 1), - 0, open, close, 1).getOr: + let openLocation = self.document.rope.findSurroundStart((s.first.line, s.first.column - 1), open, close, 1).getOr: texts.add text continue