diff --git a/ivette/package.json b/ivette/package.json index bedf12a40ddbf6d65752f15353aec760bfbebc31..665132667063b1add37e44d44a1309a4b11c2ab2 100644 --- a/ivette/package.json +++ b/ivette/package.json @@ -60,6 +60,7 @@ "codemirror": "^5.65.2", "@codemirror/view": "6.2.3", "@codemirror/state": "6.1.1", + "@codemirror/search": "6.2.3", "@codemirror/language": "6.2.1", "@codemirror/commands": "6.1.0", "@codemirror/lang-cpp": "6.0.1", diff --git a/ivette/src/dome/renderer/text/editor.tsx b/ivette/src/dome/renderer/text/editor.tsx index 6f0438512f5f3702932e122c44c7cbe4dcea7a48..3426e8b4fa6e2961bf229bf8a7e01b09d2471a25 100644 --- a/ivette/src/dome/renderer/text/editor.tsx +++ b/ivette/src/dome/renderer/text/editor.tsx @@ -31,7 +31,8 @@ import { Decoration, DecorationSet } from '@codemirror/view'; import { DOMEventMap as EventMap } from '@codemirror/view'; import { GutterMarker, gutter } from '@codemirror/view'; import { Tooltip, showTooltip } from '@codemirror/view'; -import { lineNumbers } from '@codemirror/view'; +import { lineNumbers, keymap } from '@codemirror/view'; +import { searchKeymap, search, openSearchPanel } from '@codemirror/search'; import { parser } from '@lezer/cpp'; import { tags } from '@lezer/highlight'; @@ -328,6 +329,12 @@ export const LanguageHighlighter: Extension = // Standard extensions and commands // ----------------------------------------------------------------------------- +export const ReadOnly = EditorState.readOnly.of(true); + +const SearchAlternativeKey = [{ key: 'Alt-f', run: openSearchPanel }]; +const SearchKeymap = searchKeymap.slice(1).concat(SearchAlternativeKey); +export const Search : Extension = [ search(), keymap.of(SearchKeymap) ]; + export const Selection = createSelectionField(); function createSelectionField(): Field<EditorSelection> { const cursor = EditorSelection.cursor(0); diff --git a/ivette/src/dome/renderer/text/style.css b/ivette/src/dome/renderer/text/style.css index d936197776803018537a588dc925c0d07239a09e..e339d8c9e86655ef19237ec5e918018db1ca0ed4 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; - width: 2.15em; + min-width: 2.15em; background: var(--background-report); } @@ -132,6 +132,52 @@ color: var(--disabled-text); } +.cm-editor .cm-panels { + font-size: 13px; + background: var(--background-report); + color: var(--text); +} + +.cm-editor .cm-panels-bottom { + border-top: 2px solid var(--background-softer); +} + +.cm-editor .cm-panel.cm-search { + padding: 4px 6px 4px 6px; + display: flex; + align-items: center; + gap: 2px; +} + +.cm-editor .cm-panel.cm-search label { + font-size: 80%; + display: flex; + align-items: center; +} + +.cm-editor .cm-panel.cm-search [name=close] { + color: var(--info-text); +} + +.cm-editor .cm-textfield { + background: var(--background-report); + color: var(--text); + border: 1px solid var(--border); +} + +.cm-editor .cm-button { + background-image: var(--default-button-img); + border: 1px solid var(--border); + color: var(--text); +} + +.cm-editor .cm-panel.cm-gotoLine { + padding: 4px 6px 4px 6px; + display: flex; + align-items: center; + gap: 6px; +} + .cm-property-gutter { width: 1.3em; background: var(--code-bullet); diff --git a/ivette/src/frama-c/kernel/ASTview.tsx b/ivette/src/frama-c/kernel/ASTview.tsx index 63495a564ee613daa4f03a3c70cbcd06e717b926..c923ee9d4d176bf13075d479b7bd1e26b99afbf5 100644 --- a/ivette/src/frama-c/kernel/ASTview.tsx +++ b/ivette/src/frama-c/kernel/ASTview.tsx @@ -617,6 +617,7 @@ const extensions: Editor.Extension[] = [ PropertiesGutter, TaintedLvaluesDecorator, TaintTooltip, + Editor.ReadOnly, Editor.FoldGutter, Editor.LanguageHighlighter, ]; diff --git a/ivette/src/frama-c/kernel/SourceCode.tsx b/ivette/src/frama-c/kernel/SourceCode.tsx index cc059c919a4224523c4849c5cf03e76aa0a9e008..2ede47f2e646753847e6a99bec84d8ec08f326aa 100644 --- a/ivette/src/frama-c/kernel/SourceCode.tsx +++ b/ivette/src/frama-c/kernel/SourceCode.tsx @@ -25,11 +25,12 @@ import * as Path from 'path'; import * as Dome from 'dome'; import * as System from 'dome/system'; -import * as Boxes from 'dome/layout/boxes'; import * as Editor from 'dome/text/editor'; import * as Labels from 'dome/controls/labels'; import * as Settings from 'dome/data/settings'; import * as Buttons from 'dome/controls/buttons'; +import * as Dialogs from 'dome/dialogs'; +import * as Toolbars from 'dome/frame/toolbars'; import { Selection, Document } from 'dome/text/editor'; @@ -232,6 +233,8 @@ function useSourceGetter(): GetSource { // Necessary extensions. const extensions: Editor.Extension[] = [ Source, + Editor.Search, + Editor.ReadOnly, Editor.Selection, Editor.LineNumbers, Editor.LanguageHighlighter, @@ -263,9 +266,22 @@ export default function SourceCode(): JSX.Element { React.useEffect(() => Locations.set(view, { ivette: loc }), [view, loc]); const externalEditorTitle = - 'Open the source file in an external editor.\nA Ctrl-click ' - + 'in the source code opens the editor at the selected location.' - + '\nThe editor used can be configured in Ivette settings.'; + 'Open the source file in an external editor.'; + + const shortcuts = + 'Ctrl+click: open file in an external editor at the selected location.\n' + + 'Alt+f: search for text or regexp.\n' + + 'Alt+g: go to line.'; + + const shortcutsTitle = 'Useful shortcuts:\n' + shortcuts; + + async function displayShortcuts(): Promise<void> { + await Dialogs.showMessageBox({ + buttons: [{label:"Ok"}], + details: shortcuts, + message: 'Useful shortcuts' + }); + } return ( <> @@ -277,7 +293,13 @@ export default function SourceCode(): JSX.Element { title={externalEditorTitle} /> <Labels.Code title={file}>{filename}</Labels.Code> - <Boxes.Hfill /> + <Toolbars.Filler /> + <Buttons.IconButton + icon="HELP" + onClick={displayShortcuts} + title={shortcutsTitle} + /> + <Toolbars.Inset/> </Ivette.TitleBar> <Component style={{ fontSize: `${fontSize}px` }} /> </> diff --git a/ivette/yarn.lock b/ivette/yarn.lock index aa98dbadf52cd4824088c375fd06493f2cb58f29..f7180e42c20f5b24bf92b7b5217dde6ff84cea2a 100644 --- a/ivette/yarn.lock +++ b/ivette/yarn.lock @@ -1816,6 +1816,15 @@ "@lezer/lr" "^1.0.0" style-mod "^4.0.0" +"@codemirror/search@6.2.3": + version "6.2.3" + resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.2.3.tgz#fab933fef1b1de8ef40cda275c73d9ac7a1ff40f" + integrity sha512-V9n9233lopQhB1dyjsBK2Wc1i+8hcCqxl1wQ46c5HWWLePoe4FluV3TGHoZ04rBRlGjNyz9DTmpJErig8UE4jw== + dependencies: + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + crelt "^1.0.5" + "@codemirror/state@6.1.1", "@codemirror/state@^6.0.0": version "6.1.1" resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.1.1.tgz#4f512e5e34ea23a5e10b2c1fe43f6195e90417bb" @@ -3840,6 +3849,11 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +crelt@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.5.tgz#57c0d52af8c859e354bace1883eb2e1eb182bb94" + integrity sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA== + crocket@^0.9.11: version "0.9.11" resolved "https://registry.yarnpkg.com/crocket/-/crocket-0.9.11.tgz#288fca11ef0d3dd239b62c488265f30c8edfb0c5"