diff --git a/rsconcept/frontend/src/components/RSInput/bracketMatching.ts b/rsconcept/frontend/src/components/RSInput/bracketMatching.ts index eab93330..36003ffa 100644 --- a/rsconcept/frontend/src/components/RSInput/bracketMatching.ts +++ b/rsconcept/frontend/src/components/RSInput/bracketMatching.ts @@ -1,30 +1,45 @@ import { bracketMatching, MatchResult } from '@codemirror/language'; import { Decoration, EditorView } from '@codemirror/view'; -const matchingMark = Decoration.mark({class: "cc-matchingBracket"}), - nonmatchingMark = Decoration.mark({class: "cc-nonmatchingBracket"}) +const matchingMark = Decoration.mark({class: "cc-matchingBracket"}); +const nonmatchingMark = Decoration.mark({class: "cc-nonmatchingBracket"}); function bracketRender(match: MatchResult) { - const decorations = [] - const mark = match.matched ? matchingMark : nonmatchingMark - decorations.push(mark.range(match.start.from, match.start.to)) - if (match.end) decorations.push(mark.range(match.end.from, match.end.to)) - return decorations + const decorations = []; + const mark = match.matched ? matchingMark : nonmatchingMark; + decorations.push(mark.range(match.start.from, match.start.to)); + if (match.end) { + decorations.push(mark.range(match.end.from, match.end.to)); + } + return decorations; } -export function ccBracketMatching(darkMode: boolean) { - const bracketTheme = EditorView.baseTheme({ - '.cc-matchingBracket': { - fontWeight: 600, - }, - '.cc-nonmatchingBracket': { - color: '#ef4444', - fontWeight: 700, - }, - '&.cm-focused .cc-matchingBracket': { - backgroundColor: darkMode ? '#734f00' : '#dae6f2', - }, - }); +const darkTheme = EditorView.baseTheme({ + '.cc-matchingBracket': { + fontWeight: 600, + }, + '.cc-nonmatchingBracket': { + color: '#ef4444', + fontWeight: 700, + }, + '&.cm-focused .cc-matchingBracket': { + backgroundColor: '#734f00', + }, +}); - return [bracketMatching({ renderMatch: bracketRender }), bracketTheme]; +const lightTheme = EditorView.baseTheme({ + '.cc-matchingBracket': { + fontWeight: 600, + }, + '.cc-nonmatchingBracket': { + color: '#ef4444', + fontWeight: 700, + }, + '&.cm-focused .cc-matchingBracket': { + backgroundColor: '#dae6f2', + }, +}); + +export function ccBracketMatching(darkMode: boolean) { + return [bracketMatching({ renderMatch: bracketRender }), darkMode ? darkTheme : lightTheme]; } \ No newline at end of file diff --git a/rsconcept/frontend/src/components/RSInput/index.tsx b/rsconcept/frontend/src/components/RSInput/index.tsx index ff39afd2..c6af7c74 100644 --- a/rsconcept/frontend/src/components/RSInput/index.tsx +++ b/rsconcept/frontend/src/components/RSInput/index.tsx @@ -6,10 +6,11 @@ import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef import { EditorView } from 'codemirror'; import { Ref, useMemo } from 'react'; +import { useRSForm } from '../../context/RSFormContext'; import { useConceptTheme } from '../../context/ThemeContext'; import { ccBracketMatching } from './bracketMatching'; import { RSLanguage } from './rslang'; -//import { cursorTooltip } from './tooltip'; +import { rshoverTooltip } from './tooltip'; const editorSetup: BasicSetupOptions = { highlightSpecialChars: false, @@ -51,6 +52,7 @@ function RSInput({ ...props }: RSInputProps) { const { darkMode } = useConceptTheme(); + const { schema } = useRSForm(); const cursor = useMemo(() => editable ? 'cursor-text': 'cursor-default', [editable]); const lightTheme: Extension = useMemo( @@ -64,7 +66,7 @@ function RSInput({ caret: '#5d00ff', }, styles: [ - { tag: t.name, class: 'text-[#b266ff]' }, // GlobalID + { tag: t.name, class: 'text-[#b266ff] cursor-default' }, // GlobalID { tag: t.variableName, class: 'text-[#24821a]' }, // LocalID { tag: t.propertyName, class: '' }, // Radical { tag: t.keyword, class: 'text-[#001aff]' }, // keywords @@ -85,7 +87,7 @@ function RSInput({ caret: '#ffaa00' }, styles: [ - { tag: t.name, class: 'text-[#dfbfff]' }, // GlobalID + { tag: t.name, class: 'text-[#dfbfff] cursor-default' }, // GlobalID { tag: t.variableName, class: 'text-[#69bf60]' }, // LocalID { tag: t.propertyName, class: '' }, // Radical { tag: t.keyword, class: 'text-[#808dff]' }, // keywords @@ -100,8 +102,8 @@ function RSInput({ EditorView.lineWrapping, RSLanguage, ccBracketMatching(darkMode), - //cursorTooltip(), - ], [darkMode]); + rshoverTooltip(schema?.items || []), + ], [darkMode, schema?.items]); return (
diff --git a/rsconcept/frontend/src/components/RSInput/tooltip.ts b/rsconcept/frontend/src/components/RSInput/tooltip.ts index d0c1f6c0..3514aefb 100644 --- a/rsconcept/frontend/src/components/RSInput/tooltip.ts +++ b/rsconcept/frontend/src/components/RSInput/tooltip.ts @@ -1,53 +1,64 @@ -import { EditorState, Extension, StateField } from "@codemirror/state"; -import { EditorView, showTooltip } from "@codemirror/view"; +import { Extension } from "@codemirror/state"; +import { hoverTooltip } from "@codemirror/view"; -function getCursorTooltips(state: EditorState) { - return state.selection.ranges - .filter(range => !range.empty) - .map(range => { - const line = state.doc.lineAt(range.head); - const text = `${line.number}:${range.head - line.from}`; - return { - pos: (range.to + range.from)/2, - above: false, - strictSide: true, - create: () => { - const dom = document.createElement("div"); - dom.className = "cm-tooltip-cursor"; - dom.textContent = text; - return { dom }; - } - }; - }); -} +import { IConstituenta } from '../../utils/models'; +import { getCstTypificationLabel } from '../../utils/staticUI'; -const cursorTooltipField = StateField.define({ - create: getCursorTooltips, - update(tooltips, transaction) { - if (!transaction.docChanged && !transaction.selection) { - return tooltips; - } - return getCursorTooltips(transaction.state); - }, - provide: field => showTooltip.computeN([field], state => state.field(field)) -}); - -const cursorTooltipBaseTheme = EditorView.baseTheme({ - ".cm-tooltip.cm-tooltip-cursor": { - backgroundColor: "#66b", - color: "white", - border: "none", - padding: "2px 7px", - borderRadius: "4px", - "&.cm-tooltip-arrow:before": { - borderTopColor: "#66b" - }, - "&.cm-tooltip-arrow:after": { - borderTopColor: "transparent" - } +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-20 text-sm'; + const alias = document.createElement('h1'); + alias.className = 'text-sm text-left'; + alias.textContent = `${cst.alias}: ${getCstTypificationLabel(cst)}`; + dom.appendChild(alias); + if (cst.term.resolved) { + const term = document.createElement('p'); + term.innerHTML = `Термин: ${cst.term.resolved}`; + dom.appendChild(term); } -}); - -export function cursorTooltip(): Extension { - return [cursorTooltipField, cursorTooltipBaseTheme]; + if (cst.definition.formal) { + const expression = document.createElement('p'); + expression.innerHTML = `Выражение: ${cst.definition.formal}`; + dom.appendChild(expression); + } + if (cst.definition.text.resolved) { + const definition = document.createElement('p'); + definition.innerHTML = `Определение: ${cst.definition.text.resolved}`; + dom.appendChild(definition); + } + if (cst.convention) { + const convention = document.createElement('p'); + convention.innerHTML = `Конвенция: ${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) { + return null; + } + return { + pos: start, + end: end, + above: false, + create: () => createTooltipFor(cst) + } + }); +} + +export function rshoverTooltip(items: IConstituenta[]): Extension { + return [getHoverTooltip(items)]; }