Skip to content

Commit

Permalink
Custom elements CodeChange & CodeBlock for MarkdownView
Browse files Browse the repository at this point in the history
  • Loading branch information
nighca committed Dec 27, 2024
1 parent e3c1075 commit 9b722f9
Show file tree
Hide file tree
Showing 26 changed files with 450 additions and 70 deletions.
5 changes: 3 additions & 2 deletions spx-backend/internal/controller/copilot.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,9 @@ func (ctrl *Controller) GenerateMessage(ctx context.Context, params *GenerateMes
// * Switch to official API
// * Use `[]anthropic.TextBlockParam` instead of `string`
// * Enable prompt caching if it helps
System: anthropic.Raw[[]anthropic.TextBlockParam](copilot.SystemPrompt),
Messages: anthropic.F(messages),
System: anthropic.Raw[[]anthropic.TextBlockParam](copilot.SystemPrompt),
Messages: anthropic.F(messages),
Temperature: anthropic.F(0.5),
})
if err != nil {
logger.Printf("failed to generate message: %v", err)
Expand Down
34 changes: 34 additions & 0 deletions spx-backend/internal/copilot/custom_element_code_change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# `code-change`

Display a change in the code. The change shows the difference between the original code and the new code. The user can apply the change by clicking "Apply" button in the element.

## Attributes

### `file`

Text document URI, e.g., `file:///NiuXiaoQi.spx`

### `line-range`

The range of the original code.

Format: `${startLine}-${endLine}`, e.g., `10-12`

`startLine`, `endLine` are numbers start from 1. `10-12` means the range from line 10 to line 12. The end line is exclusive.

### children

The new code.

## Examples

### Basic example

```xml
<code-change file="file:///NiuXiaoQi.spx" line-range="10-12">
show
say "Hello, world!"
</code-change>
```

This is a change in the code of sprite `NiuXiaoQi`. The original code is line 10 & line 11. The new code is `show\nsay "Hello, world!"\n`.
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ Text document URI, e.g., `file:///NiuXiaoQi.spx`

### `position`

`${line},${column}`, e.g., `10,20`.

`line` & `column` are numbers start from 1. `1,1` means the first column of the first line.
Format: `${line},${column}`, e.g., `10,20`. `line` & `column` are numbers start from 1. `1,1` means the first column of the first line.

### `range`

`${startLine},${startColumn}-${endLine}${endColumn}`, e.g., `10,20-12,10`
Format: `${startLine},${startColumn}-${endLine}${endColumn}`, e.g., `10,20-12,10`

`startLine`, `startColumn`, `endLine`, `endColumn` are numbers start from 1. `10,20-12,10` means the range from line 10, column 20 to line 12, column 10. The end position is exclusive.

### children

The text to display in the link.

## Examples

### Basic example
Expand Down
21 changes: 13 additions & 8 deletions spx-backend/internal/copilot/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,30 @@ var gopDefs string
//go:embed spx_defs.json
var spxDefs string

//go:embed custom_elements.md
var customElements string
//go:embed custom_element_code_link.md
var customElementCodeLink string

//go:embed custom_element_code_change.md
var customElementCodeChange string

//go:embed system_prompt.md
var systemPromptTpl string

type systemPromptTplData struct {
GopDefs string
SpxDefs string
CustomElements string
GopDefs string
SpxDefs string
CustomElementCodeLink string
CustomElementCodeChange string
}

var SystemPrompt string

func init() {
tplData := systemPromptTplData{
GopDefs: gopDefs,
SpxDefs: spxDefs,
CustomElements: customElements,
GopDefs: gopDefs,
SpxDefs: spxDefs,
CustomElementCodeLink: customElementCodeLink,
CustomElementCodeChange: customElementCodeChange,
}
tpl, err := template.New("system-prompt").Parse(systemPromptTpl)
if err != nil {
Expand Down
12 changes: 9 additions & 3 deletions spx-backend/internal/copilot/system_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@
</document_content>
</document>
<document>
<source>custom-elements.md</source>
<source>custom-element-code-link.md</source>
<document_content>
{{.CustomElements}}
{{.CustomElementCodeLink}}
</document_content>
</document>
<document>
<source>custom-element-code-change.md</source>
<document_content>
{{.CustomElementCodeChange}}
</document_content>
</document>
</documents>
Expand Down Expand Up @@ -153,4 +159,4 @@ Go+ Builder provides a visual programming interface for children to learn progra
* You are not allowed to provide any external links or resources to the user.
* Do not provide any personal information or ask for personal information from the user.
* If possible, use short and concise responses.
* There are some special elements you can use in your responses, you can find them in the document `custom-elements.md`. Use them just like any other HTML tags, to make your responses more interactive and informative.
* There are some special elements you can use in your responses, you can find them in documents named `custom-element-*.md`. Use them just like any other HTML tags, to make your responses more interactive and informative.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function initMonaco(
rules: [
// TODO: review colors here
{ token: 'comment', foreground: color.hint[2], fontStyle: 'italic' },
{ token: 'string', foreground: color.green[300] },
{ token: 'string', foreground: color.green[600] },
{ token: 'operator', foreground: color.blue.main },
{ token: 'number', foreground: color.blue[600] },
{ token: 'keyword', foreground: color.red[600] },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script setup lang="ts">
import { UITooltip } from '@/components/ui'
import { selection2Range } from '../../common'
import DefinitionOverviewWrapper from '../definition/DefinitionOverviewWrapper.vue'
import DefinitionDetailWrapper from '../definition/DefinitionDetailWrapper.vue'
import MarkdownView from '../markdown/MarkdownView.vue'
Expand All @@ -15,15 +14,7 @@ const props = defineProps<{
const codeEditorCtx = useCodeEditorUICtx()
function handleInsert() {
const startPosition = { line: 1, column: 1 }
let range = { start: startPosition, end: startPosition }
if (codeEditorCtx.ui.selection != null) {
range = selection2Range(codeEditorCtx.ui.selection)
}
// TODO: Optimize inserting logic with inline context. For example:
// * If no space before cursor, insert a space before inserting snippet.
// * If current line isn't empty, insert a new line before inserting snippet for kind Command or Listen.
codeEditorCtx.ui.insertSnippet(props.item.insertText, range)
codeEditorCtx.ui.insertSnippet(props.item.insertText)
codeEditorCtx.ui.editor.focus()
}
Expand Down
33 changes: 26 additions & 7 deletions spx-gui/src/components/editor/code-editor/ui/code-editor-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
getResourceModel,
type TextDocumentRange,
isRangeEmpty,
textDocumentIdEq
textDocumentIdEq,
selection2Range
} from '../common'
import { TextDocument } from '../text-document'
import type { Monaco, MonacoEditor } from '../monaco'
Expand Down Expand Up @@ -237,13 +238,29 @@ export class CodeEditorUI extends Disposable implements ICodeEditorUI {
return this.getTextDocument(this.mainTextDocumentId)
}

async insertText(text: string, range: Range) {
const editor = this.editor
const inserting = { range: toMonacoRange(range), text }
editor.executeEdits('insertText', [inserting])
private getSelectionRange() {
const pos = { line: 1, column: 1 }
let range = { start: pos, end: pos }
if (this.selection != null) {
range = selection2Range(this.selection)
}
return range
}

// TODO: Optimize inserting logic with inline context. For example:
// * If no space before cursor, insert a space before inserting snippet.
// * If current line isn't empty, insert a new line before inserting snippet for kind Command or Listen.

async insertText(text: string, range: Range = this.getSelectionRange()) {
this.activeTextDocument?.pushEdits([
{
range,
newText: text
}
])
}

async insertSnippet(snippet: string, range: Range) {
async insertSnippet(snippet: string, range: Range = this.getSelectionRange()) {
const editor = this.editor
// `executeEdits` does not support snippet, so we have to split the insertion into two steps:
// 1. remove the range with `executeEdits`
Expand All @@ -253,7 +270,9 @@ export class CodeEditorUI extends Disposable implements ICodeEditorUI {
editor.executeEdits('insertSnippet', [removing])
await timeout(0) // NOTE: the timeout is necessary, or the cursor position will be wrong after snippet inserted
}
// it's strange but it works, see details in https://github.com/Microsoft/monaco-editor/issues/342
// It's weird but works, see details in https://github.com/Microsoft/monaco-editor/issues/342
// While it prevents us to wrap a history action around the snippet insertion. See details in `TextDocument.withChangeKindProgram`
// TODO: Use better way to insert snippet
const contribution = editor.getContribution('snippetController2')
if (contribution == null) throw new Error('Snippet contribution not found')
;(contribution as any).insert(snippet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const documentation = useAsyncComputed<DefinitionDocumentationItem | null>(async
return documentBase.getDocumentation(defId)
})
const childrenText = useSlotText()
const childrenText = useSlotText('default', true)
const hasContent = computed(() => documentation.value != null || childrenText.value !== '')
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ defineProps<{
kind?: DefinitionKind
}>()
const childrenText = useSlotText()
const childrenText = useSlotText('default', true)
</script>

<template>
Expand Down
59 changes: 42 additions & 17 deletions spx-gui/src/components/editor/code-editor/ui/markdown/CodeBlock.vue
Original file line number Diff line number Diff line change
@@ -1,33 +1,58 @@
<script setup lang="ts">
import { useSlotText } from '@/utils/vnode'
import { useMessageHandle } from '@/utils/exception'
import { useEditorCtx } from '@/components/editor/EditorContextProvider.vue'
import { useCodeEditorUICtx } from '../CodeEditorUI.vue'
import BlockWrapper from './common/BlockWrapper.vue'
import BlockFooter from './common/BlockFooter.vue'
import BlockActionBtn from './common/BlockActionBtn.vue'
import CodeView from './CodeView.vue'
defineProps<{
language?: string
}>()
const editorCtx = useEditorCtx()
const codeEditorUICtx = useCodeEditorUICtx()
const code = useSlotText()
const handleInsert = useMessageHandle(
() =>
editorCtx.project.history.doAction({ name: { en: 'Insert code', zh: '插入代码' } }, () =>
codeEditorUICtx.ui.insertText(code.value)
),
{ en: 'Failed to insert code', zh: '插入代码失败' }
).fn
const handleCopy = useMessageHandle(
() => navigator.clipboard.writeText(code.value),
{ en: 'Failed to copy link to clipboard', zh: '复制到剪贴板失败' },
{ en: 'Link copied to clipboard', zh: '已复制到剪贴板' }
).fn
</script>

<template>
<section class="code-block">
<CodeView class="code-wrapper" mode="block"><slot></slot></CodeView>
<footer>
<!-- TODO: actions: insert / copy -->
</footer>
</section>
<BlockWrapper>
<div class="body">
<CodeView class="code" :language="language" mode="block" line-numbers>{{ code }}</CodeView>
</div>
<BlockFooter>
<BlockActionBtn icon="insert" @click="handleInsert">
{{ $t({ en: 'Insert', zh: '插入' }) }}
</BlockActionBtn>
<BlockActionBtn icon="copy" @click="handleCopy">
{{ $t({ en: 'Copy', zh: '复制' }) }}
</BlockActionBtn>
</BlockFooter>
</BlockWrapper>
</template>

<style lang="scss" scoped>
.code-block {
display: flex;
flex-direction: column;
align-items: stretch;
align-self: stretch;
border-radius: var(--ui-border-radius-2);
border: 1px solid var(--ui-dividing-line-1);
background: var(--ui-color-grey-100);
.body {
padding: 12px 0 12px 12px;
}
.code-wrapper {
padding: 12px;
.code {
padding-right: 12px;
min-width: 0;
overflow-x: auto;
}
Expand Down
Loading

0 comments on commit 9b722f9

Please sign in to comment.