diff --git a/ivette/.dome-pkg-app.lock b/ivette/.dome-pkg-app.lock index 6db93f438a0241f68348c0d419b47d9bc13ebfd5..694887d64d5abc25568980ca5583c522b08b8e33 100644 --- a/ivette/.dome-pkg-app.lock +++ b/ivette/.dome-pkg-app.lock @@ -1 +1 @@ -react@^16 react-dom@^16 source-map-support lodash react-virtualized react-draggable react-fast-compare codemirror@^5 diff +react@^16 react-dom@^16 source-map-support lodash react-virtualized react-draggable react-fast-compare diff diff --git a/ivette/package.json b/ivette/package.json index 00f9db30760ad478740809f246da7867c4bfb7c0..76100c824ae8078168af3a968d0fe58212e813c2 100644 --- a/ivette/package.json +++ b/ivette/package.json @@ -73,7 +73,6 @@ "@fortawesome/fontawesome-free": "", "@types/diff": "", "@types/react-window": "", - "codemirror": "^5", "cytoscape": "", "cytoscape-cola": "", "cytoscape-cose-bilkent": "", @@ -82,7 +81,7 @@ "cytoscape-klay": "", "cytoscape-panzoom": "", "cytoscape-popper": "", - "diff": "", + "diff": "^5.1.0", "gremlins.js": "^2.2.0", "immutable": "", "jsdiff": "^1.1.1", diff --git a/ivette/src/dome/renderer/text/buffers.ts b/ivette/src/dome/renderer/text/buffers.ts deleted file mode 100644 index 249f2a81ea8d5ec04c1ab5a638210358a6327d2a..0000000000000000000000000000000000000000 --- a/ivette/src/dome/renderer/text/buffers.ts +++ /dev/null @@ -1,687 +0,0 @@ -/* ************************************************************************ */ -/* */ -/* This file is part of Frama-C. */ -/* */ -/* Copyright (C) 2007-2023 */ -/* CEA (Commissariat à l'énergie atomique et aux énergies */ -/* alternatives) */ -/* */ -/* you can redistribute it and/or modify it under the terms of the GNU */ -/* Lesser General Public License as published by the Free Software */ -/* Foundation, version 2.1. */ -/* */ -/* It is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU Lesser General Public License for more details. */ -/* */ -/* See the GNU Lesser General Public License version 2.1 */ -/* for more details (enclosed in the file licenses/LGPLv2.1). */ -/* */ -/* ************************************************************************ */ - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -// -------------------------------------------------------------------------- -// --- Text Documents -// -------------------------------------------------------------------------- - -/** - @packageDocumentation - @module dome/text/buffers -*/ - -import Emitter from 'events'; -import CodeMirror from 'codemirror/lib/codemirror'; -import { Debug } from 'dome'; - -const D = new Debug('Dome.text'); - -export type Range = { from: CodeMirror.Position; to: CodeMirror.Position }; - -export interface Decorator { - /** @return a className to apply on markers with the identifier. */ - (id: string): string | undefined; -} - -export interface TextMarkerProxy { - clear(): void; - changed(): void; - find(): Range | undefined; -} - -/** - Text Marker options. - - Inherits CodeMirror - [TextMerkerOptions](https://codemirror.net/doc/manual.html#api_marker). - */ -export interface MarkerProps extends CodeMirror.TextMarkerOptions { - id?: string; - hover?: boolean; - className?: string; -} - -export interface CSSMarker { - /** Hover class `'dome-xHover-nnn'` */ - classNameId: string; - /** Marker id */ - id: string | undefined; - /** Hovered marker */ - hover: boolean; - /** Size in character */ - length: number; -} - -// -------------------------------------------------------------------------- -// --- Batched Update -// -------------------------------------------------------------------------- - -const BATCH_OPS = 500; -const BATCH_DELAY = 5; -const BATCH_RMAX = 1000; // max tag range for sorting -const BATCH_MARGINS = 20; // visible lines above the viewport - -interface MarkerOptions { - id?: string; - hover?: boolean; - className?: string; - options: CodeMirror.TextMarkerOptions; -} - -interface StackedMarker extends MarkerOptions { - startIndex: number; -} - -interface BufferedMarker extends MarkerOptions { - startIndex: number; - stopIndex: number; -} - -type BufferedTag = BufferedMarker | undefined; - -function rankTag(lmin: number, lmax: number, tg: BufferedTag): number { - if (tg && (lmin <= tg.stopIndex || tg.startIndex <= lmax)) { - const size = tg.stopIndex - tg.startIndex; - return size < BATCH_RMAX ? size : BATCH_RMAX; - } - return BATCH_RMAX; -} - -const byVisibleTag = - (lmin: number, lmax: number) => - (a: BufferedTag, b: BufferedTag): number => ( - rankTag(lmin, lmax, a) - rankTag(lmin, lmax, b) - ); - -// -------------------------------------------------------------------------- -// --- Buffer -// -------------------------------------------------------------------------- - -export type TextMarker = CodeMirror.TextMarker<CodeMirror.MarkerRange>; - -export interface RichTextBufferProps { - - /** - * CodeMirror [mode](https://codemirror.net/mode/index.html) specification. - */ - mode?: any; - - /** Maximum number of lines in the buffer. */ - maxlines?: number; - -} - -/** - Rich Text Content and State. - - A buffer encapsulate a CodeMirror document instance inside an standard - Node event emitter. CodeMirror signals are automatically linked back to - the event emitter (on a lazy basis). - - All buffer modifications are stacked and executed asynchronously inside - CodeMirror batched operations to increase performance. - - The `maxlines` will control the maximum number of lines kept in the buffer. - By default, it is set to `10000`, but you can use `null`, `0` or any negative - value to disable this behavior. See also `this.setMaxlines()` method. - - Additionnaly, a Buffer maintains an _edited_ state internally that can - be manually updated, and that is automatically set to `true` when the - underlying CodeMirror document is changed. It is associated to an `'edited'` - event of the buffer, and can be used to manage the « Saved » / « Not-Saved » - state of an editor, for instance. - - Typically, a sequence of changed events would fire a unique `'edited'` buffer - event, untill `setEdited(false)` is invoked. The `clear()` method also resets - the _edited_ state to `false`, but sill emit an `'edited'` event if the - buffer was not empty. - - Buffers can also be updated programmatically by various methods. In addition - to specified CodeMirror modes, you can also attach text markers - programmatically with a push/pop API. - - Text markers can be associated with an identifier, that can be used for - dynamic highlighting, called Decorations. Decorations are class names that - add styling attributes upon the current mode. Typically, consider only using - background colors, underlines and/or strike-through for decorations, since - font styles and colors are likely to be managed by CodeMirror modes, unless - you know exactly what your are doing. - - The `setDecorator` method can be used to set or update - the highlighting function that computes the decoration of a text marker - from its identifier. When the decorations may have change, you shall either - set again the highlighting function with a call to `setDecorator()` or call - the `updateDecorations()` method. This also triggers the `'decorated'` event. - - */ - -export class RichTextBuffer extends Emitter { - - private document: CodeMirror.Doc; - private maxlines = 10000; - private editors: CodeMirror.Editor[] = []; - private cacheIndex = 0; // document index, negative if not computed - private bufferedText: string; // buffered text to append - private bufferedTags: BufferedTag[]; - private stacked: StackedMarker[] = []; - private batched = false; - - // Indexed by CSS property dome-xHover-nnnn - private cssmarkers = new Map<string, CSSMarker>(); - - // Indexed by marker user identifier - private textmarkers = new Map<string, TextMarker[]>(); - - private decorator?: Decorator; - private edited = false; - private focused = false; - private markid = 0; - - constructor(props: RichTextBufferProps = {}) { - super(); - const { mode, maxlines } = props; - this.document = new CodeMirror.Doc('', mode); - this.cacheIndex = -1; - this.bufferedTags = []; - this.bufferedText = ''; - this.setMaxlines(maxlines); - this.setEdited = this.setEdited.bind(this); - this.setFocused = this.setFocused.bind(this); - this.clear = this.clear.bind(this); - this.append = this.append.bind(this); - this.setValue = this.setValue.bind(this); - this.getValue = this.getValue.bind(this); - this.updateDecorations = this.updateDecorations.bind(this); - this.onChange = this.onChange.bind(this); - this.doBatch = this.doBatch.bind(this); - this.log = this.log.bind(this); - } - - /** - Internal CodeMirror - [document](https://codemirror.net/doc/manual.html#api_doc) instance. - */ - getDoc(): CodeMirror.Doc { return this.document; } - - // -------------------------------------------------------------------------- - // --- Buffer Manipulation - // -------------------------------------------------------------------------- - - /** Clear buffer contents and resets _edited_ state. */ - clear(): void { this.setValue(''); } - - /** - Writes in the buffer. All parameters are converted to string and joined - with spaces. The generated change event has origin `'buffer'` and it does - not modifies the _edited_ internal state. - */ - append(...values: any[]): void { - if (values.length > 0) { - const text = values.join(' '); - this.bufferedText += text; - this.armBatch(); - } - } - - /** - Starts a new line in the buffer. If the current buffer content does not - finish at the beginning of a fresh line, inserts a newline character. - */ - flushline(): void { - const doc = this.document; - const buf = this.bufferedText; - if (buf === '') { - const start = doc.posFromIndex(Infinity); - if (start.ch > 0) doc.replaceRange('\n', start, undefined, 'buffer'); - this.cacheIndex = -1; - } else if (buf[buf.length - 1] !== '\n') this.bufferedText += '\n'; - } - - /** - Appends with newline and auto-scrolling. This is a short-cut to - `flushline()` followed by `append(...value,'\n')` and `scroll()`. - */ - log(...values: any[]): void { - this.flushline(); - this.append(...values, '\n'); - this.scroll(); - } - - /** - Replace all textual content with the given string. - Also remove all markers. - */ - setValue(txt = ''): void { - this.document.setValue(txt); - this.cssmarkers.clear(); - this.textmarkers.clear(); - this.stacked = []; - this.bufferedTags = []; - this.bufferedText = ''; - this.cacheIndex = 0; - this.edited = false; - this.focused = false; - this.markid = 0; - } - - /** Return the textual contents of the buffer. */ - getValue(): string { return this.document.getValue(); } - - // -------------------------------------------------------------------------- - // --- Text Markers - // -------------------------------------------------------------------------- - - /** - Opens a text marker. - - Opens a text marker at the current (end) position in the buffer. - - The text marker is stacked and would be actually created on the matching - [[closeTextMarker]] call. It will be applied to the full range of text - inserted between its associated [[openTextMarker]] and [[closeTextMarker]] - calls. - - The returned text marker is actually a _proxy_ to the text marker that will - be eventually created by [[closeTextMarker]]. Its methods are automatically - forwarded to the actual `CodeMirror.TextMarker` instance, once created. - Hence, you can safely invoke these methods on either the _proxy_ or the - _final_ text marker at your convenience. - */ - openTextMarker(props: MarkerProps): void { - const { id, hover, className, ...options } = props; - const startIndex = this.getLastIndex() + this.bufferedText.length; - this.stacked.push({ startIndex, id, hover, className, options }); - } - - /** - Closes the last opened marker. - */ - closeTextMarker(): void { - const tag = this.stacked.pop(); - if (tag) { - const stopIndex = this.getLastIndex() + this.bufferedText.length; - this.bufferedTags.push({ stopIndex, ...tag }); - this.armBatch(); - } - } - - /** Lookup for the text markers associated with a marker identifier. - Remove the marked tags from the buffered tag array. */ - findTextMarker(id: string): TextMarker[] { - this.doFlushText(); - this.bufferedTags.forEach((tg, idx) => { - if (tg?.id === id) { - this.doMark(tg); - this.bufferedTags[idx] = undefined; - } - }); - return this.textmarkers.get(id) ?? []; - } - - /** Lopokup for a hover class. */ - findHover(className: string): CSSMarker | undefined { - return this.cssmarkers.get(className); - } - - // -------------------------------------------------------------------------- - // --- Highlighter - // -------------------------------------------------------------------------- - - /** - Define highlighter. - - @param fn - highlighter, `fn(id)` shall return a className to apply - on text markers with the provided identifier. - */ - setDecorator(fn: Decorator): void { - this.decorator = fn; - this.emit('decorated'); - } - - /** - Current highlighter. - */ - getDecorator(): Decorator | undefined { return this.decorator; } - - /** - Rehighlight document. - - Emits the `'decorated'` event to make editors - updating the decorations of identified text markers. - - This can be used when decoration shall be re-computed, - even if the decoration function was not modified. - - The method is bound to `this`. - */ - updateDecorations(): void { this.emit('decorated'); } - - // -------------------------------------------------------------------------- - // --- Edited State - // -------------------------------------------------------------------------- - - /** - Set edited state. - - The method is automatically invoked by editor views when the user actually - edit the document. The state is _not_ modified when modifying the document - from buffer's own methods, _eg._ `append()` and `clear()`. - - The method fires the `'edited'` event on modifications. This method is - bound to `this`, hence `this.setEdited` can be used as a valid callback - function. - - @param state - the new edited state (defaults to `true`). - */ - setEdited(state = true): void { - if (state !== this.edited) { - this.edited = state; - this.emit('edited', state); - } - } - - /** - Set focused state. - - The method is automatically invoked by editor views when they gain or lose - focus or when the user actually interact with the view, - eg. mouse-scrolling, edition, cursor move, etc. The escape key `ESC` - explicitly relax the _focused_ state, although the editor view might - actually keep the _focus_. - - When a buffer is _focused_, shrinking and auto-scrolling are temporarily - deactivated to avoid confusing user's experience. - - The method fires `'focused'` events on modifications. This method is bound - to `this`, hence `this.setEdited` can be used as a valid callback function. - - @param focus - the new focused state (defaults to `true`). - */ - setFocused(state = true): void { - if (state !== this.focused) { - this.focused = state; - this.emit('focused', state); - this.doShrink(); - } - } - - /** Returns the current _edited_ state. */ - isEdited(): boolean { return this.edited; } - - /** Returns the current _focused_ state. */ - isFocused(): boolean { return this.focused; } - - // -------------------------------------------------------------------------- - // --- Document Scrolling - // -------------------------------------------------------------------------- - - /** - Set (or unset) the maximum number of lines. - - By default, the maximum number of lines is set to `10,000` to avoid - unwanted memory leaks. Setting `null`, `0` of any negative value cancel the - management of maximum number of lines. - - Although CodeMirror is powerfull enough to manage huge buffers, you should - turn this limit _off_ with care. - */ - setMaxlines(maxlines = 10000): void { - this.maxlines = maxlines > 0 ? 1 + maxlines : 0; - this.doShrink(); - } - - /** - Requires all connected views to scroll to the - specified line or identified marker. - - @param position - - defaults to the end of buffer (when not focused). - */ - scroll(position?: string | number): void { - if (position === undefined) { - if (!this.focused) { - this.doFlushText(); - this.emit('scroll', this.document.lastLine()); - } - } else if (typeof position === 'number') { - this.doFlushText(); - this.emit('scroll', position); - } else if (typeof position === 'string') { - let line = Infinity; - this.findTextMarker(position).forEach((tm) => { - const rg = tm.find(); - if (rg && rg.from) { - const ln = rg.from.line; - if (ln < line) line = ln; - } - }); - if (line !== Infinity) - this.emit('scroll', line); - } - } - - /** - Requires all connected views to select the specified line and to place it - at the top of their displays. - - @param line - the line number to select - */ - setCursorOnTop(line: number): void { - this.forEach((cm) => { - cm.setCursor(cm.lastLine()); - cm.setCursor(line - 1); - }); - } - - // -------------------------------------------------------------------------- - // --- Document Linking - // -------------------------------------------------------------------------- - - private onChange( - _editor: CodeMirror.Editor, - change: CodeMirror.EditorChange, - ): void { - if (change.origin !== 'buffer') { - this.setEdited(true); - this.cacheIndex = -1; - } - this.emit('change'); - } - - /** - Binds this buffer to a CodeMirror instance. - - Uses CodeMirror linked documents to allow several CodeMirror instances to - be linked to the same buffer. Can be released with [[unlink]]. - - @param cm - code mirror instance to link this document in. - */ - link(cm: CodeMirror.Editor): void { - const newDoc = this.document.linkedDoc( - { sharedHist: true, mode: undefined }, - ); - cm.swapDoc(newDoc); - cm.on('change', this.onChange); - this.editors.push(cm); - } - - /** - Release a linked CodeMirror document previously linked with [[link]]. - @param cm - the code mirror instance to unlink - */ - unlink(cm: CodeMirror.Editor): void { - const oldDoc = cm.getDoc(); - this.document.unlinkDoc(oldDoc); - this.editors = this.editors.filter((cm0) => { - if (cm === cm0) { - cm.off('change', this.onChange); - return false; - } - return true; - }); - } - - /** - Iterates over each linked CodeMirror instances - - The operation `fn` is performed on each code mirror - instance currently linked to this buffer. - - Exceptions raised by `fn` are catched and dumped in the console. - */ - forEach(fn: (editor: CodeMirror.Editor) => void): void { - this.editors.forEach((cm) => { - try { fn(cm); } catch (e) { D.error(e); } - }); - } - - // -------------------------------------------------------------------------- - // --- Update Operations - // -------------------------------------------------------------------------- - - /* Append Operation */ - private doFlushText(): void { - const text = this.bufferedText; - if (text.length > 0) { - this.bufferedText = ''; - const doc = this.document; - const start = doc.posFromIndex(Infinity); - doc.replaceRange(text, start, undefined, 'buffer'); - this.cacheIndex = -1; - this.doShrink(); - } - } - - /* Shrink Operation */ - private doShrink(): void { - const lines = this.document.lineCount(); - if (lines > this.maxlines) { - const p = this.document.firstLine(); - const q = p + lines - this.maxlines; - this.document.replaceRange( - '', - { line: p, ch: 0 }, - { line: q, ch: 0 }, - 'buffer', - ); - this.cacheIndex = -1; - } - } - - /* Close Operation */ - private doMark(tag: BufferedMarker): void { - const { id, hover, className, startIndex, stopIndex } = tag; - let markerId; - if (id || hover) { - markerId = `dome-xHover-${this.markid++}`; - const cmark = { - id, - classNameId: markerId, - hover: hover ?? (id !== undefined), - length: stopIndex - startIndex, - }; - this.cssmarkers.set(markerId, cmark); - } - const fullClassName = [ - 'dome-xMarked', - id && (`dome-xMark-${id}`), - markerId, - className, - ].filter((s) => !!s).join(' '); - const options = { - shared: true, - className: fullClassName, - ...tag.options, - }; - const doc = this.document; - const start = doc.posFromIndex(startIndex); - const stop = doc.posFromIndex(stopIndex); - const marker = doc.markText(start, stop, options); - if (id) { - const markers = this.textmarkers; - const ms = markers.get(id); - if (ms === undefined) - markers.set(id, [marker]); - else - ms.push(marker); - } - } - - private doFilterTags(): BufferedTag[] { - const tgs = this.bufferedTags; - if (tgs.length > 0 && this.editors.length > 0) { - let lmin = Infinity; - let lmax = 0; - this.forEach((cm) => { - const { from: fr, to } = cm.getViewport(); - if (to > lmax) lmax = to; - if (fr < lmin) lmin = fr; - }); - if (lmin <= lmax) tgs.sort(byVisibleTag(lmin, lmax + BATCH_MARGINS)); - } - return tgs.splice(0, BATCH_OPS); - } - - private getLastIndex(): number { - if (this.cacheIndex < 0) { - const doc = this.document; - const line = doc.lastLine() + 1; - this.cacheIndex = doc.indexFromPos({ line, ch: 0 }); - } - return this.cacheIndex; - } - - // -------------------------------------------------------------------------- - // --- Batched Operations - // -------------------------------------------------------------------------- - - private armBatch(): void { - if (!this.batched) { - this.batched = true; - setTimeout(this.doBatch, BATCH_DELAY); - } - } - - /* Batch Operation */ - private doBatch(): void { - this.batched = false; - if (!this.bufferedText.length && !this.bufferedTags.length) return; - try { - // Start Operations - this.forEach((cm) => cm.startOperation()); - // Flush Text - this.doFlushText(); - // Mark Tags - this.doFilterTags().forEach((tg) => { if (tg) this.doMark(tg); }); - // End Operations - this.forEach((cm) => cm.endOperation()); - } finally { - if (this.bufferedTags.length > 0) - this.armBatch(); - } - } - -} - -// -------------------------------------------------------------------------- diff --git a/ivette/src/dome/renderer/text/editors.tsx b/ivette/src/dome/renderer/text/editors.tsx deleted file mode 100644 index 1153f41e132c577bae2b08ad91144408382cf040..0000000000000000000000000000000000000000 --- a/ivette/src/dome/renderer/text/editors.tsx +++ /dev/null @@ -1,566 +0,0 @@ -/* ************************************************************************ */ -/* */ -/* This file is part of Frama-C. */ -/* */ -/* Copyright (C) 2007-2023 */ -/* CEA (Commissariat à l'énergie atomique et aux énergies */ -/* alternatives) */ -/* */ -/* you can redistribute it and/or modify it under the terms of the GNU */ -/* Lesser General Public License as published by the Free Software */ -/* Foundation, version 2.1. */ -/* */ -/* It is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU Lesser General Public License for more details. */ -/* */ -/* See the GNU Lesser General Public License version 2.1 */ -/* for more details (enclosed in the file licenses/LGPLv2.1). */ -/* */ -/* ************************************************************************ */ - -// -------------------------------------------------------------------------- -// --- Text Documents -// -------------------------------------------------------------------------- - -/** - @packageDocumentation - @module dome/text/editors -*/ - -import _ from 'lodash'; -import React from 'react'; -import * as Dome from 'dome'; -import * as Themes from 'dome/themes'; -import { Vfill } from 'dome/layout/boxes'; -import CodeMirror, { EditorConfiguration } from 'codemirror/lib/codemirror'; -import { RichTextBuffer, CSSMarker, Decorator } from './buffers'; - -import './style.css'; -import './dark-code.css'; -import 'codemirror/lib/codemirror.css'; -import 'codemirror/addon/fold/foldcode'; -import 'codemirror/addon/fold/foldgutter'; -import 'codemirror/addon/fold/comment-fold'; -import 'codemirror/addon/fold/foldgutter.css'; - -const CSS_HOVERED = 'dome-xText-hover'; -const CSS_SELECTED = 'dome-xText-select'; - -const D = new Dome.Debug('Dome'); - -/* --------------------------------------------------------------------------*/ -/* --- View Properties --- */ -/* --------------------------------------------------------------------------*/ - -export interface MarkerCallback { - (id: string, meta?: boolean): void; -} - -/** - Text Editor configuration. - - Inherits CodeMirror - [EditorConfiguration](https://codemirror.net/doc/manual.html#config) - options. - */ - -export interface TextProps extends CodeMirror.EditorConfiguration { - - /** The buffer to view. */ - buffer: RichTextBuffer | undefined; - - /** The currently selected marker identifier. */ - selection?: string; - - /** Callback on hovered marker, if some. */ - onHover?: (id?: string) => void; - - /** Callback on identified marker selection. */ - onSelection?: MarkerCallback; - - /** Callback on identified marker right-click. */ - onContextMenu?: MarkerCallback; - - /** Font Size. */ - fontSize?: number; - - /** Display or hide the component (true by default). */ - display?: boolean; - - /** Additional className for the text container. */ - className?: string; - - /** Additional style. */ - style?: React.CSSProperties; - -} - -/* --------------------------------------------------------------------------*/ -/* --- CodeMirror Configuration Utils ---*/ -/* --------------------------------------------------------------------------*/ - -function getConfig(props: TextProps): CodeMirror.EditorConfiguration { - const { - buffer, - selection, - onSelection, - onContextMenu, - fontSize, - ...config - } = props; - return config; -} - -type MouseEvt = React.MouseEvent<HTMLDivElement, MouseEvent>; -type CMoption = keyof EditorConfiguration; - -function forEachOption( - config: EditorConfiguration, - fn: (opt: CMoption) => void, -): void { - (Object.keys(config) as (keyof EditorConfiguration)[]).forEach(fn); -} - -interface _Decoration { - classNameId: string; - decoration: string | undefined; -} - -// -------------------------------------------------------------------------- -// --- Code Mirror Instance Wrapper -// -------------------------------------------------------------------------- - -class CodeMirrorWrapper extends React.Component<TextProps> { - - private currentParent?: Element | null; - private currentHeight?: number; - private currentWidth?: number; - private rootElement: HTMLDivElement | null = null; - private codeMirror?: CodeMirror.Editor; - private refreshPolling?: NodeJS.Timeout; - - // Currently hovered 'dome-xHover-nnn' - private marker?: CSSMarker; - - // Maps hovered 'dome-xMark-id' to their decorations - private decorations = new Map<string, string>(); - - constructor(props: TextProps) { - super(props); - this.mountPoint = this.mountPoint.bind(this); - this.refresh = this.refresh.bind(this); - this.autoRefresh = this.autoRefresh.bind(this); - this.onBlur = this.onBlur.bind(this); - this.onFocus = this.onFocus.bind(this); - this.onScroll = this.onScroll.bind(this); - this.onClick = this.onClick.bind(this); - this.onContextMenu = this.onContextMenu.bind(this); - this.onMouseMove = this.onMouseMove.bind(this); - this.handleKey = this.handleKey.bind(this); - this.handleUpdate = this.handleUpdate.bind(this); - this.handleHover = _.throttle(this.handleHover, 50); - this.handleScrollTo = this.handleScrollTo.bind(this); - } - - // -------------------------------------------------------------------------- - // --- Mounting - // -------------------------------------------------------------------------- - - mountPoint(elt: HTMLDivElement | null): void { - this.rootElement = elt; - if (elt !== null) { - // Mounting... - const { buffer } = this.props; - const config = getConfig(this.props); - this.codeMirror = CodeMirror(elt, { value: '' }); - if (buffer) { - buffer.link(this.codeMirror); - buffer.on('decorated', this.handleUpdate); - buffer.on('scroll', this.handleScrollTo); - } - // Passing all options to constructor does not work (Cf. CodeMirror's BTS) - forEachOption( - config, (opt) => this.codeMirror?.setOption(opt, config[opt]), - ); - // Binding events to view - this.codeMirror.on('update', this.handleUpdate); - this.codeMirror.on('keyHandled', this.handleKey); - Dome.update.on(this.refresh); - // Auto refresh - this.refreshPolling = setInterval(this.autoRefresh, 250); - this.handleUpdate(); - } else { - // Unmounting... - if (this.refreshPolling) { - clearInterval(this.refreshPolling); - this.refreshPolling = undefined; - } - Dome.update.off(this.refresh); - const { buffer } = this.props; - if (this.codeMirror && buffer) { - buffer.unlink(this.codeMirror); - buffer.off('decorated', this.handleUpdate); - buffer.off('scroll', this.handleScrollTo); - } - this.codeMirror = undefined; - this.marker = undefined; - this.decorations.clear(); - } - } - - // -------------------------------------------------------------------------- - // --- Auto Refresh - // -------------------------------------------------------------------------- - - refresh(): void { - const elt = this.rootElement; - const cm = this.codeMirror; - if (cm && elt) { - this.currentWidth = elt.offsetWidth; - this.currentHeight = elt.offsetHeight; - this.currentParent = elt.offsetParent; - cm.refresh(); - } - } - - // Polled every 250ms - autoRefresh(): void { - const elt = this.rootElement; - const cm = this.codeMirror; - if (cm && elt) { - const eltW = elt.offsetWidth; - const eltH = elt.offsetHeight; - const eltP = elt.offsetParent; - if (eltW !== this.currentWidth || - eltH !== this.currentHeight || - eltP !== this.currentParent) { - this.currentWidth = eltW; - this.currentHeight = eltH; - this.currentParent = eltP; - cm.refresh(); - } - } - } - - // -------------------------------------------------------------------------- - // --- Hover - // -------------------------------------------------------------------------- - - _findMarker(elt: Element): CSSMarker | undefined { - const { buffer } = this.props; - if (buffer) { - let best: CSSMarker | undefined; - elt.classList.forEach((name) => { - const marker = buffer.findHover(name); - if (marker && (!best || marker.length < best.length)) best = marker; - }); - return best; - } - return undefined; - } - - // eslint-disable-next-line class-methods-use-this - _findDecoration( - classes: DOMTokenList, - buffer: RichTextBuffer, - decorator: Decorator, - ): _Decoration | undefined { - let bestMarker: CSSMarker | undefined; - let bestDecorated: CSSMarker | undefined; - let bestDecoration: string | undefined; - classes.forEach((name) => { - - const marker = buffer.findHover(name); - const id = marker && marker.id; - const decoration = id && decorator(id); - - if (marker && (!bestMarker || marker.length < bestMarker.length)) { - bestMarker = marker; - } - - if (marker && decoration && - (!bestDecorated || marker.length < bestDecorated.length)) { - bestDecorated = marker; - bestDecoration = decoration; - } - - }); - return bestMarker ? { - classNameId: bestMarker.classNameId, - decoration: bestDecoration, - } : undefined; - } - - _markElementsWith(classNameId: string, className: string): void { - const root = this.rootElement; - const toMark = root && root.getElementsByClassName(classNameId); - if (toMark) { - const n = toMark.length; - if (n === 0) return; - for (let k = 0; k < n; k++) toMark[k].classList.add(className); - } - } - - _unmarkElementsWith(className: string): void { - const root = this.rootElement; - const toUnmark = root && root.getElementsByClassName(className); - if (toUnmark) { - const n = toUnmark.length; - if (n === 0) return; - const elts: Element[] = new Array(n); - for (let k = 0; k < n; k++) elts[k] = toUnmark[k]; - elts.forEach((elt) => elt.classList.remove(className)); - } - } - - handleHover(target: Element): void { - // Throttled (see constructor) - const oldMarker = this.marker; - const newMarker = this._findMarker(target); - if (oldMarker !== newMarker) { - if (oldMarker) this._unmarkElementsWith(CSS_HOVERED); - if (newMarker && newMarker.hover) - this._markElementsWith(newMarker.classNameId, CSS_HOVERED); - this.marker = newMarker; - const callback = this.props.onHover; - if (callback) callback(newMarker?.id); - } - } - - handleUpdate(): void { - const root = this.rootElement; - const marked = root && root.getElementsByClassName('dome-xMarked'); - if (!marked) return; - const n = marked.length; - if (n === 0) return; - const { marker } = this; - const hovered = (marker && marker.hover) ? marker.classNameId : undefined; - const { selection } = this.props; - const selected = selection && (`dome-xMark-${selection}`); - const { buffer } = this.props; - const decorator = buffer?.getDecorator(); - if (!hovered && !selection && !decorator) return; - const newDecorations = new Map<string, string>(); - for (let k = 0; k < n; k++) { - const elt = marked[k]; - const classes = elt.classList; - if (hovered && classes.contains(hovered)) classes.add(CSS_HOVERED); - if (selected && classes.contains(selected)) classes.add(CSS_SELECTED); - if (buffer && decorator) { - const deco = this._findDecoration(classes, buffer, decorator); - if (deco) { - const prev = this.decorations.get(deco.classNameId); - const curr = deco.decoration; - if (prev && prev !== curr) classes.remove(prev); - if (curr) { - classes.add(curr); - newDecorations.set(deco.classNameId, curr); - } - } - } - } - this.decorations = newDecorations; - } - - onMouseMove(evt: MouseEvt): void { - // Not throttled (can not leak Synthetic Events) - const tgt = evt.target; - if (tgt instanceof Element) this.handleHover(tgt); - } - - onMouseClick(evt: MouseEvt, callback: MarkerCallback | undefined): void { - // No need for throttling - if (callback) { - const { target } = evt; - if (target instanceof Element) { - const marker = this._findMarker(target); - if (marker && marker.id) callback(marker.id, evt.altKey); - } - } - this.props.buffer?.setFocused(true); - } - - onClick(evt: MouseEvt): void { - this.onMouseClick(evt, this.props.onSelection); - } - - onContextMenu(evt: MouseEvt): void { - this.onMouseClick(evt, this.props.onContextMenu); - } - - // -------------------------------------------------------------------------- - // --- Scrolling - // -------------------------------------------------------------------------- - - handleScrollTo(line: number): void { - try { - const cm = this.codeMirror; - return cm && cm.scrollIntoView({ line, ch: 0 }); - } catch (_error) { - D.warn(`unable to scroll to line ${line}: out of range.`); - } - } - - // -------------------------------------------------------------------------- - // --- Focus - // -------------------------------------------------------------------------- - - handleKey(_cm: CodeMirror.Editor, key: string, _evt: Event): void { - switch (key) { - case 'Esc': - this.props.buffer?.setFocused(false); - break; - default: - this.props.buffer?.setFocused(true); - break; - } - } - - onFocus(): void { this.props.buffer?.setFocused(true); } - onBlur(): void { this.props.buffer?.setFocused(false); } - onScroll(): void { - const cm = this.codeMirror; - const { buffer } = this.props; - if (cm && buffer) { - const rect = cm.getScrollInfo(); - const delta = rect.height - rect.top - rect.clientHeight; - buffer.setFocused(delta > 0); - } - } - - // -------------------------------------------------------------------------- - // --- Rendering - // -------------------------------------------------------------------------- - - shouldComponentUpdate(newProps: TextProps): boolean { - const cm = this.codeMirror; - if (cm) { - // Swap documents if necessary - const { - buffer: oldBuffer, - selection: oldSelect, - fontSize: oldFont, - } = this.props; - const { - buffer: newBuffer, - selection: newSelect, - fontSize: newFont, - } = newProps; - if (oldBuffer !== newBuffer) { - if (oldBuffer) oldBuffer.unlink(cm); - if (newBuffer) newBuffer.link(cm); - else cm.setValue(''); - } - const oldConfig = getConfig(this.props); - const newConfig = getConfig(newProps); - // Incremental update options - forEachOption(oldConfig, (opt) => { - if (!(opt in newConfig)) { - const defValue = CodeMirror.defaults[opt]; - if (defValue) - cm.setOption(opt, defValue); - } - }); - forEachOption(newConfig, (opt) => { - const oldValue = oldConfig[opt]; - const newValue = newConfig[opt]; - if (newValue !== oldValue) { - cm.setOption(opt, newValue); - } - }); - // Update selection - if (oldSelect !== newSelect) { - const selected = `dome-xMark-${newSelect}`; - if (oldSelect) this._unmarkElementsWith(CSS_SELECTED); - if (newSelect) this._markElementsWith(selected, CSS_SELECTED); - } - // Refresh on new font - if (oldFont !== newFont) setImmediate(this.refresh); - } - // Keep mounted node unchanged - return false; - } - - render(): JSX.Element { - return ( - <div - className="dome-xText" - ref={this.mountPoint} - onClick={this.onClick} - onContextMenu={this.onContextMenu} - onBlur={this.onBlur} - onFocus={this.onFocus} - onScroll={this.onScroll} - onMouseMove={this.onMouseMove} - onMouseLeave={this.onMouseMove} - /> - ); - } - -} - -// -------------------------------------------------------------------------- -// --- Text View -// -------------------------------------------------------------------------- - -/** #### Text Editor. - - A component rendering the content of a text buffer, that shall be instances - of the `Buffer` base class. - - The view is based on a [CodeMirror](https://codemirror.net) component linked - with the internal Code Mirror Document from the associated buffer. - - Multiple views might share the same buffer as source content. The buffer will - be kept in sync with all its linked views. - - The Text component never update its mounted NODE element, however, all - property modifications (including buffer) are propagated to the internal - CodeMirror instance. Undefined properties are set (or reset) to the - CodeMirror defaults. - - #### Themes - - The CodeMirror `theme` option allow you to style your document, especially - when using modes. - - By default, CodeMirror uses the `'default'` theme in _light_ theme and the - `'dark-code'` theme in _dark_ theme. The `'dark-code'` is provided by Dome, - Cf. `./dark-mode.css` in the source distribution. - - To use other custom themes, you shall load the associated CSS style - sheet. For instance, to use the `'ambiance'` theme provided with CodeMirror, - you shall import `'codemirror/theme/ambiance.css'` somewhere in your - application. - - #### Modes & Adds-On - - You can install modes and adds-on provided by the CodeMirror distribution by - simply importing (once, before being used) the associated modules in your - application. For instance, to use the `'javascript'` mode option, you shall - import `'codemirror/mode/javascript/javascript.js'` file in your application. - - #### Further Customization - - You can register your own extensions directly into the global `CodeMirror` - class instance. However, the correct instance must be retrieved by using - `import CodeMirror from 'codemirror/lib/codemirror.js'` ; using `from - 'codemirror'` returns a different instance of `CodeMirror` class and will not - work. */ -export function Text(props: TextProps): JSX.Element { - const [appTheme] = Themes.useColorTheme(); - let { className, style, fontSize, theme: usrTheme, ...cmprops } = props; - if (fontSize !== undefined && fontSize < 4) fontSize = 4; - if (fontSize !== undefined && fontSize > 48) fontSize = 48; - const theme = usrTheme ?? (appTheme === 'dark' ? 'dark-code' : 'default'); - const display = props.display === false ? 'none' : undefined; - return ( - <Vfill className={className} style={{ ...style, fontSize, display }}> - <CodeMirrorWrapper fontSize={fontSize} theme={theme} {...cmprops} /> - </Vfill> - ); -} - -// -------------------------------------------------------------------------- diff --git a/ivette/src/dome/renderer/text/richtext.tsx b/ivette/src/dome/renderer/text/richtext.tsx index 22405ddb87ce1895164dd4205cf0ce941d0c81ed..936ea7ad45aa74d0f47b305d1116e54525bdbaa1 100644 --- a/ivette/src/dome/renderer/text/richtext.tsx +++ b/ivette/src/dome/renderer/text/richtext.tsx @@ -258,13 +258,16 @@ export class TextBuffer extends TextProxy { return this.contents ?? this.text.toString(); } - append(data: string): void { - const view = this.proxy; - if (view) { appendContents(view, data); } - else { - this.text = this.toText().append(textOf(data)); - this.contents = undefined; - // invariant established + append(...values: unknown[]): void { + if (values.length > 0) { + const data = values.join(' '); + const view = this.proxy; + if (view) { appendContents(view, data); } + else { + this.text = this.toText().append(textOf(data)); + this.contents = undefined; + // invariant established + } } } diff --git a/ivette/src/dome/renderer/text/style.css b/ivette/src/dome/renderer/text/style.css index a572476d794c4e688d62f2247b591b733df7b879..ca2d76573124a34c36003b3d50f84b96cb6685ab 100644 --- a/ivette/src/dome/renderer/text/style.css +++ b/ivette/src/dome/renderer/text/style.css @@ -1,33 +1,3 @@ -/* -------------------------------------------------------------------------- */ -/* --- Styling Code Editors --- */ -/* -------------------------------------------------------------------------- */ - -.dome-xText { - flex: 1 1 auto ; - overflow: hidden ; - position: relative ; - fontSize: smaller ; -} - -.dome-xText .CodeMirror { - width: 100% ; - height: 100% ; - line-height: 1.2 !important; - text-shadow: none !important; -} - -.dome-xText-hover { - background: var(--code-hover) ; -} - -.dome-xText-select { - background: var(--code-select) !important ; -} - -.dome-xText-select.dome-xText-hover { - background: var(--code-select-hover) !important ; -} - /* -------------------------------------------------------------------------- */ /* --- Styling Page --- */ /* -------------------------------------------------------------------------- */ @@ -240,6 +210,7 @@ .cm-number { color: var(--codemirror-number); } .cm-keyword { color: var(--codemirror-keyword); } +.cm-activeLine .cm-active-line { background-color: var(--background); } /* -------------------------------------------------------------------------- */ diff --git a/ivette/src/dome/template/makefile.packages b/ivette/src/dome/template/makefile.packages index 3d689992f112c66ac9567c506f195c0a35bced37..f42f27f0b7d0f1cb82d2582c85beff9b8fa26413 100644 --- a/ivette/src/dome/template/makefile.packages +++ b/ivette/src/dome/template/makefile.packages @@ -50,7 +50,6 @@ DOME_APP_PACKAGES= \ react-virtualized \ react-draggable \ react-fast-compare \ - codemirror@^5 \ diff # -------------------------------------------------------------------------- diff --git a/ivette/src/frama-c/richtext.tsx b/ivette/src/frama-c/richtext.tsx index ed9fbb8d232986f67d1d9dc8e05cd6928ea93862..aa96434523bfcc635fe253f699de218802b3ec58 100644 --- a/ivette/src/frama-c/richtext.tsx +++ b/ivette/src/frama-c/richtext.tsx @@ -31,46 +31,11 @@ import React from 'react'; import * as Dome from 'dome'; -import * as DomeBuffers from 'dome/text/buffers'; import * as KernelData from 'frama-c/kernel/api/data'; import { classes } from 'dome/misc/utils'; const D = new Dome.Debug('Utils'); -// -------------------------------------------------------------------------- -// --- Print Utilities -// -------------------------------------------------------------------------- - -/** - * Print text containing tags into buffer. - * @param buffer Rich text buffer to print into. - * @param text Actual text containing tags. - * @param options Specify particular marker options. - */ -export function printTextWithTags( - buffer: DomeBuffers.RichTextBuffer, - text: KernelData.text, - options?: DomeBuffers.MarkerProps, -): void { - if (Array.isArray(text)) { - const tag = text[0]; - const marker = typeof (tag) === 'string'; - if (marker) { - buffer.openTextMarker({ id: tag, ...options ?? {} }); - } - for (let k = marker ? 1 : 0; k < text.length; k++) { - printTextWithTags(buffer, text[k], options); - } - if (marker) { - buffer.closeTextMarker(); - } - } else if (typeof text === 'string') { - buffer.append(text); - } else { - D.error('Unexpected text', text); - } -} - // -------------------------------------------------------------------------- // --- Lightweight Text Renderer // -------------------------------------------------------------------------- diff --git a/ivette/src/frama-c/server.ts b/ivette/src/frama-c/server.ts index defba404bcb97d36f12777202b4734a5bfbc6cde..7cab67fc55c2bd54b0e90048cff22c119f926f91 100644 --- a/ivette/src/frama-c/server.ts +++ b/ivette/src/frama-c/server.ts @@ -36,7 +36,7 @@ import React from 'react'; import * as Dome from 'dome'; import * as System from 'dome/system'; import * as Json from 'dome/data/json'; -import { RichTextBuffer } from 'dome/text/buffers'; +import { TextBuffer } from 'dome/text/richtext'; import { ChildProcess } from 'child_process'; import { client } from './client_socket'; // import { client } from './client_zmq'; @@ -136,7 +136,7 @@ let killingTimer: NodeJS.Timeout | undefined; // -------------------------------------------------------------------------- /** The server console buffer. */ -export const buffer = new RichTextBuffer(); +export const buffer = new TextBuffer(); // -------------------------------------------------------------------------- // --- Server Status @@ -231,7 +231,7 @@ export async function start(): Promise<void> { try { await _launch(); } catch (error) { - buffer.log('[frama-c]', error); + buffer.append('[frama-c]', error, '\n'); _exit(false); } return; @@ -480,27 +480,21 @@ async function _launch(): Promise<void> { sockaddr && System.remove(sockaddr); }); process = await System.spawn(command, params, options); - const logger = (text: string | string[]): void => { - buffer.append(text); - if (text.indexOf('\n') >= 0) { - buffer.scroll(); - } - }; - process?.stdout?.on('data', logger); - process?.stderr?.on('data', logger); + process?.stdout?.on('data', buffer.append); + process?.stderr?.on('data', buffer.append); process?.on('exit', (code: number | null, signal: string | null) => { if (signal !== null) { - buffer.log('[frama-c]', signal); + buffer.append('[frama-c]', signal, '\n'); _exit(false); return; } if (code !== 0) { - buffer.log('[frama-c] exit', code); + buffer.append('[frama-c] exit', code, '\n'); _exit(true); return; } if (Dome.DEVEL) - buffer.log('[frama-c] terminated.'); + buffer.append('[frama-c] terminated.\n'); _exit(false); return; }); diff --git a/ivette/src/ivette/prefs.tsx b/ivette/src/ivette/prefs.tsx index 61a7835539e8299f54ac3fa391212d2a1f83b009..4bb16c1276941ae45edf8abe956b20899de332d4 100644 --- a/ivette/src/ivette/prefs.tsx +++ b/ivette/src/ivette/prefs.tsx @@ -34,7 +34,6 @@ import * as Dome from 'dome'; import * as Themes from 'dome/themes'; import * as Toolbar from 'dome/frame/toolbars'; import * as Settings from 'dome/data/settings'; -import 'codemirror/mode/clike/clike'; /* -------------------------------------------------------------------------- */ /* --- Theme Switcher Button --- */ diff --git a/ivette/src/renderer/Controller.tsx b/ivette/src/renderer/Controller.tsx index d69dd2269894463c2bd0bab86a1673ba1c7f4db5..cf837e3febd75abf8bf6c60c31cf1cd6abf4c86b 100644 --- a/ivette/src/renderer/Controller.tsx +++ b/ivette/src/renderer/Controller.tsx @@ -33,8 +33,7 @@ import * as Toolbars from 'dome/frame/toolbars'; import { IconButton } from 'dome/controls/buttons'; import { LED, LEDstatus } from 'dome/controls/displays'; import { Label, Code } from 'dome/controls/labels'; -import { RichTextBuffer } from 'dome/text/buffers'; -import { Text } from 'dome/text/editors'; +import { TextBuffer, TextView } from 'dome/text/richtext'; import { resolve } from 'dome/system'; import * as Ivette from 'ivette'; @@ -223,36 +222,40 @@ export const Control = (): JSX.Element => { // --- Server Console // -------------------------------------------------------------------------- -const editor = new RichTextBuffer(); +const editor = new TextBuffer(); const RenderConsole = (): JSX.Element => { const scratch = React.useRef([] as string[]); const [cursor, setCursor] = React.useState(-1); const [isEmpty, setEmpty] = React.useState(true); - const [noTrash, setNoTrash] = React.useState(true); + const [noTrash, _setNoTrash] = React.useState(true); const [history, setHistory] = useHistory(); + /* React.useEffect(() => { const callback = (): void => { - const cmd = editor.getValue().trim(); + const cmd = editor.toString().trim(); setEmpty(cmd === ''); setNoTrash(noTrash && cmd === history[0]); }; editor.on('change', callback); return () => { editor.off('change', callback); }; }); + */ - const [maxLines] = Settings.useGlobalSettings(Preferences.ConsoleScrollback); + const [_maxLines] = Settings.useGlobalSettings(Preferences.ConsoleScrollback); + /* React.useEffect(() => { Server.buffer.setMaxlines(maxLines); }); + */ const doReload = (): void => { const cfg = Server.getConfig(); const hst = insertConfig(history, cfg); const cmd = hst[0]; scratch.current = hst.slice(); - editor.setValue(cmd); + editor.setContents(cmd); setEmpty(cmd === ''); setHistory(hst); setCursor(0); @@ -268,7 +271,7 @@ const RenderConsole = (): JSX.Element => { }; const doExec = (): void => { - const cfg = buildServerCommand(editor.getValue()); + const cfg = buildServerCommand(editor.toString()); const hst = insertConfig(history, cfg); setHistory(hst); setCursor(-1); @@ -280,11 +283,11 @@ const RenderConsole = (): JSX.Element => { const doMove = (target: number): (undefined | (() => void)) => { if (0 <= target && target < history.length && target !== cursor) return (): void => { - const cmd = editor.getValue(); + const cmd = editor.toString(); const pad = scratch.current; pad[cursor] = cmd; const cmd2 = pad[target]; - editor.setValue(cmd2); + editor.setContents(cmd2); setEmpty(cmd2 === ''); setCursor(target); }; @@ -301,7 +304,7 @@ const RenderConsole = (): JSX.Element => { pad.splice(cursor, 1); setHistory(hst); const next = cursor > 0 ? cursor - 1 : 0; - editor.setValue(pad[next]); + editor.setContents(pad[next]); setCursor(next); } }; @@ -363,9 +366,8 @@ const RenderConsole = (): JSX.Element => { title="Toggle command line editing" /> </Ivette.TitleBar> - <Text - buffer={edited ? editor : Server.buffer} - mode="text" + <TextView + text={edited ? editor : Server.buffer} readOnly={!edited} /> </> diff --git a/ivette/yarn.lock b/ivette/yarn.lock index f1883e3f52622fdf7dee22f007ef7ead226b8a6b..398f58fd1f382be0632dc429fdaf8ca878622e40 100644 --- a/ivette/yarn.lock +++ b/ivette/yarn.lock @@ -3568,11 +3568,6 @@ clsx@^1.0.4, clsx@^1.1.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== -codemirror@^5: - version "5.65.15" - resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.15.tgz#66899278f44a7acde0eb641388cd563fe6dfbe19" - integrity sha512-YC4EHbbwQeubZzxLl5G4nlbLc1T21QTrKGaOal/Pkm9dVDMZXMH7+ieSPEOZCtO9I68i8/oteJKOxzHC2zR+0g== - collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -4250,7 +4245,7 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== -diff@: +diff@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==