From 8e6dc720dc282c5389932de99427988e24a8951b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr> Date: Wed, 8 Nov 2023 16:41:53 +0100 Subject: [PATCH] [dome/richtext] fixed gutters --- ivette/src/dome/renderer/text/richtext.tsx | 19 +++++---- ivette/src/dome/renderer/text/style.css | 2 +- ivette/src/sandbox/sandbox.css | 3 +- ivette/src/sandbox/text.tsx | 47 ++++++++++++++++++---- 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/ivette/src/dome/renderer/text/richtext.tsx b/ivette/src/dome/renderer/text/richtext.tsx index 90713ed06b1..57acb9b6cde 100644 --- a/ivette/src/dome/renderer/text/richtext.tsx +++ b/ivette/src/dome/renderer/text/richtext.tsx @@ -35,8 +35,8 @@ export interface Range extends Offset { length: number } export interface Position extends Offset { line: number } export interface Selection extends Range { fromLine: number, toLine: number } -export const empty : Range & Selection = - { offset: 0, length: 0, fromLine: 0, toLine: 0 }; +export const emptySelection : Range & Selection = + { offset: 0, length: 0, fromLine: 1, toLine: 1 }; export function byDepth(a : Range, b : Range): number { @@ -434,6 +434,7 @@ function isGutterDecoration(d : Decoration) : d is GutterDecoration class GutterMark extends CM.GutterMarker { private spec: GutterDecoration; + constructor(spec: GutterDecoration) { super(); this.spec = spec; @@ -441,14 +442,15 @@ class GutterMark extends CM.GutterMarker { toDOM(): Node { const { gutter, className, title } = this.spec; - const textNode = document.createElement(gutter); + const textNode = document.createTextNode(gutter); if (!className && !title) return textNode; - const span = document.createElement("span"); + const span = document.createElement("div"); span.appendChild(textNode); if (className) span.className = className; if (title) span.title = title; return span; } + } const GutterMarks : Map<string, GutterMark> = new Map(); @@ -608,10 +610,8 @@ const Decorations: CS.Extension = [ DecoratorState, CM.EditorView.decorations.from(DecoratorState, ({ ranges }) => ranges), CM.gutter({ - initialSpacer: () => gutterMark(GutterInit), + initialSpacer: () => new GutterMark(GutterInit), markers: (view) => view.state.field(DecoratorState).gutters, - lineMarkerChange: (update) => - update.transactions.some((tr) => !tr.annotation(DecoratorSpec)) }), ]; @@ -621,7 +621,10 @@ const Decorations: CS.Extension = [ function createView(parent: Element): CM.EditorView { const extensions : CS.Extension[] = [ - ReadOnly, OnChange, OnSelect, Decorations + CM.lineNumbers(), + CM.highlightActiveLine(), + CM.highlightActiveLineGutter(), + ReadOnly, OnChange, OnSelect, Decorations, ]; const state = CS.EditorState.create({ extensions }); return new CM.EditorView({ state, parent }); diff --git a/ivette/src/dome/renderer/text/style.css b/ivette/src/dome/renderer/text/style.css index 3b26fd8a537..ee063ac5ca4 100644 --- a/ivette/src/dome/renderer/text/style.css +++ b/ivette/src/dome/renderer/text/style.css @@ -124,7 +124,7 @@ .cm-editor .cm-gutters { border-right: 0px; - min-width: 2.15em; + /*min-width: 2.15em;*/ background: var(--background-report); } diff --git a/ivette/src/sandbox/sandbox.css b/ivette/src/sandbox/sandbox.css index 026652934fc..305b7d9465b 100644 --- a/ivette/src/sandbox/sandbox.css +++ b/ivette/src/sandbox/sandbox.css @@ -12,7 +12,6 @@ font-style: italic; } -.line-decoration { +.cm-global-box .line-decoration { background: lightgreen; } - diff --git a/ivette/src/sandbox/text.tsx b/ivette/src/sandbox/text.tsx index fbf585882e1..4f578a8e7f7 100644 --- a/ivette/src/sandbox/text.tsx +++ b/ivette/src/sandbox/text.tsx @@ -34,7 +34,7 @@ import { TextView, TextProxy, TextBuffer, - empty, + emptySelection, Decoration, } from 'dome/text/richtext'; import { registerSandbox } from 'ivette'; @@ -45,15 +45,18 @@ import { registerSandbox } from 'ivette'; function UseText(): JSX.Element { const [prefix, setPrefix] = React.useState(''); + const [useLines, flipUseLines] = Dome.useFlipState(true); const [readOnly, flipReadOnly] = Dome.useFlipState(false); const [useProxy, flipUseProxy] = Dome.useFlipState(false); + const [changed, setChanged] = React.useState(false); const [changes, setChanges] = React.useState(0); - const [s, onSelection] = React.useState(empty); + const [s, onSelection] = React.useState(emptySelection); const proxy = React.useMemo(() => new TextProxy(), []); const buffer = React.useMemo(() => new TextBuffer(), []); const text = useProxy ? proxy : buffer; const updatePrefix = React.useCallback( () => { + setChanged(true); setChanges((n) => 1+n); setPrefix(text.toString().substring(0, 20).trim()); }, [text]); @@ -63,9 +66,21 @@ function UseText(): JSX.Element { }, [text]); const onChange = Dome.useDebounced(updatePrefix, 200); const [decorations, setDecorations] = React.useState<Decoration[]>([]); - const clearDecorations = React.useCallback(() => setDecorations([]), []); + const inconsistent = decorations.length > 0 && changed; + + const clearDecorations = React.useCallback(() => { + setChanged(false); + setDecorations([]); + }, []); + + const clearText = React.useCallback(() => { + setChanged(false); + setDecorations([]); + text.clear(); + }, [text]); const addDecoration = React.useCallback(() => { + setChanged(false); setDecorations([...decorations, { offset: s.offset, length: s.length, @@ -75,6 +90,7 @@ function UseText(): JSX.Element { }, [decorations, s]); const addLineDecoration = React.useCallback(() => { + setChanged(false); setDecorations([...decorations, { line: s.fromLine, className: 'line-decoration', @@ -83,15 +99,25 @@ function UseText(): JSX.Element { }, [decorations, s]); const addGutterDecoration = React.useCallback(() => { + setChanged(false); setDecorations([...decorations, { line: s.fromLine, gutter: '*', }]); }, [decorations, s]); + const isLine = s.fromLine === s.toLine; + const isRange = s.length > 0; + return ( <> <ToolBar> + <Button + icon="ITEMS.LIST" + selected={useLines} + title={'Line Numbers'} + onClick={flipUseLines} + /> <Button icon={readOnly ? 'LOCK' : 'EDIT'} title={readOnly ? 'Read Only' : 'Editable'} @@ -104,34 +130,39 @@ function UseText(): JSX.Element { /> <Code label={`Offset ${s.offset}-${s.offset + s.length}`} /> <Code label={`Line ${s.fromLine}-${s.toLine}`} /> - <Code label={`Decorations ${decorations.length}`} /> + <Code + icon={inconsistent ? 'WARNING' : undefined} + title={inconsistent ? 'Iconsistent (modified text)' : undefined} + label={`Decorations ${decorations.length}`} + /> <IconButton - display={s.length === 0} + display={isLine} icon="CIRC.INFO" title="Add Gutter Decoration" onClick={addGutterDecoration} /> <IconButton - display={s.length === 0} + display={isLine} icon="CIRC.CHECK" title="Add Line Decoration" onClick={addLineDecoration} /> <IconButton - display={s.length > 0} + display={isRange} icon="CIRC.PLUS" title="Add Decoration" onClick={addDecoration} /> <IconButton display={decorations.length > 0} + kind={inconsistent ? 'negative' : 'default'} icon="CIRC.CLOSE" title="Clear Decorations" onClick={clearDecorations} /> <Filler /> <Code>{`"${prefix}" (${changes})`}</Code> <Button label="Push" onClick={push} /> - <Button label="Clear" kind='negative' onClick={text.clear} /> + <Button label="Clear" kind='negative' onClick={clearText} /> </ToolBar> <TextView text={text} -- GitLab