I’m using vue-codemirror and have some working code that selects an entire substring like <input-value>
when single clicking into it. I do a view dispatch selection to select the range and it works fine. However, when typing to replace the placeholder, the selection range still remains after the first keystroke. I was expecting the cursor to revert to normal behavior and be at a single position when typing. Essentially I want dispatch selection to work the same as manually highlighting a range, and then typing. How can I achieve this?
Below is my code, see the dispatch line in highlightPlaceholder(). Please let me know if my question make sense. Thanks!
<template>
<div>
<codemirror
v-model="code"
:placeholder="placeholder"
:autofocus="false"
:indent-with-tab="true"
:tab-size="2"
:extensions="extensions"
@ready="handleReady"
@change="console.log('change', $event)"
@focus="console.log('focus', $event)"
@blur="console.log('blur', $event)"
@update="highlightPlaceholder()"
:style="{ 'min-height': '250px', 'max-height': '70vh',
'border': `${showError ? '2px solid red' : '2px solid black'}`
}"
/>
<caption
:class="{ invisible: showError?.length === 0 || showError === undefined ? true : false }"
class="row text-caption q-ml-md"
style="color: rgb(193, 0, 21); font-size: 12px;"
>
{{ showError || '...' }}
</caption>
</div>
</template>
<script setup>
import { computed, shallowRef, ref } from 'vue'
import { Codemirror } from 'vue-codemirror'
import { yaml } from '@codemirror/lang-yaml'
import { oneDark } from '@codemirror/theme-one-dark'
import { linter, lintGutter } from "@codemirror/lint"
import parser from "js-yaml"
import { python } from '@codemirror/lang-python'
import { EditorState } from '@codemirror/state'
const props = defineProps(['placeholder', 'language', 'readOnly', 'showError'])
const code = defineModel()
// Codemirror EditorView instance ref
const view = shallowRef()
const handleReady = (payload) => {
view.value = payload.view
}
defineExpose({ insertText })
function insertText(text) {
console.log('inserting text')
const range = view.value.state.selection.ranges[0]
view.value.dispatch({
changes: { from: range.from, to: range.to, insert: text }
})
}
function highlightPlaceholder() {
if(!view.value) return
const range = view.value.state.selection.ranges[0]
const from = range.from
const to = range.to
if (from === to) {
// Define the substring you are looking for
const placeholder = '<input-value>'
// Use a loop to find all instances of the placeholder
let startIndex = code.value.indexOf(placeholder)
while (startIndex !== -1) {
const endIndex = startIndex + placeholder.length
// Check if the cursor position is within the bounds of the current placeholder instance
if (from >= startIndex && from <= endIndex) {
console.log(`Cursor is at position ${from}, within the substring '<input-value>' from index ${startIndex} to ${endIndex}`)
view.value.dispatch({selection: {anchor: startIndex, head: endIndex}})
break
}
// Move to the next possible start index to continue searching
startIndex = code.value.indexOf(placeholder, startIndex + placeholder.length)
}
}
}
const yamlLinter = linter((view) => {
const diagnostics = [];
try {
parser.load(view.state.doc);
} catch (e) {
const loc = e.mark;
const from = loc ? loc.position : 0;
const to = from;
const severity = "error";
diagnostics.push({
from,
to,
message: e.message,
severity
});
}
return diagnostics;
})
const extensions = computed(() => {
if(props.language === 'python') {
return [python(), oneDark]
}
return [
yaml(),
oneDark,
yamlLinter,
lintGutter(),
EditorState.readOnly.of(props.readOnly)
]
})
</script>