mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 12:25:22 +00:00
Basic syntax highlighting support for search bar (#66)
This commit is contained in:
parent
9cba4f274f
commit
276086d2d6
8 changed files with 165 additions and 57 deletions
|
|
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Added support for syntax highlighting in the search bar. ([#66](https://github.com/sourcebot-dev/sourcebot/pull/66))
|
||||||
|
|
||||||
## [2.4.1] - 2024-11-11
|
## [2.4.1] - 2024-11-11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
"@tanstack/react-query": "^5.53.3",
|
"@tanstack/react-query": "^5.53.3",
|
||||||
"@tanstack/react-table": "^8.20.5",
|
"@tanstack/react-table": "^8.20.5",
|
||||||
"@tanstack/react-virtual": "^3.10.8",
|
"@tanstack/react-virtual": "^3.10.8",
|
||||||
|
"@uiw/codemirror-themes": "^4.23.6",
|
||||||
"@uiw/react-codemirror": "^4.23.0",
|
"@uiw/react-codemirror": "^4.23.0",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"client-only": "^0.0.1",
|
"client-only": "^0.0.1",
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
--chart-3: 197 37% 24%;
|
--chart-3: 197 37% 24%;
|
||||||
--chart-4: 43 74% 66%;
|
--chart-4: 43 74% 66%;
|
||||||
--chart-5: 27 87% 67%;
|
--chart-5: 27 87% 67%;
|
||||||
|
--highlight: 224, 76%, 48%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
|
|
@ -56,6 +57,7 @@
|
||||||
--chart-3: 30 80% 55%;
|
--chart-3: 30 80% 55%;
|
||||||
--chart-4: 280 65% 60%;
|
--chart-4: 280 65% 60%;
|
||||||
--chart-5: 340 75% 55%;
|
--chart-5: 340 75% 55%;
|
||||||
|
--highlight: 217, 91%, 60%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,6 +89,10 @@
|
||||||
border: solid;
|
border: solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cm-editor.cm-focused {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.truncate-start {
|
.truncate-start {
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ const HowToSection = ({ title, children }: { title: string, children: React.Reac
|
||||||
|
|
||||||
const Highlight = ({ children }: { children: React.ReactNode }) => {
|
const Highlight = ({ children }: { children: React.ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<span className="text-blue-700 dark:text-blue-500">
|
<span className="text-highlight">
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,39 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import {
|
import { useTailwind } from "@/hooks/useTailwind";
|
||||||
Form,
|
import { SearchQueryParams } from "@/lib/types";
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { cn, createPathWithQueryParams } from "@/lib/utils";
|
import { cn, createPathWithQueryParams } from "@/lib/utils";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import {
|
||||||
|
cursorCharLeft,
|
||||||
|
cursorCharRight,
|
||||||
|
cursorDocEnd,
|
||||||
|
cursorDocStart,
|
||||||
|
cursorLineBoundaryBackward,
|
||||||
|
cursorLineBoundaryForward,
|
||||||
|
deleteCharBackward,
|
||||||
|
deleteCharForward,
|
||||||
|
deleteGroupBackward,
|
||||||
|
deleteGroupForward,
|
||||||
|
deleteLineBoundaryBackward,
|
||||||
|
deleteLineBoundaryForward,
|
||||||
|
history,
|
||||||
|
historyKeymap,
|
||||||
|
selectAll,
|
||||||
|
selectCharLeft,
|
||||||
|
selectCharRight,
|
||||||
|
selectDocEnd,
|
||||||
|
selectDocStart,
|
||||||
|
selectLineBoundaryBackward,
|
||||||
|
selectLineBoundaryForward
|
||||||
|
} from "@codemirror/commands";
|
||||||
|
import { LanguageSupport, StreamLanguage } from "@codemirror/language";
|
||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
import { createTheme } from '@uiw/codemirror-themes';
|
||||||
|
import CodeMirror, { KeyBinding, keymap, ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
||||||
import { cva } from "class-variance-authority";
|
import { cva } from "class-variance-authority";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useForm } from "react-hook-form";
|
import { useMemo, useRef, useState } from "react";
|
||||||
import { z } from "zod";
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
|
||||||
import { useRef } from "react";
|
|
||||||
import { SearchQueryParams } from "@/lib/types";
|
|
||||||
|
|
||||||
interface SearchBarProps {
|
interface SearchBarProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
@ -25,12 +42,53 @@ interface SearchBarProps {
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formSchema = z.object({
|
const searchBarKeymap: readonly KeyBinding[] = ([
|
||||||
query: z.string(),
|
{ key: "ArrowLeft", run: cursorCharLeft, shift: selectCharLeft, preventDefault: true },
|
||||||
|
{ key: "ArrowRight", run: cursorCharRight, shift: selectCharRight, preventDefault: true },
|
||||||
|
|
||||||
|
{ key: "Home", run: cursorLineBoundaryBackward, shift: selectLineBoundaryBackward, preventDefault: true },
|
||||||
|
{ key: "Mod-Home", run: cursorDocStart, shift: selectDocStart },
|
||||||
|
|
||||||
|
{ key: "End", run: cursorLineBoundaryForward, shift: selectLineBoundaryForward, preventDefault: true },
|
||||||
|
{ key: "Mod-End", run: cursorDocEnd, shift: selectDocEnd },
|
||||||
|
|
||||||
|
{ key: "Mod-a", run: selectAll },
|
||||||
|
|
||||||
|
{ key: "Backspace", run: deleteCharBackward, shift: deleteCharBackward },
|
||||||
|
{ key: "Delete", run: deleteCharForward },
|
||||||
|
{ key: "Mod-Backspace", mac: "Alt-Backspace", run: deleteGroupBackward },
|
||||||
|
{ key: "Mod-Delete", mac: "Alt-Delete", run: deleteGroupForward },
|
||||||
|
{ mac: "Mod-Backspace", run: deleteLineBoundaryBackward },
|
||||||
|
{ mac: "Mod-Delete", run: deleteLineBoundaryForward }
|
||||||
|
] as KeyBinding[]).concat(historyKeymap);
|
||||||
|
|
||||||
|
const zoektLanguage = StreamLanguage.define({
|
||||||
|
token: (stream) => {
|
||||||
|
if (stream.match(/-?(file|branch|revision|rev|case|repo|lang|content|sym):/)) {
|
||||||
|
return t.keyword.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream.match(/\bor\b/)) {
|
||||||
|
return t.keyword.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.next();
|
||||||
|
return null;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const zoekt = () =>{
|
||||||
|
return new LanguageSupport(zoektLanguage);
|
||||||
|
}
|
||||||
|
|
||||||
|
const extensions = [
|
||||||
|
keymap.of(searchBarKeymap),
|
||||||
|
history(),
|
||||||
|
zoekt()
|
||||||
|
];
|
||||||
|
|
||||||
const searchBarVariants = cva(
|
const searchBarVariants = cva(
|
||||||
"w-full",
|
"flex items-center w-full p-0.5 border rounded-md",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
size: {
|
size: {
|
||||||
|
|
@ -42,7 +100,7 @@ const searchBarVariants = cva(
|
||||||
size: "default",
|
size: "default",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
export const SearchBar = ({
|
export const SearchBar = ({
|
||||||
className,
|
className,
|
||||||
|
|
@ -50,55 +108,71 @@ export const SearchBar = ({
|
||||||
defaultQuery,
|
defaultQuery,
|
||||||
autoFocus,
|
autoFocus,
|
||||||
}: SearchBarProps) => {
|
}: SearchBarProps) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const tailwind = useTailwind();
|
||||||
|
|
||||||
|
const theme = useMemo(() => {
|
||||||
|
return createTheme({
|
||||||
|
theme: 'light',
|
||||||
|
settings: {
|
||||||
|
background: tailwind.theme.colors.background,
|
||||||
|
foreground: tailwind.theme.colors.foreground,
|
||||||
|
caret: '#AEAFAD',
|
||||||
|
},
|
||||||
|
styles: [
|
||||||
|
{
|
||||||
|
tag: t.keyword,
|
||||||
|
color: tailwind.theme.colors.highlight,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}, [tailwind]);
|
||||||
|
|
||||||
|
const [query, setQuery] = useState(defaultQuery ?? "");
|
||||||
|
const editorRef = useRef<ReactCodeMirrorRef>(null);
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
useHotkeys('/', (event) => {
|
useHotkeys('/', (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
inputRef.current?.focus();
|
editorRef.current?.view?.focus();
|
||||||
});
|
if (editorRef.current?.view) {
|
||||||
|
cursorDocEnd({
|
||||||
const router = useRouter();
|
state: editorRef.current.view.state,
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
dispatch: editorRef.current.view.dispatch,
|
||||||
resolver: zodResolver(formSchema),
|
});
|
||||||
defaultValues: {
|
|
||||||
query: defaultQuery ?? "",
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = (values: z.infer<typeof formSchema>) => {
|
const onSubmit = () => {
|
||||||
const url = createPathWithQueryParams('/search',
|
const url = createPathWithQueryParams('/search',
|
||||||
[SearchQueryParams.query, values.query],
|
[SearchQueryParams.query, query],
|
||||||
)
|
)
|
||||||
router.push(url);
|
router.push(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<div
|
||||||
<form
|
className={cn(searchBarVariants({ size, className }))}
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onKeyDown={(e) => {
|
||||||
className="w-full"
|
if (e.key === 'Enter') {
|
||||||
>
|
e.preventDefault();
|
||||||
<FormField
|
onSubmit();
|
||||||
control={form.control}
|
}
|
||||||
name="query"
|
}}
|
||||||
render={( { field }) => (
|
>
|
||||||
<FormItem>
|
<CodeMirror
|
||||||
<FormControl>
|
ref={editorRef}
|
||||||
<Input
|
className="grow"
|
||||||
placeholder="Search..."
|
placeholder={"Search..."}
|
||||||
className={cn(searchBarVariants({ size, className }))}
|
value={query}
|
||||||
{...field}
|
onChange={(value) => {
|
||||||
ref={inputRef}
|
setQuery(value);
|
||||||
autoFocus={autoFocus ?? false}
|
}}
|
||||||
// This is needed to prevent mobile browsers from zooming in when the input is focused
|
theme={theme}
|
||||||
style={{ fontSize: '1rem' }}
|
basicSetup={false}
|
||||||
/>
|
extensions={extensions}
|
||||||
</FormControl>
|
indentWithTab={false}
|
||||||
<FormMessage />
|
autoFocus={autoFocus ?? false}
|
||||||
</FormItem>
|
/>
|
||||||
)}
|
</div>
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
13
packages/web/src/hooks/useTailwind.ts
Normal file
13
packages/web/src/hooks/useTailwind.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import resolveConfig from 'tailwindcss/resolveConfig';
|
||||||
|
import tailwindConfig from '../../tailwind.config';
|
||||||
|
|
||||||
|
export const useTailwind = () => {
|
||||||
|
const tailwind = useMemo(() => {
|
||||||
|
return resolveConfig(tailwindConfig);
|
||||||
|
}, [tailwindConfig]);
|
||||||
|
|
||||||
|
return tailwind;
|
||||||
|
}
|
||||||
|
|
@ -52,6 +52,7 @@ const config = {
|
||||||
DEFAULT: "hsl(var(--card))",
|
DEFAULT: "hsl(var(--card))",
|
||||||
foreground: "hsl(var(--card-foreground))",
|
foreground: "hsl(var(--card-foreground))",
|
||||||
},
|
},
|
||||||
|
highlight: "hsl(var(--highlight))",
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: "var(--radius)",
|
lg: "var(--radius)",
|
||||||
|
|
|
||||||
|
|
@ -1743,6 +1743,15 @@
|
||||||
"@codemirror/state" "^6.0.0"
|
"@codemirror/state" "^6.0.0"
|
||||||
"@codemirror/view" "^6.0.0"
|
"@codemirror/view" "^6.0.0"
|
||||||
|
|
||||||
|
"@uiw/codemirror-themes@^4.23.6":
|
||||||
|
version "4.23.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@uiw/codemirror-themes/-/codemirror-themes-4.23.6.tgz#47a101733a9c4aa382696178bc4b7bc0ecf0e5fa"
|
||||||
|
integrity sha512-0dpuLQW+V6zrKvfvor/eo71V3tpr2L2Hsu8QZAdtSzksjWABxTOzH3ShaBRxCEsrz6sU9sa9o7ShwBMMDz59bQ==
|
||||||
|
dependencies:
|
||||||
|
"@codemirror/language" "^6.0.0"
|
||||||
|
"@codemirror/state" "^6.0.0"
|
||||||
|
"@codemirror/view" "^6.0.0"
|
||||||
|
|
||||||
"@uiw/react-codemirror@^4.23.0":
|
"@uiw/react-codemirror@^4.23.0":
|
||||||
version "4.23.5"
|
version "4.23.5"
|
||||||
resolved "https://registry.yarnpkg.com/@uiw/react-codemirror/-/react-codemirror-4.23.5.tgz#6a16c23062067732cba105ac33ad69cf8e5c2111"
|
resolved "https://registry.yarnpkg.com/@uiw/react-codemirror/-/react-codemirror-4.23.5.tgz#6a16c23062067732cba105ac33ad69cf8e5c2111"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue