Skip to content

Commit

Permalink
add new code editor
Browse files Browse the repository at this point in the history
  • Loading branch information
nighca committed Nov 20, 2024
1 parent 92cf881 commit 78fc2e2
Show file tree
Hide file tree
Showing 21 changed files with 889 additions and 45 deletions.
114 changes: 114 additions & 0 deletions spx-gui/src/components/editor/code-editor/CodeEditor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<script setup lang="ts">
import { computed } from 'vue'
import Emitter from '@/utils/emitter'
import { useEditorCtx } from '../EditorContextProvider.vue'
import { Copilot } from './copilot'
import { DocumentBase } from './document-base'
import { Spxlc } from './lsp'
import { Runtime } from './runtime'
import {
CodeEditorUIComp,
type ICodeEditorUI,
type Diagnostic,
type DiagnosticsContext,
type IDiagnosticsProvider,
type IResourceReferencesProvider,
type ResourceReference,
type ResourceReferencesContext
} from './ui'
const editorCtx = useEditorCtx()
function initialize(ui: ICodeEditorUI) {
const copilot = new Copilot()
const documentBase = new DocumentBase()
const spxlc = new Spxlc()
const runtime = new Runtime()
ui.registerAPIReferenceProvider({
async provideAPIReference(ctx, position) {
console.warn('TODO', ctx, position, documentBase, spxlc)
return []
}
})
ui.registerCompletionProvider({
async provideCompletion(ctx, position) {
console.warn('TODO', ctx, position)
return []
}
})
ui.registerContextMenuProvider({
async provideContextMenu(ctx, position) {
console.warn('TODO', ctx, position)
return []
},
async provideSelectionContextMenu(ctx, selection) {
console.warn('TODO', ctx, selection)
return []
}
})
ui.registerCopilot(copilot)
class DiagnosticsProvider
extends Emitter<{
didChangeDiagnostics: []
}>
implements IDiagnosticsProvider
{
async provideDiagnostics(ctx: DiagnosticsContext): Promise<Diagnostic[]> {
console.warn('TODO', ctx, runtime)
return []
}
}
ui.registerDiagnosticsProvider(new DiagnosticsProvider())
ui.registerFormattingEditProvider({
async provideDocumentFormattingEdits(ctx) {
console.warn('TODO', ctx)
return []
}
})
ui.registerHoverProvider({
async provideHover(ctx, position) {
console.warn('TODO', ctx, position)
return null
}
})
class ResourceReferencesProvider
extends Emitter<{
didChangeResourceReferences: []
}>
implements IResourceReferencesProvider
{
async provideResourceReferences(ctx: ResourceReferencesContext): Promise<ResourceReference[]> {
console.warn('TODO', ctx)
return []
}
}
ui.registerResourceReferencesProvider(new ResourceReferencesProvider())
}
const editorKey = computed(() => {
const selected = editorCtx.project.selected
if (selected?.type === 'stage') return 'stage'
if (selected?.type === 'sprite') return `sprite:${selected.id}`
return ''
})
defineExpose({
async format() {
console.warn('TODO')
}
})
</script>

<template>
<CodeEditorUIComp :key="editorKey" :project="editorCtx.project" @initialize="initialize" />
</template>
20 changes: 20 additions & 0 deletions spx-gui/src/components/editor/code-editor/FormatButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<template>
<UIButton type="boring" :loading="handleFormat.isLoading.value" @click="handleFormat.fn">
{{ $t({ en: 'Format', zh: '格式化' }) }}
</UIButton>
</template>

<script setup lang="ts">
import { UIButton } from '@/components/ui'
import { useMessageHandle } from '@/utils/exception'
import type CodeEditor from './CodeEditor.vue'
const props = defineProps<{
codeEditor: InstanceType<typeof CodeEditor>
}>()
const handleFormat = useMessageHandle(() => props.codeEditor.format(), {
en: 'Failed to format',
zh: '格式化失败'
})
</script>
143 changes: 143 additions & 0 deletions spx-gui/src/components/editor/code-editor/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
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
* <Overview>func turn(dDirection float64)</Overview>
* <Detail>
* Turn with given direction change.
* </Detail>
* ```
*/
export type Documentation = MarkdownString

// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface CommandConstraint<A, R> {}

export type Command<A extends any[], R> = string & CommandConstraint<A, R>
export type CommandHandler<A extends any[], R> = (...args: A) => Promise<R>
export type CommandInfo<A extends any[], R> = {
icon: Icon
title: string
handler: CommandHandler<A, R>
}

export type Action<I extends any[] = any, R = any> = {
title: string
command: Command<I, R>
arguments: I
}

export type BaseContext = {
/** Current active text document */
textDocument: TextDocument
/** Signal to abort long running operations */
signal: AbortSignal
}

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'
8 changes: 8 additions & 0 deletions spx-gui/src/components/editor/code-editor/copilot.ts
Original file line number Diff line number Diff line change
@@ -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<ChatMessage | null> {
console.warn('TODO', ctx, chat)
return null
}
}
9 changes: 9 additions & 0 deletions spx-gui/src/components/editor/code-editor/document-base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { DefinitionIdentifier } from './common'
import type { APIReferenceItem } from './ui/api-reference'

export class DocumentBase {
async getDocumentaion(definition: DefinitionIdentifier): Promise<APIReferenceItem | null> {
console.warn('TODO', definition)
return null
}
}
41 changes: 41 additions & 0 deletions spx-gui/src/components/editor/code-editor/lsp.ts
Original file line number Diff line number Diff line change
@@ -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<T>(method: string, params?: any): Promise<T> {
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')
}
}
23 changes: 23 additions & 0 deletions spx-gui/src/components/editor/code-editor/runtime.ts
Original file line number Diff line number Diff line change
@@ -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<RuntimeOutput[]> {
console.warn('TODO')
return []
}
}
Loading

0 comments on commit 78fc2e2

Please sign in to comment.