Script Command Panel
The key model is ScriptCommand.
The command holds two values: ScriptText, a C# script, and
ScriptTitle, the name it runs under. Both are written into
the project file, so the script travels with the project. Run evaluates the text against the
session shell as the globals object, and when the script returns a sequence of actions that sequence
is yielded into the run.
The title is what the mission row and the tree node show: the label is composed as Script [title],
falling back to a bare Script while the title is empty. A newly added command starts empty on both
clients — no title, no text.
The panel renders on the command's own node in the Mission branch of the Execution page's Control Tree, below the move / duplicate / delete control bar. A single-purpose kind like this one embeds its whole editor on that node rather than growing section child nodes. Its tree checkbox only decides whether the run plays the command; a disabled script is skipped during play and its editor stays fully usable.
Layout
- Head Line
- Script Title Text Field
- The model is ScriptTitle.
- Labelled "Title (optional)".
- Autosave Indicator (web)
- Shares the title row. See Saving.
- Script Title Text Field
- Script Editor Area
- The model is ScriptText.
- Fills the rest of the panel.
The Editor
The web editor is CodeMirror 6 running the mission-script mode: a stream tokenizer written for
the dialect a mission script actually is — top-level statements, not a compilation unit. It colours
keywords and boolean literals, strings (plain, verbatim @"", interpolated $"" and $@""),
character literals, hex and floating-point numbers, line and block comments, and it tags a call site
apart from a plain identifier. Because it is linear rather than a parser, every identifier of the
same kind gets the same colour no matter where it sits in the file.
The C# Lezer grammar the client also ships is not what this editor runs: its compilationUnit rule
rejects top-level statements, so it would recover from an error on line 1 and tag identical names
inconsistently. That grammar serves the separate csharp mode a .cs file opens in elsewhere in
the app.
Nothing in the highlighting is semantic. Everything the editor knows about types and members comes from the backend, through completion.
Completion
Typing an identifier — or asking explicitly — sends the whole script text plus the zero-based cursor
offset to POST /api/script/completions, and the response is an items array. Each item carries a
label, a kind already normalised to CodeMirror's own completion type names, a detail (the
signature, shown dim beside the label), a documentation (the XML <summary>, rendered to plain
text by Roslyn and shown in the popup's info pane) and an insertText.
Completion is Roslyn in-process, over the very ScriptOptions — the same references and imports —
that the run compiles the script with, so the session shell's members and the runtime API surface
complete for real rather than by name matching. The service prepends the synthesised
using / using static prefix those imports need and offsets the cursor by its length, so what the
list offers is what the evaluator will see. A cancelled request is answered 499, and a result that
arrives after the editor has moved on is dropped rather than shown.
Picking a method inserts the server-formatted call with each argument as a Tab stop, so the cursor
lands on the first parameter and Tab walks the rest; a method with no parameters inserts as ().
Properties, fields, types and keywords insert as their plain text — a snippet there would be noise.
Saving
There is no Save button. Every keystroke in either field updates the model and schedules one save 500 ms after the last edit, and the pill beside the title reports the state: Idle, Dirty, Staging…, Staged, or Error with the message in its tooltip. Staged means the server runtime holds the value — its tooltip says so, because a staged script is not yet a committed project.
The write is a single PUT carrying the script text, the title and the content hash the last load or save handed back. Each field is applied only when the body carries it. The response returns the new hash to chain into the next save.
Two prompts guard the edges, each with three buttons because neither is a yes/no question:
- Unsaved Changes, raised when the Control Tree tries to move the selection off a command whose save is still pending: Save & switch flushes and then allows the switch, Discard drops the pending write, Cancel keeps the selection where it is.
- Script changed elsewhere, raised when the server rejects the save because the hash no longer matches — another tab, or a project reload, changed the command underneath. The rejection carries the server's current text, title and hash, so the panel can offer Discard & reload, Force overwrite, or Cancel without a second round trip.
The panel snapshots its command path at mount, and the autosave closes over that snapshot rather than over the live selection, so a flush can never land on whichever command happens to be selected when it fires. After each successful save the tree relabels the node in place instead of rebuilding the branch, which would remount the editor mid-edit.
The Compile Gate
A script that does not compile does nothing at run time: the command reports the compile failure into the run's message stream and yields no work, so the run continues without it. Starting a run therefore compile-checks every enabled script first — with the same script options and globals type the evaluator uses — and refuses to start when any of them has an error, naming the first offender's title, path, diagnostic id, message and line. The SPA's Start sends nothing to bypass that check, so the refusal is what a user sees. The same check is also an endpoint of its own, per command and for the whole mission.
Source Code Path
See HiNC App Anatomy for git repository links.
Web Application
HiNC-2025-webservice (Quasar CLI SPA):
wwwroot-src/src/components/mission/ScriptCommandPanel.vue— this panel: the title row sharing its line with the autosave indicator, the editor below it, the 500 ms autosave, the two three-button prompts, the path snapshot taken at mount, and the change notice that relabels the tree node.wwwroot-src/src/components/widgets/TextEditor.vue— the CodeMirror 6 wrapper: language and read-only swapped through compartments, CRLF normalised on the way in so an unedited file cannot dirty the autosave, an opt-in completion source that overrides the language's own, and a widened monospace popup so full signatures fit before the dim detail column truncates.wwwroot-src/src/components/widgets/missionScriptLanguage.ts— themission-scriptmode: the keyword set, the string and character rules, numbers, comments, call-site tagging, and its own highlight style.wwwroot-src/src/components/mission/csharpCompletionSource.ts— the completion source: fires on an identifier prefix or an explicit request, sends the whole document with the cursor offset, drops stale results, and applies every candidate as a snippet.wwwroot-src/src/api/scriptCompletion.ts— the typed wrapper over the completion endpoint and the kind union that maps straight onto CodeMirror's completion types.wwwroot-src/src/api/mission.ts— the script command shape (text, title, content hash), its loader, the save that carries the expected hash, and the conflict error that carries the server's current copy.wwwroot-src/src/composables/useAutoSave.ts— the debounce, the state machine and the conflict recovery behind the panel's saving, saved and error states.wwwroot-src/src/components/widgets/AutoSaveIndicator.vue— the status pill beside the title.wwwroot-src/src/components/controlTree/useControlTreeHost.ts— funnels every selection change through the active panel's switch gate, which is how the Unsaved Changes prompt gets its say.wwwroot-src/src/components/controlTree/missionItemTypes.ts— maps thescriptkind to this panel and gives it no section children, so the whole editor lives on the command node.wwwroot-src/src/i18n/en/mission.ts— the panel's wording: both prompts, their buttons, and the load and save error contexts.Missions/ScriptCompletionController.cs—POST /api/script/completions: a thin wrapper that returns the items, 499 on cancellation and 500 with the message on failure.Missions/ScriptCompletionService.cs— the singleton holding one workspace, built from the same script options the session evaluates with, and the snippet insert text it builds for methods.Missions/ScriptCompileCheckService.cs— compiles a script without running it, and walks the mission for every enabled one; it backs the two compile-check endpoints and the pre-start gate.Missions/MissionController.cs— the single script PUT with its hash-based concurrency, the content hash stamped into the command snapshot, and the two compile-check endpoints.Execution/ExecutionController.cs— the start gate that compile-checks the enabled scripts and refuses to run while one of them has an error.
HiAPI Engine
HiNc/SessionCommands/ScriptCommand.cs— the model: the title and text, the Program-category catalog registration, the XML round-trip that puts the script into the project file, the evaluation against the session shell with a compile failure reported into the run's message stream, the returned actions yielded into the run, and the label rule that brackets the title after the command name.
See Also
- NcCodeCommand Panel — the other command that stores its text in the project, and the one that ships no editor chrome
- The Other Commands — the task this command serves, beside the settings and output kinds
- Mission — the rest of the command panels