From a0bba782a8d636e27ca2a508d7e928bc55e338c1 Mon Sep 17 00:00:00 2001 From: Dane Roelofs <75864249+Rethora@users.noreply.github.com> Date: Thu, 30 Mar 2023 21:44:00 -0700 Subject: [PATCH] adds terminal search support (#395) --- package-lock.json | 33 ++++++++++---- package.json | 1 + src/components/terminal.tsx | 86 ++++++++++++++++++++++++++++++++++++- src/index.css | 17 +++++++- 4 files changed, 126 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6f2930c..9613146 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,6 +91,7 @@ "watcher": "^2.2.2", "webpack": "^5.75.0", "xterm": "^4.19.0", + "xterm-addon-search": "^0.9.0", "xterm-addon-web-links": "^0.6.0" }, "devDependencies": { @@ -2953,6 +2954,15 @@ "node": ">= 14.17.5" } }, + "node_modules/@electron-forge/web-multi-logger/node_modules/xterm-addon-search": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/xterm-addon-search/-/xterm-addon-search-0.8.2.tgz", + "integrity": "sha512-I1863mjn8P6uVrqm/X+btalVsqjAKLhnhpbP7SavAOpEkI1jJhbHU2UTp7NjeRtcKTks6UWk/ycgds5snDSejg==", + "dev": true, + "peerDependencies": { + "xterm": "^4.0.0" + } + }, "node_modules/@electron/asar": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.3.tgz", @@ -23634,10 +23644,9 @@ } }, "node_modules/xterm-addon-search": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/xterm-addon-search/-/xterm-addon-search-0.8.2.tgz", - "integrity": "sha512-I1863mjn8P6uVrqm/X+btalVsqjAKLhnhpbP7SavAOpEkI1jJhbHU2UTp7NjeRtcKTks6UWk/ycgds5snDSejg==", - "dev": true, + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/xterm-addon-search/-/xterm-addon-search-0.9.0.tgz", + "integrity": "sha512-aoolI8YuHvdGw+Qjg8g2M4kst0v86GtB7WeBm4F0jNXA005/6QbWWy9eCsvnIDLJOFI5JSSrZnD6CaOkvBQYPA==", "peerDependencies": { "xterm": "^4.0.0" } @@ -25849,6 +25858,15 @@ "xterm": "^4.9.0", "xterm-addon-fit": "^0.5.0", "xterm-addon-search": "^0.8.0" + }, + "dependencies": { + "xterm-addon-search": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/xterm-addon-search/-/xterm-addon-search-0.8.2.tgz", + "integrity": "sha512-I1863mjn8P6uVrqm/X+btalVsqjAKLhnhpbP7SavAOpEkI1jJhbHU2UTp7NjeRtcKTks6UWk/ycgds5snDSejg==", + "dev": true, + "requires": {} + } } }, "@electron/asar": { @@ -41287,10 +41305,9 @@ "requires": {} }, "xterm-addon-search": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/xterm-addon-search/-/xterm-addon-search-0.8.2.tgz", - "integrity": "sha512-I1863mjn8P6uVrqm/X+btalVsqjAKLhnhpbP7SavAOpEkI1jJhbHU2UTp7NjeRtcKTks6UWk/ycgds5snDSejg==", - "dev": true, + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/xterm-addon-search/-/xterm-addon-search-0.9.0.tgz", + "integrity": "sha512-aoolI8YuHvdGw+Qjg8g2M4kst0v86GtB7WeBm4F0jNXA005/6QbWWy9eCsvnIDLJOFI5JSSrZnD6CaOkvBQYPA==", "requires": {} }, "xterm-addon-web-links": { diff --git a/package.json b/package.json index 5369d44..07641b8 100644 --- a/package.json +++ b/package.json @@ -172,6 +172,7 @@ "watcher": "^2.2.2", "webpack": "^5.75.0", "xterm": "^4.19.0", + "xterm-addon-search": "^0.9.0", "xterm-addon-web-links": "^0.6.0" } } diff --git a/src/components/terminal.tsx b/src/components/terminal.tsx index e9c18b1..ffe3062 100644 --- a/src/components/terminal.tsx +++ b/src/components/terminal.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef } from 'react' import { Terminal } from 'xterm' import { FitAddon } from 'xterm-addon-fit' import { WebLinksAddon } from 'xterm-addon-web-links' +import { SearchAddon } from 'xterm-addon-search' import 'xterm/css/xterm.css' import { useAppDispatch, useAppSelector } from '../app/hooks' import { FullState } from '../features/window/state' @@ -9,9 +10,11 @@ import * as gs from '../features/globalSlice' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faTimes } from '@fortawesome/free-solid-svg-icons' import { throttleCallback } from './componentUtils' +import { faChevronDown, faChevronUp } from '@fortawesome/pro-regular-svg-icons' export function XTermComponent({ height }: { height: number }) { const terminalRef = useRef(null) + const searchBarInputRef = useRef(null) const terminal = useRef(null) const fitAddon = useRef(new FitAddon()) const webLinksAddon = useRef( @@ -20,6 +23,8 @@ export function XTermComponent({ height }: { height: number }) { connector.terminalClickLink(url) }) ) + const searchAddon = useRef(new SearchAddon()) + const [searchBarOpen, setSearchBarOpen] = React.useState(false) const handleIncomingData = (e: any, data: any) => { terminal.current!.write(data) @@ -38,6 +43,7 @@ export function XTermComponent({ height }: { height: number }) { terminal.current.loadAddon(fitAddon.current) terminal.current.loadAddon(webLinksAddon.current) + terminal.current.loadAddon(searchAddon.current) if (terminalRef.current) { terminal.current.open(terminalRef.current) @@ -49,6 +55,17 @@ export function XTermComponent({ height }: { height: number }) { connector.terminalInto(e) }) + terminal.current.attachCustomKeyEventHandler((e) => { + if (e.ctrlKey && e.key === 'f') { + openSearchBar() + return false + } else if (e.key === 'Escape') { + closeSearchBar() + return false + } + return true + }) + connector.registerIncData(handleIncomingData) // Make the terminal's size and geometry fit the size of #terminal-container @@ -68,12 +85,77 @@ export function XTermComponent({ height }: { height: number }) { } }, [height, terminal, fitAddon]) + const openSearchBar = () => { + setSearchBarOpen(true) + searchBarInputRef.current?.focus() + } + + const closeSearchBar = () => { + setSearchBarOpen(false) + terminal.current?.focus() + } + + const findNextSearchResult = () => { + if (searchBarInputRef.current?.value) { + searchAddon.current.findNext(searchBarInputRef.current.value) + } + } + + const findPreviousSearchResult = () => { + if (searchBarInputRef.current?.value) { + searchAddon.current.findPrevious(searchBarInputRef.current.value) + } + } + return (
+ style={{ height: height + 'px', position: 'relative' }} + > + {searchBarOpen && ( +
{ + if (e.key === 'Escape') { + closeSearchBar() + } + }} + > + { + if (e.key === 'Enter') { + findNextSearchResult() + } + }} + /> +
+ + + +
+
+ )} + ) } export const BottomTerminal: React.FC = () => { diff --git a/src/index.css b/src/index.css index ae16ee0..f263421 100644 --- a/src/index.css +++ b/src/index.css @@ -1517,6 +1517,13 @@ cm-diff-cancel > div { padding: 12px; } +.search-input { + border-radius: 5px; + background-color: var(--input-background); + color: var(--input-text); + padding: 4px; +} + /* .cm-diagnostic { display: block; word-break: keep-all; @@ -2416,6 +2423,14 @@ free servers .disabled_command .shortcut__block { align-items: center; } +.terminalInnerContainer .icon { + color: #bbb; + font-size: 18px; + margin: auto; + width: 24px; + height: 24px; +} + .terminalTitle { color: #bbb; flex-grow: 1; @@ -2567,4 +2582,4 @@ free servers .disabled_command .shortcut__block { .markdownpopup p { margin: 12px 0px; -} \ No newline at end of file +}