mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 21:10:38 +03:00
Display text reference tooltips
This commit is contained in:
parent
1f8f904626
commit
78c6a2306e
|
@ -13,7 +13,7 @@ import Label from '../Common/Label';
|
|||
import { ccBracketMatching } from './bracketMatching';
|
||||
import { RSLanguage } from './rslang';
|
||||
import { getSymbolSubstitute,TextWrapper } from './textEditing';
|
||||
import { rshoverTooltip as rsHoverTooltip } from './tooltip';
|
||||
import { rsHoverTooltip } from './tooltip';
|
||||
|
||||
const editorSetup: BasicSetupOptions = {
|
||||
highlightSpecialChars: false,
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import {LRLanguage} from '@codemirror/language'
|
||||
|
||||
import { parser } from './parser';
|
||||
import { Function, Global, Predicate } from './parser.terms';
|
||||
|
||||
export const GlobalTokens: number[] = [
|
||||
Global, Function, Predicate
|
||||
]
|
||||
|
||||
export const RSLanguage = LRLanguage.define({
|
||||
parser: parser,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { printTree } from '../../../utils/print-lezer-tree';
|
||||
import { printTree } from '../../../utils/codemirror';
|
||||
import { parser } from './parser';
|
||||
|
||||
const testData = [
|
||||
|
|
|
@ -1,50 +1,33 @@
|
|||
import { syntaxTree } from "@codemirror/language"
|
||||
import { Extension } from '@codemirror/state';
|
||||
import { hoverTooltip } from '@codemirror/view';
|
||||
import { EditorState } from '@uiw/react-codemirror';
|
||||
|
||||
import { IConstituenta } from '../../models/rsform';
|
||||
import { labelCstTypification } from '../../utils/labels';
|
||||
import { findEnvelopingNodes } from '../../utils/codemirror';
|
||||
import { domTooltipConstituenta } from '../../utils/codemirror';
|
||||
import { GlobalTokens } from './rslang';
|
||||
|
||||
function createTooltipFor(cst: IConstituenta) {
|
||||
const dom = document.createElement('div');
|
||||
dom.className = 'overflow-y-auto border shadow-md max-h-[25rem] max-w-[25rem] min-w-[10rem] w-fit z-tooltip text-sm px-2 py-2';
|
||||
const alias = document.createElement('p');
|
||||
alias.innerHTML = `<b>${cst.alias}:</b> ${labelCstTypification(cst)}`;
|
||||
dom.appendChild(alias);
|
||||
if (cst.term_resolved) {
|
||||
const term = document.createElement('p');
|
||||
term.innerHTML = `<b>Термин:</b> ${cst.term_resolved}`;
|
||||
dom.appendChild(term);
|
||||
function findAliasAt(pos: number, state: EditorState) {
|
||||
const { from: lineStart, to: lineEnd, text } = state.doc.lineAt(pos);
|
||||
const nodes = findEnvelopingNodes(pos, pos, syntaxTree(state), GlobalTokens);
|
||||
let alias = '';
|
||||
let start = 0;
|
||||
let end = 0;
|
||||
nodes.forEach(
|
||||
node => {
|
||||
if (node.to <= lineEnd && node.from >= lineStart) {
|
||||
alias = text.slice(node.from - lineStart, node.to - lineStart);
|
||||
start = node.from;
|
||||
end = node.to;
|
||||
}
|
||||
if (cst.definition_formal) {
|
||||
const expression = document.createElement('p');
|
||||
expression.innerHTML = `<b>Выражение:</b> ${cst.definition_formal}`;
|
||||
dom.appendChild(expression);
|
||||
}
|
||||
if (cst.definition_resolved) {
|
||||
const definition = document.createElement('p');
|
||||
definition.innerHTML = `<b>Определение:</b> ${cst.definition_resolved}`;
|
||||
dom.appendChild(definition);
|
||||
}
|
||||
if (cst.convention) {
|
||||
const convention = document.createElement('p');
|
||||
convention.innerHTML = `<b>Конвенция:</b> ${cst.convention}`;
|
||||
dom.appendChild(convention);
|
||||
}
|
||||
return { dom: dom }
|
||||
});
|
||||
return {alias, start, end};
|
||||
}
|
||||
|
||||
export const getHoverTooltip = (items: IConstituenta[]) => {
|
||||
return hoverTooltip((view, pos, side) => {
|
||||
const {from, to, text} = view.state.doc.lineAt(pos);
|
||||
let start = pos, end = pos;
|
||||
while (start > from && /\w/.test(text[start - from - 1]))
|
||||
start--;
|
||||
while (end < to && /\w/.test(text[end - from]))
|
||||
end++;
|
||||
if (start === pos && side < 0 || end === pos && side > 0) {
|
||||
return null;
|
||||
}
|
||||
const alias = text.slice(start - from, end - from);
|
||||
const globalsHoverTooltip = (items: IConstituenta[]) => {
|
||||
return hoverTooltip((view, pos) => {
|
||||
const { alias, start, end } = findAliasAt(pos, view.state);
|
||||
const cst = items.find(cst => cst.alias === alias);
|
||||
if (!cst) {
|
||||
return null;
|
||||
|
@ -53,11 +36,11 @@ export const getHoverTooltip = (items: IConstituenta[]) => {
|
|||
pos: start,
|
||||
end: end,
|
||||
above: false,
|
||||
create: () => createTooltipFor(cst)
|
||||
create: () => domTooltipConstituenta(cst)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function rshoverTooltip(items: IConstituenta[]): Extension {
|
||||
return [getHoverTooltip(items)];
|
||||
export function rsHoverTooltip(items: IConstituenta[]): Extension {
|
||||
return [globalsHoverTooltip(items)];
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import Label from '../Common/Label';
|
|||
import Modal from '../Common/Modal';
|
||||
import PrettyJson from '../Common/PrettyJSON';
|
||||
import { NaturalLanguage } from './parse';
|
||||
import { rshoverTooltip as rsHoverTooltip } from './tooltip';
|
||||
import { refsHoverTooltip } from './tooltip';
|
||||
|
||||
const editorSetup: BasicSetupOptions = {
|
||||
highlightSpecialChars: false,
|
||||
|
@ -87,7 +87,7 @@ function RefsInput({
|
|||
selection: colors.bgHover
|
||||
},
|
||||
styles: [
|
||||
{ tag: tags.name, color: colors.fgPurple }, // GlobalID
|
||||
{ tag: tags.name, color: colors.fgPurple, cursor: 'pointer' }, // GlobalID
|
||||
{ tag: tags.literal, color: colors.fgTeal }, // literals
|
||||
]
|
||||
}), [editable, colors, darkMode]);
|
||||
|
@ -96,8 +96,8 @@ function RefsInput({
|
|||
() => [
|
||||
EditorView.lineWrapping,
|
||||
NaturalLanguage,
|
||||
rsHoverTooltip(schema?.items || []),
|
||||
], [schema?.items]);
|
||||
refsHoverTooltip(schema?.items || [], colors),
|
||||
], [schema?.items, colors]);
|
||||
|
||||
function handleChange(newValue: string) {
|
||||
if (onChange) onChange(newValue);
|
||||
|
|
|
@ -3,8 +3,9 @@ import {styleTags, tags} from '@lezer/highlight';
|
|||
export const highlighting = styleTags({
|
||||
RefEntity: tags.name,
|
||||
Global: tags.name,
|
||||
Gram: tags.name,
|
||||
Grams: tags.name,
|
||||
|
||||
RefSyntactic: tags.literal,
|
||||
Offset: tags.literal,
|
||||
Nominal: tags.literal,
|
||||
});
|
|
@ -3,8 +3,8 @@ export const
|
|||
Text = 1,
|
||||
RefEntity = 2,
|
||||
Global = 3,
|
||||
Gram = 4,
|
||||
Grams = 4,
|
||||
RefSyntactic = 5,
|
||||
Offset = 6,
|
||||
Nominal = 7,
|
||||
Word = 8
|
||||
Filler = 8
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { printTree } from '../../../utils/print-lezer-tree';
|
||||
import { printTree } from '../../../utils/codemirror';
|
||||
import { parser } from './parser';
|
||||
|
||||
const testData = [
|
||||
['', '[Text]'],
|
||||
['тест русский', '[Text[Word][Word]]'],
|
||||
['test english', '[Text[Word][Word]]'],
|
||||
['test greek σσσ', '[Text[Word][Word][Word]]'],
|
||||
['X1 раз два X2', '[Text[Word][Word][Word][Word]]'],
|
||||
['тест русский', '[Text[Filler]]'],
|
||||
['test english', '[Text[Filler]]'],
|
||||
['test greek σσσ', '[Text[Filler]]'],
|
||||
['X1 раз два X2', '[Text[Filler]]'],
|
||||
|
||||
['@{1| черный }', '[Text[RefSyntactic[Offset][Nominal[Word]]]]'],
|
||||
['@{-1| черный }', '[Text[RefSyntactic[Offset][Nominal[Word]]]]'],
|
||||
['@{-100| черный слон }', '[Text[RefSyntactic[Offset][Nominal[Word][Word]]]]'],
|
||||
['@{X1|VERB,past,sing}', '[Text[RefEntity[Global][Gram][Gram][Gram]]]'],
|
||||
['@{X12|VERB,past,sing}', '[Text[RefEntity[Global][Gram][Gram][Gram]]]'],
|
||||
['@{1| черный }', '[Text[RefSyntactic[Offset][Nominal]]]'],
|
||||
['@{-1| черный }', '[Text[RefSyntactic[Offset][Nominal]]]'],
|
||||
['@{-100| черный слон }', '[Text[RefSyntactic[Offset][Nominal]]]'],
|
||||
['@{X1|VERB,past,sing}', '[Text[RefEntity[Global][Grams]]]'],
|
||||
['@{X12|VERB,past,sing}', '[Text[RefEntity[Global][Grams]]]'],
|
||||
];
|
||||
|
||||
describe('Testing NaturalParser', () => {
|
||||
|
|
|
@ -3,16 +3,16 @@ import {LRParser} from "@lezer/lr"
|
|||
import {highlighting} from "./highlight.ts"
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "#rQVQPOOO_QQO'#C^OOQO'#Ck'#CkOOQO'#Cj'#CjOOQO'#Ce'#CeQVQPOOOgQPO,58xOlQPO,58{OOQO-E6c-E6cOqQSO1G.dOvQPO1G.gO{QQO'#CoO!TQPO7+$OOOQO'#Cf'#CfO!YQPO'#CcO!bQPO7+$ROqQSO,59ZOOQO<<Gj<<GjOOQO-E6d-E6dOOQO<<Gm<<GmOOQO1G.u1G.u",
|
||||
stateData: "!j~O]OS~OWROaPO~ORUOUVO~ObXO~ObYO~OSZO~OW]O~Od`O`cX~O`aO~OW]O`VX~O`cO~OW]~",
|
||||
goto: "!WdPPePPePiPlrPPPx|PPP!QTQOTR_YQTORWTQ^YRb^TSOTTROTQ[XRd`",
|
||||
nodeNames: "⚠ Text RefEntity Global Gram RefSyntactic Offset Nominal Word",
|
||||
maxTerm: 20,
|
||||
states: "$[QVQPOOO_QQO'#C^OOQO'#Ck'#CkOgQPO'#CsOOQO'#Cd'#CdOOQO'#Cj'#CjOOQO'#Ce'#CeQVQPOOOrQPO,58xOwQPO,58{OOQO,59_,59_OOQO-E6c-E6cO|QSO1G.dO!RQPO1G.gO!WQQO'#CoOOQO'#C`'#C`O!`QPO7+$OOOQO'#Cf'#CfO!eQPO'#CcO!mQPO7+$RO|QSO,59ZOOQO<<Gj<<GjOOQO-E6d-E6dOOQO<<Gm<<GmOOQO1G.u1G.u",
|
||||
stateData: "!u~O]OS~OaPOfRO~ORWOUXO~OfROZgXagX~Ob[O~Ob]O~Od^O~OfaO~OedO`cX~O`eO~OfaO`VX~O`gO~Of]~",
|
||||
goto: "!fhPPiPmiPpsw}PPP!TsPPP!XPPP!_TQOVR`[Rc]TTOVQVORZVQb]RfbTUOVQ_[RhdSSOVRYR",
|
||||
nodeNames: "⚠ Text RefEntity Global Grams RefSyntactic Offset Nominal Filler",
|
||||
maxTerm: 23,
|
||||
propSources: [highlighting],
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 2,
|
||||
tokenData: "-}~R!TOX$bXZ%SZ^%w^p$bpq%Sq|$b|}'p}!O(^!O!Q$b!Q!R)a!R![*a![!b$b!b!c+c!c!d+n!d!e)a!e!f+n!f!g+n!g!h)a!h!i+n!i!r)a!r!s+n!s!t)a!t!u+n!u!v+n!v!w+n!w!z)a!z!{+n!{!})a!}#T$b#T#o)a#p#q-s#q#r-x#r#y$b#y#z%w#z$f$b$f$g%w$g#BY$b#BY#BZ%w#BZ$IS$b$IS$I_%w$I_$I|$b$I|$JO%w$JO$JT$b$JT$JU%w$JU$KV$b$KV$KW%w$KW&FU$b&FU&FV%w&FV;'S$b;'S;=`$|<%lO$bP$gVWPOX$bZp$bq!b$b!c#o$b#r;'S$b;'S;=`$|<%lO$bP%PP;=`<%l$b~%XY]~X^%Spq%S#y#z%S$f$g%S#BY#BZ%S$IS$I_%S$I|$JO%S$JT$JU%S$KV$KW%S&FU&FV%S~&OjWP]~OX$bXZ%SZ^%w^p$bpq%Sq!b$b!c#o$b#r#y$b#y#z%w#z$f$b$f$g%w$g#BY$b#BY#BZ%w#BZ$IS$b$IS$I_%w$I_$I|$b$I|$JO%w$JO$JT$b$JT$JU%w$JU$KV$b$KV$KW%w$KW&FU$b&FU&FV%w&FV;'S$b;'S;=`$|<%lO$bR'wVdQWPOX$bZp$bq!b$b!c#o$b#r;'S$b;'S;=`$|<%lO$bV(e^SSWPOX$bZp$bq}$b}!O)a!O!Q$b!Q!R)a!R![*a![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$bT)h]SSWPOX$bZp$bq}$b}!O)a!O!Q$b!Q![)a![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$bV*j]SSUQWPOX$bZp$bq}$b}!O)a!O!Q$b!Q![*a![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$b~+fP#o#p+i~+nOa~V+u^SSWPOX$bZp$bq}$b}!O)a!O!Q$b!Q!R)a!R![,q![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$bV,z]RQSSWPOX$bZp$bq}$b}!O)a!O!Q$b!Q![,q![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$b~-xOb~~-}O`~",
|
||||
tokenData: "-}~R!TOX$bXZ%SZ^%w^p$bpq%Sq|$b|}'p}!O(^!O!Q$b!Q!R)a!R![*a![!b$b!b!c+c!c!d+n!d!e)a!e!f+n!f!g+n!g!h)a!h!i+n!i!r)a!r!s+n!s!t)a!t!u+n!u!v+n!v!w+n!w!z)a!z!{+n!{!})a!}#T$b#T#o)a#p#q-s#q#r-x#r#y$b#y#z%w#z$f$b$f$g%w$g#BY$b#BY#BZ%w#BZ$IS$b$IS$I_%w$I_$I|$b$I|$JO%w$JO$JT$b$JT$JU%w$JU$KV$b$KV$KW%w$KW&FU$b&FU&FV%w&FV;'S$b;'S;=`$|<%lO$bP$gVfPOX$bZp$bq!b$b!c#o$b#r;'S$b;'S;=`$|<%lO$bP%PP;=`<%l$b~%XY]~X^%Spq%S#y#z%S$f$g%S#BY#BZ%S$IS$I_%S$I|$JO%S$JT$JU%S$KV$KW%S&FU&FV%S~&OjfP]~OX$bXZ%SZ^%w^p$bpq%Sq!b$b!c#o$b#r#y$b#y#z%w#z$f$b$f$g%w$g#BY$b#BY#BZ%w#BZ$IS$b$IS$I_%w$I_$I|$b$I|$JO%w$JO$JT$b$JT$JU%w$JU$KV$b$KV$KW%w$KW&FU$b&FU&FV%w&FV;'S$b;'S;=`$|<%lO$bR'wVeQfPOX$bZp$bq!b$b!c#o$b#r;'S$b;'S;=`$|<%lO$bV(e^dSfPOX$bZp$bq}$b}!O)a!O!Q$b!Q!R)a!R![*a![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$bT)h]dSfPOX$bZp$bq}$b}!O)a!O!Q$b!Q![)a![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$bV*j]UQdSfPOX$bZp$bq}$b}!O)a!O!Q$b!Q![*a![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$b~+fP#o#p+i~+nOa~V+u^dSfPOX$bZp$bq}$b}!O)a!O!Q$b!Q!R)a!R![,q![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$bV,z]RQdSfPOX$bZp$bq}$b}!O)a!O!Q$b!Q![,q![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$b~-xOb~~-}O`~",
|
||||
tokenizers: [0, 1, 2],
|
||||
topRules: {"Text":[0,1]},
|
||||
tokenPrec: 69
|
||||
tokenPrec: 80
|
||||
})
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@precedence {
|
||||
text @right
|
||||
p1
|
||||
p2
|
||||
}
|
||||
|
@ -13,16 +14,22 @@
|
|||
Offset { $[-]?$[1-9]$[0-9]* }
|
||||
Global { $[XCSDATFPR]$[1-9]$[0-9]* }
|
||||
|
||||
Word { ![@{|} \t\n]+ }
|
||||
word { ![@{|} \t\n]+ }
|
||||
|
||||
Gram { $[-a-zA-Z0-9]+ }
|
||||
gram { $[-a-zA-Z0-9]+ }
|
||||
|
||||
@precedence { Word, space }
|
||||
@precedence { word, space }
|
||||
}
|
||||
|
||||
textItem {
|
||||
!p1 ref |
|
||||
!p2 Word
|
||||
!p2 Filler
|
||||
}
|
||||
|
||||
Filler { word_enum }
|
||||
word_enum {
|
||||
word |
|
||||
word !text word_enum
|
||||
}
|
||||
|
||||
ref {
|
||||
|
@ -31,19 +38,18 @@ ref {
|
|||
}
|
||||
|
||||
RefEntity {
|
||||
"@{" Global "|" grams "}"
|
||||
"@{" Global "|" Grams "}"
|
||||
}
|
||||
Grams { gram_enum }
|
||||
gram_enum {
|
||||
gram |
|
||||
gram "," gram_enum
|
||||
}
|
||||
|
||||
RefSyntactic {
|
||||
"@{" Offset "|" Nominal "}"
|
||||
}
|
||||
|
||||
Nominal { Word+ }
|
||||
|
||||
grams {
|
||||
Gram |
|
||||
Gram "," grams
|
||||
}
|
||||
Nominal { word+ }
|
||||
|
||||
@detectDelim
|
||||
|
||||
|
|
|
@ -1,63 +1,43 @@
|
|||
import { syntaxTree } from "@codemirror/language"
|
||||
import { Extension } from '@codemirror/state';
|
||||
import { hoverTooltip } from '@codemirror/view';
|
||||
|
||||
import { parseEntityReference, parseSyntacticReference } from '../../models/language';
|
||||
import { IConstituenta } from '../../models/rsform';
|
||||
import { labelCstTypification } from '../../utils/labels';
|
||||
import { domTooltipEntityReference, domTooltipSyntacticReference, findEnvelopingNodes } from '../../utils/codemirror';
|
||||
import { IColorTheme } from '../../utils/color';
|
||||
import { RefEntity, RefSyntactic } from './parse/parser.terms';
|
||||
|
||||
function createTooltipFor(cst: IConstituenta) {
|
||||
const dom = document.createElement('div');
|
||||
dom.className = 'overflow-y-auto border shadow-md max-h-[25rem] max-w-[25rem] min-w-[10rem] w-fit z-tooltip text-sm px-2 py-2';
|
||||
const alias = document.createElement('p');
|
||||
alias.innerHTML = `<b>${cst.alias}:</b> ${labelCstTypification(cst)}`;
|
||||
dom.appendChild(alias);
|
||||
if (cst.term_resolved) {
|
||||
const term = document.createElement('p');
|
||||
term.innerHTML = `<b>Термин:</b> ${cst.term_resolved}`;
|
||||
dom.appendChild(term);
|
||||
}
|
||||
if (cst.definition_formal) {
|
||||
const expression = document.createElement('p');
|
||||
expression.innerHTML = `<b>Выражение:</b> ${cst.definition_formal}`;
|
||||
dom.appendChild(expression);
|
||||
}
|
||||
if (cst.definition_resolved) {
|
||||
const definition = document.createElement('p');
|
||||
definition.innerHTML = `<b>Определение:</b> ${cst.definition_resolved}`;
|
||||
dom.appendChild(definition);
|
||||
}
|
||||
if (cst.convention) {
|
||||
const convention = document.createElement('p');
|
||||
convention.innerHTML = `<b>Конвенция:</b> ${cst.convention}`;
|
||||
dom.appendChild(convention);
|
||||
}
|
||||
return { dom: dom }
|
||||
}
|
||||
|
||||
export const getHoverTooltip = (items: IConstituenta[]) => {
|
||||
return hoverTooltip((view, pos, side) => {
|
||||
const {from, to, text} = view.state.doc.lineAt(pos);
|
||||
let start = pos, end = pos;
|
||||
while (start > from && /\w/.test(text[start - from - 1]))
|
||||
start--;
|
||||
while (end < to && /\w/.test(text[end - from]))
|
||||
end++;
|
||||
if (start === pos && side < 0 || end === pos && side > 0) {
|
||||
return null;
|
||||
}
|
||||
const alias = text.slice(start - from, end - from);
|
||||
const cst = items.find(cst => cst.alias === alias);
|
||||
if (!cst) {
|
||||
export const globalsHoverTooltip = (items: IConstituenta[], colors: IColorTheme) => {
|
||||
return hoverTooltip((view, pos) => {
|
||||
const nodes = findEnvelopingNodes(pos, pos, syntaxTree(view.state), [RefEntity, RefSyntactic]);
|
||||
if (nodes.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
const start = nodes[0].from;
|
||||
const end = nodes[0].to;
|
||||
const text = view.state.doc.sliceString(start, end);
|
||||
if (nodes[0].type.id === RefEntity) {
|
||||
const ref = parseEntityReference(text);
|
||||
const cst = items.find(cst => cst.alias === ref.entity);
|
||||
return {
|
||||
pos: start,
|
||||
end: end,
|
||||
above: false,
|
||||
create: () => createTooltipFor(cst)
|
||||
create: () => domTooltipEntityReference(ref, cst, colors)
|
||||
}
|
||||
} else {
|
||||
const ref = parseSyntacticReference(text);
|
||||
return {
|
||||
pos: start,
|
||||
end: end,
|
||||
above: false,
|
||||
create: () => domTooltipSyntacticReference(ref)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function rshoverTooltip(items: IConstituenta[]): Extension {
|
||||
return [getHoverTooltip(items)];
|
||||
export function refsHoverTooltip(items: IConstituenta[], colors: IColorTheme): Extension {
|
||||
return [globalsHoverTooltip(items, colors)];
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Grammeme, parseGrammemes } from './language';
|
||||
import { Grammeme, parseEntityReference, parseGrammemes, parseSyntacticReference } from './language';
|
||||
|
||||
|
||||
describe('Testing grammeme parsing', () => {
|
||||
|
@ -11,10 +11,28 @@ describe('Testing grammeme parsing', () => {
|
|||
|
||||
test('regular grammemes',
|
||||
() => {
|
||||
expect(parseGrammemes('NOUN')).toStrictEqual([{type: Grammeme.NOUN, data: 'NOUN'}]);
|
||||
expect(parseGrammemes('sing,nomn')).toStrictEqual([
|
||||
{type: Grammeme.sing, data: 'sing'},
|
||||
{type: Grammeme.nomn, data: 'nomn'}
|
||||
]);
|
||||
expect(parseGrammemes('NOUN')).toStrictEqual([Grammeme.NOUN]);
|
||||
expect(parseGrammemes('sing,nomn')).toStrictEqual([Grammeme.sing, Grammeme.nomn]);
|
||||
expect(parseGrammemes('nomn,sing')).toStrictEqual([Grammeme.sing, Grammeme.nomn]);
|
||||
expect(parseGrammemes('nomn,invalid,sing')).toStrictEqual([Grammeme.sing, Grammeme.nomn, 'invalid']);
|
||||
expect(parseGrammemes('invalid,test')).toStrictEqual(['invalid', 'test']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Testing reference parsing', () => {
|
||||
test('entity reference',
|
||||
() => {
|
||||
expect(parseEntityReference('@{ X1 | NOUN,sing }')).toStrictEqual({entity: 'X1', form: 'NOUN,sing'});
|
||||
expect(parseEntityReference('@{X1|NOUN,sing}')).toStrictEqual({entity: 'X1', form: 'NOUN,sing'});
|
||||
expect(parseEntityReference('@{X111|NOUN,sing}')).toStrictEqual({entity: 'X111', form: 'NOUN,sing'});
|
||||
});
|
||||
|
||||
test('syntactic reference',
|
||||
() => {
|
||||
expect(parseSyntacticReference('@{1|test test}')).toStrictEqual({offset: 1, nominal: 'test test'});
|
||||
expect(parseSyntacticReference('@{101|test test}')).toStrictEqual({offset: 101, nominal: 'test test'});
|
||||
expect(parseSyntacticReference('@{-1|test test}')).toStrictEqual({offset: -1, nominal: 'test test'});
|
||||
expect(parseSyntacticReference('@{-99|test test}')).toStrictEqual({offset: -99, nominal: 'test test'});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// Module: Natural language model declarations.
|
||||
/**
|
||||
* Module: Natural language model declarations.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents API result for text output.
|
||||
|
@ -11,9 +13,6 @@ export interface ITextResult {
|
|||
* Represents single unit of language Morphology.
|
||||
*/
|
||||
export enum Grammeme {
|
||||
// Неизвестная граммема
|
||||
UNKN = 'UNKN',
|
||||
|
||||
// Части речи
|
||||
NOUN = 'NOUN', ADJF = 'ADJF', ADJS = 'ADJS', COMP = 'COMP',
|
||||
VERB = 'VERB', INFN = 'INFN', PRTF = 'PRTF', PRTS = 'PRTS',
|
||||
|
@ -204,17 +203,14 @@ export const VerbGrams = [
|
|||
/**
|
||||
* Represents {@link Grammeme} parse data.
|
||||
*/
|
||||
export interface IGramData {
|
||||
type: Grammeme
|
||||
data: string
|
||||
}
|
||||
export type GramData = Grammeme | string;
|
||||
|
||||
/**
|
||||
* Represents specific wordform attached to {@link Grammeme}s.
|
||||
*/
|
||||
export interface IWordForm {
|
||||
text: string
|
||||
grams: IGramData[]
|
||||
grams: GramData[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -232,16 +228,6 @@ export interface ILexemeData {
|
|||
items: IWordFormPlain[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality comparator for {@link IGramData}. Compares text data for unknown grammemes
|
||||
*/
|
||||
export function matchGrammeme(left: IGramData, right: IGramData): boolean {
|
||||
if (left.type !== right.type) {
|
||||
return false;
|
||||
}
|
||||
return left.type !== Grammeme.UNKN || left.data === right.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality comparator for {@link IWordForm}. Compares a set of Grammemes attached to wordforms
|
||||
*/
|
||||
|
@ -250,41 +236,43 @@ export function matchWordForm(left: IWordForm, right: IWordForm): boolean {
|
|||
return false;
|
||||
}
|
||||
for (let index = 0; index < left.grams.length; ++index) {
|
||||
if (!matchGrammeme(left.grams[index], right.grams[index])) {
|
||||
if (left.grams[index] !== right.grams[index]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function parseSingleGrammeme(text: string): IGramData {
|
||||
function parseSingleGrammeme(text: string): GramData {
|
||||
if (Object.values(Grammeme).includes(text as Grammeme)) {
|
||||
return {
|
||||
data: text,
|
||||
type: text as Grammeme
|
||||
}
|
||||
return text as Grammeme;
|
||||
} else {
|
||||
return {
|
||||
data: text,
|
||||
type: Grammeme.UNKN
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
export function sortGrammemes<TData extends IGramData>(input: TData[]): TData[] {
|
||||
const result: TData[] = [];
|
||||
Object.values(Grammeme).forEach(
|
||||
gram => {
|
||||
const item = input.find(data => data.type === gram);
|
||||
if (item) {
|
||||
result.push(item);
|
||||
/**
|
||||
* Compares {@link GramData} based on Grammeme enum and alpha order for strings.
|
||||
*/
|
||||
export function compareGrammemes(left: GramData, right: GramData): number {
|
||||
const indexLeft = Object.values(Grammeme).findIndex(gram => gram === left as Grammeme);
|
||||
const indexRight = Object.values(Grammeme).findIndex(gram => gram === right as Grammeme);
|
||||
if (indexLeft === -1 && indexRight === -1) {
|
||||
return left.localeCompare(right);
|
||||
} else if (indexLeft === -1 && indexRight !== -1) {
|
||||
return 1;
|
||||
} else if (indexLeft !== -1 && indexRight === -1) {
|
||||
return -1;
|
||||
} else {
|
||||
return indexLeft - indexRight;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function parseGrammemes(termForm: string): IGramData[] {
|
||||
const result: IGramData[] = [];
|
||||
/**
|
||||
* Transforms {@link Grammeme} enumeration to {@link GramData}.
|
||||
*/
|
||||
export function parseGrammemes(termForm: string): GramData[] {
|
||||
const result: GramData[] = [];
|
||||
const chunks = termForm.split(',');
|
||||
chunks.forEach(chunk => {
|
||||
chunk = chunk.trim();
|
||||
|
@ -292,7 +280,7 @@ export function parseGrammemes(termForm: string): IGramData[] {
|
|||
result.push(parseSingleGrammeme(chunk));
|
||||
}
|
||||
});
|
||||
return sortGrammemes(result);
|
||||
return result.sort(compareGrammemes);
|
||||
}
|
||||
|
||||
// ====== Reference resolution =====
|
||||
|
@ -353,3 +341,29 @@ export interface IResolutionData {
|
|||
output: string
|
||||
refs: IResolvedReference[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts {@link IEntityReference} from string representation.
|
||||
*
|
||||
* @param text - Reference text in a valid pattern. Must fit format '\@\{GLOBAL_ID|GRAMMEMES\}'
|
||||
*/
|
||||
export function parseEntityReference(text: string): IEntityReference {
|
||||
const blocks = text.slice(2, text.length - 1).split('|');
|
||||
return {
|
||||
entity: blocks[0].trim(),
|
||||
form: blocks[1].trim()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts {@link ISyntacticReference} from string representation.
|
||||
*
|
||||
* @param text - Reference text in a valid pattern. Must fit format '\@\{OFFSET|NOMINAL_FORM\}'
|
||||
*/
|
||||
export function parseSyntacticReference(text: string): ISyntacticReference {
|
||||
const blocks = text.slice(2, text.length - 1).split('|');
|
||||
return {
|
||||
offset: Number(blocks[0].trim()),
|
||||
nominal: blocks[1].trim()
|
||||
}
|
||||
}
|
|
@ -10,15 +10,13 @@ import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronDoubleDownIcon, CrossI
|
|||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import useConceptText from '../../hooks/useConceptText';
|
||||
import {
|
||||
Grammeme, GrammemeGroups, ITextRequest, IWordForm,
|
||||
IWordFormPlain,
|
||||
matchWordForm, NounGrams, parseGrammemes,
|
||||
sortGrammemes, VerbGrams
|
||||
GramData, Grammeme, GrammemeGroups, ITextRequest, IWordForm,
|
||||
IWordFormPlain, matchWordForm, NounGrams, parseGrammemes, VerbGrams
|
||||
} from '../../models/language';
|
||||
import { IConstituenta, TermForm } from '../../models/rsform';
|
||||
import { colorfgGrammeme } from '../../utils/color';
|
||||
import { labelGrammeme } from '../../utils/labels';
|
||||
import { IGrammemeOption, SelectorGrammemesList, SelectorGrammems } from '../../utils/selectors';
|
||||
import { compareGrammemeOptions,IGrammemeOption, SelectorGrammemesList, SelectorGrammems } from '../../utils/selectors';
|
||||
|
||||
interface DlgEditTermProps {
|
||||
hideWindow: () => void
|
||||
|
@ -44,7 +42,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
|||
forms.forEach(
|
||||
({text, grams}) => result.push({
|
||||
text: text,
|
||||
tags: grams.map(gram => gram.data).join(',')
|
||||
tags: grams.join(',')
|
||||
}));
|
||||
return result;
|
||||
}
|
||||
|
@ -67,32 +65,32 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
|||
// Filter grammemes when input changes
|
||||
useEffect(
|
||||
() => {
|
||||
let newFilter: Grammeme[] = [];
|
||||
inputGrams.forEach(({type: gram}) => {
|
||||
let newFilter: GramData[] = [];
|
||||
inputGrams.forEach(({value: gram}) => {
|
||||
if (!newFilter.includes(gram)) {
|
||||
if (NounGrams.includes(gram)) {
|
||||
if (NounGrams.includes(gram as Grammeme)) {
|
||||
newFilter.push(...NounGrams);
|
||||
}
|
||||
if (VerbGrams.includes(gram)) {
|
||||
if (VerbGrams.includes(gram as Grammeme)) {
|
||||
newFilter.push(...VerbGrams);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
inputGrams.forEach(({type: gram}) =>
|
||||
inputGrams.forEach(({value: gram}) =>
|
||||
GrammemeGroups.forEach(group => {
|
||||
if (group.includes(gram)) {
|
||||
newFilter = newFilter.filter(item => !group.includes(item) || item === gram);
|
||||
if (group.includes(gram as Grammeme)) {
|
||||
newFilter = newFilter.filter(item => !group.includes(item as Grammeme) || item === gram);
|
||||
}
|
||||
}));
|
||||
|
||||
newFilter.push(...inputGrams.map(({type: gram}) => gram));
|
||||
newFilter.push(...inputGrams.map(({value}) => value));
|
||||
if (newFilter.length === 0) {
|
||||
newFilter = [...VerbGrams, ...NounGrams];
|
||||
}
|
||||
|
||||
newFilter = [... new Set(newFilter)];
|
||||
setOptions(SelectorGrammems.filter(({type: gram}) => newFilter.includes(gram)));
|
||||
setOptions(SelectorGrammems.filter(({value}) => newFilter.includes(value)));
|
||||
}, [inputGrams]);
|
||||
|
||||
const handleSubmit = () => onSave(getData());
|
||||
|
@ -100,10 +98,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
|||
function handleAddForm() {
|
||||
const newForm: IWordForm = {
|
||||
text: inputText,
|
||||
grams: inputGrams.map(item => ({
|
||||
type: item.type,
|
||||
data: item.data
|
||||
}))
|
||||
grams: inputGrams.map(item => item.value)
|
||||
};
|
||||
setForms(forms => [
|
||||
newForm,
|
||||
|
@ -127,7 +122,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
|||
|
||||
function handleRowClicked(form: IWordForm) {
|
||||
setInputText(form.text);
|
||||
setInputGrams(SelectorGrammems.filter(gram => form.grams.find(test => test.type === gram.type)));
|
||||
setInputGrams(SelectorGrammems.filter(gram => form.grams.find(test => test === gram.value)));
|
||||
}
|
||||
|
||||
function handleResetForm() {
|
||||
|
@ -138,7 +133,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
|||
function handleInflect() {
|
||||
const data: IWordFormPlain = {
|
||||
text: term,
|
||||
grams: inputGrams.map(gram => gram.data).join(',')
|
||||
grams: inputGrams.map(gram => gram.value).join(',')
|
||||
}
|
||||
textProcessor.inflect(data, response => setInputText(response.result));
|
||||
}
|
||||
|
@ -149,7 +144,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
|||
}
|
||||
textProcessor.parse(data, response => {
|
||||
const grams = parseGrammemes(response.result);
|
||||
setInputGrams(SelectorGrammems.filter(gram => grams.find(test => test.type === gram.type)));
|
||||
setInputGrams(SelectorGrammems.filter(gram => grams.find(test => test === gram.value)));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -166,7 +161,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
|||
const newForms: IWordForm[] = response.items.map(
|
||||
form => ({
|
||||
text: form.text,
|
||||
grams: parseGrammemes(form.grams).filter(gram => SelectorGrammemesList.find(item => item === gram.type))
|
||||
grams: parseGrammemes(form.grams).filter(gram => SelectorGrammemesList.find(item => item === gram as Grammeme))
|
||||
}));
|
||||
setForms(forms => [
|
||||
...newForms,
|
||||
|
@ -196,13 +191,13 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
|||
{ props.getValue().map(
|
||||
gram =>
|
||||
<div
|
||||
key={`${props.cell.id}-${gram.type}`}
|
||||
key={`${props.cell.id}-${gram}`}
|
||||
className='min-w-[3rem] px-1 text-sm text-center rounded-md whitespace-nowrap'
|
||||
title=''
|
||||
style={{
|
||||
borderWidth: '1px',
|
||||
borderColor: colorfgGrammeme(gram.type, colors),
|
||||
color: colorfgGrammeme(gram.type, colors),
|
||||
borderColor: colorfgGrammeme(gram, colors),
|
||||
color: colorfgGrammeme(gram, colors),
|
||||
fontWeight: 600,
|
||||
backgroundColor: colors.bgInput
|
||||
}}
|
||||
|
@ -260,7 +255,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
|||
value={inputText}
|
||||
onChange={event => setInputText(event.target.value)}
|
||||
/>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center justify-between select-none'>
|
||||
<div className='flex items-center justify-start'>
|
||||
<MiniButton
|
||||
tooltip='Добавить словоформу'
|
||||
|
@ -281,6 +276,9 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
|||
onClick={handleGenerateLexeme}
|
||||
/>
|
||||
</div>
|
||||
<div className='text-sm'>
|
||||
Словоформ: {forms.length}
|
||||
</div>
|
||||
<div className='flex items-center justify-start'>
|
||||
<MiniButton
|
||||
tooltip='Генерировать словоформу'
|
||||
|
@ -304,7 +302,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
|||
|
||||
value={inputGrams}
|
||||
isDisabled={textProcessor.loading}
|
||||
onChange={newValue => setInputGrams(sortGrammemes([...newValue]))}
|
||||
onChange={newValue => setInputGrams([...newValue].sort(compareGrammemeOptions))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
219
rsconcept/frontend/src/utils/codemirror.ts
Normal file
219
rsconcept/frontend/src/utils/codemirror.ts
Normal file
|
@ -0,0 +1,219 @@
|
|||
import { NodeType, Tree, TreeCursor } from '@lezer/common'
|
||||
|
||||
import { IEntityReference, ISyntacticReference, parseGrammemes } from '../models/language'
|
||||
import { IConstituenta } from '../models/rsform'
|
||||
import { colorfgGrammeme,IColorTheme } from './color'
|
||||
import { describeConstituentaTerm, labelCstTypification, labelGrammeme } from './labels'
|
||||
|
||||
/**
|
||||
* Represents syntax tree node data.
|
||||
*/
|
||||
export interface SyntaxNode {
|
||||
type: NodeType
|
||||
from: number
|
||||
to: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents syntax tree cursor data.
|
||||
*/
|
||||
export interface CursorNode
|
||||
extends SyntaxNode {
|
||||
isLeaf: boolean
|
||||
}
|
||||
|
||||
function cursorNode({ type, from, to }: TreeCursor, isLeaf = false): CursorNode {
|
||||
return { type, from, to, isLeaf }
|
||||
}
|
||||
|
||||
type TreeTraversalOptions = {
|
||||
beforeEnter?: (cursor: TreeCursor) => void
|
||||
onEnter: (node: CursorNode) => false | void
|
||||
onLeave?: (node: CursorNode) => false | void
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements depth-first traversal.
|
||||
*/
|
||||
export function traverseTree(tree: Tree, { beforeEnter, onEnter, onLeave, }: TreeTraversalOptions) {
|
||||
const cursor = tree.cursor();
|
||||
for (;;) {
|
||||
let node = cursorNode(cursor)
|
||||
let leave = false
|
||||
const enter = !node.type.isAnonymous
|
||||
if (enter && beforeEnter) beforeEnter(cursor)
|
||||
node.isLeaf = !cursor.firstChild()
|
||||
if (enter) {
|
||||
leave = true
|
||||
if (onEnter(node) === false) return
|
||||
}
|
||||
if (!node.isLeaf) continue
|
||||
for (;;) {
|
||||
node = cursorNode(cursor, node.isLeaf)
|
||||
if (leave && onLeave) if (onLeave(node) === false) return;
|
||||
leave = cursor.type.isAnonymous
|
||||
node.isLeaf = false
|
||||
if (cursor.nextSibling()) break;
|
||||
if (!cursor.parent()) return;
|
||||
leave = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints tree to compact string.
|
||||
*/
|
||||
export function printTree(tree: Tree): string {
|
||||
const state = {
|
||||
output: '',
|
||||
prefixes: [] as string[]
|
||||
}
|
||||
traverseTree(tree, {
|
||||
onEnter: node => {
|
||||
state.output += '[';
|
||||
state.output += node.type.name;
|
||||
},
|
||||
onLeave: () => {
|
||||
state.output += ']';
|
||||
},
|
||||
})
|
||||
return state.output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reteives a list of all nodes, containing given range and corresponding to a filter.
|
||||
*/
|
||||
export function findEnvelopingNodes(start: number, finish: number, tree: Tree, filter?: number[]): SyntaxNode[] {
|
||||
const result: SyntaxNode[] = [];
|
||||
tree.cursor().iterate(
|
||||
node => {
|
||||
if (
|
||||
(!filter || filter.includes(node.type.id)) &&
|
||||
node.to >= start && node.from <= finish
|
||||
) {
|
||||
result.push({
|
||||
type: node.type,
|
||||
to: node.to,
|
||||
from: node.from
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reteives a list of all nodes, contained in given range and corresponding to a filter.
|
||||
*/
|
||||
export function findContainedNodes(start: number, finish: number, tree: Tree, filter?: number[]): SyntaxNode[] {
|
||||
const result: SyntaxNode[] = [];
|
||||
tree.cursor().iterate(
|
||||
node => {
|
||||
if (
|
||||
(!filter || filter.includes(node.type.id)) &&
|
||||
node.to <= start && node.from >= finish
|
||||
) {
|
||||
result.push({
|
||||
type: node.type,
|
||||
to: node.to,
|
||||
from: node.from
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DOM tooltip for {@link Constituenta}.
|
||||
*/
|
||||
export function domTooltipConstituenta(cst: IConstituenta) {
|
||||
const dom = document.createElement('div');
|
||||
dom.className = 'overflow-y-auto border shadow-md max-h-[25rem] max-w-[25rem] min-w-[10rem] w-fit z-tooltip text-sm px-2 py-2';
|
||||
|
||||
const alias = document.createElement('p');
|
||||
alias.innerHTML = `<b>${cst.alias}:</b> ${labelCstTypification(cst)}`;
|
||||
dom.appendChild(alias);
|
||||
|
||||
if (cst.term_resolved) {
|
||||
const term = document.createElement('p');
|
||||
term.innerHTML = `<b>Термин:</b> ${cst.term_resolved}`;
|
||||
dom.appendChild(term);
|
||||
}
|
||||
|
||||
if (cst.definition_formal) {
|
||||
const expression = document.createElement('p');
|
||||
expression.innerHTML = `<b>Выражение:</b> ${cst.definition_formal}`;
|
||||
dom.appendChild(expression);
|
||||
}
|
||||
|
||||
if (cst.definition_resolved) {
|
||||
const definition = document.createElement('p');
|
||||
definition.innerHTML = `<b>Определение:</b> ${cst.definition_resolved}`;
|
||||
dom.appendChild(definition);
|
||||
}
|
||||
|
||||
if (cst.convention) {
|
||||
const convention = document.createElement('p');
|
||||
convention.innerHTML = `<b>Конвенция:</b> ${cst.convention}`;
|
||||
dom.appendChild(convention);
|
||||
}
|
||||
return { dom: dom };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DOM tooltip for {@link IEntityReference}.
|
||||
*/
|
||||
export function domTooltipEntityReference(ref: IEntityReference, cst: IConstituenta | undefined, colors: IColorTheme) {
|
||||
const DIMENSIONS = 'max-h-[25rem] max-w-[25rem] min-w-[10rem] w-fit z-tooltip px-2 py-2';
|
||||
const LAYOUT = 'flex flex-col gap-1 overflow-y-auto'
|
||||
|
||||
const dom = document.createElement('div');
|
||||
dom.className = `${DIMENSIONS} ${LAYOUT} border shadow-md text-sm select-none cursor-auto`;
|
||||
|
||||
const term = document.createElement('p');
|
||||
term.innerHTML = `<b>${ref.entity}:</b> ${describeConstituentaTerm(cst)}`;
|
||||
dom.appendChild(term);
|
||||
|
||||
const grams = document.createElement('div');
|
||||
grams.className = 'flex flex-wrap gap-1';
|
||||
parseGrammemes(ref.form).forEach(
|
||||
gramStr => {
|
||||
const gram = document.createElement('div');
|
||||
gram.id =`tooltip-${gramStr}`;
|
||||
gram.className='min-w-[3rem] px-1 text-sm text-center rounded-md whitespace-nowrap';
|
||||
gram.style.borderWidth = '1px';
|
||||
gram.style.borderColor = colorfgGrammeme(gramStr, colors);
|
||||
gram.style.color = colorfgGrammeme(gramStr, colors);
|
||||
gram.style.fontWeight = '600';
|
||||
gram.style.backgroundColor = colors.bgInput;
|
||||
gram.innerText = labelGrammeme(gramStr);
|
||||
grams.appendChild(gram);
|
||||
});
|
||||
dom.appendChild(grams);
|
||||
|
||||
return { dom: dom };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DOM tooltip for {@link ISyntacticReference}.
|
||||
*/
|
||||
export function domTooltipSyntacticReference(ref: ISyntacticReference) {
|
||||
const DIMENSIONS = 'max-h-[25rem] max-w-[25rem] min-w-[10rem] w-fit z-tooltip px-2 py-2';
|
||||
const LAYOUT = 'flex flex-col gap-1 overflow-y-auto'
|
||||
|
||||
const dom = document.createElement('div');
|
||||
dom.className = `${DIMENSIONS} ${LAYOUT} border shadow-md text-sm select-none cursor-auto`;
|
||||
|
||||
const title = document.createElement('p');
|
||||
title.innerHTML = '<b>Синтаксическая ссылка</b>';
|
||||
dom.appendChild(title);
|
||||
|
||||
const offset = document.createElement('p');
|
||||
offset.innerHTML = `<b>Смещение:</b> ${ref.offset}`;
|
||||
dom.appendChild(offset);
|
||||
|
||||
const nominal = document.createElement('p');
|
||||
nominal.innerHTML = `<b>Начальная форма:</b> ${ref.nominal}`;
|
||||
dom.appendChild(nominal);
|
||||
|
||||
return { dom: dom };
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// =========== Modules contains all dynamic color definitions ==========
|
||||
|
||||
import { Grammeme, NounGrams, PartOfSpeech, VerbGrams } from '../models/language'
|
||||
import { GramData, Grammeme, NounGrams, PartOfSpeech, VerbGrams } from '../models/language'
|
||||
import { CstClass, ExpressionStatus } from '../models/rsform'
|
||||
import { ISyntaxTreeNode, TokenID } from '../models/rslang'
|
||||
|
||||
|
@ -385,30 +385,31 @@ export function colorbgCstClass(cstClass: CstClass, colors: IColorTheme): string
|
|||
}
|
||||
}
|
||||
|
||||
export function colorfgGrammeme(gram: Grammeme, colors: IColorTheme): string {
|
||||
if (PartOfSpeech.includes(gram)) {
|
||||
export function colorfgGrammeme(gram: GramData, colors: IColorTheme): string {
|
||||
if (PartOfSpeech.includes(gram as Grammeme)) {
|
||||
return colors.fgBlue;
|
||||
}
|
||||
if (NounGrams.includes(gram)) {
|
||||
if (NounGrams.includes(gram as Grammeme)) {
|
||||
return colors.fgGreen;
|
||||
}
|
||||
if (VerbGrams.includes(gram)) {
|
||||
if (VerbGrams.includes(gram as Grammeme)) {
|
||||
return colors.fgTeal;
|
||||
}
|
||||
if (gram === Grammeme.UNKN) {
|
||||
if (!Object.values(Grammeme).includes(gram as Grammeme)) {
|
||||
return colors.fgRed;
|
||||
}
|
||||
} else {
|
||||
return colors.fgPurple;
|
||||
}
|
||||
}
|
||||
|
||||
export function colorbgGrammeme(gram: Grammeme, colors: IColorTheme): string {
|
||||
if (PartOfSpeech.includes(gram)) {
|
||||
export function colorbgGrammeme(gram: GramData, colors: IColorTheme): string {
|
||||
if (PartOfSpeech.includes(gram as Grammeme)) {
|
||||
return colors.bgBlue;
|
||||
}
|
||||
if (NounGrams.includes(gram)) {
|
||||
if (NounGrams.includes(gram as Grammeme)) {
|
||||
return colors.bgGreen;
|
||||
}
|
||||
if (VerbGrams.includes(gram)) {
|
||||
if (VerbGrams.includes(gram as Grammeme)) {
|
||||
return colors.bgTeal;
|
||||
}
|
||||
return colors.bgInput;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// =========== Modules contains all text descriptors ==========
|
||||
|
||||
import { Grammeme,IGramData } from '../models/language';
|
||||
import { GramData,Grammeme } from '../models/language';
|
||||
import { CstMatchMode, DependencyMode, HelpTopic } from '../models/miscelanious';
|
||||
import { CstClass, CstType, ExpressionStatus, IConstituenta } from '../models/rsform';
|
||||
import { IFunctionArg, IRSErrorDescription, ISyntaxTreeNode, ParsingStatus, RSErrorType, TokenID } from '../models/rslang';
|
||||
|
@ -23,6 +23,17 @@ export function describeConstituenta(cst: IConstituenta): string {
|
|||
}
|
||||
}
|
||||
|
||||
export function describeConstituentaTerm(cst: IConstituenta | undefined): string {
|
||||
if (!cst) {
|
||||
return '!Конституента отсутствует!';
|
||||
}
|
||||
if (!cst.term_resolved) {
|
||||
return '!Пустой термин!';
|
||||
} else {
|
||||
return cst.term_resolved;
|
||||
}
|
||||
}
|
||||
|
||||
export function labelConstituenta(cst: IConstituenta) {
|
||||
return `${cst.alias}: ${describeConstituenta(cst)}`;
|
||||
}
|
||||
|
@ -346,8 +357,10 @@ export function labelSyntaxTree(node: ISyntaxTreeNode): string {
|
|||
return 'UNKNOWN ' + String(node.typeID);
|
||||
}
|
||||
|
||||
export function labelGrammeme(gram: IGramData): string {
|
||||
switch (gram.type) {
|
||||
export function labelGrammeme(gram: GramData): string {
|
||||
switch (gram) {
|
||||
default: return `Неизв: ${gram}`;
|
||||
|
||||
case Grammeme.NOUN: return 'ЧР: сущ';
|
||||
case Grammeme.VERB: return 'ЧР: глагол';
|
||||
case Grammeme.INFN: return 'ЧР: глагол инф';
|
||||
|
@ -411,8 +424,6 @@ export function labelGrammeme(gram: IGramData): string {
|
|||
case Grammeme.Slng: return 'Стиль: жаргон';
|
||||
case Grammeme.Arch: return 'Стиль: устаревший';
|
||||
case Grammeme.Litr: return 'Стиль: литературный';
|
||||
|
||||
case Grammeme.UNKN: return `Неизв: ${gram.data}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
import { NodeType, Tree, TreeCursor } from '@lezer/common'
|
||||
|
||||
export type CursorNode = {
|
||||
type: NodeType
|
||||
from: number
|
||||
to: number
|
||||
isLeaf: boolean
|
||||
}
|
||||
|
||||
function cursorNode({ type, from, to }: TreeCursor, isLeaf = false): CursorNode {
|
||||
return { type, from, to, isLeaf }
|
||||
}
|
||||
|
||||
type TreeTraversalOptions = {
|
||||
beforeEnter?: (cursor: TreeCursor) => void
|
||||
onEnter: (node: CursorNode) => false | void
|
||||
onLeave?: (node: CursorNode) => false | void
|
||||
}
|
||||
|
||||
export function traverseTree(tree: Tree, { beforeEnter, onEnter, onLeave, }: TreeTraversalOptions) {
|
||||
const cursor = tree.cursor();
|
||||
for (;;) {
|
||||
let node = cursorNode(cursor)
|
||||
let leave = false
|
||||
const enter = !node.type.isAnonymous
|
||||
if (enter && beforeEnter) beforeEnter(cursor)
|
||||
node.isLeaf = !cursor.firstChild()
|
||||
if (enter) {
|
||||
leave = true
|
||||
if (onEnter(node) === false) return
|
||||
}
|
||||
if (!node.isLeaf) continue
|
||||
for (;;) {
|
||||
node = cursorNode(cursor, node.isLeaf)
|
||||
if (leave && onLeave) if (onLeave(node) === false) return;
|
||||
leave = cursor.type.isAnonymous
|
||||
node.isLeaf = false
|
||||
if (cursor.nextSibling()) break;
|
||||
if (!cursor.parent()) return;
|
||||
leave = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function printTree(tree: Tree): string {
|
||||
const state = {
|
||||
output: '',
|
||||
prefixes: [] as string[]
|
||||
}
|
||||
traverseTree(tree, {
|
||||
onEnter: node => {
|
||||
state.output += '[';
|
||||
state.output += node.type.name;
|
||||
},
|
||||
onLeave: () => {
|
||||
state.output += ']';
|
||||
},
|
||||
})
|
||||
return state.output;
|
||||
}
|
|
@ -1,13 +1,15 @@
|
|||
// Module: Selector maps
|
||||
import { LayoutTypes } from 'reagraph';
|
||||
|
||||
import { Grammeme, IGramData } from '../models/language';
|
||||
import { compareGrammemes,type GramData, Grammeme } from '../models/language';
|
||||
import { CstType } from '../models/rsform';
|
||||
import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph';
|
||||
import { labelGrammeme } from './labels';
|
||||
import { labelCstType } from './labels';
|
||||
|
||||
|
||||
/**
|
||||
* Represents options for GraphLayout selector.
|
||||
*/
|
||||
export const SelectorGraphLayout: { value: LayoutTypes, label: string }[] = [
|
||||
{ value: 'treeTd2d', label: 'Граф: ДеревоВ 2D' },
|
||||
{ value: 'treeTd3d', label: 'Граф: ДеревоВ 3D' },
|
||||
|
@ -24,12 +26,18 @@ export const SelectorGraphLayout: { value: LayoutTypes, label: string }[] = [
|
|||
// { value: 'hierarchicalLr', label: 'hierarchicalLr'}
|
||||
];
|
||||
|
||||
/**
|
||||
* Represents options for {@link ColoringScheme} selector.
|
||||
*/
|
||||
export const SelectorGraphColoring: { value: ColoringScheme, label: string }[] = [
|
||||
{ value: 'none', label: 'Цвет: моно' },
|
||||
{ value: 'status', label: 'Цвет: статус' },
|
||||
{ value: 'type', label: 'Цвет: класс' },
|
||||
];
|
||||
|
||||
/**
|
||||
* Represents options for {@link CstType} selector.
|
||||
*/
|
||||
export const SelectorCstType = (
|
||||
Object.values(CstType)).map(
|
||||
typeStr => ({
|
||||
|
@ -38,11 +46,24 @@ export const SelectorCstType = (
|
|||
})
|
||||
);
|
||||
|
||||
export interface IGrammemeOption extends IGramData {
|
||||
value: string
|
||||
/**
|
||||
* Represents single option for {@link Grammeme} selector.
|
||||
*/
|
||||
export interface IGrammemeOption {
|
||||
value: GramData
|
||||
label: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares {@link IGrammemeOption} based on Grammeme comparison.
|
||||
*/
|
||||
export function compareGrammemeOptions(left: IGrammemeOption, right: IGrammemeOption): number {
|
||||
return compareGrammemes(left.value, right.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents list of {@link Grammeme}s available in reference construction.
|
||||
*/
|
||||
export const SelectorGrammemesList = [
|
||||
Grammeme.NOUN, Grammeme.VERB,
|
||||
|
||||
|
@ -62,11 +83,12 @@ export const SelectorGrammemesList = [
|
|||
Grammeme.pssv, Grammeme.actv,
|
||||
];
|
||||
|
||||
/**
|
||||
* Represents options for {@link Grammeme} selector.
|
||||
*/
|
||||
export const SelectorGrammems: IGrammemeOption[] =
|
||||
SelectorGrammemesList.map(
|
||||
gram => ({
|
||||
type: gram,
|
||||
data: gram as string,
|
||||
value: gram as string,
|
||||
label: labelGrammeme({type: gram, data: ''} as IGramData)
|
||||
value: gram,
|
||||
label: labelGrammeme(gram)
|
||||
}));
|
||||
|
|
Loading…
Reference in New Issue
Block a user