Add syntax highlighting

This commit is contained in:
IRBorisov 2023-08-13 00:57:31 +03:00
parent 67d60ade1c
commit 616ceebcb3
13 changed files with 139 additions and 70 deletions

View File

@ -4,7 +4,7 @@
"version": "1.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"prepare": "lezer-generator src/components/RSInput/rslang.grammar -o src/components/RSInput/rslangparser.ts", "prepare": "lezer-generator src/components/RSInput/rslang/rslang.grammar -o src/components/RSInput/rslang/parser.ts",
"test": "jest", "test": "jest",
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",

View File

@ -1,7 +0,0 @@
import {styleTags, tags} from '@lezer/highlight';
export const highlighting = styleTags({
Identifier: tags.name,
Number: tags.number,
String: tags.string
});

View File

@ -1,11 +1,13 @@
import { bracketMatching } from '@codemirror/language'; import { bracketMatching } from '@codemirror/language';
import { Extension } from '@codemirror/state'; import { Extension } from '@codemirror/state';
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes'; import { createTheme } from '@uiw/codemirror-themes';
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror'; import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
import { EditorView } from 'codemirror'; import { EditorView } from 'codemirror';
import { Ref, useMemo } from 'react'; import { Ref, useMemo } from 'react';
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import { RSLanguage } from './rslang';
const editorSetup: BasicSetupOptions = { const editorSetup: BasicSetupOptions = {
highlightSpecialChars: true, highlightSpecialChars: true,
@ -37,6 +39,7 @@ const editorSetup: BasicSetupOptions = {
const editorExtensions = [ const editorExtensions = [
EditorView.lineWrapping, EditorView.lineWrapping,
RSLanguage,
bracketMatching() bracketMatching()
]; ];
@ -62,15 +65,16 @@ function RSInput({
fontFamily: 'inherit', fontFamily: 'inherit',
background: editable ? '#ffffff' : '#f0f2f7', background: editable ? '#ffffff' : '#f0f2f7',
foreground: '#000000', foreground: '#000000',
selection: '#036dd626', selection: '#aacef2',
selectionMatch: '#036dd626',
caret: '#5d00ff', caret: '#5d00ff',
}, },
styles: [ styles: [
// { tag: t.comment, color: '#787b8099' }, { tag: t.name, class: 'text-[#b266ff]' }, // GlobalID
// { tag: t.variableName, color: '#0080ff' }, { tag: t.variableName, class: 'text-[#24821a]' }, // LocalID
// { tag: [t.string, t.special(t.brace)], color: '#5c6166' }, { tag: t.propertyName, class: '' }, // Radical
// { tag: t.definition(t.typeName), color: '#5c6166' }, { tag: t.keyword, class: 'text-[#001aff]' }, // keywords
{ tag: t.controlKeyword, class: 'font-semibold'}, // R | I | D
{ tag: t.unit, class: 'text-[0.75rem]' }, // indicies
] ]
}), [editable]); }), [editable]);
@ -81,20 +85,21 @@ function RSInput({
fontFamily: 'inherit', fontFamily: 'inherit',
background: editable ? '#070b12' : '#374151', background: editable ? '#070b12' : '#374151',
foreground: '#e4e4e7', foreground: '#e4e4e7',
selection: '#ffae00b0', selection: '#8c6000',
selectionMatch: '#ffae00b0',
caret: '#ffaa00' caret: '#ffaa00'
}, },
styles: [ styles: [
// { tag: t.comment, color: '#787b8099' }, { tag: t.name, class: 'text-[#dfbfff]' }, // GlobalID
// { tag: t.variableName, color: '#0080ff' }, { tag: t.variableName, class: 'text-[#69bf60]' }, // LocalID
// { tag: [t.string, t.special(t.brace)], color: '#5c6166' }, { tag: t.propertyName, class: '' }, // Radical
// { tag: t.definition(t.typeName), color: '#5c6166' }, { tag: t.keyword, class: 'text-[#808dff]' }, // keywords
{ tag: t.controlKeyword, class: 'font-semibold'}, // R | I | D
{ tag: t.unit, class: 'text-[0.75rem]' }, // indicies
] ]
}), [editable]); }), [editable]);
return ( return (
<div className={`w-full h-[${height}] ${cursor}`}> <div className={`w-full h-[${height}] ${cursor} text-lg`}>
<CodeMirror <CodeMirror
ref={innerref} ref={innerref}
basicSetup={editorSetup} basicSetup={editorSetup}

View File

@ -1,20 +0,0 @@
@top Program { expression* }
@skip { space }
expression {
Identifier |
String |
Application { "(" expression* ")" }
}
@tokens {
space { @whitespace+ }
Identifier { $[a-zA-Z_\-0-9]+ }
String { '"' (!["\\] | "\\" _)* '"' }
"(" ")"
}
@detectDelim
@external propSource highlighting from "./highlight.ts"

View File

@ -0,0 +1,14 @@
import {styleTags, tags} from '@lezer/highlight';
export const highlighting = styleTags({
Index: tags.unit,
ComplexIndex: tags.unit,
Integer: tags.literal,
Radical: tags.propertyName,
Global: tags.name,
Local: tags.variableName,
TextFunction: tags.keyword,
ConstructPrefix: tags.controlKeyword
});

View File

@ -0,0 +1,8 @@
import {LRLanguage} from "@codemirror/language"
import { parser } from './parser';
export const RSLanguage = LRLanguage.define({
parser: parser,
languageData: {}
});

View File

@ -0,0 +1,11 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
export const
Expression = 1,
TextFunction = 2,
ComplexIndex = 3,
ConstructPrefix = 4,
Integer = 5,
Global = 6,
Radical = 7,
Local = 8,
Index = 9

View File

@ -0,0 +1,18 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
import {LRParser} from "@lezer/lr"
import {highlighting} from "./highlight.ts"
export const parser = LRParser.deserialize({
version: 14,
states: "!^QVQPOOO}QPO'#C^OOQO'#C^'#C^O!SQQO'#CdOOQO'#Cj'#CjOOQO'#Cf'#CfQVQPOOOOQO,58x,58xOOQO,59O,59OOOQO-E6d-E6d",
stateData: "#a~O]OS~OSSOTSOUSOVSO_PO`POaPObQOcQOdQOeQOfRO~ORVO~OXWOSWXTWXUWXVWXZWX_WX`WXaWXbWXcWXdWXeWXfWX~Oa_T]UVSbcde`bf~",
goto: "n_PP`PPPPP`PdPPPjTSOUQUORXUTTOU",
nodeNames: "⚠ Expression TextFunction ComplexIndex ConstructPrefix Integer Global Radical Local Index",
maxTerm: 22,
propSources: [highlighting],
skippedNodes: [0],
repeatNodeCount: 1,
tokenData: ".p~RqX^#Ypq#Y!Q!R%m!R![%u!c!d&b!e!f&b!f!g&p!h!i&x!k!l'W!r!s']!t!u'k!u!v&b!v!w&b!z!{&b#R#S'{#T#U'{#U#V(W#V#W)j#W#X*y#X#d'{#d#e-P#e#f'{#f#g-o#g#o'{#y#z#Y$f$g#Y5i6S'{#BY#BZ#Y$IS$I_#Y$I|$JO#Y$JT$JU#Y$KV$KW#Y&FU&FV#Y~#_Z]~X^#Ypq#Y!Q![$Q#y#z#Y$f$g#Y#BY#BZ#Y$IS$I_#Y$I|$JO#Y$JT$JU#Y$KV$KW#Y&FU&FV#Y~$VZT~X^$xpq$x!Q![$Q#y#z$x$f$g$x#BY#BZ$x$IS$I_$x$I|$JO$x$JT$JU$x$KV$KW$x&FU&FV$x~$}YT~X^$xpq$x#y#z$x$f$g$x#BY#BZ$x$IS$I_$x$I|$JO$x$JT$JU$x$KV$KW$x&FU&FV$xQ%rPXQ!Q![%mR%|QRPXQ|}&S!Q![%mP&VP!R![&YP&_PRP|}&S~&eP!Q![&h~&mPU~!Q![&h~&uPS~!Q![&h~&{Q!Q![&h#]#^'R~'WOa~~']OS~~'`Q!Q![&h#f#g'f~'kO_~~'pPS~!Q!['s~'xPV~!Q!['s~(QQf~#T#o'{5i6S'{~(]Sf~#T#c'{#c#d(i#d#o'{5i6S'{~(nSf~#T#c'{#c#d(z#d#o'{5i6S'{~)PSf~#T#`'{#`#a)]#a#o'{5i6S'{~)dQc~f~#T#o'{5i6S'{~)oRf~#T#U)x#U#o'{5i6S'{~)}Sf~#T#f'{#f#g*Z#g#o'{5i6S'{~*`Sf~#T#W'{#W#X*l#X#o'{5i6S'{~*sQb~f~#T#o'{5i6S'{~+OSf~#T#X'{#X#Y+[#Y#o'{5i6S'{~+aSf~#T#U'{#U#V+m#V#o'{5i6S'{~+rSf~#T#c'{#c#d,O#d#o'{5i6S'{~,TSf~#T#c'{#c#d,a#d#o'{5i6S'{~,fSf~#T#`'{#`#a,r#a#o'{5i6S'{~,yQd~f~#T#o'{5i6S'{~-USf~#T#f'{#f#g-b#g#o'{5i6S'{~-iQ`~f~#T#o'{5i6S'{~-tSf~#T#X'{#X#Y.Q#Y#o'{5i6S'{~.VSf~#T#W'{#W#X.c#X#o'{5i6S'{~.jQe~f~#T#o'{5i6S'{",
tokenizers: [0, 1],
topRules: {"Expression":[0,1]},
tokenPrec: 94
})

View File

@ -0,0 +1,61 @@
@top Expression { token* }
@skip { space }
@tokens {
space { @whitespace+ }
Index { $[0-9]+ }
ComplexIndex { $[1-9](","$[1-9])* }
Integer { space$[0-9]+space? }
bigPr { "Pr" }
smallPr { "pr" }
filter { "Fi" }
card { "card" }
bool { "bool" }
debool { "debool" }
red { "red" }
ConstructPrefix { "D" | "R" | "I" }
Global { $[XCSDAPTF]$[0-9]+ }
Radical { "R"$[0-9]+ }
local { $[_a-zα-ω]$[a-zα-ω]* }
"(" ")"
"[" "]"
"{" "}"
@precedence { filter bigPr Global Radical ConstructPrefix }
@precedence { card bool debool red smallPr local }
@precedence { Integer space }
}
TextFunction {
bigPr ComplexIndex |
smallPr ComplexIndex |
filter ComplexIndex |
card | bool | debool | red
}
Local { local Index? }
token {
TextFunction |
ConstructPrefix |
Integer |
Global |
Radical |
Local
}
@detectDelim
@external propSource highlighting from "./highlight.ts"
//@asciiLetter matches $[a-zA-Z]
//@asciiLowercase matches $[a-z]
//@asciiUppercase is equivalent to $[A-Z]
//@digit matches $[0-9]
//@whitespace matches any character the Unicode standard defines as whitespace.
//@eof matches the end of the input

View File

@ -1,5 +0,0 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
export const
Program = 1,
Identifier = 2,
String = 3

View File

@ -1,22 +0,0 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
import {LRParser} from "@lezer/lr"
import {highlighting} from "./highlight.ts"
export const parser = LRParser.deserialize({
version: 14,
states: "!WQVQPOOObQPO'#CbOOQO'#Cg'#CgOOQO'#Cc'#CcQVQPOOOOQO,58|,58|OpQPO,58|OOQO-E6a-E6aOOQO1G.h1G.h",
stateData: "!O~OYOS~OQQORQOTPO~OQQORQOSTOTPO~OQQORQOSWOTPO~O",
goto: "s[PPPPPP]cPPPmXQOPSUQSOQUPTVSUXROPSU",
nodeNames: "⚠ Program Identifier String ) ( Application",
maxTerm: 11,
nodeProps: [
["openedBy", 4,"("],
["closedBy", 5,")"]
],
propSources: [highlighting],
skippedNodes: [0],
repeatNodeCount: 1,
tokenData: "%[~RbX^!Zpq!Zrs#Oxy$lyz$q}!O$v!Q![$v!c!}$v#R#S$v#T#o$v#y#z!Z$f$g!Z#BY#BZ!Z$IS$I_!Z$I|$JO!Z$JT$JU!Z$KV$KW!Z&FU&FV!Z~!`YY~X^!Zpq!Z#y#z!Z$f$g!Z#BY#BZ!Z$IS$I_!Z$I|$JO!Z$JT$JU!Z$KV$KW!Z&FU&FV!Z~#RVOr#Ors#hs#O#O#O#P#m#P;'S#O;'S;=`$f<%lO#O~#mOR~~#pRO;'S#O;'S;=`#y;=`O#O~#|WOr#Ors#hs#O#O#O#P#m#P;'S#O;'S;=`$f;=`<%l#O<%lO#O~$iP;=`<%l#O~$qOT~~$vOS~~${TQ~}!O$v!Q![$v!c!}$v#R#S$v#T#o$v",
tokenizers: [0],
topRules: {"Program":[0,1]},
tokenPrec: 0
})

View File

@ -19,11 +19,17 @@
} }
.cm-editor { .cm-editor {
@apply border shadow rounded clr-border px-2 @apply border shadow rounded clr-border px-1
} }
.cm-editor.cm-focused { .cm-editor.cm-focused {
@apply border shadow rounded outline-2 outline @apply border shadow rounded outline-2 outline
} }
.cm-matchingBracket {
@apply font-semibold
}
.cm-nonmatchingBracket {
@apply font-bold text-red-500
}
@layer components { @layer components {
h1 { h1 {

View File

@ -4,7 +4,7 @@ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import Button from '../../components/Common/Button'; import Button from '../../components/Common/Button';
import Label from '../../components/Common/Label'; import Label from '../../components/Common/Label';
import { Loader } from '../../components/Common/Loader'; import { Loader } from '../../components/Common/Loader';
import RSInput from '../../components/RSInput/RSInput'; import RSInput from '../../components/RSInput';
import { getSymbolSubstitute, TextWrapper } from '../../components/RSInput/textEditing'; import { getSymbolSubstitute, TextWrapper } from '../../components/RSInput/textEditing';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import useCheckExpression from '../../hooks/useCheckExpression'; import useCheckExpression from '../../hooks/useCheckExpression';