Skip to content

Commit

Permalink
adds terminal search support (getcursor#395)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rethora authored Mar 31, 2023
1 parent 48820cd commit a0bba78
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 11 deletions.
33 changes: 25 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
86 changes: 84 additions & 2 deletions src/components/terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ 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'
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<HTMLDivElement>(null)
const searchBarInputRef = useRef<HTMLInputElement>(null)
const terminal = useRef<Terminal | null>(null)
const fitAddon = useRef<FitAddon>(new FitAddon())
const webLinksAddon = useRef<WebLinksAddon>(
Expand All @@ -20,6 +23,8 @@ export function XTermComponent({ height }: { height: number }) {
connector.terminalClickLink(url)
})
)
const searchAddon = useRef<SearchAddon>(new SearchAddon())
const [searchBarOpen, setSearchBarOpen] = React.useState(false)

const handleIncomingData = (e: any, data: any) => {
terminal.current!.write(data)
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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 (
<div
className="terminalInnerContainer"
ref={terminalRef}
style={{ height: height + 'px' }}
></div>
style={{ height: height + 'px', position: 'relative' }}
>
{searchBarOpen && (
<div
className="search-input flex justify-end absolute top-1 right-4 z-10 md:w-80"
onKeyDown={(e) => {
if (e.key === 'Escape') {
closeSearchBar()
}
}}
>
<input
className="search-input w-full"
placeholder="Search..."
autoFocus
ref={searchBarInputRef}
onKeyDown={(e) => {
if (e.key === 'Enter') {
findNextSearchResult()
}
}}
/>
<div className="flex">
<button
className="icon"
onClick={() => findPreviousSearchResult()}
>
<FontAwesomeIcon icon={faChevronUp} />
</button>
<button
className="icon"
onClick={() => findNextSearchResult()}
>
<FontAwesomeIcon icon={faChevronDown} />
</button>
<button
className="icon"
onClick={() => closeSearchBar()}
>
<FontAwesomeIcon icon={faTimes} />
</button>
</div>
</div>
)}
</div>
)
}
export const BottomTerminal: React.FC = () => {
Expand Down
17 changes: 16 additions & 1 deletion src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -2567,4 +2582,4 @@ free servers .disabled_command .shortcut__block {

.markdownpopup p {
margin: 12px 0px;
}
}

0 comments on commit a0bba78

Please sign in to comment.