diff --git a/package-lock.json b/package-lock.json index 9ee4d5a..8721a30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "rdt-editor", "version": "5.0", "dependencies": { - "brace": "^0.11.1", + "ace-code": "^1.32.8", "md5": "^2.3.0", "naive-ui": "^2.38.1", "pinia": "^2.1.7", @@ -1179,6 +1179,14 @@ "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==", "dev": true }, + "node_modules/ace-code": { + "version": "1.32.8", + "resolved": "https://registry.npmjs.org/ace-code/-/ace-code-1.32.8.tgz", + "integrity": "sha512-4VJUVvsmGeolY56JfT6/y0sJEaYf5mGVdeLehz8/MoBry2QVZxdK85ZQsuOj7c7hCRhaph3+/WRjvbDuzZ+zOQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -1294,11 +1302,6 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, - "node_modules/brace": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz", - "integrity": "sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg=" - }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", diff --git a/package.json b/package.json index d3ff18a..c2272bc 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "eslint --ext .ts,.vue src" }, "dependencies": { - "brace": "^0.11.1", + "ace-code": "^1.32.8", "md5": "^2.3.0", "naive-ui": "^2.38.1", "pinia": "^2.1.7", diff --git a/src/components/Editor.vue b/src/components/Editor.vue index 8cc80e1..4ea0b12 100644 --- a/src/components/Editor.vue +++ b/src/components/Editor.vue @@ -17,14 +17,11 @@ import { useCssModule, } from 'vue'; -import brace, { - type Position, - type IEditSession, - type Editor, -} from 'brace'; -import 'brace/theme/tomorrow'; -import 'brace/ext/language_tools'; -import '@/editor/rdt'; +import { Ace, edit as aceEdit, config as aceConfig } from 'ace-code'; +import 'ace-code/src/ext/language_tools'; +import 'ace-code/styles/ace.css'; +import 'ace-code/styles/theme/tomorrow.css'; +import * as RDT from '@/editor/rdt'; import type ISelection from '@/types/selection'; import type IIcon from '@/types/icon'; @@ -40,7 +37,7 @@ const props = defineProps<{ }>(); const holder = ref(); -const editor = ref(); +const editor = ref(); const content = defineModel('content'); bindEditorValue(editor, content); @@ -51,15 +48,21 @@ bindEditorSelection(editor, selection); const scroll = defineModel('scroll'); bindEditorScroll(editor, scroll); -onMounted(() => { - editor.value = brace.edit(holder.value!); - editor.value.$blockScrolling = Infinity; +const aceModules: Record = { + 'ace/theme/tomorrow': { cssClass: 'ace-tomorrow' }, + 'ace/mode/rdt': RDT, +}; - const session = editor.value.getSession(); - session.setMode('ace/mode/rdt'); +aceConfig.setLoader((path, callback) => { + callback(null, aceModules[path]); +}); - editor.value.setTheme('ace/theme/tomorrow'); - editor.value.setOption('enableLiveAutocompletion', [{ getCompletions, getDocTooltip }]); +onMounted(() => { + editor.value = aceEdit(holder.value!, { + theme: 'ace/theme/tomorrow', + mode: 'ace/mode/rdt', + enableLiveAutocompletion: [new Completer()], + }); }); onBeforeUnmount(() => { @@ -70,21 +73,19 @@ onBeforeUnmount(() => { const holderSize = useClientSize(holder); watch(holderSize, () => editor.value?.resize()); -function getCompletions( - _editor: Editor, - _session: IEditSession, - _pos: Position, - _prefix: string, - callback: (err: Error | null, completions?: { value: string }[]) => void -): void { - callback(null, Object.keys(props.icons) - .filter((icon) => !!props.icons[icon]) - .map((icon) => ({ value: icon }))); -} +class Completer implements Ace.Completer { + private style = useCssModule(); + + getCompletions(_editor: Ace.Editor, _session: Ace.EditSession, _pos: Ace.Point, _prefix: string, callback: Ace.CompleterCallback): void { + callback(null, Object.keys(props.icons) + .filter((icon) => !!props.icons[icon]) + .map((icon) => ({ value: icon }))); + } -const style = useCssModule(); -function getDocTooltip(item: { value: string; docHTML: string }): void { - item.docHTML = ``; + getDocTooltip(item: Ace.ValueCompletion): string | Ace.ValueCompletion | undefined { + item.docHTML = ``; + return; + } } diff --git a/src/composables/bindEditorScroll.ts b/src/composables/bindEditorScroll.ts index a767b13..d36edc5 100644 --- a/src/composables/bindEditorScroll.ts +++ b/src/composables/bindEditorScroll.ts @@ -1,9 +1,9 @@ import { watch, type Ref } from 'vue'; -import type { Editor } from 'brace'; +import type { Ace } from 'ace-code'; import onRefAssigned from './onRefAssigned'; -export default function bindEditorScroll(editor: Ref, scroll: Ref): void { +export default function bindEditorScroll(editor: Ref, scroll: Ref): void { onRefAssigned(editor, (value) => { const session = value.getSession(); session.on('changeScrollTop', () => { diff --git a/src/composables/bindEditorSelection.ts b/src/composables/bindEditorSelection.ts index 61ed4d8..e7c640b 100644 --- a/src/composables/bindEditorSelection.ts +++ b/src/composables/bindEditorSelection.ts @@ -1,16 +1,16 @@ import { watch, type Ref } from 'vue'; -import type { Editor, Range, VirtualRenderer } from 'brace'; +import type { Ace } from 'ace-code'; import onRefAssigned from './onRefAssigned'; import type ISelection from '@/types/selection'; -interface Renderer extends VirtualRenderer { +interface Renderer extends Ace.VirtualRenderer { scrollTop: number; scrollSelectionIntoView(): void; } -export default function bindEditorSelection(editorRef: Ref, selection: Ref): void { +export default function bindEditorSelection(editorRef: Ref, selection: Ref): void { onRefAssigned(editorRef, (editor) => { applyRange(editor, toRange({ row: 0, offset: 0, length: 0 })); @@ -32,14 +32,14 @@ export default function bindEditorSelection(editorRef: Ref, }); } -function toRange({ row, offset, length }: Omit): Range { +function toRange({ row, offset, length }: Omit): Ace.Range { return { start: { row: row, column: offset }, end: { row: row, column: offset + length }, - } as Range; + } as Ace.Range; } -function applyRange(editor: Editor, range: Range) { +function applyRange(editor: Ace.Editor, range: Ace.Range) { const renderer = editor.renderer as Renderer; editor.selection.setRange(range, false); renderer.scrollToX(0); @@ -47,7 +47,7 @@ function applyRange(editor: Editor, range: Range) { editor.focus(); } -function toSelection(range: Range, from: ISelection['from']): ISelection { +function toSelection(range: Ace.Range, from: ISelection['from']): ISelection { return { row: range.start.row, offset: range.start.column, diff --git a/src/composables/bindEditorValue.ts b/src/composables/bindEditorValue.ts index 2fe47f8..8e132d6 100644 --- a/src/composables/bindEditorValue.ts +++ b/src/composables/bindEditorValue.ts @@ -1,9 +1,9 @@ import type { Ref } from 'vue'; -import type { Editor } from 'brace'; +import type { Ace } from 'ace-code'; import onRefAssigned from './onRefAssigned'; -export default function bindEditorValue(editor: Ref, val: Ref): void { +export default function bindEditorValue(editor: Ref, val: Ref): void { onRefAssigned(editor, (value) => { if (val.value) { value.setValue(val.value); diff --git a/src/editor/rdt.js b/src/editor/rdt.js deleted file mode 100644 index 8fabcaf..0000000 --- a/src/editor/rdt.js +++ /dev/null @@ -1,57 +0,0 @@ -import ace from 'brace'; - -ace.define('ace/mode/rdt_highlight_rules', - ['require', 'exports', 'ace/mode/text_highlight_rules'], - (acequire, exports) => { - const { TextHighlightRules } = acequire('./text_highlight_rules'); - - class RDTHighlightRules extends TextHighlightRules { - - static metaData = { - name: 'RDT File', - scopeName: 'source.rdt', - fileTypes: ['rdt'], - }; - - $rules = { - start: [ - { - token: 'keyword.operator.rdt', - regex: '(?:\\\\|!~)', - }, - { - token: 'punctuation.definition.string', - regex: '(?:~~.*$)', - }, - { - token: 'keyword.control.statement.rdt', - regex: '(?:[^\\\\(~~)(!~)]+)', - }, - ], - }; - - constructor() { - super(); - this.normalizeRules(); - } - - } - - exports.RDTHighlightRules = RDTHighlightRules; - }); - -ace.define('ace/mode/rdt', - ['require', 'exports', 'ace/mode/text', 'ace/mode/rdt_highlight_rules'], - (acequire, exports) => { - const { Mode: TextMode } = acequire('./text'); - const { RDTHighlightRules } = acequire('./rdt_highlight_rules'); - - class Mode extends TextMode { - - $id = 'ace/mode/rdt'; - HighlightRules = RDTHighlightRules; - - } - - exports.Mode = Mode; - }); diff --git a/src/editor/rdt.ts b/src/editor/rdt.ts new file mode 100644 index 0000000..1250c07 --- /dev/null +++ b/src/editor/rdt.ts @@ -0,0 +1,31 @@ +import { Mode as TextMode } from 'ace-code/src/mode/text'; +import { TextHighlightRules } from 'ace-code/src/mode/text_highlight_rules'; + +export class RDTHighlightRules extends TextHighlightRules { + constructor() { + super(); + this.normalizeRules(); + } + + $rules = { + start: [ + { + token: 'keyword.operator.rdt', + regex: '(?:\\\\|!~)', + }, + { + token: 'punctuation.definition.string', + regex: '(?:~~.*$)', + }, + { + token: 'keyword.control.statement.rdt', + regex: '(?:[^\\\\(~~)(!~)]+)', + }, + ], + }; +} + +export class Mode extends TextMode { + $id = 'ace/mode/rdt'; + HighlightRules = RDTHighlightRules; +}