Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions packages/web/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--highlight: 224, 76%, 48%;
}

.dark {
Expand Down Expand Up @@ -56,6 +57,7 @@
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--highlight: 217, 91%, 60%;
}
}

Expand Down Expand Up @@ -87,6 +89,10 @@
border: solid;
}

.cm-editor.cm-focused {
outline: none !important;
}

.truncate-start {
direction: rtl;
text-align: left;
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
)
Expand Down
186 changes: 130 additions & 56 deletions packages/web/src/app/searchBar.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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: {
Expand All @@ -42,63 +100,79 @@ const searchBarVariants = cva(
size: "default",
}
}
)
);

export const SearchBar = ({
className,
size,
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 changes: 13 additions & 0 deletions packages/web/src/hooks/useTailwind.ts
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions packages/web/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const config = {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
highlight: "hsl(var(--highlight))",
},
borderRadius: {
lg: "var(--radius)",
Expand Down
9 changes: 9 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down