Skip to content

Commit 276086d

Browse files
Basic syntax highlighting support for search bar (#66)
1 parent 9cba4f2 commit 276086d

File tree

8 files changed

+165
-57
lines changed

8 files changed

+165
-57
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Added support for syntax highlighting in the search bar. ([#66](https://github.com/sourcebot-dev/sourcebot/pull/66))
13+
1014
## [2.4.1] - 2024-11-11
1115

1216
### Added

packages/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@tanstack/react-query": "^5.53.3",
4040
"@tanstack/react-table": "^8.20.5",
4141
"@tanstack/react-virtual": "^3.10.8",
42+
"@uiw/codemirror-themes": "^4.23.6",
4243
"@uiw/react-codemirror": "^4.23.0",
4344
"class-variance-authority": "^0.7.0",
4445
"client-only": "^0.0.1",

packages/web/src/app/globals.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
--chart-3: 197 37% 24%;
3030
--chart-4: 43 74% 66%;
3131
--chart-5: 27 87% 67%;
32+
--highlight: 224, 76%, 48%;
3233
}
3334

3435
.dark {
@@ -56,6 +57,7 @@
5657
--chart-3: 30 80% 55%;
5758
--chart-4: 280 65% 60%;
5859
--chart-5: 340 75% 55%;
60+
--highlight: 217, 91%, 60%;
5961
}
6062
}
6163

@@ -87,6 +89,10 @@
8789
border: solid;
8890
}
8991

