This module is KNet's reusable, HTTP-independent source and payload editor. It provides a versioned document, an editor session, delta-based history, search, an additive language SPI, and a virtualized Compose desktop surface. Traffic, API Studio, GraphQL, and scripting may consume it, but their models and behavior must never be moved into this module.
The detailed design and extension guide is the repository-level
docs/code_editor_architecture.md.
- Immutable, versioned document snapshots and position/range/selection coordinates.
- A chunked document implementation that shares unchanged line chunks between versions.
- Directional selection, caret, selection replacement/deletion, editing commands, and one session mutation owner.
- Delta-based bounded undo and redo history.
- Literal and regular-expression find/replace in document coordinates.
- Language registration and optional syntax, folding, indentation, bracket, and comment capabilities.
- Stateful cross-line tokenization and incremental token projection.
- Bounded immediate syntax presentation that keeps unchanged semantic colors stable between a document edit and authoritative background-token convergence.
- Compose state, semantic-token rendering, virtualized lines, keyboard/pointer and IME interaction, folding, bracket auto-closing, language-aware comment toggling, built-in find/replace, clipboard actions, theming, and the controlled-string compatibility facade.
- A structurally stable editor toolbar whose fold-action slot is owned by configuration plus language capability; asynchronous fold results enable or disable its actions without adding, removing, or shifting toolbar controls.
- An ordered consumer-action rendering boundary. Features declare callback-free
CodeEditorHeaderActionvalues with existingEditorCommandId.Customidentities and handle them through the genericCodeEditorActions.onCommanddispatcher. Stable command identities prevent asynchronous enabled-state changes from recreating toolbar nodes. - Viewport-contained word wrapping by default, variable-height logical rows, and wrapped-row-aware caret navigation. Horizontal scrolling remains an explicit standalone-consumer opt-out.
- Cooperative cancellation checkpoints for syntax, folding, and search work.
- HTTP request/response models, traffic capture, API Studio state, GraphQL transport semantics, or script execution.
- Persistence, files, networking, dependency injection, or feature navigation.
- Payload formatting or other format-specific command behavior. Consumers may expose these operations through declarative header actions and the generic custom-command dispatcher.
- Autocomplete, completion providers, or language servers. These are intentionally outside the current scope.
KNetCodeEditor(state, configuration, actions, registry, style)is the scalable primary API.KNetCodeEditor(code, configuration, actions, registry, style)is the controlled-string adapter. It serializes full text only whenCodeEditorActions.onTextChangeis supplied.CodeEditorStateobserves oneEditorSessionwithout copying all lines into Compose state.EditorSession,EditorDocument, andEditorDocumentSnapshotare UI-neutral editing boundaries.EditorLanguageRegistryandEditorLanguageSupportare the additive language extension boundary.EditorSearchEngineandEditorSearchSessionprovide UI-neutral find/replace behavior.EditorCommandDispatchermaps platform-neutral commands to a session and accepts custom handlers.CodeEditorHeaderActionprojects a consumer-ownedEditorCommandId.Custominto the toolbar without teaching editor core the command's format semantics.
Everything under component, gesture, modifier, render, shortcut, and viewport is an
implementation detail. Feature modules must not depend on those packages.
HTTP panel / Traffic / API Studio / Script UI
|
v
codeEditor public API + language SPI
|
+---------+----------+
| |
v v
document/session/search Compose viewport/rendering
The module depends only on Compose, coroutines, and reusable :ui:core desktop facilities. It has
no dependency on KNet domain, traffic, application, engine, storage, or product modules.
- A consumer creates or remembers
CodeEditorState. - UI intent is translated into an
EditorTextEditor session command. Consumer header actions emit their existing custom command identity back throughCodeEditorActions.onCommand. EditorSessionis the only document/history/caret/selection mutation owner.ChunkedEditorDocumentpublishes a new immutable snapshot and exact change delta.- Compose observes the snapshot and immediately projects small syntax changes onto the new version without discarding unchanged token chunks.
- Authoritative syntax, folding, and search work runs on a worker dispatcher using the immutable snapshot and cooperative cancellation.
- Only visible logical lines are composed. Syntax and document projections reuse unchanged chunks.
- Consumers receive exact session events; full-string serialization is explicit and opt-in.
A workspace with multiple logical documents retains one CodeEditorState per document. Switching the presented
state must not replace one session's complete text with another document, because that would discard document-local
history and leak caret or selection ownership between tabs.
Pointer ownership is fixed at the initial primary-button press. A gesture that starts over text keeps selection and auto-scroll ownership even after crossing a scrollbar hit zone; a gesture that starts on a scrollbar remains scrollbar-owned until release. Wrapped viewports do not reserve a bottom scrollbar hit zone because no horizontal scrollbar is rendered.
Viewport selection painting is deterministic before and after a row receives native text-layout data. Selected line breaks occupy one character cell, zero-width range endpoints do not paint, and active-line text input does not publish focus-driven caret changes while a pointer drag selection is in progress. The keyed logical row retains compatible measured geometry while its read-only and editable renderers swap, while pointer-down gesture ownership protects selection from native caret and focus side effects before the selection has non-zero length. Active-line paint depends only on the caret line and remains stable while a selection is created or cleared on that line. Real whitespace remains selectable; unused viewport width never becomes a selection rectangle. The active line reads pointer ownership directly during native callbacks, so a double-click word range cannot be cleared by a line-field caret event later in the same pointer dispatch. After a document range is established, an invisible Compose text-input bridge owns keyboard, dead-key, and IME composition until the active line safely retakes focus. Committed text dispatches the existing typed insertion command and therefore replaces single-line, reverse, multiline, and whole-document ranges through the same session transaction. Both renderer content surfaces retain the gutter's minimum logical-line height even when wrapping is enabled, so selection paint is continuous between logical rows while wrapped content can still grow naturally. Selection paint owns each complete visual-line slot, including the leading around centred text, so adjacent selected lines have no seams while typography, baselines, and vertical line spacing remain unchanged.
The default keyboard adapter provides Ctrl/Cmd+F for find/replace and Ctrl/Cmd+/ for the active
language's line or block comment capability. Ctrl/Cmd+A retains the current viewport while selecting the complete
document; the session still records the correct directional selection and document-end active caret. Partial
selection, search navigation, ordinary caret movement, editing, and drag-selection auto-scroll continue to reveal
their active position. Backspace and Delete first dispatch the typed DeleteSelection command against the live
session. The viewport consumes the key only when that command deletes a non-empty selection; otherwise the focused
line editor retains ordinary character and line-boundary deletion behavior. Select All transfers focus to the
editor input boundary without moving the viewport, so deletion or replacement remains immediately available even
when the document-end caret is not composed.
- Session mutation and listener delivery stay on the owning UI/controller thread.
- Immutable snapshots may be read by background workers.
- A one-line edit copies at most one bounded document chunk plus the outer chunk index.
- History retains changed fragments, not complete document snapshots.
- Incremental syntax retains unchanged token chunks and cross-line lexical state.
- Immediate presentation retokenizes at most 32 changed lines and 32 KiB of changed text on the UI thread; larger edited lines are temporarily unstyled until background convergence.
- The ordinary unfolded viewport uses an identity map without per-line mapping arrays.
- Collapsed-fold mappings allocate only while folds are actually collapsed.
- Word wrapping changes only visual layout. It never inserts document newlines, changes logical line numbers, or alters serialized request, response, GraphQL, or script text.
- Long-running syntax, folding, and search loops invoke cancellation checkpoints every bounded block.
Adding a language is additive:
- Define a
CodeLanguage.Customidentifier. - Provide only the
EditorLanguageSupportcapabilities the language actually needs. - Add the support to a registry using
BuiltInEditorLanguages.registry.with(...). - Pass that registry and language through
CodeEditorConfiguration.
Do not add a central when branch, modify the editor session, or introduce Compose types into a
tokenizer/folding provider.
For a future standalone Kotlin code-editor repository, move the packages as two source sets:
- UI-neutral foundation:
document,session,command,search,language,concurrency, andmodel.CodeLanguage. - Compose adapter:
api,component,gesture,modifier,render,theme, andviewport.
No KNet HTTP, proxy, persistence, or product model migration is required. The only KNet-specific
integration to replace is the small :ui:core clipboard/context-menu/design-token adapter.