Skip to content

Commit

Permalink
saerch files
Browse files Browse the repository at this point in the history
  • Loading branch information
liangfung committed Mar 28, 2024
1 parent c609349 commit 6b68e4d
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 72 deletions.
130 changes: 82 additions & 48 deletions ee/tabby-ui/app/files/components/file-tree-header.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
'use client'

import React, { useContext } from 'react'
import { useQuery } from 'urql'

import { graphql } from '@/lib/gql/generates'
import { useDebounceCallback } from '@/lib/hooks/use-debounce'
import useRouterStuff from '@/lib/hooks/use-router-stuff'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import {
Expand All @@ -27,12 +28,15 @@ import {
SelectValue
} from '@/components/ui/select'

import { SourceCodeBrowserContext } from './source-code-browser'
import { resolveRepoNameFromPath } from './utils'
import { graphql } from '@/lib/gql/generates'
import { useQuery } from 'urql'
import { SourceCodeBrowserContext, TFileMap } from './source-code-browser'
import {
fetchEntriesFromPath,
getDirectoriesFromBasename,
resolveFileNameFromPath,
resolveRepoNameFromPath
} from './utils'

interface FileTreeHeaderProps extends React.HTMLAttributes<HTMLDivElement> { }
interface FileTreeHeaderProps extends React.HTMLAttributes<HTMLDivElement> {}

type SearchOption = { path: string; type: string; id: string }

Expand All @@ -49,87 +53,111 @@ const FileTreeHeader: React.FC<FileTreeHeaderProps> = ({
className,
...props
}) => {
const { activePath, fileTreeData, setActivePath, initialized } = useContext(
SourceCodeBrowserContext
)
const { updateSearchParams } = useRouterStuff()
const {
activePath,
fileTreeData,
setActivePath,
initialized,
updateFileMap,
setExpandedKeys
} = useContext(SourceCodeBrowserContext)
const curerntRepoName = resolveRepoNameFromPath(activePath)

const inputRef = React.useRef<HTMLInputElement>(null)
const ignoreFetchResultRef = React.useRef(false)
const [input, setInput] = React.useState<string>()
const [searchFileter, setSearchFilter] = React.useState<string>()
const [repositorySearchFilter, setRepositorySearchFilter] =
React.useState<string>()
const [options, setOptions] = React.useState<Array<SearchOption>>()
const [optionsVisible, setOptionsVisible] = React.useState(false)

const repoName = resolveRepoNameFromPath(activePath)

const noIndexedRepo = initialized && !fileTreeData?.length


const [{ data }] = useQuery({
const [{ data: repositorySearchData }] = useQuery({
query: repositorySearch,
// todo
pause: !searchFileter,
variables: { repository: curerntRepoName, filter: searchFileter, topN: 20 }
variables: {
repository: curerntRepoName,
filter: repositorySearchFilter,
topN: 20
},
pause: !repositorySearchFilter
})

React.useEffect(() => {
const _options = data?.repositorySearch?.map(option => ({
...option,
id: option.path
})) ?? []
const _options =
repositorySearchData?.repositorySearch?.map(option => ({
...option,
id: option.path
})) ?? []
setOptions(_options)
setOptionsVisible(!!_options?.length)
}, [data?.repositorySearch])

}, [repositorySearchData?.repositorySearch])

const onSelectRepo = (name: string) => {
setActivePath(name)
}

const memoizedMatchedIndices = React.useMemo(() => {
return options?.map(option =>
searchFileter ? getMatchedIndices(searchFileter, option.path) : []
repositorySearchFilter
? getMatchedIndices(repositorySearchFilter, option.path)
: []
)
}, [options, searchFileter])
}, [options, repositorySearchFilter])

const onInputValueChange = useDebounceCallback((v: string | undefined) => {
if (!v) {
ignoreFetchResultRef.current = true
setSearchFilter('')
setRepositorySearchFilter('')
setOptionsVisible(false)
} else {
ignoreFetchResultRef.current = false
setSearchFilter(v)
// if (v === 'test-not-found') {
// ignoreFetchResultRef.current = true
// setOptions([])
// setOptionsVisible(true)
// }
// setTimeout(() => {
// if (!ignoreFetchResultRef.current) {
// let ops: SearchOption[] = [
// { entry: 'test1', type: 'file', id: 'test1' },
// { entry: 'test2', type: 'file', id: 'test2' },
// { entry: 'path/to/test', type: 'dir', id: 'path/to/test' }
// ]
// setSearchFilter(v)
// setOptions(ops)
// setOptionsVisible(true)
// }
// }, 100)
setRepositorySearchFilter(v)
}
}, 300)
}, 500)

const onClearInput = () => {
onInputValueChange.run('')
onInputValueChange.flush()
}

