DocsSoma NodeEditor & Intellisense

Editor & Intellisense

The Soma editor is a fully custom, lightweight code editor built without third-party libraries. It layers a transparent <textarea> over a syntax-highlighted <pre> element and ships autocomplete, hover tooltips, and per-user colour customisation — all in-process inside the node panel.

Editor Architecture

The editor is implemented in SomaEditor.tsx as a single React component. It intentionally avoids Monaco, CodeMirror, and similar heavy editors to keep the bundle small and keep the UI fully integrated with the node panel layout.

Overlay technique

A contenteditable <textarea> and a <pre> element are stacked on top of each other with identical font, line-height, padding, and size. The textarea is transparent — the user types into it and their raw input is captured normally. The <pre> is rendered behind the textarea and contains the same text, but HTML-escaped and wrapped in <span> elements with inline colour styles. The visible caret comes from the textarea; the visible text colours come from the <pre>.

Syntax highlighter

highlightSoma(source, colors) is a single-pass inline tokeniser. It splits the source into lines and processes each character: whitespace is passed through, # starts a comment span to the end of the line, a digit starts a number token, an alphabetic character starts an identifier (then a lookahead peek checks for ( to classify it as a function name), and every operator character gets its own span. It produces an HTML string that is injected into the <pre> via dangerouslySetInnerHTML.

Input handling

The textarea's onInput handler reads the new value and calls the parent onChange callback, which triggers a React re-render. Tab key press is intercepted to insert two spaces instead of focus-shifting. Scroll sync: the pre element's scrollTop and scrollLeft are kept in sync with the textarea's on every scroll event, ensuring both layers always show the same viewport.

Font

The editor renders using the font stack: 'Fira Code', 'Cascadia Code', 'Consolas', monospace — matching common developer fonts in order of preference. No custom font is loaded; the stack falls back gracefully to Consolas on Windows and the system monospace on other platforms.

Colour System

Syntax colours are managed by SomaConfig.ts through the SomaColors interface. Two palettes are provided out of the box — one for dark mode and one for light mode. Users can override any token colour; preferences are persisted to localStorage under the key traderoid_soma_editor_settings.

SomaColors interface

FieldDark defaultLight defaultApplied to
keyword#c792ea#7c3aedinput, output, param, let, as, for, in, if, else, while, and, or, not, true, false, break, continue, int, float, bool
function#82aaff#2563ebIdentifiers followed by ( — built-in and user-defined function calls
variable#d1d4dc#333333All other identifiers — local variables, input/output names
number#f78c6c#ea580cNumeric literals: integers and decimals
comment#546e7a#9ca3af# comment text through to end of line
operator#89ddff#0891b2Operators and punctuation: + − * / = [ ] ( ) , { } > < ! & |
plain#d1d4dc#333333Any character not matched by the above rules

Settings persistence

Settings are read from localStorage on component mount via getSomaEditorSettings(). When a user changes a colour in the node property panel, saveSomaEditorSettings(newSettings) serialises the full settings object to JSON and writes it to localStorage. It then dispatches a custom DOM event (traderoid-soma-editor-settings-change) so that all open Soma editors on the page update simultaneously without a page reload.

// Subscribe to live colour changes inside SomaEditor
useEffect(() => {
  setColors(getSomaEditorSettings().colors[theme]);
  return subscribeToSomaEditorSettings(s => setColors(s.colors[theme]));
}, [theme]);

Intellisense Autocomplete

Autocomplete is implemented in SomaIntellisense.tsx as a custom React hook (useSomaIntellisense). It triggers on every keystroke inside the editor and renders a floating suggestion popup near the text cursor.

Trigger conditions

  • A continuous run of alphabetic characters, digits, or underscores at the cursor position. Minimum 1 character typed.
  • The popup is dismissed if the prefix drops to empty (backspace past start) or if the user types a non-word character such as a space or operator.
  • Escape always closes the popup without inserting.

Navigation & insertion

  • ↑ / ↓ arrow keys move the highlighted row. The list wraps at both ends.
  • Tab or Enter insert the selected suggestion: the current prefix is replaced with the suggestion's insertText. Keyword snippets include a trailing space; function snippets include () and move the cursor inside the parens.
  • Clicking a suggestion row has the same effect as pressing Tab.

Suggestion sources

Three pools are searched on every keystroke, all filtered by prefix match (case-sensitive from the start of the token):

PoolSizeDetail columnInsert
Keywords17Short description (e.g. "declare an input channel")Keyword text + trailing space or snippet
Built-in functions43Full function signature (e.g. "clamp(x, lo, hi)")function name + ()
User-declared variablesDynamic"variable" badgeVariable name

User-declared variables are extracted from the current script text by scanning the source for let, input, output, and param declarations every time the popup opens. This means newly typed names appear as suggestions immediately.

Hover Tooltips

The useSomaHover hook and SomaHoverTooltip component provide inline documentation when the user hovers over a token.

Detection

On mousemove over the <pre> highlight layer, the hook uses document.caretPositionFromPoint() (or caretRangeFromPoint() for WebKit) to determine the character offset under the cursor. It then scans left and right from that offset to extract the full token (letters, digits, underscores).

Lookup

The extracted token is looked up in the FUNCTIONS map. If found, the tooltip shows the full function signature on the first line and the one-line description below it. Keywords and user variables do not show a tooltip.

Positioning

The tooltip is absolutely positioned just above the hovered token using the bounding rect of the hovered element. It clamps to the viewport edges to avoid overflow.

Soma Logger

The logger panel (SomaLogger.tsx) displays the output of all print() calls executed during the most recent graph evaluation. It is rendered beneath the script editor inside the node property panel.

Array display

Arrays are shown up to 8 elements: [1.2, 3.4, 5.6, … (100 total)]. Scalars are formatted with up to 6 decimal places; trailing zeros are trimmed. NaN and Infinity are displayed literally.

Log lifecycle

Logs are cleared at the start of each evaluation and re-populated as the script runs. The panel shows a scrollable list of all print() calls in source order. Each row includes the argument labels (the source-text of each argument) alongside the computed values.

Execution PipelineEnd of Soma documentation