Skip to content

Commit

Permalink
feat: optimize mention selector
Browse files Browse the repository at this point in the history
  • Loading branch information
2214962083 committed Sep 10, 2024
1 parent 56b0b8c commit 6860c22
Show file tree
Hide file tree
Showing 19 changed files with 548 additions and 247 deletions.
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
/**
* Represents information about a file.
*/
export interface FileInfo {
/**
* The content of the file.
*/
content: string
import type { FileInfo, FolderInfo } from '@extension/file-utils/traverse-fs'

/**
* The relative path of the file.
*/
relativePath: string
export type { FileInfo, FolderInfo }
// /**
// * Represents information about a file.
// */
// export interface FileInfo {
// /**
// * The content of the file.
// */
// content: string
// /**
// * The relative path of the file.
// */
// relativePath: string

/**
* The full path of the file.
*/
fullPath: string
}
// /**
// * The full path of the file.
// */
// fullPath: string
// }

export interface FolderInfo {
fullPath: string
relativePath: string
}
// export interface FolderInfo {
// fullPath: string
// relativePath: string
// }

export interface ImageInfo {
url: string
Expand Down
120 changes: 120 additions & 0 deletions src/extension/webview-api/controllers/git.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { getWorkspaceFolder } from '@extension/utils'
import simpleGit, { SimpleGit } from 'simple-git'

import type {
GitCommit,
GitDiff
} from '../chat-context-processor/types/chat-context'
import { Controller } from '../types'

export class GitController extends Controller {
readonly name = 'git'

private git: SimpleGit

constructor() {
super()
const workspaceFolder = getWorkspaceFolder()
this.git = simpleGit(workspaceFolder.uri.fsPath)
}

async getHistoryCommits(req: { maxCount?: number }): Promise<GitCommit[]> {
const { maxCount = 50 } = req
const log = await this.git.log({ maxCount })

const commits: GitCommit[] = await Promise.all(
log.all.map(async commit => {
const diff = await this.git.diff([`${commit.hash}^`, commit.hash])
return {
sha: commit.hash,
message: commit.message,
diff: this.parseDiff(diff),
author: commit.author_name,
date: commit.date
}
})
)

return commits
}

async getDiffWithRemoteMainBranch(req: {
file?: string
}): Promise<GitDiff[]> {
const mainBranchName = await this.getMainBranchName()
const { file } = req
let diff: string

if (file) {
diff = await this.git.diff([`origin/${mainBranchName}`, '--', file])
} else {
diff = await this.git.diff([`origin/${mainBranchName}`])
}

return this.parseDiff(diff)
}

async getDiffWithWorkingState(req: { file?: string }): Promise<GitDiff[]> {
const { file } = req
let diff: string

if (file) {
diff = await this.git.diff(['HEAD', '--', file])
} else {
diff = await this.git.diff(['HEAD'])
}

return this.parseDiff(diff)
}

private parseDiff(diff: string): GitDiff[] {
const files = diff.split('diff --git')

const diffs: GitDiff[] = []

files.slice(1).forEach(file => {
const [header, ...chunks] = file.split('@@')
if (!header) return

const [from, to] = header.match(/a\/(.+) b\/(.+)/)?.slice(1) || ['', '']

if (!from || !to) return

diffs.push({
from,
to,
chunks: chunks.map(chunk => {
const [content, ...lines] = chunk.split('\n')
return {
content: `@@ ${content}`,
lines: lines.filter(line => line.trim() !== '')
}
})
})
})

return diffs
}

async getCurrentBranch(): Promise<string> {
return await this.git.revparse(['--abbrev-ref', 'HEAD'])
}

async getStatus(): Promise<any> {
return await this.git.status()
}

async getRemotes(): Promise<any[]> {
const remotes = await this.git.getRemotes(true)
return remotes
}

private async getMainBranchName(): Promise<string> {
const branches = await this.git.branch()
const mainBranch = ['main', 'master', 'trunk', 'development'].find(branch =>
branches.all.includes(branch)
)

return mainBranch || 'main'
}
}
2 changes: 2 additions & 0 deletions src/extension/webview-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as vscode from 'vscode'

import { ChatController } from './controllers/chat.controller'
import { FileController } from './controllers/file.controller'
import { GitController } from './controllers/git.controller'
import { SystemController } from './controllers/system.controller'
import type {
Controller,
Expand Down Expand Up @@ -86,6 +87,7 @@ class APIManager {
export const controllers = [
ChatController,
FileController,
GitController,
SystemController
] as const
export type Controllers = typeof controllers
Expand Down
2 changes: 1 addition & 1 deletion src/extension/webview-api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type ControllerMethod<TReq = any, TRes = any> = (
export abstract class Controller {
abstract readonly name: string;

[key: string]: ControllerMethod | string | undefined
[key: string]: ControllerMethod | string | unknown | undefined
}

export type ControllerClass = new () => Controller
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ export const FileTreeView: React.FC<FileTreeViewProps> = ({
isSelected,
isIndeterminate,
isExpanded,
hasChildren,
onToggleSelect,
onToggleExpand,
level
Expand All @@ -179,7 +178,7 @@ export const FileTreeView: React.FC<FileTreeViewProps> = ({
return (
<div
className={cn(
'flex items-center py-1 text-sm cursor-pointer',
'flex items-center py-1 text-sm cursor-pointer hover:bg-secondary rounded-sm',
visibleIndex === focusedIndex && 'bg-secondary'
)}
style={{ marginLeft: `${level * 20}px` }}
Expand All @@ -199,11 +198,11 @@ export const FileTreeView: React.FC<FileTreeViewProps> = ({
className="mx-1 custom-checkbox"
/>

{hasChildren && <ArrowIcon className="size-4 mr-1" />}
{!item.isLeaf && <ArrowIcon className="size-4 mr-1" />}

<FileIcon
className="size-4 mr-1"
isFolder={hasChildren}
isFolder={!item.isLeaf}
isOpen={isExpanded}
filePath={item.name}
/>
Expand Down
9 changes: 9 additions & 0 deletions src/webview/components/chat/selectors/file-selector/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import {
KeyboardShortcutsInfo,
type ShortcutInfo
Expand Down Expand Up @@ -98,6 +99,14 @@ export const FileSelector: React.FC<FileSelectorProps> = ({

useEvent('keydown', handleKeyDown)

const queryClient = useQueryClient()
useEffect(() => {
if (!isOpen) return
queryClient.invalidateQueries({
queryKey: ['realtime']
})
}, [isOpen, queryClient])

return (
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger asChild>{children}</PopoverTrigger>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,17 @@ export const MentionFilePreview: React.FC<MentionOption> = memo(
)

const renderItem = useCallback(
({
item,
isExpanded,
hasChildren,
onToggleExpand,
level
}: TreeNodeRenderProps) => (
({ item, isExpanded, onToggleExpand, level }: TreeNodeRenderProps) => (
<div
className="flex items-center py-1 text-sm cursor-pointer"
style={{ marginLeft: `${level * 20}px` }}
onClick={onToggleExpand}
>
{hasChildren && <ChevronDownIcon className="size-4 mr-1" />}
{!item.isLeaf && <ChevronDownIcon className="size-4 mr-1" />}

<FileIcon
className="size-4 mr-1"
isFolder={hasChildren}
isFolder={!item.isLeaf}
isOpen={isExpanded}
filePath={item.name}
/>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { memo, useCallback, useMemo } from 'react'
import { ChevronDownIcon, ChevronRightIcon } from '@radix-ui/react-icons'
import { FileIcon } from '@webview/components/file-icon'
import { Tree, type TreeNodeRenderProps } from '@webview/components/tree'
import { useFilesTreeItems } from '@webview/hooks/chat/use-files-tree-items'
import type { FolderInfo, MentionOption } from '@webview/types/chat'

export const MentionFolderPreview: React.FC<MentionOption> = memo(
mentionOption => {
const folderInfo = mentionOption.data as FolderInfo
const { treeItems, traverseTree } = useFilesTreeItems({
files: [folderInfo]
})

const allExpandedIds = useMemo(() => {
const ids: string[] = []

traverseTree(item => {
if (item.children?.length) {
ids.push(item.id)
}
})

return ids
}, [traverseTree])

const renderItem = useCallback(
({ item, isExpanded, onToggleExpand, level }: TreeNodeRenderProps) => {
const ArrowIcon = isExpanded ? ChevronDownIcon : ChevronRightIcon

return (
<div
className="flex items-center py-1 text-sm cursor-pointer"
style={{ marginLeft: `${level * 20}px` }}
onClick={onToggleExpand}
>
{!item.isLeaf && <ArrowIcon className="size-4 mr-1" />}

<FileIcon
className="size-4 mr-1"
isFolder={!item.isLeaf}
isOpen={isExpanded}
filePath={item.name}
/>

<span>{item.name}</span>
</div>
)
},
[]
)

return (
<div className="flex flex-col h-full">
<div className="flex flex-col flex-1 overflow-auto">
<Tree
items={treeItems}
expandedItemIds={allExpandedIds}
renderItem={renderItem}
/>
</div>
</div>
)
}
)
Loading

0 comments on commit 6860c22

Please sign in to comment.