F: Replace ligature combinations with unicode symbols

This commit is contained in:
Ivan 2025-10-31 17:53:26 +03:00
parent 6ba05b79bf
commit 6b7f56ec39
4 changed files with 116 additions and 56 deletions

View File

@ -23,7 +23,7 @@ import { extractGlobals } from '../../models/rslang-api';
import { ccBracketMatching } from './bracket-matching'; import { ccBracketMatching } from './bracket-matching';
import { rsNavigation } from './click-navigation'; import { rsNavigation } from './click-navigation';
import { RSLanguage } from './rslang'; import { RSLanguage } from './rslang';
import { getSymbolSubstitute, RSTextWrapper } from './text-editing'; import { getLigatureSymbol, getSymbolSubstitute, isPotentialLigature, RSTextWrapper } from './text-editing';
import { rsHoverTooltip } from './tooltip'; import { rsHoverTooltip } from './tooltip';
const editorSetup: BasicSetupOptions = { const editorSetup: BasicSetupOptions = {
@ -130,18 +130,15 @@ export const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
...(noTooltip || !schema ? [] : [rsHoverTooltip(schema, onOpenEdit !== undefined)]) ...(noTooltip || !schema ? [] : [rsHoverTooltip(schema, onOpenEdit !== undefined)])
]; ];
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) { function handleAutoComplete(text: RSTextWrapper): boolean {
if (!thisRef.current) {
return;
}
const text = new RSTextWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
if ((event.ctrlKey || event.metaKey) && event.code === 'Space') {
const selection = text.getSelection(); const selection = text.getSelection();
if (!selection.empty || !schema) { if (!selection.empty || !schema) {
return; return false;
} }
const wordRange = text.getWord(selection.from); const wordRange = text.getWord(selection.from);
if (wordRange) { if (!wordRange) {
return false;
}
const word = text.getText(wordRange.from, wordRange.to); const word = text.getText(wordRange.from, wordRange.to);
if (word.length > 2 && (word.startsWith('Pr') || word.startsWith('pr'))) { if (word.length > 2 && (word.startsWith('Pr') || word.startsWith('pr'))) {
text.setSelection(wordRange.from, wordRange.from + 2); text.setSelection(wordRange.from, wordRange.from + 2);
@ -150,36 +147,60 @@ export const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
} else { } else {
text.replaceWith('Pr'); text.replaceWith('Pr');
} }
event.preventDefault(); return true;
event.stopPropagation();
return;
} }
}
const hint = text.getText(selection.from - 1, selection.from); const hint = text.getText(selection.from - 1, selection.from);
const type = guessCstType(hint); const type = guessCstType(hint);
if (hint === getCstTypePrefix(type)) { if (hint === getCstTypePrefix(type)) {
text.setSelection(selection.from - 1, selection.from); text.setSelection(selection.from - 1, selection.from);
} }
const takenAliases = [...extractGlobals(thisRef.current.view?.state.doc.toString() ?? '')]; const takenAliases = [...extractGlobals(thisRef.current?.view?.state.doc.toString() ?? '')];
const newAlias = generateAlias(type, schema, takenAliases); const newAlias = generateAlias(type, schema, takenAliases);
text.replaceWith(newAlias); text.replaceWith(newAlias);
event.preventDefault(); return true;
event.stopPropagation();
} else if (event.altKey) {
if (text.processAltKey(event.code, event.shiftKey)) {
event.preventDefault();
event.stopPropagation();
} }
} else if (!(event.ctrlKey || event.metaKey)) {
function processInput(event: React.KeyboardEvent<HTMLDivElement>): boolean {
const text = new RSTextWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
if ((event.ctrlKey || event.metaKey) && event.code === 'Space') {
return handleAutoComplete(text);
}
if (event.altKey) {
return text.processAltKey(event.code, event.shiftKey);
}
if (!(event.ctrlKey || event.metaKey)) {
if (isPotentialLigature(event.key)) {
const selection = text.getSelection();
const prevSymbol = text.getText(selection.from - 1, selection.from);
const newSymbol = getLigatureSymbol(prevSymbol, event.key);
if (newSymbol) {
text.setSelection(selection.from - 1, selection.to);
text.replaceWith(newSymbol);
}
return !!newSymbol;
} else {
const newSymbol = getSymbolSubstitute(event.code, event.shiftKey); const newSymbol = getSymbolSubstitute(event.code, event.shiftKey);
if (newSymbol) { if (newSymbol) {
text.replaceWith(newSymbol); text.replaceWith(newSymbol);
event.preventDefault();
event.stopPropagation();
} }
} else if (event.code === 'KeyQ' && onAnalyze) { return !!newSymbol;
}
}
if (event.code === 'KeyQ' && onAnalyze) {
onAnalyze(); onAnalyze();
return true;
}
return false;
}
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
if (!thisRef.current) {
return;
}
if (processInput(event)) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
} }

