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]
|
||||
|
||||
### 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
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
"@tanstack/react-query": "^5.53.3",
|
||||
"@tanstack/react-table": "^8.20.5",
|
||||
"@tanstack/react-virtual": "^3.10.8",
|
||||
"@uiw/codemirror-themes": "^4.23.6",
|
||||
"@uiw/react-codemirror": "^4.23.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"client-only": "^0.0.1",
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
--highlight: 224, 76%, 48%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
|
|
@ -56,6 +57,7 @@
|
|||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
--highlight: 217, 91%, 60%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +89,10 @@
|
|||
border: solid;
|
||||
}
|
||||
|
||||
.cm-editor.cm-focused {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.truncate-start {
|
||||
direction: rtl;
|
||||
text-align: left;
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ const HowToSection = ({ title, children }: { title: string, children: React.Reac
|
|||
|
||||
const Highlight = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<span className="text-blue-700 dark:text-blue-500">
|
||||
<span className="text-highlight">
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,22 +1,39 @@
|
|||
'use client';
|
||||
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useTailwind } from "@/hooks/useTailwind";
|
||||
import { SearchQueryParams } from "@/lib/types";
|
||||
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 { useRouter } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { useRef } from "react";
|
||||
import { SearchQueryParams } from "@/lib/types";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
|
||||
interface SearchBarProps {
|
||||
className?: string;
|
||||
|
|
@ -25,12 +42,53 @@ interface SearchBarProps {
|
|||
autoFocus?: boolean;
|
||||
}
|
||||
|
||||
const formSchema = z.object({
|
||||
query: z.string(),
|
||||
const searchBarKeymap: readonly KeyBinding[] = ([
|
||||
{ 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(
|
||||
"w-full",
|
||||
"flex items-center w-full p-0.5 border rounded-md",
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
|
|
@ -42,7 +100,7 @@ const searchBarVariants = cva(
|
|||
size: "default",
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export const SearchBar = ({
|
||||
className,
|
||||
|
|
@ -50,55 +108,71 @@ export const SearchBar = ({
|
|||
defaultQuery,
|
||||
autoFocus,
|
||||
}: 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) => {
|
||||
event.preventDefault();
|
||||
inputRef.current?.focus();
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
query: defaultQuery ?? "",
|
||||
editorRef.current?.view?.focus();
|
||||
if (editorRef.current?.view) {
|
||||
cursorDocEnd({
|
||||
state: editorRef.current.view.state,
|
||||
dispatch: editorRef.current.view.dispatch,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const onSubmit = (values: z.infer<typeof formSchema>) => {
|
||||
const onSubmit = () => {
|
||||
const url = createPathWithQueryParams('/search',
|
||||
[SearchQueryParams.query, values.query],
|
||||
[SearchQueryParams.query, query],
|
||||
)
|
||||
router.push(url);
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="w-full"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="query"
|
||||
render={( { field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Search..."
|
||||
className={cn(searchBarVariants({ size, className }))}
|
||||
{...field}
|
||||
ref={inputRef}
|
||||
autoFocus={autoFocus ?? false}
|
||||
// This is needed to prevent mobile browsers from zooming in when the input is focused
|
||||
style={{ fontSize: '1rem' }}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
<div
|
||||
className={cn(searchBarVariants({ size, className }))}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
onSubmit();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CodeMirror
|
||||
ref={editorRef}
|
||||
className="grow"
|
||||
placeholder={"Search..."}
|
||||
value={query}
|
||||
onChange={(value) => {
|
||||
setQuery(value);
|
||||
}}
|
||||
theme={theme}
|
||||
basicSetup={false}
|
||||
extensions={extensions}
|
||||
indentWithTab={false}
|
||||
autoFocus={autoFocus ?? false}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
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))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
highlight: "hsl(var(--highlight))",
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
|
|
|
|||
|
|
@ -1743,6 +1743,15 @@
|
|||
"@codemirror/state" "^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":
|
||||
version "4.23.5"
|
||||
resolved "https://registry.yarnpkg.com/@uiw/react-codemirror/-/react-codemirror-4.23.5.tgz#6a16c23062067732cba105ac33ad69cf8e5c2111"
|
||||
|
|
|
|||
Loading…
Reference in a new issue