diff --git a/src/simulator/src/Verilog2CV.js b/src/simulator/src/Verilog2CV.js deleted file mode 100644 index bc1a755d..00000000 --- a/src/simulator/src/Verilog2CV.js +++ /dev/null @@ -1,305 +0,0 @@ -import { - createNewCircuitScope, - switchCircuit, - changeCircuitName, -} from './circuit' -import SubCircuit from './subcircuit' -import { simulationArea } from './simulationArea' -import CodeMirror from 'codemirror/lib/codemirror.js' -import 'codemirror/lib/codemirror.css' - -// Importing CodeMirror themes -import 'codemirror/theme/3024-day.css' -import 'codemirror/theme/solarized.css' -import 'codemirror/theme/elegant.css' -import 'codemirror/theme/neat.css' -import 'codemirror/theme/idea.css' -import 'codemirror/theme/neo.css' -import 'codemirror/theme/3024-night.css' -import 'codemirror/theme/blackboard.css' -import 'codemirror/theme/cobalt.css' -import 'codemirror/theme/the-matrix.css' -import 'codemirror/theme/night.css' -import 'codemirror/theme/monokai.css' -import 'codemirror/theme/midnight.css' - -import 'codemirror/addon/hint/show-hint.css' -import 'codemirror/mode/verilog/verilog.js' -import 'codemirror/addon/edit/closebrackets.js' -import 'codemirror/addon/hint/anyword-hint.js' -import 'codemirror/addon/hint/show-hint.js' -import 'codemirror/addon/display/autorefresh.js' -import { showError, showMessage } from './utils' -import { showProperties } from './ux' -import { useSimulatorMobileStore } from '#/store/simulatorMobileStore' -import { toRefs } from 'vue' - -var editor -var verilogMode = false - -export async function createVerilogCircuit() { - const returned = await createNewCircuitScope( - undefined, - undefined, - true, - true - ) - - if (returned) { - verilogModeSet(true) - - try { - const simulatorMobileStore = toRefs(useSimulatorMobileStore()) - simulatorMobileStore.isVerilog.value = true - } catch (error) { - console.error('Failed to update simulatorMobileStore:', error) - } - } -} - -export function saveVerilogCode() { - var code = editor.getValue() - globalScope.verilogMetadata.code = code - generateVerilogCircuit(code) -} - -export function applyVerilogTheme(theme) { - localStorage.setItem('verilog-theme', theme) - editor.setOption('theme', theme) -} - -export function resetVerilogCode() { - editor.setValue(globalScope.verilogMetadata.code) -} - -export function hasVerilogCodeChanges() { - return editor.getValue() != globalScope.verilogMetadata.code -} - -export function verilogModeGet() { - return verilogMode -} - -export function verilogModeSet(mode) { - if (mode == verilogMode) return - verilogMode = mode - if (mode) { - const code_window = document.getElementById('code-window') - if(code_window) - document.getElementById('code-window').style.display = 'block' - - const elementPanel = document.querySelector('.elementPanel') - if(elementPanel) - document.querySelector('.elementPanel').style.display = 'none' - - const timingDiagramPanel = document.querySelector('.timing-diagram-panel') - if(timingDiagramPanel) - document.querySelector('.timing-diagram-panel').style.display = 'none' - - const quickBtn = document.querySelector('.quick-btn') - if(quickBtn) - document.querySelector('.quick-btn').style.display = 'none' - - const verilogEditorPanel = document.getElementById('verilogEditorPanel') - if(verilogEditorPanel) - document.getElementById('verilogEditorPanel').style.display = 'block' - - if (!embed) { - simulationArea.lastSelected = globalScope.root - showProperties(undefined) - showProperties(simulationArea.lastSelected) - } - resetVerilogCode() - } else { - const code_window = document.getElementById('code-window') - if(code_window) - document.getElementById('code-window').style.display = 'none' - - const elementPanel = document.querySelector('.elementPanel') - if(elementPanel) - document.querySelector('.elementPanel').style.display = '' - - const timingDiagramPanel = document.querySelector('.timing-diagram-panel') - if(timingDiagramPanel) - document.querySelector('.timing-diagram-panel').style.display = '' - - const quickBtn = document.querySelector('.quick-btn') - if(quickBtn) - document.querySelector('.quick-btn').style.display = '' - - const verilogEditorPanel = document.getElementById('verilogEditorPanel') - if(verilogEditorPanel) - document.getElementById('verilogEditorPanel').style.display = 'none' - } -} - -import yosysTypeMap from './VerilogClasses' - -class verilogSubCircuit { - constructor(circuit) { - this.circuit = circuit - } - - getPort(portName) { - var numInputs = this.circuit.inputNodes.length - var numOutputs = this.circuit.outputNodes.length - - for (var i = 0; i < numInputs; i++) { - if (this.circuit.data.Input[i].label == portName) { - return this.circuit.inputNodes[i] - } - } - - for (var i = 0; i < numOutputs; i++) { - if (this.circuit.data.Output[i].label == portName) { - return this.circuit.outputNodes[i] - } - } - } -} - -export function YosysJSON2CV( - JSON, - parentScope = globalScope, - name = 'verilogCircuit', - subCircuitScope = {}, - root = false -) { - var parentID = parentScope.id - var subScope - if (root) { - subScope = parentScope - } else { - subScope = newCircuit(name, undefined, true, false) - } - var circuitDevices = {} - - for (var subCircuitName in JSON.subcircuits) { - var scope = YosysJSON2CV( - JSON.subcircuits[subCircuitName], - subScope, - subCircuitName, - subCircuitScope - ) - subCircuitScope[subCircuitName] = scope.id - } - - for (var device in JSON.devices) { - var deviceType = JSON.devices[device].type - if (deviceType == 'Subcircuit') { - var subCircuitName = JSON.devices[device].celltype - circuitDevices[device] = new verilogSubCircuit( - new SubCircuit( - 500, - 500, - undefined, - subCircuitScope[subCircuitName] - ) - ) - } else { - circuitDevices[device] = new yosysTypeMap[deviceType]( - JSON.devices[device] - ) - } - } - - for (var connection in JSON.connectors) { - var fromId = JSON.connectors[connection]['from']['id'] - var fromPort = JSON.connectors[connection]['from']['port'] - var toId = JSON.connectors[connection]['to']['id'] - var toPort = JSON.connectors[connection]['to']['port'] - - var fromObj = circuitDevices[fromId] - var toObj = circuitDevices[toId] - - var fromPortNode = fromObj.getPort(fromPort) - var toPortNode = toObj.getPort(toPort) - - fromPortNode.connect(toPortNode) - } - - if (!root) { - switchCircuit(parentID) - return subScope - } -} - -export default function generateVerilogCircuit( - verilogCode, - scope = globalScope -) { - var params = { code: verilogCode } - fetch('/api/v1/simulator/verilogcv', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(params), - }) - .then((response) => { - if (!response.ok) { - throw response - } - return response.json() - }) - .then((circuitData) => { - scope.initialize() - for (var id in scope.verilogMetadata.subCircuitScopeIds) - delete scopeList[id] - scope.verilogMetadata.subCircuitScopeIds = [] - scope.verilogMetadata.code = verilogCode - var subCircuitScope = {} - YosysJSON2CV( - circuitData, - globalScope, - 'verilogCircuit', - subCircuitScope, - true - ) - changeCircuitName(circuitData.name) - showMessage('Verilog Circuit Successfully Created') - document.getElementById('verilogOutput').innerHTML = '' - }) - .catch((error) => { - if (error.status == 500) { - showError('Could not connect to Yosys') - } else { - showError('There is some issue with the code') - error.json().then((errorMessage) => { - document.getElementById('verilogOutput').innerHTML = - errorMessage.message - }) - } - }) -} - -export function setupCodeMirrorEnvironment() { - var myTextarea = document.getElementById('codeTextArea') - - CodeMirror.commands.autocomplete = function (cm) { - cm.showHint({ hint: CodeMirror.hint.anyword }) - } - - editor = CodeMirror.fromTextArea(myTextarea, { - mode: 'verilog', - autoRefresh: true, - styleActiveLine: true, - lineNumbers: true, - autoCloseBrackets: true, - smartIndent: true, - indentWithTabs: true, - extraKeys: { 'Ctrl-Space': 'autocomplete' }, - }) - - if (!localStorage.getItem('verilog-theme')) { - localStorage.setItem('verilog-theme', 'default') - } else { - const prevtheme = localStorage.getItem('verilog-theme') - editor.setOption('theme', prevtheme) - } - - editor.setValue('// Write Some Verilog Code Here!') - setTimeout(function () { - editor.refresh() - }, 1) -} diff --git a/src/simulator/src/Verilog2CV.ts b/src/simulator/src/Verilog2CV.ts new file mode 100644 index 00000000..680287b6 --- /dev/null +++ b/src/simulator/src/Verilog2CV.ts @@ -0,0 +1,362 @@ +import { + createNewCircuitScope, + switchCircuit, + changeCircuitName, +} from './circuit'; +import SubCircuit from './subcircuit'; +import { simulationArea } from './simulationArea'; +import CodeMirror from 'codemirror/lib/codemirror.js'; +import 'codemirror/lib/codemirror.css'; + +// Importing CodeMirror themes +import 'codemirror/theme/3024-day.css'; +import 'codemirror/theme/solarized.css'; +import 'codemirror/theme/elegant.css'; +import 'codemirror/theme/neat.css'; +import 'codemirror/theme/idea.css'; +import 'codemirror/theme/neo.css'; +import 'codemirror/theme/3024-night.css'; +import 'codemirror/theme/blackboard.css'; +import 'codemirror/theme/cobalt.css'; +import 'codemirror/theme/the-matrix.css'; +import 'codemirror/theme/night.css'; +import 'codemirror/theme/monokai.css'; +import 'codemirror/theme/midnight.css'; + +import 'codemirror/addon/hint/show-hint.css'; +import 'codemirror/addon/hint/show-hint.js'; +import 'codemirror/addon/hint/anyword-hint.js'; +import 'codemirror/mode/verilog/verilog.js'; +import 'codemirror/addon/edit/closebrackets.js'; +import 'codemirror/addon/display/autorefresh.js'; +import { showError, showMessage } from './utils'; +import { showProperties } from './ux'; +import { useSimulatorMobileStore } from '#/store/simulatorMobileStore'; +import { toRefs } from 'vue'; + +import { CircuitElement,YosysDevice,YosysJSON,SimulatorMobileStore,Node,GlobalScope,ScopeList } from './types/verilog.types'; + +const globalScope: GlobalScope = { + id: 'global', + verilogMetadata: { + code: '', + subCircuitScopeIds: [], + }, + root: {} as CircuitElement, + initialize: () => {}, +}; +const embed: boolean = false; +const scopeList: ScopeList = {}; + +let editor: CodeMirror.Editor; +let verilogMode: boolean = false; + +export async function createVerilogCircuit(): Promise { + try { + const returned = await createNewCircuitScope( + undefined, + undefined, + true, + true + ); + + if (returned) { + verilogModeSet(true); + + const simulatorMobileStore = toRefs(useSimulatorMobileStore()) as SimulatorMobileStore; + simulatorMobileStore.isVerilog.value = true; + } + } catch (error) { + console.error('Failed to create Verilog circuit:', error); + showError('Failed to create Verilog circuit. Please try again.'); + } +} + +export function saveVerilogCode(): void { + if (!editor) { + showError('Editor not initialized'); + return; + } + const code: string = editor.getValue(); + if (!globalScope?.verilogMetadata) { + showError('Circuit scope not initialized'); + return; + } + globalScope.verilogMetadata.code = code; + generateVerilogCircuit(code); +} + +export function applyVerilogTheme(theme: string): void { + localStorage.setItem('verilog-theme', theme); + editor.setOption('theme', theme); +} + +export function resetVerilogCode(): void { + editor.setValue(globalScope.verilogMetadata.code); +} + +export function hasVerilogCodeChanges(): boolean { + return editor.getValue() !== globalScope.verilogMetadata.code; +} + +export function verilogModeGet(): boolean { + return verilogMode; +} + +export function verilogModeSet(mode: boolean): void { + if (mode === verilogMode) return; + verilogMode = mode; + + if (mode) { + enableVerilogMode(); + } else { + disableVerilogMode(); + } +} + +function enableVerilogMode(): void { + setElementDisplay('code-window', 'block'); + setElementDisplay('.elementPanel', 'none'); + setElementDisplay('.timing-diagram-panel', 'none'); + setElementDisplay('.quick-btn', 'none'); + setElementDisplay('#verilogEditorPanel', 'block'); + + if (!embed) { + simulationArea.lastSelected = globalScope.root; + showProperties(null); + showProperties(simulationArea.lastSelected); + } + resetVerilogCode(); +} + +function disableVerilogMode(): void { + setElementDisplay('code-window', 'none'); + setElementDisplay('.elementPanel', ''); + setElementDisplay('.timing-diagram-panel', ''); + setElementDisplay('.quick-btn', ''); + setElementDisplay('#verilogEditorPanel', 'none'); +} + +function setElementDisplay(selector: string, display: string): void { + const element = selector.startsWith('.') || selector.startsWith('#') + ? document.querySelector(selector) + : document.getElementById(selector); + if (element) (element as HTMLElement).style.display = display; +} + +import yosysTypeMap from './VerilogClasses'; + +class VerilogSubCircuit { + circuit: CircuitElement; + + constructor(circuit: CircuitElement) { + this.circuit = circuit; + } + + getPort(portName: string): Node | undefined { + return ( + findPortInNodes(this.circuit.inputNodes, this.circuit.data.Input, portName) || + findPortInNodes(this.circuit.outputNodes, this.circuit.data.Output, portName) + ); + } +} + +function findPortInNodes(nodes: Node[], data: { label: string }[], portName: string): Node | undefined { + for (let i = 0; i < nodes.length; i++) { + if (data[i].label === portName) { + return nodes[i]; + } + } + return undefined; +} + +export async function YosysJSON2CV( + json: YosysJSON, + parentScope: GlobalScope = globalScope, + name: string = 'verilogCircuit', + subCircuitScope: { [key: string]: string } = {}, + root: boolean = false +): Promise { + const parentID = parentScope.id; + const subScope = root ? parentScope : (await createNewCircuitScope(name, undefined, true, false)) as unknown as GlobalScope; + const circuitDevices: { [key: string]: VerilogSubCircuit | unknown } = {}; + + await processSubCircuits(json, subScope, subCircuitScope); + processDevices(json, circuitDevices, subCircuitScope); + processConnectors(json, circuitDevices); + + if (!root) { + switchCircuit(parentID); + return subScope; + } + return subScope; +} + +async function processSubCircuits(json: YosysJSON, subScope: GlobalScope, subCircuitScope: { [key: string]: string }): Promise { + for (const subCircuitName in json.subcircuits) { + const scope = await YosysJSON2CV( + json.subcircuits[subCircuitName], + subScope, + subCircuitName, + subCircuitScope + ); + subCircuitScope[subCircuitName] = scope.id; + } +} + +function processDevices(json: YosysJSON, circuitDevices: { [key: string]: VerilogSubCircuit | unknown }, subCircuitScope: { [key: string]: string }): void { + for (const device in json.devices) { + processDevice(device, json.devices[device], circuitDevices, subCircuitScope); + } +} + +function processDevice( + device: string, + deviceData: YosysDevice, + circuitDevices: Record, + subCircuitScope: Record +): void { + const deviceType = deviceData.type; + if (deviceType === 'Subcircuit') { + processSubCircuitDevice(device, deviceData, circuitDevices, subCircuitScope); + } else { + processStandardDevice(device, deviceData, circuitDevices); + } +} + +function processSubCircuitDevice( + device: string, + deviceData: YosysDevice, + circuitDevices: { [key: string]: VerilogSubCircuit | unknown }, + subCircuitScope: { [key: string]: string } +): void { + const subCircuitName = deviceData.celltype; // This is a string + if (!subCircuitName || subCircuitScope[subCircuitName] === undefined) { + throw new Error(`subCircuitScope[${subCircuitName}] is undefined`); + } + const GRID_SIZE = 100; + let lastX = 0; + let lastY = 0; + + function getNextPosition(): { x: number, y: number } { + const position = { x: lastX, y: lastY }; + lastX += GRID_SIZE; + if (lastX > 1500) { // max width + lastX = 0; + lastY += GRID_SIZE; + } + return position; + } + + const { x, y } = getNextPosition(); + circuitDevices[device] = new VerilogSubCircuit( + new SubCircuit( + x, + y, + null, // someParam + undefined // subCircuitId (explicitly undefined) + ) + ); +} + +function processStandardDevice(device: string, deviceData: YosysDevice, circuitDevices: { [key: string]: VerilogSubCircuit | unknown }): void { + const deviceType = deviceData.type as keyof typeof yosysTypeMap; + circuitDevices[device] = new yosysTypeMap[deviceType](deviceData); +} + +function processConnectors(json: YosysJSON, circuitDevices: { [key: string]: VerilogSubCircuit | unknown }): void { + for (const connection in json.connectors) { + const { from, to } = json.connectors[connection]; + const fromObj = circuitDevices[from.id] as VerilogSubCircuit; + const toObj = circuitDevices[to.id] as VerilogSubCircuit; + + const fromPortNode = fromObj.getPort(from.port); + const toPortNode = toObj.getPort(to.port); + + if (fromPortNode && toPortNode) { + fromPortNode.connect(toPortNode); + } + } +} + +function generateVerilogCircuit( + verilogCode: string, + scope: GlobalScope = globalScope +): void { + const params = { code: verilogCode }; + fetch('/api/v1/simulator/verilogcv', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + }) + .then((response) => { + if (!response.ok) { + throw response; + } + return response.json(); + }) + .then((circuitData: YosysJSON) => { + scope.initialize(); + for (const id in scope.verilogMetadata.subCircuitScopeIds) + delete scopeList[id]; + scope.verilogMetadata.subCircuitScopeIds = []; + scope.verilogMetadata.code = verilogCode; + const subCircuitScope: { [key: string]: string } = {}; + YosysJSON2CV( + circuitData, + globalScope, + 'verilogCircuit', + subCircuitScope, + true + ); + changeCircuitName(circuitData.name); + showMessage('Verilog Circuit Successfully Created'); + const verilogOutput = document.getElementById('verilogOutput'); + if (verilogOutput) verilogOutput.innerHTML = ''; + }) + .catch(async (error: Response) => { + const errorMessage = error.status === 500 + ? 'Could not connect to Yosys server. Please try again later.' + : await error.json().then((e: { message: string }) => e.message) + .catch(() => 'An unexpected error occurred'); + + showError(errorMessage); + const verilogOutput = document.getElementById('verilogOutput'); + if (verilogOutput) verilogOutput.innerHTML = errorMessage; + }); +} + +function setupCodeMirrorEnvironment(): void { + const myTextarea = document.getElementById('codeTextArea') as HTMLTextAreaElement; + if (!myTextarea) { + showError('Code editor textarea not found'); + return; + } + + CodeMirror.commands.autocomplete = function (cm: CodeMirror.Editor) { + cm.showHint({ hint: CodeMirror.hint.anyword }); + }; + + editor = CodeMirror.fromTextArea(myTextarea, { + mode: 'verilog', + autoRefresh: true, + styleActiveLine: true, + lineNumbers: true, + autoCloseBrackets: true, + smartIndent: true, + indentWithTabs: true, + extraKeys: { 'Ctrl-Space': 'autocomplete' }, + }); + + const theme = localStorage.getItem('verilog-theme') || 'default'; + editor.setOption('theme', theme); + + editor.setValue('// Write Some Verilog Code Here!'); + setTimeout(function () { + editor.refresh(); + }, 1); +} + +export { generateVerilogCircuit as default, setupCodeMirrorEnvironment }; \ No newline at end of file diff --git a/src/simulator/src/types/verilog.types.ts b/src/simulator/src/types/verilog.types.ts new file mode 100644 index 00000000..0cbeebcd --- /dev/null +++ b/src/simulator/src/types/verilog.types.ts @@ -0,0 +1,57 @@ +export interface CircuitElement { + inputNodes: Node[]; + outputNodes: Node[]; + data: CircuitData; +} + +export interface CircuitData { + Input: { label: string }[]; + Output: { label: string }[]; +} + +export interface YosysDevice { + type: string; + celltype?: string; +} + +export interface YosysConnector { + from: { id: string; port: string }; + to: { id: string; port: string }; +} + +export interface YosysJSON { + subcircuits: { [key: string]: YosysJSON }; + devices: { [key: string]: YosysDevice }; + connectors: { [key: string]: YosysConnector }; + name: string; +} + +export interface SimulatorMobileStore { + isVerilog: { value: boolean }; +} + +export interface Node { + verilogLabel?: string; + parent: { + verilogLabel?: string; + }; + label?: string; + connect(node: Node): void; +} + +export interface VerilogMetadata { + code: string; + subCircuitScopeIds: string[]; +} + +export interface GlobalScope { + id: string; + verilogMetadata: VerilogMetadata; + root: CircuitElement; + initialize: () => void; +} + +export interface ScopeList { + [key: string]: GlobalScope; +} + diff --git a/tsconfig.json b/tsconfig.json index 17f36459..555f100f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,6 +21,7 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "dist", + "rootDir": "src", "declarationMap": true }, "include": [ @@ -29,7 +30,8 @@ "src/**/*.tsx", "src/**/*.vue", "src/**/*", - "commitlint.config.js" + "commitlint.config.js", + "types/**/*.d.ts" ], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/types/codemirror.d.ts b/types/codemirror.d.ts new file mode 100644 index 00000000..efa86d80 --- /dev/null +++ b/types/codemirror.d.ts @@ -0,0 +1,98 @@ +declare module 'codemirror/lib/codemirror.js' { + export function fromTextArea(textarea: HTMLTextAreaElement, options?: CodeMirror.EditorConfiguration): CodeMirror.Editor; + export function getValue(): string; + export function setValue(value: string): void; + + namespace CodeMirror { + function fromTextArea(textarea: HTMLTextAreaElement, options?: EditorConfiguration): Editor; + function getValue(): string; + function setValue(value: string): void; + + interface Editor { + getValue(): string; + setValue(value: string): void; + on(eventName: string, handler: (instance: Editor, event: any) => void): void; + off(eventName: string, handler: (instance: Editor, event: any) => void): void; + refresh(): void; + focus(): void; + getOption(option: string): any; + setOption(option: string, value: any): void; + addKeyMap(map: any): void; + removeKeyMap(map: any): void; + addLineClass(line: number, where: string, className: string): void; + removeLineClass(line: number, where: string, className: string): void; + lineCount(): number; + getLine(line: number): string; + getCursor(): Position; + setCursor(pos: Position): void; + replaceRange(replacement: string, from: Position, to?: Position, origin?: string): void; + getDoc(): Doc; + // Add other methods and properties as needed + } + + interface Doc { + getValue(): string; + setValue(value: string): void; + replaceRange(replacement: string, from: Position, to?: Position, origin?: string): void; + getCursor(): Position; + setCursor(pos: Position): void; + // Add other methods and properties as needed + } + + interface EditorConfiguration { + value?: string; + mode?: string | object; + theme?: string; + indentUnit?: number; + smartIndent?: boolean; + tabSize?: number; + indentWithTabs?: boolean; + electricChars?: boolean; + specialChars?: RegExp; + specialCharPlaceholder?: (char: string) => HTMLElement; + rtlMoveVisually?: boolean; + keyMap?: string; + extraKeys?: any; + lineWrapping?: boolean; + lineNumbers?: boolean; + firstLineNumber?: number; + lineNumberFormatter?: (line: number) => string; + gutters?: string[]; + fixedGutter?: boolean; + scrollbarStyle?: string; + coverGutterNextToScrollbar?: boolean; + inputStyle?: string; + readOnly?: boolean | string; + showCursorWhenSelecting?: boolean; + lineWiseCopyCut?: boolean; + pasteLinesPerSelection?: boolean; + selectionsMayTouch?: boolean; + cursorBlinkRate?: number; + cursorScrollMargin?: number; + cursorHeight?: number; + workTime?: number; + workDelay?: number; + pollInterval?: number; + flattenSpans?: boolean; + addModeClass?: boolean; + dragDrop?: boolean; + allowDropFileTypes?: string[]; + cursorActivity?: () => void; + viewportChange?: (from: number, to: number) => void; + swapDoc?: (doc: Doc) => void; + maxHighlightLength?: number; + viewportMargin?: number; + lint?: boolean | object; + autoRefresh?: boolean; + styleActiveLine?: boolean; + autoCloseBrackets?: boolean; + // Add other configuration options as needed + } + + interface Position { + line: number; + ch: number; + sticky?: string; + } + } +} \ No newline at end of file