@@ -3,7 +3,7 @@ import './App.css'
33import { ChevronsDown , ChevronsUp , CheckSquare , Square , Sun , Moon , Folder , FolderGit2 , ListChecks , Copy , ArrowLeftRight } from 'lucide-react'
44import { FileTreeView , PreviewModal , GitHubStarIconButton , BugIconButton } from '@gitcontext/ui'
55import { type FileDiffStatus , isBinaryPath , MAX_CONCURRENT_READS } from '@gitcontext/core'
6- import { writeText } from '@tauri-apps/plugin-clipboard-manager'
6+ import { readText , writeText } from '@tauri-apps/plugin-clipboard-manager'
77import { useGitRepository } from './hooks/useGitRepository'
88import { useFileTree } from './hooks/useFileTree'
99import { SelectedFilesPanel } from './components/SelectedFilesPanel'
@@ -21,6 +21,12 @@ import { TokenCountsProvider } from './context/TokenCountsContext'
2121import { mapWithConcurrency } from './utils/concurrency'
2222import { logError } from './utils/logger'
2323import { debounce } from './utils/debounce'
24+ import {
25+ INVALID_CLIPBOARD_FORMAT_MESSAGE ,
26+ NO_MATCHING_FILES_MESSAGE ,
27+ parseClipboardPathLines ,
28+ resolveSelectablePaths ,
29+ } from './utils/clipboardBatchSelect'
2430
2531function AppContent ( ) {
2632 const [ , setAppStatus ] = useState < AppStatus > ( { state : 'IDLE' } )
@@ -152,6 +158,7 @@ function AppContent() {
152158 collapseAll,
153159 selectAll,
154160 deselectAll,
161+ addSelectedPaths,
155162 revealPath,
156163 } = useFileTree ( setAppStatus )
157164
@@ -356,6 +363,37 @@ function AppContent() {
356363 }
357364 } , [ gitClient , baseBranch , compareBranch , selectedPaths , diffContextLines , statusByPath , userInstructions , fileTree , includeFileTree , showChangedOnly , currentDir ] )
358365
366+ const handleBatchSelectFromClipboard = useCallback ( async ( ) => {
367+ if ( ! currentDir || ! fileTree ) return
368+
369+ try {
370+ const clipboardText = await readText ( )
371+ const lines = parseClipboardPathLines ( clipboardText )
372+ if ( lines . length === 0 ) {
373+ setErrorMessage ( INVALID_CLIPBOARD_FORMAT_MESSAGE )
374+ return
375+ }
376+
377+ const selectableSet = new Set ( statusByPath . keys ( ) )
378+ const { matched, invalidCount, outsideRepoCount } = resolveSelectablePaths ( lines , currentDir , selectableSet )
379+
380+ if ( matched . length === 0 ) {
381+ if ( invalidCount === lines . length && outsideRepoCount === 0 ) {
382+ setErrorMessage ( INVALID_CLIPBOARD_FORMAT_MESSAGE )
383+ } else {
384+ setErrorMessage ( NO_MATCHING_FILES_MESSAGE )
385+ }
386+ return
387+ }
388+
389+ addSelectedPaths ( matched )
390+ setErrorMessage ( null )
391+ } catch ( err ) {
392+ logError ( 'batchSelectFromClipboard' , err )
393+ setErrorMessage ( 'Failed to read clipboard content.' )
394+ }
395+ } , [ currentDir , fileTree , statusByPath , addSelectedPaths ] )
396+
359397 // Calculate file tree tokens
360398 useEffect ( ( ) => {
361399 if ( ! includeFileTree || ! fileTree || selectedPaths . size === 0 ) {
@@ -539,6 +577,7 @@ function AppContent() {
539577 < button onClick = { collapseAll } className = "btn btn-ghost btn-icon" title = "Collapse All" disabled = { ! fileTree } > < ChevronsUp size = { 14 } /> </ button >
540578 < button onClick = { ( ) => selectAll ( treeFilter ) } className = "btn btn-ghost btn-icon" title = "Select All" disabled = { ! fileTree } > < CheckSquare size = { 14 } /> </ button >
541579 < button onClick = { ( ) => deselectAll ( treeFilter ) } className = "btn btn-ghost btn-icon" title = "Deselect All" disabled = { ! fileTree } > < Square size = { 14 } /> </ button >
580+ < button onClick = { ( ) => void handleBatchSelectFromClipboard ( ) } className = "btn btn-ghost btn-icon" title = "Batch Select from Clipboard" disabled = { ! fileTree || ! currentDir } > < ListChecks size = { 14 } /> </ button >
542581 </ div >
543582 < label className = "tree-filter-checkbox" >
544583 < input type = "checkbox" checked = { showChangedOnly } onChange = { ( e ) => setShowChangedOnly ( e . target . checked ) } />
0 commit comments