Kortex is an ultra-fast, hardware-accelerated code and text editor component built for Kotlin Multiplatform and Compose Multiplatform (targeting Desktop JVM, Android, iOS, and WebAssembly / WASM-JS).
Engineered from the ground up for high throughput, sub-millisecond input latency, and locked 60+ FPS scrolling across
massive documents, Kortex bypasses standard composable layout tree overhead by coupling a persistent Red-Black Rope
text buffer (Krope) directly with a custom virtualized Skia/Skiko direct-canvas
rendering pipeline and a deterministic unidirectional MVI state reducer.
- Why Kortex?
- Key Features
- Architecture & Design Principles
- Module Structure
- Supported Platforms
- Gradle Installation
- Quick Start Guide
- Deep Dive & Customization
- Architecture & API Cheatsheet
- License
Standard text composables in declarative frameworks (like Jetpack Compose BasicTextField or BasicTextField2)
represent text through nested UI nodes, complex layout measurers, and frequent heap allocations during typing or
scrolling. When handling source files with tens of thousands of lines, multi-cursors, syntax highlighting, or rapid
typing, this architecture inevitably suffers from GC pauses, frame drops, and latency spikes.
Kortex eliminates this overhead:
-
No Composable-Per-Line: Text layout, line numbers, selection highlights, carets, diagnostic squiggles, and indent
guides are rendered in unified, virtualized drawing passes directly to a Skia
Canvas. -
Logarithmic Text Mutations: Backed by Krope, text operations (insert, delete, slice, batch) run
in
$O (\log N)$ time with persistent structural sharing and zero full-buffer copies. -
Pure Deterministic Reducer Loop: UI events never mutate internal state directly. Everything passes through a
thread-safe unidirectional MVI loop (
(EditorState, EditorAction) -> EditorResult). -
Zero-Allocation Hot Paths: Skia
Paragraph,TextLine, andPaintobjects are managed by thread-safe, size-bounded LRU caches.
- ⚡ Direct Skia Hardware Acceleration: Bypasses Compose UI node overhead using Skiko canvas operations
(
drawWithCache,nativeCanvas,SkikoSurfaceView). - 🌲 Backed by Krope Buffer: Instantaneous navigation and edits on 100K+ line documents with sub-millisecond execution.
- 👥 Native Multi-Cursor & Multi-Selection: Fully interactive multiple carets via
Alt + Click,Alt + Drag, and VS Code-styleSelect Next Match(Ctrl+D/Cmd+D) with automatic overlap coalescing. - 🎨 Hierarchical Token Theming: Flexible syntax theming with automatic dot-separated fallback resolution (e.g.,
keyword.controlautomatically falls back tokeyword). - 🔍 Language Tooling Engine:
- Asynchronous, debounced tokenization pipeline (50ms debounce).
- Background diagnostics engine (300ms debounce) with sine-wave / zigzag squiggle rendering for Errors, Warnings, Information, and Hints.
- Floating auto-completion popup with keyboard navigation (
Up/Down,Enter/Tab,Escape). - Hover documentation popups via
DocumentationProvider(debounced, resolved against the caret offset). - Active caret symbol occurrence highlighting.
- 📂 Logarithmic Code Folding (
FoldMap): Cumulative line interval trees delivering$O (\log K)$ binary search conversions between document lines and visual lines. - ⏱️ Debounced Undo/Redo History: Automatic contiguous typing/backspacing grouping within a 700ms sliding window with exact selection and cursor restoration.
- ⌨️ Extensible Keymap DSL: Modular shortcut configuration with platform-aware primary modifiers (
Ctrlon Linux/Windows,Cmdon macOS) and read-only protections. - 🖥️ Desktop-Ready UX: Native Compose scrollbar adapters, right-click contextual menu (Cut, Copy, Paste, Undo, Redo, Select All), column rulers, and line gutter events.
flowchart LR
subgraph CORE[":kortex-core<br/>headless"]
E1["EditorEngine<br/>EditorState · Caret · Selection<br/>FoldMap · HistoryManager<br/>EditorReducer"]
end
subgraph CORE_COMPOSE[":kortex-core-compose"]
E2["SkiaCanvas<br/>EditorFont · FontManager<br/>EditorScrollController<br/>EditorInputArea · Keymaps<br/>KortexEditor"]
end
subgraph CODE[":kortex-code"]
E3["CodeEditorEngine<br/>LanguageAnalysisService<br/>Token · Diagnostics<br/>Completion · Documentation"]
end
subgraph CODE_COMPOSE[":kortex-code-compose"]
E4["KortexCodeEditor<br/>syntax layers · folding markers<br/>column ruler<br/>completion overlay"]
end
KROPE[("Krope<br/>RopeTextBuffer")]
UI(["Compose UI"])
UI --> CODE_COMPOSE
CODE_COMPOSE --> CODE
CODE_COMPOSE --> CORE_COMPOSE
CODE --> CORE
CORE_COMPOSE --> CORE
CORE --> KROPE
- Unidirectional State Flow: The UI observes an immutable
StateFlow<EditorState>orStateFlow<CodeEditorState>. UI gestures emit actions, which reducers map to state updates and side-effect commands (EditorCommand.ApplyText,Undo,Redo). - Buffer / State Decoupling: Text mutations are dispatched as
TextOperation.DatatoRopeTextBuffer. Edit deltas (TextEdit.Data) map back through surviving selections and carets without shifting or desynchronizing editor state. - Multi-Level LRU Cache System: Bounded LRU caches for
Paragraph,TextLine, andPaintprevent GC pressure and allocations during continuous 60 FPS scrolling and fast typing.
| Cache | Default capacity | Key |
|---|---|---|
ParagraphCache |
1000 | (text, font, textColor) |
CodeParagraphCache |
1000 | (line, text, tokens, font, syntaxTheme) |
TextLineCache |
1000 | (text, font) — used for gutter line numbers |
PaintCache |
1000 | (color, isAntiAlias, mode, strokeWidth) |
All caches implement LruCache and are closed automatically when the composable leaves composition (DisposableEffect
in rememberLayerFactory / rememberCodeLayerFactory).
| Module | Maven Artifact | Description |
|---|---|---|
:kortex-core |
io.github.numq.kortex:core:<version> |
Headless editor primitives: EditorEngine, EditorState, Caret, Selection, FoldMap, HistoryManager, and functional reducers. Zero Compose dependencies. |
:kortex-core-compose |
io.github.numq.kortex:core-compose:<version> |
Skia canvas rendering integration (SkiaCanvas), EditorFont, FontManager, EditorScrollController, EditorInputArea, keymaps, and default context menus. |
:kortex-code |
io.github.numq.kortex:code:<version> |
Code abstractions: CodeEditorEngine, LanguageAnalysisService, tokenization models, diagnostics, occurrence tracking, and completion. |
:kortex-code-compose |
io.github.numq.kortex:code-compose:<version> |
High-level KortexCodeEditor composable, syntax highlighting layers, folding markers, column ruler, and code completion overlays. |
Kortex leverages Kotlin Multiplatform and Skiko/Skia bindings:
| Platform | Target Identifier | Architecture / Runtime |
|---|---|---|
| JVM (Desktop) | jvm |
macOS, Windows, Linux (Java 17+) |
| Android | android |
API 24+ via SkikoSurfaceView |
| iOS | iosX64, iosArm64, iosSimulatorArm64 |
Native Skia canvas rendering |
| WebAssembly | wasmJs |
Modern browsers supporting WebAssembly & WebGL/WebGPU |
Add mavenCentral() to your repositories:
repositories {
mavenCentral()
}Add dependencies to your build.gradle.kts in your multiplatform commonMain or platform source sets:
dependencies {
// Core text manipulation engine (Headless)
implementation("io.github.numq.kortex:core:1.0.0")
// Compose Multiplatform basic Skia text editor
implementation("io.github.numq.kortex:core-compose:1.0.0")
// Code editor engine & language analysis abstractions
implementation("io.github.numq.kortex:code:1.0.0")
// Complete Compose Multiplatform IDE code editor (Includes syntax, folding, completions)
implementation("io.github.numq.kortex:code-compose:1.0.0")
// Krope buffer dependency (Required for buffer initialization)
implementation("io.github.numq.krope:text:1.1.1")
}Requires
:kortex-core-compose(and:kortex-coretransitively).
If you need a lightweight, high-performance text buffer editor without code-specific tooling:
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import io.github.numq.kortex.core.EditorEngine
import io.github.numq.kortex.core.compose.KortexEditor
import io.github.numq.kortex.core.compose.font.EditorFont
import io.github.numq.kortex.core.compose.theme.EditorTheme
import io.github.numq.krope.core.Encoding
import io.github.numq.krope.text.RopeTextBuffer
import io.github.numq.krope.text.TextLineEnding
import org.jetbrains.skia.FontMgr
import org.jetbrains.skia.FontStyle
import org.jetbrains.skia.Typeface
@Composable
fun SimpleEditor() {
val scope = rememberCoroutineScope()
val buffer = remember {
RopeTextBuffer(
initialText = "Welcome to Kortex Plain Text Editor!",
initialLineEnding = TextLineEnding.LF,
initialEncoding = Encoding.UTF8,
enablePooling = true
)
}
val engine = remember(buffer) { EditorEngine(scope = scope, buffer = buffer) }
val font = remember {
val typeface = FontMgr.default.matchFamilyStyle("JetBrains Mono", FontStyle.NORMAL)
?: FontMgr.default.matchFamilyStyle(null, FontStyle.NORMAL)
?: Typeface.makeEmpty()
EditorFont(typeface = typeface, size = 14f, lineSpacing = 1.2f)
}
val theme = remember {
EditorTheme.fromColorScheme(androidx.compose.material3.MaterialTheme.colorScheme)
}
KortexEditor(engine = engine, font = font, theme = theme, modifier = Modifier.fillMaxSize())
}Requires
:kortex-code-compose(pulls:kortex-codeand:kortex-core-composetransitively).
Setting up KortexCodeEditor with line numbers, code folding, scrollbars, and syntax rendering:
import androidx.compose.foundation.HorizontalScrollbar
import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import io.github.numq.kortex.code.CodeEditorEngine
import io.github.numq.kortex.code.analysis.LanguageAnalysisService
import io.github.numq.kortex.code.compose.KortexCodeEditor
import io.github.numq.kortex.code.compose.theme.CodeEditorTheme
import io.github.numq.kortex.core.EditorEngine
import io.github.numq.kortex.core.compose.font.EditorFont
import io.github.numq.kortex.core.compose.scroll.rememberEditorScrollController
import io.github.numq.kortex.core.compose.scrollbar.rememberHorizontalScrollbarAdapter
import io.github.numq.kortex.core.compose.scrollbar.rememberVerticalScrollbarAdapter
import io.github.numq.krope.core.Encoding
import io.github.numq.krope.text.RopeTextBuffer
import io.github.numq.krope.text.TextLineEnding
import org.jetbrains.skia.FontMgr
import org.jetbrains.skia.FontStyle
import org.jetbrains.skia.Typeface
@Composable
fun FullCodeEditorScreen() {
val scope = rememberCoroutineScope()
// 1. Persistent Krope buffer
val buffer = remember {
RopeTextBuffer(
initialText = """
package example
fun main() {
println("Hello from Kortex Skia Code Editor!")
}
""".trimIndent(),
initialLineEnding = TextLineEnding.LF,
initialEncoding = Encoding.UTF8,
enablePooling = true
)
}
// 2. Instantiate engines
val editorEngine = remember(buffer) { EditorEngine(scope = scope, buffer = buffer) }
val codeEngine = remember(editorEngine) {
CodeEditorEngine(
scope = scope,
engine = editorEngine,
analysisService = object : LanguageAnalysisService {} // Plug custom analysis here
)
}
// 3. Configure typography & theme
val font = remember {
val typeface = FontMgr.default.matchFamilyStyle("JetBrains Mono", FontStyle.NORMAL)
?: FontMgr.default.matchFamilyStyle(null, FontStyle.NORMAL)
?: Typeface.makeEmpty()
EditorFont(typeface = typeface, size = 14f, lineSpacing = 1.25f)
}
val isDark = isSystemInDarkTheme()
val colorScheme = if (isDark) darkColorScheme() else lightColorScheme()
val theme = remember(colorScheme, isDark) { CodeEditorTheme.fromColorScheme(colorScheme, isDark) }
val scrollController = rememberEditorScrollController()
MaterialTheme(colorScheme = colorScheme) {
Box(modifier = Modifier.fillMaxSize()) {
KortexCodeEditor(
engine = codeEngine,
font = font,
theme = theme,
scrollController = scrollController,
columnRuler = 80,
modifier = Modifier.fillMaxSize()
)
// Desktop vertical scrollbar
if (scrollController.canScrollVertically) {
VerticalScrollbar(
adapter = rememberVerticalScrollbarAdapter(scrollController),
modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight()
)
}
// Desktop horizontal scrollbar aligned with text content area
if (scrollController.canScrollHorizontally) {
val density = LocalDensity.current
val gutterPadding = with(density) { scrollController.gutterWidth.toDp() }
HorizontalScrollbar(
adapter = rememberHorizontalScrollbarAdapter(scrollController),
modifier = Modifier
.align(Alignment.BottomStart)
.fillMaxWidth()
.padding(start = gutterPadding)
)
}
}
}
}To equip Kortex with syntax highlighting, diagnostics, autocompletion, and hover information, provide an implementation
of LanguageAnalysisService:
val customLanguageService = object : LanguageAnalysisService {
override val tokenProvider = TokenProvider { snapshot, range ->
// Return Map<Int, List<Token>> (Key = Line Index)
// Executed asynchronously in background (debounced at 50ms)
parseSyntaxTokens(snapshot, range)
}
override val diagnosticsProvider = DiagnosticsProvider { snapshot ->
// Return List<CodeIssue>
// Executed asynchronously in background (debounced at 300ms)
runLinter(snapshot)
}
override val completionProvider = CompletionProvider { snapshot, offset ->
// Return List<CodeSuggestion>
// Triggered via Ctrl+Space / Cmd+Space or typing triggers
computeAutocompletions(snapshot, offset)
}
override val documentationProvider = DocumentationProvider { snapshot, offset ->
// Return CodeDocumentation(content, range)
computeHoverDocumentation(snapshot, offset)
}
}Debounce intervals (50ms for tokens, 300ms for diagnostics) are fixed in
CodeEditorEngine. Providers run onDispatchers.Defaultand receive the latestTextSnapshotrevision only - stale results are dropped viadistinctUntilChangedBy(TextSnapshot::revision).
Tokens are mapped by zero-based line indices using TextRange and TokenType:
// A TokenProvider receives `range = null` (whole document) or a specific TextRange
// for incremental re-tokenization after an edit.
val token = Token(
range = TextRange(
start = TextPosition(line = 0, column = 0),
end = TextPosition(line = 0, column = 7)
),
type = TokenType.KEYWORD
)Built-in token types include:
TokenType.KEYWORD,TokenType.KEYWORD_CONTROLTokenType.TYPE,TokenType.TYPE_BUILTINTokenType.FUNCTION,TokenType.FUNCTION_METHODTokenType.VARIABLE,TokenType.VARIABLE_PARAMETERTokenType.STRING,TokenType.NUMBERTokenType.COMMENT,TokenType.COMMENT_LINE,TokenType.COMMENT_BLOCKTokenType.OPERATOR,TokenType.PUNCTUATION,TokenType.PUNCTUATION_BRACKET,TokenType.PUNCTUATION_DELIMITERTokenType.MARKUP_LINK,TokenType.DEFAULT
Issues are rendered as smooth, high-precision sine-wave underline paths on the Skia canvas:
val compilerError = CodeIssue.Error(
range = TextRange(
start = TextPosition(line = 1, column = 4),
end = TextPosition(line = 1, column = 16)
),
message = "Unresolved reference: unresolvedMethod",
source = "Compiler",
code = "E0042"
)Available issue types: CodeIssue.Error, CodeIssue.Warning, CodeIssue.Information, CodeIssue.Hint,
CodeIssue.Unknown.
val suggestion = CodeSuggestion(
label = "calculateMetrics(buffer)",
kind = CodeSuggestion.Kind.FUNCTION,
text = "calculateMetrics()",
detail = "Result",
documentation = "Analyzes the persistent rope buffer and computes structural metrics."
)If range is provided, applying the suggestion replaces that range (useful for partial word completion); otherwise
it inserts text at the caret anchor. This is handled by CodeEditorAction.Completion.Apply →
TextOperation.Data.Single.Replace / .Insert.
Popup controls when visible:
DirectionDown/DirectionUp: Navigate items.Enter/Tab: Apply selected completion.Escape: Dismiss overlay.
val docs = CodeDocumentation(
content = "RopeTextBuffer: Thread-safe, balanced Red-Black tree buffer.",
// `range` is optional - omit it to attach documentation to the whole document
range = TextRange(
start = TextPosition(line = 0, column = 5),
end = TextPosition(line = 0, column = 19)
)
)Kortex themes use a hierarchical dot-separated resolver. When styling keyword.control, if no explicit style exists, it
automatically inherits from keyword:
val mySyntaxTheme = SyntaxTheme.build(
defaultText = Color(0xFFABB2BF)
) {
put(TokenType.KEYWORD.name, Color(0xFFC678DD))
put(TokenType.KEYWORD_CONTROL.name, Color(0xFFE06C75))
put(TokenType.FUNCTION.name, Color(0xFF61AFEF))
put(TokenType.STRING.name, Color(0xFF98C379))
put(TokenType.NUMBER.name, Color(0xFFD19A66))
put(TokenType.COMMENT.name, Color(0xFF5C6370))
}
val editorTheme = CodeEditorTheme(
editor = EditorTheme.fromColorScheme(colorScheme),
syntax = mySyntaxTheme,
diagnostics = DiagnosticsTheme(
error = Color(0xFFE06C75),
warning = Color(0xFFE5C07B),
info = Color(0xFF61AFEF),
hint = Color(0xFF5C6370)
),
occurrences = OccurrencesTheme(
matchBackground = Color(0xFF3E4451).copy(alpha = 0.5f),
currentMatchBackground = Color(0xFF528BFF).copy(alpha = 0.4f)
),
folding = FoldingTheme(
iconColor = Color(0xFF61AFEF)
),
ruler = Color(0xFF3E4451).copy(alpha = 0.3f)
)Extend or customize keyboard behaviors using buildEditorKeymap:
val myCustomKeymap = buildEditorKeymap(baseKeymap = DefaultCodeEditorKeymap.create(codeEngine)) {
// Quick Save: Ctrl+S / Cmd+S
bind(Key.S, primary = true, description = "Save File") {
val content = state.snapshot.text
saveToFile(content)
}
// Move current line up: Alt+Up
bind(Key.DirectionUp, alt = true, requiresEditable = true, description = "Move Line Up") {
dispatch(EditAction.MoveLineUp)
}
// Format document: Ctrl+Alt+L
bind(Key.L, primary = true, alt = true, requiresEditable = true, description = "Reformat Code") {
formatSource()
}
}| Shortcut (PC / Mac) | Action Description |
|---|---|
Ctrl+A / Cmd+A |
Select entire document |
Ctrl+D / Cmd+D |
Select word / Add next occurrence match (Multi-Cursor) |
Alt + Click / Drag |
Add independent cursor / rectangular selection drag |
Ctrl+Z / Cmd+Z |
Undo |
Ctrl+Shift+Z / Cmd+Shift+Z |
Redo |
Ctrl+C / Ctrl+X / Ctrl+V |
Copy / Cut / Paste (works with multi-cursors) |
Ctrl+Shift+D |
Duplicate current line or selection |
Alt + Up / Down |
Move current line or block up / down |
Tab / Shift+Tab |
Indent / Unindent selected block or insert soft tabs |
Ctrl+/ / Cmd+/ |
Toggle line comment (// ) |
Ctrl+Space |
Request IntelliSense code completion |
Ctrl+Backspace / Ctrl+Delete |
Delete word left / right |
Home / End |
Smart move to indent start / line end |
Ctrl+Home / Ctrl+End |
Move to document start / document end |
PageUp / PageDown |
Viewport page scrolling with caret tracking |
Escape |
Dismiss completion popup / collapse extra carets |
Kortex easily renders files with hundreds of thousands of lines by rendering only the visible viewport slice:
val foldMap = state.foldMap
val visualLineIndex = foldMap.documentToVisualLine(actualDocumentLine)
val documentLineIndex = foldMap.visualToDocumentLine(visualLineIndex)-
Binary Search Tree Intervals:
FoldMapmanages collapsed regions using interval trees with$O (\log K)$ mapping. - Render Culling: Collapsed lines are completely bypassed during Skia layout passes.
-
Interactive Gutters:
KortexCodeEditorwires gutter clicks toViewportAction.ToggleFolding(line)when aFoldingRegionstarts on that line.KortexEditor(plain text) exposes a genericonGutterClick(line, offset)callback instead — you decide what a gutter click means.
Typing individual characters does not create hundreds of micro undo entries. HistoryManager automatically merges edits
when:
- Edits share the same operation type (
Insert+InsertorDelete+Delete). - Edits occur within 700ms of each other.
- No newline characters are added.
- Carets remain contiguous.
Executing Undo/Redo restores both buffer contents and exact caret/selection states.
Add multiple cursors programmatically or interactively:
// Add cursor at specific location
editorEngine.dispatch(CaretAction.AddCursor(TextPosition(line = 10, column = 4)))
// Add next match to selection
editorEngine.dispatch(SelectionAction.SelectNextMatch)SelectNextMatch behaves like VS Code's Ctrl+D:
- With an empty selection, it selects the word under the caret.
- Otherwise, it searches forward from
primary.range.end, wrapping to the start of the document. - Duplicate matches are coalesced automatically, so repeatedly pressing
Ctrl+Dadvances to the next distinct occurrence.
- Selection Normalization: Overlapping carets and ranges are automatically merged into clean, non-intersecting
intervals via
Selection.normalize(). - Direction Awareness: Preserves anchor and caret orientations (
FORWARD/BACKWARD). - Sticky Column Navigation: Preserves original column offsets when navigating vertically across uneven lines.
Kortex provides adapters for Compose Desktop scrollbars:
EditorScrollController.asVerticalScrollbarAdapter()/rememberVerticalScrollbarAdapter(...)EditorScrollController.asHorizontalScrollbarAdapter()/rememberHorizontalScrollbarAdapter(...)
A built-in Material 3 right-click context menu handles Cut, Copy, Paste, Undo, Redo, and Select All out of the box.
| Class / Interface | Responsibility |
|---|---|
EditorEngine |
Coordinates text buffer operations, history tracking, and reducer dispatch loops. |
CodeEditorEngine |
Extends EditorEngine with asynchronous debounced language analysis pipelines. |
EditorConfig |
Immutable editor preferences: tabSize, useSoftTabs, lineSeparator (derived tabString). |
EditorState |
Immutable snapshot of text snapshot, selections, collapsed ranges, and config. |
CodeEditorState |
Extends EditorState with syntax tokens, lint diagnostics, and completion state. |
EditorFont |
Thread-safe Skia typeface wrapper managing font collections, ascents, descents, and paragraph builders. |
EditorScrollController |
Stable controller handling viewport offsets, bounds, and auto-scrolling to carets. |
FoldMap |
High-performance coordinate mapper between logical lines and visible lines. |
EditorConfig is passed into EditorState.config and can be updated at runtime via
EditorAction.UpdateConfig(EditorConfig(tabSize = 2, useSoftTabs = true)).
CodeEditorState.CompletionState is a sealed hierarchy: Hidden or
Visible(suggestions: List<CodeSuggestion>, selectedIndex: Int, anchorPosition: TextPosition). The selectedItem
property resolves the currently highlighted suggestion.
EditAction:Insert,Paste,Enter,Tab,Untab,Backspace,Delete,WordDeleteLeft,WordDeleteRight,MoveLineUp,MoveLineDown,Duplicate,JoinLinesCaretAction:Move,AddCursor,MoveLeft,MoveRight,MoveUp,MoveDown,MoveWordLeft,MoveWordRight,MoveLineStart,MoveLineEnd,MoveDocStart,MoveDocEnd.SelectionAction:Clear,SelectAll,SelectWordAt,SelectLine,SelectRange,AddSelection,SetSelections,SelectNextMatch.ViewportAction:ToggleFolding,UpdateCollapsedLines,UpdateFoldingRegions.HistoryAction:Undo,Redo.CodeEditorAction:Core,Edit.ToggleComment,External.UpdateSyntax,External.UpdateAnalysis,Completion.Show,Completion.Apply,Completion.Dismiss,Completion.SelectNext,Completion.SelectPrevious.
EditActionandCaretActionmutate the buffer or selection directly.ViewportActiononly rebuildsFoldMap(buffer is untouched).HistoryActionis routed asEditorCommand.Undo/Redoand replays the inverseTextOperation.Datarecorded byHistoryManager.
Apache-2.0 License - see LICENSE file for details.