Skip to content

Commit

Permalink
DocumentBase & draft of APIReference (goplus#1094)
Browse files Browse the repository at this point in the history
  • Loading branch information
nighca authored Nov 22, 2024
1 parent 5d3f774 commit 4cdb1ad
Show file tree
Hide file tree
Showing 15 changed files with 297 additions and 45 deletions.
11 changes: 9 additions & 2 deletions spx-gui/src/components/editor/code-editor/CodeEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
type ResourceReference,
type ResourceReferencesContext
} from './ui'
import type { DefinitionDocumentationItem, DefinitionIdentifier } from './common'
const editorCtx = useEditorCtx()
Expand All @@ -27,8 +28,14 @@ function initialize(ui: ICodeEditorUI) {
ui.registerAPIReferenceProvider({
async provideAPIReference(ctx, position) {
console.warn('TODO', ctx, position, documentBase, spxlc)
return []
console.warn('TODO: get api references from LS', ctx, position, spxlc)
const ids: DefinitionIdentifier[] = [
{ package: 'github.com/goplus/spx', name: 'onStart' },
{ package: 'github.com/goplus/spx', name: 'Sprite.setXYpos' },
{ name: 'for_iterate (TODO)' }
]
const documentations = await Promise.all(ids.map((id) => documentBase.getDocumentation(id)))
return documentations as DefinitionDocumentationItem[]
}
})
Expand Down
77 changes: 62 additions & 15 deletions spx-gui/src/components/editor/code-editor/common.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { LocaleMessage } from '@/utils/i18n'

export type Position = {
line: number
column: number
Expand Down Expand Up @@ -36,8 +38,8 @@ export enum DefinitionKind {
Function,
/** Function or method for reading data */
Read,
/** Function or method for causing effect, e.g., writing data */
Effect,
/** Function or method for executing commands, e.g., move a sprite */
Command,
/** Function or method for listening to event */
Listen,
/** Language defined statements, e.g., `for { ... }` */
Expand Down Expand Up @@ -75,40 +77,85 @@ export type DefinitionIdentifier = {
overloadIndex?: number
}

export function stringifyDefinitionId(defId: DefinitionIdentifier): string {
if (defId.name == null) {
if (defId.package == null) throw new Error('package expected for ' + defId)
return defId.package
}
const suffix = defId.overloadIndex == null ? '' : `[${defId.overloadIndex}]`
if (defId.package == null) return defId.name + suffix
return defId.package + '|' + defId.name + suffix
}

/**
* Model for text document
* Similar to https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.ITextModel.html
*/
interface TextDocument {
export interface ITextDocument {
id: TextDocumentIdentifier
getOffsetAt(position: Position): number
getPositionAt(offset: number): Position
getValueInRange(range: IRange): string
}

export type MarkdownString = {
export type MarkdownStringFlag = 'basic' | 'advanced'

/**
* Markdown string with MDX support.
* We use flag to distinguish different types of Markdown string.
* Different types of Markdown string expect different rendering behaviors, especially for custom components support in MDX.
*/
export type MarkdownString<F extends MarkdownStringFlag> = {
flag?: F
/** Markdown string with MDX support. */
value: string
value: string | LocaleMessage
}

/**
* Markdown string with support of basic MDX components.
* Typically, it is used in `DefinitionDocumentationItem.detail`.
*/
export type BasicMarkdownString = MarkdownString<'basic'>

export function makeBasicMarkdownString(value: string | LocaleMessage): BasicMarkdownString {
return { value, flag: 'basic' }
}

/**
* Markdown string with support of advanced MDX components, e.g., `OverviewWrapper`, `Detail` (which reads data from `DocumentBase` & render with `DetailWrapper`).
* Typically, it is used in `CompletionItem.documentation` or `Hover.contents`.
*/
export type AdvancedMarkdownString = MarkdownString<'advanced'>

export type Icon = string

/**
* Documentation for a definition. Typically:
* Documentation string for a definition. Typically:
* ```mdx
* <Overview>func turn(dDirection float64)</Overview>
* <Detail>
* Turn with given direction change.
* </Detail>
* <OverviewWrapper>func turn(dDirection float64)</OverviewWrapper>
* <Detail id="github.com/goplus/spx|Sprite.turn[0]" />
* ```
*/
export type Documentation = MarkdownString

export type DocumentationItem = {
export type DefinitionDocumentationString = AdvancedMarkdownString

export const categoryEvent = 'event'
export const categoryEventGame = [categoryEvent, 'game']
export const categoryMotion = 'motion'
export const categoryMotionPosition = [categoryMotion, 'position']
export const categoryControl = 'control'
export const categoryControlFlow = [categoryControl, 'flow']

export type DefinitionDocumentationItem = {
/** For classification when listed in a group, e.g., `[["event", "game"]]` */
categories: string[][]
kind: DefinitionKind
definition: DefinitionIdentifier
/** Text to insert when completion / snippet is applied */
insertText: string
documentation: Documentation
/** Brief explanation for the definition, typically the signature string */
overview: string
/** Detailed explanation for the definition, overview not included */
detail: BasicMarkdownString
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand All @@ -130,7 +177,7 @@ export type Action<I extends any[] = any, R = any> = {

export type BaseContext = {
/** Current active text document */
textDocument: TextDocument
textDocument: ITextDocument
/** Signal to abort long running operations */
signal: AbortSignal
}
Expand Down
8 changes: 0 additions & 8 deletions spx-gui/src/components/editor/code-editor/document-base.ts

This file was deleted.

15 changes: 15 additions & 0 deletions spx-gui/src/components/editor/code-editor/document-base/gop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {
DefinitionKind,
type DefinitionDocumentationItem,
makeBasicMarkdownString,
categoryControlFlow
} from '../common'

export const forIterate: DefinitionDocumentationItem = {
categories: [categoryControlFlow],
kind: DefinitionKind.Statement,
definition: { name: 'for_iterate (TODO)' },
insertText: 'for ${1:i}, ${2:v} <- ${3:set} {\n\t${4:}\n}',
overview: 'for i, v <- set {} (TODO)',
detail: makeBasicMarkdownString({ en: 'Iterate within given set', zh: '遍历指定集合' })
}
23 changes: 23 additions & 0 deletions spx-gui/src/components/editor/code-editor/document-base/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { type DefinitionIdentifier, type DefinitionDocumentationItem, stringifyDefinitionId } from '../common'
import * as gopDefinitions from './gop'
import * as spxDefinitions from './spx'

export class DocumentBase {
private storage = new Map<string, DefinitionDocumentationItem>()

constructor() {
;[...Object.values(gopDefinitions), ...Object.values(spxDefinitions)].forEach((d) => {
this.addDocumentation(d)
})
}

private addDocumentation(documentation: DefinitionDocumentationItem) {
const key = stringifyDefinitionId(documentation.definition)
this.storage.set(key, documentation)
}

async getDocumentation(defId: DefinitionIdentifier): Promise<DefinitionDocumentationItem | null> {
const key = stringifyDefinitionId(defId)
return this.storage.get(key) ?? null
}
}
33 changes: 33 additions & 0 deletions spx-gui/src/components/editor/code-editor/document-base/spx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
DefinitionKind,
type DefinitionDocumentationItem,
makeBasicMarkdownString,
categoryEventGame,
categoryMotionPosition
} from '../common'

const packageSpx = 'github.com/goplus/spx'

export const onStart: DefinitionDocumentationItem = {
categories: [categoryEventGame],
kind: DefinitionKind.Listen,
definition: {
package: packageSpx,
name: 'onStart'
},
insertText: 'onStart => {\n\t${1}\n}',
overview: 'func onStart(callback func())',
detail: makeBasicMarkdownString({ en: 'Listen to game start', zh: '游戏开始时执行' })
}

export const setXYpos: DefinitionDocumentationItem = {
categories: [categoryMotionPosition],
kind: DefinitionKind.Command,
definition: {
package: packageSpx,
name: 'Sprite.setXYpos'
},
insertText: 'setXYpos ${1:X}, ${2:Y}',
overview: 'func setXYpos(x, y float64)',
detail: makeBasicMarkdownString({ en: 'Move to given position', zh: '移动到指定位置' })
}
33 changes: 33 additions & 0 deletions spx-gui/src/components/editor/code-editor/ui/APIReference.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script setup lang="ts">
import type { APIReference } from '.'
import MarkdownView from './MarkdownView.vue'
defineProps<{
apiReference: APIReference
}>()
function handleInsert() {
console.warn('TODO: insert')
}
function handleExplain() {
console.warn('TODO: explain')
}
</script>

<template>
<ul class="api-reference">
<li v-for="(item, i) in apiReference.items" :key="i" class="item">
<h4>{{ item.overview }}</h4>
<MarkdownView :value="item.detail.value" />
<button type="button" @click="handleInsert">insert</button>
<button type="button" @click="handleExplain">explain</button>
</li>
</ul>
</template>

<style lang="scss" scoped>
.api-reference {
padding: 1em;
}
</style>
17 changes: 14 additions & 3 deletions spx-gui/src/components/editor/code-editor/ui/CodeEditorUI.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { onUnmounted } from 'vue'
import type { Project } from '@/models/project'
import { type ICodeEditorUI, CodeEditorUI } from '.'
import MonacoEditor, { type Editor, type Monaco } from './MonacoEditor.vue'
import APIReference from './APIReference.vue'
const props = defineProps<{
project: Project
Expand All @@ -14,14 +15,19 @@ const emit = defineEmits<{
const ui = new CodeEditorUI(props.project)
const ctrl = new AbortController()
onUnmounted(() => {
ctrl.abort()
})
function handleMonaco(monaco: Monaco) {
ui.initializeMonaco(monaco)
ui.initMonaco(monaco)
}
function handleEditor(editor: Editor) {
ui.initializeEditor(editor)
ui.initEditor(editor)
emit('initialize', ui)
ui.initialize()
ui.init(ctrl.signal)
}
onUnmounted(() => {
Expand All @@ -31,6 +37,7 @@ onUnmounted(() => {

<template>
<div class="code-editor">
<APIReference class="api-reference" :api-reference="ui.apiReference" />
<MonacoEditor class="monaco-editor" @monaco="handleMonaco" @editor="handleEditor" />
</div>
</template>
Expand All @@ -44,6 +51,10 @@ onUnmounted(() => {
justify-content: stretch;
}
.api-reference {
flex: 0 0 160px;
}
.monaco-editor {
flex: 1 1 0;
}
Expand Down
28 changes: 28 additions & 0 deletions spx-gui/src/components/editor/code-editor/ui/MarkdownView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n, type LocaleMessage } from '@/utils/i18n'
const props = defineProps<{
value: string | LocaleMessage
}>()
const i18n = useI18n()
const rendered = computed(() => {
const value = props.value
// TODO:
// * markdown renderer
// * basic / advanced support
if (typeof value === 'string') {
return value
}
return i18n.t(value)
})
</script>

<template>
<div class="markdown-view">
{{ rendered }}
</div>
</template>

<style lang="scss" scoped></style>
29 changes: 27 additions & 2 deletions spx-gui/src/components/editor/code-editor/ui/api-reference.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
import { type BaseContext, type Position, type DocumentationItem } from '../common'
import { shallowReactive } from 'vue'
import { Disposable } from '@/utils/disposable'
import { type BaseContext, type Position, type DefinitionDocumentationItem } from '../common'
import type { CodeEditorUI } from '.'

export type APIReferenceItem = DocumentationItem
export type APIReferenceItem = DefinitionDocumentationItem

export type APIReferenceContext = BaseContext

export interface IAPIReferenceProvider {
provideAPIReference(ctx: APIReferenceContext, position: Position): Promise<APIReferenceItem[]>
}

export class APIReference extends Disposable {
items: APIReferenceItem[] = shallowReactive([])

private provider: IAPIReferenceProvider | null = null
registerProvider(provider: IAPIReferenceProvider) {
this.provider = provider
}

constructor(private ui: CodeEditorUI) {
super()
}

async init(signal: AbortSignal) {
const context: APIReferenceContext = {
textDocument: this.ui.activeTextDocument!,
signal
}
const items = await this.provider!.provideAPIReference(context, { line: 0, column: 0 })
this.items.splice(0, this.items.length, ...items)
}
}
4 changes: 2 additions & 2 deletions spx-gui/src/components/editor/code-editor/ui/completion.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DefinitionKind, type BaseContext, type Documentation, type Position } from '../common'
import { DefinitionKind, type BaseContext, type DefinitionDocumentationString, type Position } from '../common'

export type CompletionContext = BaseContext

Expand All @@ -7,7 +7,7 @@ export type CompletionItemKind = DefinitionKind
export type CompletionItem = {
label: string
kind: CompletionItemKind
documentation: Documentation
documentation: DefinitionDocumentationString
}

export interface ICompletionProvider {
Expand Down
Loading

0 comments on commit 4cdb1ad

Please sign in to comment.