2 Code Edits
dxtr edited this page 2026-10-19 07:17:49 +02:00

Code Edits

ui:code-edit is a text edit for code, as Godot's CodeEdit is a TextEdit. It keeps everything a text edit does, its caret, selection, keys, undo and texts of any length, and adds what code wants. It knows no language of its own: what it shows of one comes from objects and functions the program gives it, a syntax highlighter, a completer, a hinter and an indenter. The editor's Script screen is built on it, and a game may use it too. ui:find-replace-bar finds and replaces in any text edit.

(defclass hash-comments (ui:syntax-highlighter) ()
  (:documentation "Comments from # to a line's end."))

(defmethod ui:highlighter-start-state ((highlighter hash-comments))
  nil)

(defmethod ui:highlight-line ((highlighter hash-comments) text state)
  (let ((start (position #\# text)))
    (values (if start (vector 0 :text start :comment) (vector 0 :text))
            state)))

(make-instance 'ui:code-edit
               :highlighter (make-instance 'hash-comments)
               :draw-line-numbers t :draw-fold-gutter t :line-folding t
               :highlight-matching-braces t :auto-brace-completion t
               :auto-indent t :indent-use-spaces t :indent-size 2
               :completer (lambda (edit)
                            ;; Every word, completing the text from column 0.
                            (declare (ignore edit))
                            (values (list "print" "println") 0)))

Colours

A ui:syntax-highlighter colours a line from the state it is in at the line's start, as Godot's SyntaxHighlighter does: ui:highlight-line returns the line's runs, a simple vector of each run's start and its ui:highlight-kind, one after the other, and the state at the line's end; ui:highlighter-start-state gives the state at the text's start. The code edit colours a line as it first shows, or as a bracket's match or a fold needs it, and after a change colours the lines changed, and those after them only while the state at their start differs from before, comparing states with equal. So a keystroke colours its line, and a long text costs what shows. The last run says what the line's end is in: a string closed at the line's end is followed by a run of :text at the line's length.

The kinds are :text, :comment, :doc-comment, :string, :number, :symbol (punctuation and operators), :keyword, :control-flow, :type, :function, :definition, :constant and :variable, each drawn in the code edit's theme colour named after it, as :comment-color, and :text in :font-color. Brackets count, as the code edit matches and folds them, only in :text and :symbol, so a bracket in a string or a comment does not. ui:code-edit-place-kind tells the kind of a place between two characters: just after a string's closing quote is :text.

Gutters and marks

At the left of the text, as Godot's: with code-edit-draw-marks, the icons of the marks set on lines; with code-edit-draw-line-numbers, the lines' numbers, zero padded with code-edit-line-numbers-zero-padded; and with code-edit-draw-fold-gutter, arrows that fold and unfold lines.

A ui:make-code-mark holds an icon, a tooltip and a colour that underlines columns of its line, as compiler notes and bookmarks want:

(setf (ui:code-edit-line-marks edit 12)
      (list (ui:make-code-mark :icon warning-image :tooltip "Unused variable X."
                               :color '(1 0.8 0.2) :start 8 :end 9)))

A mark moves with its line as lines are added and taken away before it, and with its text when Return is pressed at the line's start. Its tooltip shows as the pointer rests on its icon or the text it underlines, and a click on its icon emits ui:mark-clicked with the line. ui:code-edit-clear-marks takes them all away.

Brackets and folding

With code-edit-highlight-matching-braces, the bracket at the caret, or just before it, and the one that matches it show underlined, in :brace-mismatch-color where none matches. A search for a match passes a line at a step, from how each line's brackets count, and reads at most 2,500 lines, as Emacs bounds its search: a match farther away is not found, and not taken for a mismatch.

With code-edit-line-folding, a line with a bracket left open folds to the line of the bracket that closes it, hiding the lines between: ui:code-edit-fold-line, ui:code-edit-unfold-line, ui:code-edit-toggle-fold, ui:code-edit-fold-all and ui:code-edit-unfold-all, as clicking a fold arrow does. The arrows of the lines that fold show while the pointer is over the gutters, and those of folded lines always, with a mark after a folded line that unfolds it when clicked. The caret, a search or a change that reaches hidden lines unfolds them.

Typing and indenting

With code-edit-auto-brace-completion, typing the first of one of code-edit-brace-pairs, by default (), [], {}, "" and '', puts the second after the caret, as Godot's: not before a letter or a digit, not after one for a quote, and not inside a comment, or a string for a quote; typing the second over it steps past it, and Backspace between the two deletes both. With a selection, the pair goes around it. A pair may open and close with longer strings, as Lisp's #| and |#.

Tab indents: a tab, or with code-edit-indent-use-spaces spaces to the next multiple of code-edit-indent-size, or every line selected; Shift and Tab takes an indentation away. With code-edit-auto-indent, Return indents the line it makes as code-edit-indenter, a function of the code edit and a line that returns the column the line's text should start at, says, or as the line before is, one indentation more after an open bracket or a colon. ui:code-edit-indent-lines indents lines by the indenter, one after the other. Each of these is one change to undo.

For the lines a selection reaches, or the caret's, as Godot's CodeEdit: ui:code-edit-move-lines-up and ui:code-edit-move-lines-down move them past the line above or below, the caret and the selection with them; ui:code-edit-delete-lines deletes them; ui:code-edit-duplicate-lines puts a copy of them above them; and ui:code-edit-duplicate-selection puts a copy of the selection before it, or of the caret's line above it. ui:code-edit-toggle-comment comments them with a delimiter, as ";; " for Lisp, after each line's indentation, or uncomments them where each starts with it, leaving blank lines among them blank, as Emacs's comment-region. Each is one change to undo. A program binds them to keys, as the editor's Script screen does.

Completion and hints

Control and Space ask code-edit-completer, a function of the code edit, for the candidates at the caret: it returns a list of candidates, ui:make-code-completions or strings, the column where the text they complete starts, and optionally where the text Tab would replace ends. They show in a list under the caret, asked again as the text changes, and closed when the completer gives nothing or the caret leaves the line: Up and Down choose, Page Up and Page Down a page at a time, Return puts the candidate in, Tab puts it in place of the word, and Escape closes the list; a click chooses one and a double click puts it in. A candidate may have an icon, a display text of its own, and the ranges the typed text matched, which show marked.

code-edit-hinter, a function of the code edit, gives a hint as the caret moves, a string and the part of it to mark, as an argument list marks the argument at the caret; it shows under the caret's row, or above it without code-edit-hint-below. ui:code-edit-show-hint and ui:code-edit-hide-hint show and hide one from code, and Escape hides it.

With code-edit-symbol-lookup-on-click, a click with Control held emits ui:symbol-lookup with the line and column clicked, as going to a definition wants.

Finding and replacing

A text edit finds text with ui:text-edit-search, line by line from a place, going round the text's ends, and marks every occurrence in the lines that show with ui:text-edit-search-text. A ui:find-replace-bar, Godot's FindReplaceBar, does this from a bar under the text edit:

(let* ((edit (make-instance 'ui:code-edit :deselect-on-focus-loss-enabled nil
                                          :expand-vertical t))
       (bar (make-instance 'ui:find-replace-bar :text-edit edit))
       (column (make-instance 'ui:vbox-container)))
  (sc:add-child column edit)
  (sc:add-child column bar)
  ;; As Control and F would.
  (ui:find-replace-bar-popup-search bar)
  column)

ui:find-replace-bar-popup-search shows its row that finds, with the selection's text, and ui:find-replace-bar-popup-replace the row that replaces too. Typing selects the next occurrence from the selection's start, Return and ui:find-replace-bar-search-next the next after it, ui:find-replace-bar-search-previous the one before, with Match Case and Whole Words; Replace replaces the occurrence selected and selects the next, and ui:find-replace-bar-replace-all replaces every one, or with Selection Only those selected, as one change to undo. Escape in its fields, or ui:find-replace-bar-hide, hides it. Turn the text edit's text-edit-deselect-on-focus-loss-enabled off, as Godot's script editor does, so that the occurrence selected stays selected while the bar has the keyboard.

Theme items

For code-edit: a text edit's, with the font :font, JetBrains Mono in the default theme; the colours of the highlight kinds; :line-number-color, :code-folding-color, :brace-mismatch-color, :completion-background-color, :completion-selected-color, :completion-existing-color, :completion-scroll-color and :hint-font-color; the style boxes :completion and :hint; the constants :completion-lines, 7, and :completion-max-width, 50 characters; and the icons :can-fold, :folded and :folded-eol, Pyssel's own. For find-replace-bar: the icons :previous, :next and :close.

Thread rules

As for any node, a code edit is used from its tree's thread, where its highlighter, completer, hinter and indenter are called. A highlighter used by several code edits on several threads keeps no state of its own between calls.