const onSelectFile = (value: SearchOption) => {
// todo fetch dirs and then update activePath, or implement this logic in code borwser fetcher
console.log(value)
const onSelectFile = async (value: SearchOption) => {
const path = value.path
if (!path) return

const fullPath = `${repoName}/${path}`
const entries = await fetchEntriesFromPath(fullPath)
const initialExpandedDirs = getDirectoriesFromBasename(path)

const patchMap: TFileMap = {}
// fetch dirs
for (const entry of entries) {
const path = `${repoName}/${entry.basename}`
patchMap[path] = {
file: entry,
name: resolveFileNameFromPath(path),
fullPath: path,
treeExpanded: initialExpandedDirs.includes(entry.basename)
}
}
const expandedKeys = initialExpandedDirs.map(dir =>
[repoName, dir].filter(Boolean).join('/')
)
if (patchMap) {
updateFileMap(patchMap)
}
if (expandedKeys?.length) {
setExpandedKeys(prevKeys => {
const newSet = new Set(prevKeys)
for (const k of expandedKeys) {
newSet.add(k)
}
return newSet
})
}
setActivePath(fullPath)
}

// shortcut 't'
Expand Down Expand Up @@ -210,10 +238,16 @@ const FileTreeHeader: React.FC<FileTreeHeaderProps> = ({
<div className="relative">
<ComboboxInput
className="pr-8"
placeholder="Go to file"
// placeholder="Go to file"
placeholder={
repoName
? 'Go to file'
: 'Go to file (pick a repository first)'
}
spellCheck={false}
value={input}
ref={inputRef}
disabled={!repoName}
onChange={e => {
let value = e.target.value
setInput(value)
Expand Down
26 changes: 3 additions & 23 deletions ee/tabby-ui/app/files/components/source-code-browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { FileTreePanel } from './file-tree-panel'
import { RawFileView } from './raw-file-view'
import { TextFileView } from './text-file-view'
import {
fetchEntriesFromPath,
getDirectoriesFromBasename,
resolveBasenameFromPath,
resolveFileNameFromPath,
Expand Down Expand Up @@ -261,7 +262,7 @@ const SourceCodeBrowserRenderer: React.FC<SourceCodeBrowserProps> = ({

React.useEffect(() => {
const init = async () => {
const { patchMap, expandedKeys } = await getInitialFileMap(activePath)
const { patchMap, expandedKeys } = await getInitialFileData(activePath)
if (patchMap) {
updateFileMap(patchMap)
}
Expand Down Expand Up @@ -364,7 +365,7 @@ const SourceCodeBrowser: React.FC<SourceCodeBrowserProps> = props => {
)
}

async function getInitialFileMap(path?: string) {
async function getInitialFileData(path?: string) {
const initialRepositoryName = resolveRepoNameFromPath(path)
const initialBasename = resolveBasenameFromPath(path)

Expand Down Expand Up @@ -442,27 +443,6 @@ async function getInitialFileMap(path?: string) {
}
}

async function fetchEntriesFromPath(path: string | undefined) {
if (!path) return []
const repoName = resolveRepoNameFromPath(path)
const basename = resolveBasenameFromPath(path)
// array of dir basename that do not include the repo name.
const directoryPaths = getDirectoriesFromBasename(basename)
// fetch all dirs from path
const requests: Array<() => Promise<ResolveEntriesResponse>> =
directoryPaths.map(
dir => () => fetcher(`/repositories/${repoName}/resolve/${dir}`).catch(e => [])
)
const entries = await Promise.all(requests.map(fn => fn()))
let result: TFile[] = []
for (let entry of entries) {
if (entry?.entries?.length) {
result = [...result, ...entry.entries]
}
}
return result
}

function isReadableTextFile(blob: Blob) {
return new Promise((resolve, reject) => {
const chunkSize = 1024
Expand Down
28 changes: 27 additions & 1 deletion ee/tabby-ui/app/files/components/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { isNil } from 'lodash-es'

import fetcher from '@/lib/tabby/fetcher'
import { ResolveEntriesResponse, TFile } from '@/lib/types'

function resolveRepoNameFromPath(path: string | undefined) {
if (!path) return ''
return path.split('/')?.[0]
Expand Down Expand Up @@ -29,9 +32,32 @@ function getDirectoriesFromBasename(path: string, isDir?: boolean): string[] {
return result
}

async function fetchEntriesFromPath(path: string | undefined) {
if (!path) return []
const repoName = resolveRepoNameFromPath(path)
const basename = resolveBasenameFromPath(path)
// array of dir basename that do not include the repo name.
const directoryPaths = getDirectoriesFromBasename(basename)
// fetch all dirs from path
const requests: Array<() => Promise<ResolveEntriesResponse>> =
directoryPaths.map(
dir => () =>
fetcher(`/repositories/${repoName}/resolve/${dir}`).catch(e => [])
)
const entries = await Promise.all(requests.map(fn => fn()))
let result: TFile[] = []
for (let entry of entries) {
if (entry?.entries?.length) {
result = [...result, ...entry.entries]
}
}
return result
}

export {
resolveRepoNameFromPath,
resolveBasenameFromPath,
resolveFileNameFromPath,
getDirectoriesFromBasename
getDirectoriesFromBasename,
fetchEntriesFromPath
}

0 comments on commit 6b68e4d

Please sign in to comment.