mirror of https://github.com/getcursor/cursor
adds terminal search support (#395)
parent
48820cdd10
commit
a0bba782a8
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<HTMLDivElement>(null)
|
||||
const searchBarInputRef = useRef<HTMLInputElement>(null)
|
||||
const terminal = useRef<Terminal | null>(null)
|
||||
const fitAddon = useRef<FitAddon>(new FitAddon())
|
||||
const webLinksAddon = useRef<WebLinksAddon>(
|
||||
|
@ -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)
|
||||
|
@ -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 (
|
||||
<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 = () => {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue