feat: first release !
This commit is contained in:
1 parent
77a333e8d9
commit
664527182d
66 files changed
+13812
-16
No files matched your search
@@ -0,0 +1,97 @@
|
||||
// Spelling: wavy underlines under the words the Go checker reports. The
|
||||
// word being typed is left alone until the cursor leaves it.
|
||||
import { EditorState, Plugin, PluginKey, Transaction } from 'prosemirror-state'
|
||||
import { Decoration, DecorationSet } from 'prosemirror-view'
|
||||
import { Node as PMNode } from 'prosemirror-model'
|
||||
import type { Misspelling } from './backend'
|
||||
import { blockText, flatElements } from './schema'
|
||||
import { utf16Offset } from './decorations'
|
||||
|
||||
interface SpellState {
|
||||
set: DecorationSet
|
||||
typing: boolean // the last change was an edit, not a cursor move
|
||||
}
|
||||
|
||||
interface WordSpec {
|
||||
word: string
|
||||
}
|
||||
|
||||
export const spellKey = new PluginKey<SpellState>('spell')
|
||||
|
||||
export function buildSpellDecorations(doc: PMNode, found: Misspelling[], ignored: Set<string>): DecorationSet {
|
||||
const flat = flatElements(doc)
|
||||
const decos: Decoration[] = []
|
||||
for (const m of found) {
|
||||
const entry = flat[m.element]
|
||||
if (!entry || ignored.has(m.word.toLowerCase())) continue
|
||||
const text = blockText(entry.node)
|
||||
const start = entry.pos + 1
|
||||
const spec: WordSpec = { word: m.word }
|
||||
decos.push(
|
||||
Decoration.inline(
|
||||
start + utf16Offset(text, m.offset),
|
||||
start + utf16Offset(text, m.offset + m.length),
|
||||
{ class: 'misspelled' },
|
||||
spec,
|
||||
),
|
||||
)
|
||||
}
|
||||
return DecorationSet.create(doc, decos)
|
||||
}
|
||||
|
||||
/** Maps the underlines through a change, dropping those it touched. */
|
||||
function mapThrough(set: DecorationSet, tr: Transaction): DecorationSet {
|
||||
let mapped = set.map(tr.mapping, tr.doc)
|
||||
tr.mapping.maps.forEach((map, i) => {
|
||||
const rest = tr.mapping.slice(i + 1)
|
||||
map.forEach((_oldStart, _oldEnd, start, end) => {
|
||||
const touched = mapped.find(rest.map(start, -1), rest.map(end, 1))
|
||||
if (touched.length) mapped = mapped.remove(touched)
|
||||
})
|
||||
})
|
||||
return mapped
|
||||
}
|
||||
|
||||
export function spellPlugin(): Plugin<SpellState> {
|
||||
return new Plugin<SpellState>({
|
||||
key: spellKey,
|
||||
state: {
|
||||
init: () => ({ set: DecorationSet.empty, typing: false }),
|
||||
apply(tr, prev) {
|
||||
const fresh = tr.getMeta(spellKey) as DecorationSet | undefined
|
||||
const typing = tr.docChanged ? true : tr.selectionSet ? false : prev.typing
|
||||
if (fresh) return { set: fresh, typing }
|
||||
if (tr.docChanged) return { set: mapThrough(prev.set, tr), typing }
|
||||
return typing === prev.typing ? prev : { set: prev.set, typing }
|
||||
},
|
||||
},
|
||||
props: {
|
||||
decorations(state) {
|
||||
const { set, typing } = spellKey.getState(state)!
|
||||
if (!typing) return set
|
||||
const head = state.selection.head
|
||||
const here = set.find(head, head)
|
||||
return here.length ? set.remove(here) : set
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export interface MisspelledWord {
|
||||
from: number
|
||||
to: number
|
||||
word: string
|
||||
}
|
||||
|
||||
/** The underlined word at pos, if any. */
|
||||
export function misspellingAt(state: EditorState, pos: number): MisspelledWord | null {
|
||||
const d = spellKey.getState(state)?.set.find(pos, pos).find((d) => d.from <= pos && pos <= d.to)
|
||||
return d ? { from: d.from, to: d.to, word: (d.spec as WordSpec).word } : null
|
||||
}
|
||||
|
||||
/** The current underlines without those of word (added or ignored). */
|
||||
export function withoutWord(state: EditorState, word: string): DecorationSet {
|
||||
const set = spellKey.getState(state)!.set
|
||||
const lower = word.toLowerCase()
|
||||
return set.remove(set.find(undefined, undefined, (spec: WordSpec) => spec.word.toLowerCase() === lower))
|
||||
}
|
||||
Reference in new issue
Block a user