92+
.cm-editor.cm-focused {
93+
outline: none !important;
94+
}
95+
9096
.truncate-start {
9197
direction: rtl;
9298
text-align: left;

packages/web/src/app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ const HowToSection = ({ title, children }: { title: string, children: React.Reac
148148

149149
const Highlight = ({ children }: { children: React.ReactNode }) => {
150150
return (
151-
<span className="text-blue-700 dark:text-blue-500">
151+
<span className="text-highlight">
152152
{children}
153153
</span>
154154
)

packages/web/src/app/searchBar.tsx

Lines changed: 130 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,39 @@
11
'use client';
22

3-
import {
4-
Form,
5-
FormControl,
6-
FormField,
7-
FormItem,
8-
FormMessage,
9-
} from "@/components/ui/form";
10-
import { Input } from "@/components/ui/input";
3+
import { useTailwind } from "@/hooks/useTailwind";
4+
import { SearchQueryParams } from "@/lib/types";
115
import { cn, createPathWithQueryParams } from "@/lib/utils";
12-
import { zodResolver } from "@hookform/resolvers/zod";
6+
import {
7+
cursorCharLeft,
8+
cursorCharRight,
9+
cursorDocEnd,
10+
cursorDocStart,
11+
cursorLineBoundaryBackward,
12+
cursorLineBoundaryForward,
13+
deleteCharBackward,
14+
deleteCharForward,
15+
deleteGroupBackward,
16+
deleteGroupForward,
17+
deleteLineBoundaryBackward,
18+
deleteLineBoundaryForward,
19+
history,
20+
historyKeymap,
21+
selectAll,
22+
selectCharLeft,
23+
selectCharRight,
24+
selectDocEnd,
25+
selectDocStart,
26+
selectLineBoundaryBackward,
27+
selectLineBoundaryForward
28+
} from "@codemirror/commands";
29+
import { LanguageSupport, StreamLanguage } from "@codemirror/language";
30+
import { tags as t } from '@lezer/highlight';
31+
import { createTheme } from '@uiw/codemirror-themes';
32+
import CodeMirror, { KeyBinding, keymap, ReactCodeMirrorRef } from "@uiw/react-codemirror";
1333
import { cva } from "class-variance-authority";
1434
import { useRouter } from "next/navigation";
15-
import { useForm } from "react-hook-form";
16-
import { z } from "zod";
17-
import { useHotkeys } from 'react-hotkeys-hook'
18-
import { useRef } from "react";
19-
import { SearchQueryParams } from "@/lib/types";
35+
import { useMemo, useRef, useState } from "react";
36+
import { useHotkeys } from 'react-hotkeys-hook';
2037

2138
interface SearchBarProps {
2239
className?: string;
@@ -25,12 +42,53 @@ interface SearchBarProps {
2542
autoFocus?: boolean;
2643
}
2744

28-
const formSchema = z.object({
29-
query: z.string(),
45+
const searchBarKeymap: readonly KeyBinding[] = ([
46+
{ key: "ArrowLeft", run: cursorCharLeft, shift: selectCharLeft, preventDefault: true },
47+
{ key: "ArrowRight", run: cursorCharRight, shift: selectCharRight, preventDefault: true },
48+
49+
{ key: "Home", run: cursorLineBoundaryBackward, shift: selectLineBoundaryBackward, preventDefault: true },
50+
{ key: "Mod-Home", run: cursorDocStart, shift: selectDocStart },
51+
52+
{ key: "End", run: cursorLineBoundaryForward, shift: selectLineBoundaryForward, preventDefault: true },
53+
{ key: "Mod-End", run: cursorDocEnd, shift: selectDocEnd },
54+
55+
{ key: "Mod-a", run: selectAll },
56+
57+
{ key: "Backspace", run: deleteCharBackward, shift: deleteCharBackward },
58+
{ key: "Delete", run: deleteCharForward },
59+
{ key: "Mod-Backspace", mac: "Alt-Backspace", run: deleteGroupBackward },
60+
{ key: "Mod-Delete", mac: "Alt-Delete", run: deleteGroupForward },
61+
{ mac: "Mod-Backspace", run: deleteLineBoundaryBackward },
62+
{ mac: "Mod-Delete", run: deleteLineBoundaryForward }
63+
] as KeyBinding[]).concat(historyKeymap);
64+
65+
const zoektLanguage = StreamLanguage.define({
66+
token: (stream) => {
67+
if (stream.match(/-?(file|branch|revision|rev|case|repo|lang|content|sym):/)) {
68+
return t.keyword.toString();
69+
}
70+
71+
if (stream.match(/\bor\b/)) {
72+
return t.keyword.toString();
73+
}
74+
75+
stream.next();
76+
return null;
77+
},
3078
});
3179

80+
const zoekt = () =>{
81+
return new LanguageSupport(zoektLanguage);
82+
}
83+
84+
const extensions = [
85+
keymap.of(searchBarKeymap),
86+
history(),
87+
zoekt()
88+
];
89+
3290
const searchBarVariants = cva(
33-
"w-full",
91+
"flex items-center w-full p-0.5 border rounded-md",
3492
{
3593
variants: {
3694
size: {
@@ -42,63 +100,79 @@ const searchBarVariants = cva(
42100
size: "default",
43101
}
44102
}
45-
)
103+
);
46104

47105
export const SearchBar = ({
48106
className,
49107
size,
50108
defaultQuery,
51109
autoFocus,
52110
}: SearchBarProps) => {
111+
const router = useRouter();
112+
const tailwind = useTailwind();
113+
114+
const theme = useMemo(() => {
115+
return createTheme({
116+
theme: 'light',
117+
settings: {
118+
background: tailwind.theme.colors.background,
119+
foreground: tailwind.theme.colors.foreground,
120+
caret: '#AEAFAD',
121+
},
122+
styles: [
123+
{
124+
tag: t.keyword,
125+
color: tailwind.theme.colors.highlight,
126+
},
127+
],
128+
});
129+
}, [tailwind]);
130+
131+
const [query, setQuery] = useState(defaultQuery ?? "");
132+
const editorRef = useRef<ReactCodeMirrorRef>(null);
53133

54-
const inputRef = useRef<HTMLInputElement>(null);
55134
useHotkeys('/', (event) => {
56135
event.preventDefault();
57-
inputRef.current?.focus();
58-
});
59-
60-
const router = useRouter();
61-
const form = useForm<z.infer<typeof formSchema>>({
62-
resolver: zodResolver(formSchema),
63-
defaultValues: {
64-
query: defaultQuery ?? "",
136+
editorRef.current?.view?.focus();
137+
if (editorRef.current?.view) {
138+
cursorDocEnd({
139+
state: editorRef.current.view.state,
140+
dispatch: editorRef.current.view.dispatch,
141+
});
65142
}
66143
});
67144

68-
const onSubmit = (values: z.infer<typeof formSchema>) => {
145+
const onSubmit = () => {
69146
const url = createPathWithQueryParams('/search',
70-
[SearchQueryParams.query, values.query],
147+
[SearchQueryParams.query, query],
71148
)
72149
router.push(url);
73150
}
74151

75152
return (
76-
<Form {...form}>
77-
<form
78-
onSubmit={form.handleSubmit(onSubmit)}
79-
className="w-full"
80-
>
81-
<FormField
82-
control={form.control}
83-
name="query"
84-
render={( { field }) => (
85-
<FormItem>
86-
<FormControl>
87-
<Input
88-
placeholder="Search..."
89-
className={cn(searchBarVariants({ size, className }))}
90-
{...field}
91-
ref={inputRef}
92-
autoFocus={autoFocus ?? false}
93-
// This is needed to prevent mobile browsers from zooming in when the input is focused
94-
style={{ fontSize: '1rem' }}
95-
/>
96-
</FormControl>
97-
<FormMessage />
98-
</FormItem>
99-
)}
100-
/>
101-
</form>
102-
</Form>
153+
<div
154+
className={cn(searchBarVariants({ size, className }))}
155+
onKeyDown={(e) => {
156+
if (e.key === 'Enter') {
157+
e.preventDefault();
158+
onSubmit();
159+
}
160+
}}
161+
>
162+
<CodeMirror
163+
ref={editorRef}
164+
className="grow"
165+
placeholder={"Search..."}
166+
value={query}
167+
onChange={(value) => {
168+
setQuery(value);
169+
}}
170+
theme={theme}
171+
basicSetup={false}
172+
extensions={extensions}
173+
indentWithTab={false}
174+
autoFocus={autoFocus ?? false}
175+
/>
176+
</div>
103177
)
104178
}

packages/web/src/hooks/useTailwind.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use client';
2+
3+
import { useMemo } from "react";
4+
import resolveConfig from 'tailwindcss/resolveConfig';
5+
import tailwindConfig from '../../tailwind.config';
6+
7+
export const useTailwind = () => {
8+
const tailwind = useMemo(() => {
9+
return resolveConfig(tailwindConfig);
10+
}, [tailwindConfig]);
11+
12+
return tailwind;
13+
}

packages/web/tailwind.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const config = {
5252
DEFAULT: "hsl(var(--card))",
5353
foreground: "hsl(var(--card-foreground))",
5454
},
55+
highlight: "hsl(var(--highlight))",
5556
},
5657
borderRadius: {
5758
lg: "var(--radius)",

yarn.lock

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1743,6 +1743,15 @@
17431743
"@codemirror/state" "^6.0.0"
17441744
"@codemirror/view" "^6.0.0"
17451745

1746+
"@uiw/codemirror-themes@^4.23.6":
1747+
version "4.23.6"
1748+
resolved "https://registry.yarnpkg.com/@uiw/codemirror-themes/-/codemirror-themes-4.23.6.tgz#47a101733a9c4aa382696178bc4b7bc0ecf0e5fa"
1749+
integrity sha512-0dpuLQW+V6zrKvfvor/eo71V3tpr2L2Hsu8QZAdtSzksjWABxTOzH3ShaBRxCEsrz6sU9sa9o7ShwBMMDz59bQ==
1750+
dependencies:
1751+
"@codemirror/language" "^6.0.0"
1752+
"@codemirror/state" "^6.0.0"
1753+
"@codemirror/view" "^6.0.0"
1754+
17461755
"@uiw/react-codemirror@^4.23.0":
17471756
version "4.23.5"
17481757
resolved "https://registry.yarnpkg.com/@uiw/react-codemirror/-/react-codemirror-4.23.5.tgz#6a16c23062067732cba105ac33ad69cf8e5c2111"

0 commit comments

Comments
 (0)