View File

@ -6,6 +6,24 @@ import { CodeMirrorWrapper } from '@/utils/codemirror';
import { TokenID } from '../../backend/types'; import { TokenID } from '../../backend/types';
/* Determines whether an input key is a potential ligature. */
export function isPotentialLigature(text: string): boolean {
return text == '=';
}
/* Determines symbol for ligature replacement. */
export function getLigatureSymbol(prevSymbol: string, text: string): string | undefined {
if (text == '=') {
// prettier-ignore
switch (prevSymbol) {
case '!': return '≠';
case '>': return '≥';
case '<': return '≤';
}
}
return undefined;
}
export function getSymbolSubstitute(keyCode: string, shiftPressed: boolean): string | undefined { export function getSymbolSubstitute(keyCode: string, shiftPressed: boolean): string | undefined {
// prettier-ignore // prettier-ignore
if (shiftPressed) { if (shiftPressed) {
@ -172,7 +190,12 @@ export class RSTextWrapper extends CodeMirrorWrapper {
this.replaceWith('∃'); this.replaceWith('∃');
return true; return true;
case TokenID.SET_IN: case TokenID.SET_IN:
if (this.getPrevSymbol() == '!') {
this.setSelection(selection.from - 1, selection.to);
this.replaceWith('∉');
} else {
this.replaceWith('∈'); this.replaceWith('∈');
}
return true; return true;
case TokenID.SET_NOT_IN: case TokenID.SET_NOT_IN:
this.replaceWith('∉'); this.replaceWith('∉');
@ -184,7 +207,12 @@ export class RSTextWrapper extends CodeMirrorWrapper {
this.replaceWith('&'); this.replaceWith('&');
return true; return true;
case TokenID.SUBSET_OR_EQ: case TokenID.SUBSET_OR_EQ:
if (this.getPrevSymbol() == '!') {
this.setSelection(selection.from - 1, selection.to);
this.replaceWith('⊄');
} else {
this.replaceWith('⊆'); this.replaceWith('⊆');
}
return true; return true;
case TokenID.LOGIC_IMPLICATION: case TokenID.LOGIC_IMPLICATION:
this.replaceWith('⇒'); this.replaceWith('⇒');
@ -208,13 +236,23 @@ export class RSTextWrapper extends CodeMirrorWrapper {
this.replaceWith('Z'); this.replaceWith('Z');
return true; return true;
case TokenID.SUBSET: case TokenID.SUBSET:
if (this.getPrevSymbol() == '!') {
this.setSelection(selection.from - 1, selection.to);
this.replaceWith('⊄');
} else {
this.replaceWith('⊂'); this.replaceWith('⊂');
}
return true; return true;
case TokenID.NOT_SUBSET: case TokenID.NOT_SUBSET:
this.replaceWith('⊄'); this.replaceWith('⊄');
return true; return true;
case TokenID.EQUAL: case TokenID.EQUAL:
if (this.getPrevSymbol() == '!') {
this.setSelection(selection.from - 1, selection.to);
this.replaceWith('≠');
} else {
this.replaceWith('='); this.replaceWith('=');
}
return true; return true;
case TokenID.NOTEQUAL: case TokenID.NOTEQUAL:
this.replaceWith('≠'); this.replaceWith('≠');

View File

@ -133,7 +133,7 @@ export function EditorRSExpression({
} }
const text = new RSTextWrapper(rsInput.current as Required<ReactCodeMirrorRef>); const text = new RSTextWrapper(rsInput.current as Required<ReactCodeMirrorRef>);
if (id === TokenID.ID_LOCAL) { if (id === TokenID.ID_LOCAL) {
text.insertChar(key ?? 'unknown_local'); text.replaceWith(key ?? 'unknown_local');
} else { } else {
text.insertToken(id); text.insertToken(id);
} }

View File

@ -123,6 +123,11 @@ export class CodeMirrorWrapper {
this.ref = object; this.ref = object;
} }
getPrevSymbol(): string {
const selection = this.getSelection();
return this.getText(selection.from - 1, selection.from);
}
getText(from: number, to: number): string { getText(from: number, to: number): string {
return this.ref.view.state.doc.sliceString(from, to); return this.ref.view.state.doc.sliceString(from, to);
} }
@ -149,10 +154,6 @@ export class CodeMirrorWrapper {
}); });
} }
insertChar(key: string) {
this.replaceWith(key);
}
replaceWith(data: string) { replaceWith(data: string) {
this.ref.view.dispatch(this.ref.view.state.replaceSelection(data)); this.ref.view.dispatch(this.ref.view.state.replaceSelection(data));
} }