mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-15 05:10:36 +03:00
Refactor CodeMirror wrappers and simplify available grams
This commit is contained in:
parent
1054db3a8a
commit
a8ad142544
|
@ -12,7 +12,7 @@ import { TokenID } from '../../models/rslang';
|
|||
import Label from '../Common/Label';
|
||||
import { ccBracketMatching } from './bracketMatching';
|
||||
import { RSLanguage } from './rslang';
|
||||
import { getSymbolSubstitute,TextWrapper } from './textEditing';
|
||||
import { getSymbolSubstitute,RSTextWrapper } from './textEditing';
|
||||
import { rsHoverTooltip } from './tooltip';
|
||||
|
||||
const editorSetup: BasicSetupOptions = {
|
||||
|
@ -99,7 +99,7 @@ function RSInput({
|
|||
if (!thisRef.current) {
|
||||
return;
|
||||
}
|
||||
const text = new TextWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
const text = new RSTextWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
if (event.shiftKey && event.key === '*' && !event.altKey) {
|
||||
text.insertToken(TokenID.DECART);
|
||||
} else if (event.altKey) {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||
|
||||
import { TokenID } from '../../models/rslang';
|
||||
import { CodeMirrorWrapper } from '../../utils/codemirror';
|
||||
|
||||
export function getSymbolSubstitute(keyCode: string, shiftPressed: boolean): string | undefined {
|
||||
if (shiftPressed) {
|
||||
|
@ -41,43 +42,17 @@ export function getSymbolSubstitute(keyCode: string, shiftPressed: boolean): str
|
|||
return undefined;
|
||||
}
|
||||
|
||||
// Note: Wrapper class for textareafield.
|
||||
// WARNING! Manipulations on value do not support UNDO browser
|
||||
// WARNING! No checks for selection out of text boundaries
|
||||
export class TextWrapper {
|
||||
ref: Required<ReactCodeMirrorRef>
|
||||
|
||||
/**
|
||||
* Wrapper class for RSLang editor.
|
||||
*/
|
||||
export class RSTextWrapper extends CodeMirrorWrapper {
|
||||
constructor(object: Required<ReactCodeMirrorRef>) {
|
||||
this.ref = object;
|
||||
}
|
||||
|
||||
replaceWith(data: string) {
|
||||
this.ref.view.dispatch(this.ref.view.state.replaceSelection(data));
|
||||
}
|
||||
|
||||
envelopeWith(left: string, right: string) {
|
||||
const hasSelection = this.ref.view.state.selection.main.from !== this.ref.view.state.selection.main.to
|
||||
const newSelection = hasSelection ? {
|
||||
anchor: this.ref.view.state.selection.main.from,
|
||||
head: this.ref.view.state.selection.main.to + left.length + right.length
|
||||
} : {
|
||||
anchor: this.ref.view.state.selection.main.to + left.length + right.length - 1,
|
||||
}
|
||||
this.ref.view.dispatch({
|
||||
changes: [
|
||||
{from: this.ref.view.state.selection.main.from, insert: left},
|
||||
{from: this.ref.view.state.selection.main.to, insert: right}
|
||||
],
|
||||
selection: newSelection
|
||||
});
|
||||
}
|
||||
|
||||
insertChar(key: string) {
|
||||
this.replaceWith(key);
|
||||
super(object);
|
||||
}
|
||||
|
||||
insertToken(tokenID: TokenID): boolean {
|
||||
const hasSelection = this.ref.view.state.selection.main.from !== this.ref.view.state.selection.main.to
|
||||
const selection = this.getSelection();
|
||||
const hasSelection = selection.from !== selection.to
|
||||
switch (tokenID) {
|
||||
case TokenID.NT_DECLARATIVE_EXPR: {
|
||||
if (hasSelection) {
|
||||
|
@ -87,7 +62,7 @@ export class TextWrapper {
|
|||
}
|
||||
this.ref.view.dispatch({
|
||||
selection: {
|
||||
anchor: this.ref.view.state.selection.main.from + 2,
|
||||
anchor: selection.from + 2,
|
||||
}
|
||||
});
|
||||
return true;
|
||||
|
@ -120,7 +95,7 @@ export class TextWrapper {
|
|||
this.envelopeWith('(', ')');
|
||||
this.ref.view.dispatch({
|
||||
selection: {
|
||||
anchor: hasSelection ? this.ref.view.state.selection.main.to: this.ref.view.state.selection.main.from + 1,
|
||||
anchor: hasSelection ? selection.to: selection.from + 1,
|
||||
}
|
||||
});
|
||||
return true;
|
||||
|
@ -130,14 +105,14 @@ export class TextWrapper {
|
|||
if (hasSelection) {
|
||||
this.ref.view.dispatch({
|
||||
selection: {
|
||||
anchor: hasSelection ? this.ref.view.state.selection.main.to: this.ref.view.state.selection.main.from + 1,
|
||||
anchor: hasSelection ? selection.to: selection.from + 1,
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case TokenID.BOOLEAN: {
|
||||
const selStart = this.ref.view.state.selection.main.from;
|
||||
const selStart = selection.from;
|
||||
if (hasSelection && this.ref.view.state.sliceDoc(selStart, selStart + 1) === 'ℬ') {
|
||||
this.envelopeWith('ℬ', '');
|
||||
} else {
|
||||
|
|
|
@ -9,10 +9,11 @@ import { RefObject, useCallback, useMemo, useRef, useState } from 'react';
|
|||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import useResolveText from '../../hooks/useResolveText';
|
||||
import { CodeMirrorWrapper } from '../../utils/codemirror';
|
||||
import Label from '../Common/Label';
|
||||
import Modal from '../Common/Modal';
|
||||
import PrettyJson from '../Common/PrettyJSON';
|
||||
import { NaturalLanguage } from './parse';
|
||||
import { NaturalLanguage, ReferenceTokens } from './parse';
|
||||
import { refsHoverTooltip } from './tooltip';
|
||||
|
||||
const editorSetup: BasicSetupOptions = {
|
||||
|
@ -97,7 +98,7 @@ function RefsInput({
|
|||
() => [
|
||||
EditorView.lineWrapping,
|
||||
NaturalLanguage,
|
||||
refsHoverTooltip(schema?.items || [], colors),
|
||||
refsHoverTooltip(schema?.items || [], colors)
|
||||
], [schema?.items, colors]);
|
||||
|
||||
function handleChange(newValue: string) {
|
||||
|
@ -116,7 +117,7 @@ function RefsInput({
|
|||
|
||||
const handleInput = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (!thisRef.current) {
|
||||
if (!thisRef.current?.view) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
@ -129,6 +130,10 @@ function RefsInput({
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (event.ctrlKey && event.code === 'Space') {
|
||||
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
wrap.fixSelection(ReferenceTokens);
|
||||
}
|
||||
}, [thisRef, resolveText, value]);
|
||||
|
||||
return (
|
||||
|
@ -164,6 +169,7 @@ function RefsInput({
|
|||
onKeyDown={handleInput}
|
||||
onFocus={handleFocusIn}
|
||||
onBlur={handleFocusOut}
|
||||
// spellCheck={true} // TODO: figure out while automatic spellcheck doesnt work or implement with extension
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import {LRLanguage} from '@codemirror/language'
|
||||
|
||||
import { parser } from './parser';
|
||||
import { RefEntity, RefSyntactic } from './parser.terms';
|
||||
|
||||
export const ReferenceTokens: number[] = [
|
||||
RefSyntactic, RefEntity
|
||||
]
|
||||
|
||||
export const NaturalLanguage = LRLanguage.define({
|
||||
parser: parser,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { syntaxTree } from "@codemirror/language"
|
||||
import { syntaxTree } from '@codemirror/language'
|
||||
import { Extension } from '@codemirror/state';
|
||||
import { hoverTooltip } from '@codemirror/view';
|
||||
|
||||
|
@ -6,11 +6,13 @@ import { parseEntityReference, parseSyntacticReference } from '../../models/lang
|
|||
import { IConstituenta } from '../../models/rsform';
|
||||
import { domTooltipEntityReference, domTooltipSyntacticReference, findContainedNodes, findEnvelopingNodes } from '../../utils/codemirror';
|
||||
import { IColorTheme } from '../../utils/color';
|
||||
import { ReferenceTokens } from './parse';
|
||||
import { RefEntity, RefSyntactic } from './parse/parser.terms';
|
||||
|
||||
export const globalsHoverTooltip = (items: IConstituenta[], colors: IColorTheme) => {
|
||||
return hoverTooltip((view, pos) => {
|
||||
const nodes = findEnvelopingNodes(pos, pos, syntaxTree(view.state), [RefEntity, RefSyntactic]);
|
||||
return hoverTooltip(
|
||||
(view, pos) => {
|
||||
const nodes = findEnvelopingNodes(pos, pos, syntaxTree(view.state), ReferenceTokens);
|
||||
if (nodes.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
@ -26,7 +28,7 @@ export const globalsHoverTooltip = (items: IConstituenta[], colors: IColorTheme)
|
|||
above: false,
|
||||
create: () => domTooltipEntityReference(ref, cst, colors)
|
||||
}
|
||||
} else {
|
||||
} else if (nodes[0].type.id === RefSyntactic) {
|
||||
const ref = parseSyntacticReference(text);
|
||||
let masterText: string | undefined = undefined;
|
||||
if (ref.offset > 0) {
|
||||
|
@ -49,6 +51,8 @@ export const globalsHoverTooltip = (items: IConstituenta[], colors: IColorTheme)
|
|||
above: false,
|
||||
create: () => domTooltipSyntacticReference(ref, masterText)
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -158,15 +158,18 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
|||
text: inputText
|
||||
}
|
||||
textProcessor.generateLexeme(data, response => {
|
||||
const newForms: IWordForm[] = response.items.map(
|
||||
form => ({
|
||||
const lexeme: IWordForm[] = [];
|
||||
response.items.forEach(
|
||||
form => {
|
||||
const newForm: IWordForm = {
|
||||
text: form.text,
|
||||
grams: parseGrammemes(form.grams).filter(gram => SelectorGrammemesList.find(item => item === gram as Grammeme))
|
||||
}));
|
||||
setForms(forms => [
|
||||
...newForms,
|
||||
...forms.filter(value => !newForms.find(test => matchWordForm(value, test))),
|
||||
]);
|
||||
}
|
||||
if (newForm.grams.length === 2 && !lexeme.some(test => matchWordForm(test, newForm))) {
|
||||
lexeme.push(newForm);
|
||||
}
|
||||
});
|
||||
setForms(lexeme);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|||
import Button from '../../components/Common/Button';
|
||||
import { ConceptLoader } from '../../components/Common/ConceptLoader';
|
||||
import RSInput from '../../components/RSInput';
|
||||
import { TextWrapper } from '../../components/RSInput/textEditing';
|
||||
import { RSTextWrapper } from '../../components/RSInput/textEditing';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import useCheckExpression from '../../hooks/useCheckExpression';
|
||||
import { IConstituenta } from '../../models/rsform';
|
||||
|
@ -97,7 +97,7 @@ function EditorRSExpression({
|
|||
if (!rsInput.current || !rsInput.current.editor || !rsInput.current.state || !rsInput.current.view) {
|
||||
return;
|
||||
}
|
||||
const text = new TextWrapper(rsInput.current as Required<ReactCodeMirrorRef>);
|
||||
const text = new RSTextWrapper(rsInput.current as Required<ReactCodeMirrorRef>);
|
||||
if (id === TokenID.ID_LOCAL) {
|
||||
text.insertChar(key ?? 'unknown_local');
|
||||
} else {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { syntaxTree } from '@codemirror/language'
|
||||
import { NodeType, Tree, TreeCursor } from '@lezer/common'
|
||||
import { ReactCodeMirrorRef, SelectionRange } from '@uiw/react-codemirror'
|
||||
|
||||
import { IEntityReference, ISyntacticReference, parseGrammemes } from '../models/language'
|
||||
import { IConstituenta } from '../models/rsform'
|
||||
|
@ -225,3 +227,76 @@ export function domTooltipSyntacticReference(ref: ISyntacticReference, masterRef
|
|||
|
||||
return { dom: dom };
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class for CodeMirror editor.
|
||||
*
|
||||
* Assumes single range selection.
|
||||
*/
|
||||
export class CodeMirrorWrapper {
|
||||
ref: Required<ReactCodeMirrorRef>;
|
||||
|
||||
constructor(object: Required<ReactCodeMirrorRef>) {
|
||||
this.ref = object;
|
||||
}
|
||||
|
||||
getSelection(): SelectionRange {
|
||||
return this.ref.view.state.selection.main;
|
||||
}
|
||||
|
||||
setSelection(from: number, to: number) {
|
||||
this.ref.view.dispatch({
|
||||
selection: {
|
||||
anchor: from,
|
||||
head: to
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
replaceWith(data: string) {
|
||||
this.ref.view.dispatch(this.ref.view.state.replaceSelection(data));
|
||||
}
|
||||
|
||||
envelopeWith(left: string, right: string) {
|
||||
const selection = this.getSelection();
|
||||
const newSelection = !selection.empty ? {
|
||||
anchor: selection.from,
|
||||
head: selection.to + left.length + right.length
|
||||
} : {
|
||||
anchor: selection.to + left.length + right.length - 1,
|
||||
};
|
||||
this.ref.view.dispatch({
|
||||
changes: [
|
||||
{ from: selection.from, insert: left },
|
||||
{ from: selection.to, insert: right }
|
||||
],
|
||||
selection: newSelection
|
||||
});
|
||||
}
|
||||
|
||||
insertChar(key: string) {
|
||||
this.replaceWith(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enlarges selection to nearest spaces.
|
||||
*
|
||||
* If tokenFilter is provided then minimal valid token is selected.
|
||||
*/
|
||||
fixSelection(tokenFilter?: number[]) {
|
||||
const selection = this.getSelection();
|
||||
if (tokenFilter) {
|
||||
const nodes = findEnvelopingNodes(selection.from, selection.to, syntaxTree(this.ref.view.state), tokenFilter);
|
||||
if (nodes.length > 0) {
|
||||
const target = nodes[nodes.length - 1];
|
||||
this.setSelection(target.from, target.to);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const startWord = this.ref.view.state.wordAt(selection.from);
|
||||
const endWord = this.ref.view.state.wordAt(selection.to);
|
||||
if (startWord || endWord) {
|
||||
this.setSelection(startWord?.from ?? selection.from, endWord?.to ?? selection.to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,22 +65,9 @@ export function compareGrammemeOptions(left: IGrammemeOption, right: IGrammemeOp
|
|||
* Represents list of {@link Grammeme}s available in reference construction.
|
||||
*/
|
||||
export const SelectorGrammemesList = [
|
||||
Grammeme.NOUN, Grammeme.VERB,
|
||||
|
||||
Grammeme.sing, Grammeme.plur,
|
||||
Grammeme.nomn, Grammeme.gent, Grammeme.datv,
|
||||
Grammeme.accs, Grammeme.ablt, Grammeme.loct,
|
||||
|
||||
Grammeme.INFN, Grammeme.ADJF, Grammeme.PRTF,
|
||||
Grammeme.ADJS, Grammeme.PRTS,
|
||||
|
||||
Grammeme.perf, Grammeme.impf,
|
||||
Grammeme.tran, Grammeme.intr,
|
||||
Grammeme.pres, Grammeme.past, Grammeme.futr,
|
||||
Grammeme.per1, Grammeme.per2, Grammeme.per3,
|
||||
Grammeme.impr, Grammeme.indc,
|
||||
Grammeme.incl, Grammeme.excl,
|
||||
Grammeme.pssv, Grammeme.actv,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue
Block a user