diff --git a/spx-gui/src/components/editor/code-editor/CodeEditor.vue b/spx-gui/src/components/editor/code-editor/CodeEditor.vue
new file mode 100644
index 00000000..bef6071c
--- /dev/null
+++ b/spx-gui/src/components/editor/code-editor/CodeEditor.vue
@@ -0,0 +1,55 @@
+
+
+
+ TODO
+
+
+
diff --git a/spx-gui/src/components/editor/code-editor/common.ts b/spx-gui/src/components/editor/code-editor/common.ts
new file mode 100644
index 00000000..f8ff23e3
--- /dev/null
+++ b/spx-gui/src/components/editor/code-editor/common.ts
@@ -0,0 +1,140 @@
+export type Position = {
+ line: number
+ column: number
+}
+
+export type IRange = {
+ start: Position
+ end: Position
+}
+
+export type ResourceIdentifier = {
+ uri: string
+}
+
+export type TextDocumentIdentifier = {
+ uri: string
+}
+
+export type TextDocumentPosition = {
+ textDocument: TextDocumentIdentifier
+ position: Position
+}
+
+export type TextDocumentRange = {
+ textDocument: TextDocumentIdentifier
+ range: IRange
+}
+
+export type CodeSegment = {
+ range: IRange
+ content: string
+}
+
+export enum DefinitionKind {
+ /** General function or method */
+ Function,
+ /** Function or method for reading data */
+ Read,
+ /** Function or method for causing effect, e.g., writing data */
+ Effect,
+ /** Function or method for listening to event */
+ Listen,
+ /** Language defined statements, e.g., `for { ... }` */
+ Statement,
+ /** Variable or field definition */
+ Variable,
+ /** Constant definition */
+ Constant,
+ /** Package definition */
+ Package
+}
+
+export type DefinitionIdentifier = {
+ /**
+ * Full name of source package.
+ * If not provided, it's assumed to be kind-statement.
+ * If `main`, it's the current user package.
+ * Exmples:
+ * - `fmt`
+ * - `github.com/goplus/spx`
+ * - `main`
+ */
+ package?: string
+ /**
+ * Exported name of the definition.
+ * If not provided, it's assumed to be kind-package.
+ * Examples:
+ * - `Println`
+ * - `Sprite`
+ * - `Sprite.turn`
+ * - `for_statement_with_single_condition`: kind-statement
+ */
+ name?: string
+ /** Index in overloads. */
+ overloadIndex?: number
+}
+
+/**
+ * Model for text document
+ * Similar to https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.ITextModel.html
+ */
+interface TextDocument {
+ id: TextDocumentIdentifier
+ getOffsetAt(position: Position): number
+ getPositionAt(offset: number): Position
+ getValueInRange(range: IRange): string
+}
+
+export type MarkdownString = {
+ /** Markdown string with MDX support. */
+ value: string
+}
+
+export type Icon = string
+
+/**
+ * Documentation for an identifier, keyword, etc. Typically:
+ * ```mdx
+ * func turn(dDirection float64)
+ *
+ * Turn with given direction change.
+ *
+ * ```
+ */
+export type Documentation = MarkdownString
+
+export type Action = {
+ title: string
+ command: Command
+ arguments: I
+}
+
+export type BaseContext = {
+ /** Current active text document */
+ textDocument: TextDocument
+ /** Signal to abort long running operations */
+ signal: AbortSignal
+}
+
+export type Command = string
+export type CommandHandler = (...args: A) => Promise
+export type CommandInfo = {
+ icon: Icon
+ title: string
+ handler: CommandHandler
+}
+
+export type TextEdit = {
+ range: Range
+ newText: string
+}
+
+export type WorkspaceEdit = {
+ changes?: { [uri: string]: TextEdit[] }
+}
+
+// const builtInCommandCopilotChat: Command<[ChatTopic], void> = 'spx.copilot.chat'
+// const builtInCommandGoToDefinition: Command<[TextDocumentPosition], void> = 'spx.goToDefinition'
+// const builtInCommandRename: Command<[TextDocumentPosition], void> = 'spx.rename'
+// const builtInCommandResourceReferenceModify: Command<[TextDocumentRange, ResourceIdentifier], void> = 'spx.resourceReference.modify'
diff --git a/spx-gui/src/components/editor/code-editor/copilot.ts b/spx-gui/src/components/editor/code-editor/copilot.ts
new file mode 100644
index 00000000..66ff94cb
--- /dev/null
+++ b/spx-gui/src/components/editor/code-editor/copilot.ts
@@ -0,0 +1,8 @@
+import type { Chat, ChatContext, ChatMessage, ICopilot } from './ui/copilot'
+
+export class Copilot implements ICopilot {
+ async getChatCompletion(ctx: ChatContext, chat: Chat): Promise {
+ console.warn('TODO', ctx, chat)
+ return null
+ }
+}
diff --git a/spx-gui/src/components/editor/code-editor/document-base.ts b/spx-gui/src/components/editor/code-editor/document-base.ts
new file mode 100644
index 00000000..260213e3
--- /dev/null
+++ b/spx-gui/src/components/editor/code-editor/document-base.ts
@@ -0,0 +1,9 @@
+import type { DefinitionIdentifier } from './common'
+import type { APIReferenceItem } from './ui/api-reference'
+
+export class DocumentBase {
+ async getDocumentaion(definition: DefinitionIdentifier): Promise {
+ console.warn('TODO', definition)
+ return null
+ }
+}
diff --git a/spx-gui/src/components/editor/code-editor/lsp.ts b/spx-gui/src/components/editor/code-editor/lsp.ts
new file mode 100644
index 00000000..3c7e1e98
--- /dev/null
+++ b/spx-gui/src/components/editor/code-editor/lsp.ts
@@ -0,0 +1,41 @@
+import type { Disposer } from '@/utils/disposable'
+
+// TODO: use implementation from `/tools/spxls/client.ts`
+export class Spxlc {
+ /**
+ * Sends a request to the language server and waits for response.
+ * @param method LSP method name.
+ * @param params Method parameters.
+ * @returns Promise that resolves with the response.
+ */
+ request(method: string, params?: any): Promise {
+ console.warn('TODO', method, params)
+ return null as any
+ }
+
+ /**
+ * Sends a notification to the language server (no response expected).
+ * @param method LSP method name.
+ * @param params Method parameters.
+ */
+ notify(method: string, params?: any): void {
+ console.warn('TODO', method, params)
+ }
+
+ /**
+ * Registers a handler for server notifications.
+ * @param method LSP method name.
+ * @param handler Function to handle the notification.
+ */
+ onNotification(method: string, handler: (params: any) => void): Disposer {
+ console.warn('TODO', method, handler)
+ return () => {}
+ }
+
+ /**
+ * Cleans up client resources.
+ */
+ dispose(): void {
+ console.warn('TODO')
+ }
+}
diff --git a/spx-gui/src/components/editor/code-editor/runtime.ts b/spx-gui/src/components/editor/code-editor/runtime.ts
new file mode 100644
index 00000000..bca8172d
--- /dev/null
+++ b/spx-gui/src/components/editor/code-editor/runtime.ts
@@ -0,0 +1,23 @@
+import Emitter from '@/utils/emitter'
+import type { TextDocumentRange } from './common'
+
+enum RuntimeOutputKind {
+ Error,
+ Log
+}
+
+interface RuntimeOutput {
+ kind: RuntimeOutputKind
+ time: number
+ message: string
+ source: TextDocumentRange
+}
+
+export class Runtime extends Emitter<{
+ didChangeOutput: []
+}> {
+ async getOutput(): Promise {
+ console.warn('TODO')
+ return []
+ }
+}
diff --git a/spx-gui/src/components/editor/code-editor/ui/api-reference.ts b/spx-gui/src/components/editor/code-editor/ui/api-reference.ts
new file mode 100644
index 00000000..83dae350
--- /dev/null
+++ b/spx-gui/src/components/editor/code-editor/ui/api-reference.ts
@@ -0,0 +1,20 @@
+import {
+ DefinitionKind,
+ type BaseContext,
+ type Documentation,
+ type Position,
+ type DefinitionIdentifier
+} from '../common'
+
+export type APIReferenceItem = {
+ kind: DefinitionKind
+ definition: DefinitionIdentifier
+ insertText: string
+ documentation: Documentation
+}
+
+export type APIReferenceContext = BaseContext
+
+export interface IAPIReferenceProvider {
+ provideAPIReference(ctx: APIReferenceContext, position: Position): Promise
+}
diff --git a/spx-gui/src/components/editor/code-editor/ui/completion.ts b/spx-gui/src/components/editor/code-editor/ui/completion.ts
new file mode 100644
index 00000000..75915c88
--- /dev/null
+++ b/spx-gui/src/components/editor/code-editor/ui/completion.ts
@@ -0,0 +1,15 @@
+import { DefinitionKind, type BaseContext, type Documentation, type Position } from '../common'
+
+export type CompletionContext = BaseContext
+
+export type CompletionItemKind = DefinitionKind
+
+export type CompletionItem = {
+ label: string
+ kind: CompletionItemKind
+ documentation: Documentation
+}
+
+export interface ICompletionProvider {
+ provideCompletion(ctx: CompletionContext, position: Position): Promise
+}
diff --git a/spx-gui/src/components/editor/code-editor/ui/context-menu.ts b/spx-gui/src/components/editor/code-editor/ui/context-menu.ts
new file mode 100644
index 00000000..29eb3d28
--- /dev/null
+++ b/spx-gui/src/components/editor/code-editor/ui/context-menu.ts
@@ -0,0 +1,14 @@
+import { type Action, type BaseContext, type IRange, type Position } from '../common'
+
+export type ContextMenuContext = BaseContext
+
+export type Selection = IRange
+
+export type MenuItem = {
+ action: Action
+}
+
+export interface IContextMenuProvider {
+ provideContextMenu(ctx: ContextMenuContext, position: Position): Promise