@@ -20,6 +20,7 @@ import { formatError } from './common/utils';
2020import { EXTENSION_ID } from './constants' ;
2121import { CrossChatSessionWithPR } from './github/copilotApi' ;
2222import { CopilotRemoteAgentManager , SessionIdForPr } from './github/copilotRemoteAgent' ;
23+ import { guessExtensionFromMime , pickFilesForUpload , placeholdersForNames , runFileUploads , runPendingUploads } from './github/fileUpload' ;
2324import { FolderRepositoryManager } from './github/folderRepositoryManager' ;
2425import { GitHubRepository } from './github/githubRepository' ;
2526import { Issue } from './github/interface' ;
@@ -1379,6 +1380,167 @@ ${contents}
13791380 } )
13801381 ) ;
13811382
1383+ context . subscriptions . push (
1384+ vscode . commands . registerCommand ( 'pr.uploadFile' , async ( reply : CommentReply | GHPRComment | undefined ) => {
1385+ /* __GDPR__
1386+ "pr.uploadFile" : {}
1387+ */
1388+ telemetry . sendTelemetryEvent ( 'pr.uploadFile' ) ;
1389+
1390+ let potentialThread : GHPRCommentThread | undefined ;
1391+ if ( reply === undefined ) {
1392+ potentialThread = findActiveHandler ( ) ?. commentController . activeCommentThread as vscode . CommentThread2 as GHPRCommentThread | undefined ;
1393+ } else {
1394+ potentialThread = reply instanceof GHPRComment ? reply . parent : reply ?. thread ;
1395+ }
1396+
1397+ if ( ! potentialThread ) {
1398+ return ;
1399+ }
1400+ const thread = potentialThread ;
1401+
1402+ const commentEditor = vscode . window . activeTextEditor ?. document . uri . scheme === Schemes . Comment ? vscode . window . activeTextEditor
1403+ : vscode . window . visibleTextEditors . find ( visible => ( visible . document . uri . scheme === Schemes . Comment ) && ( visible . document . uri . query === '' ) ) ;
1404+ if ( ! commentEditor ) {
1405+ Logger . error ( 'No comment editor visible for uploading a file.' , logId ) ;
1406+ vscode . window . showErrorMessage ( vscode . l10n . t ( 'No available comment editor to upload a file in.' ) ) ;
1407+ return ;
1408+ }
1409+ const commentEditorUri = commentEditor . document . uri . toString ( ) ;
1410+
1411+ const folderManager = reposManager . getManagerForFile ( thread . uri ) ;
1412+ const githubRepository = folderManager ?. activePullRequest ?. githubRepository
1413+ ?? folderManager ?. gitHubRepositories [ 0 ] ;
1414+ if ( ! githubRepository ) {
1415+ vscode . window . showErrorMessage ( vscode . l10n . t ( 'Cannot upload files: no GitHub repository found for this comment.' ) ) ;
1416+ return ;
1417+ }
1418+
1419+ const uploads = await pickFilesForUpload ( ) ;
1420+ if ( ! uploads ) {
1421+ return ;
1422+ }
1423+
1424+ // Insert placeholders at the current cursor position
1425+ const placeholdersText = uploads . map ( u => u . placeholder ) . join ( '\n' ) ;
1426+ const cursor = commentEditor . selection . end ;
1427+ const before = commentEditor . document . getText ( new vscode . Range ( new vscode . Position ( 0 , 0 ) , cursor ) ) ;
1428+ const separator = before . length > 0 && ! before . endsWith ( '\n' ) ? '\n' : '' ;
1429+ await commentEditor . edit ( editBuilder => {
1430+ editBuilder . insert ( cursor , `${ separator } ${ placeholdersText } \n` ) ;
1431+ } ) ;
1432+
1433+ const replacePlaceholder = async ( placeholder : string , replacement : string ) => {
1434+ const editor = vscode . window . visibleTextEditors . find ( e => e . document . uri . toString ( ) === commentEditorUri ) ;
1435+ if ( ! editor ) {
1436+ return ;
1437+ }
1438+ const text = editor . document . getText ( ) ;
1439+ const idx = text . indexOf ( placeholder ) ;
1440+ if ( idx < 0 ) {
1441+ return ;
1442+ }
1443+ const start = editor . document . positionAt ( idx ) ;
1444+ const end = editor . document . positionAt ( idx + placeholder . length ) ;
1445+ await editor . edit ( editBuilder => {
1446+ editBuilder . replace ( new vscode . Range ( start , end ) , replacement ) ;
1447+ } ) ;
1448+ } ;
1449+
1450+ runFileUploads (
1451+ githubRepository ,
1452+ uploads ,
1453+ logId ,
1454+ ( placeholder , _name , markdown ) => replacePlaceholder ( placeholder , markdown ) ,
1455+ ( placeholder , name , error ) => {
1456+ vscode . window . showErrorMessage ( vscode . l10n . t ( 'Failed to upload {0}: {1}' , name , error ) ) ;
1457+ return replacePlaceholder ( placeholder , '' ) ;
1458+ } ,
1459+ ) ;
1460+ } )
1461+ ) ;
1462+
1463+ context . subscriptions . push (
1464+ vscode . languages . registerDocumentPasteEditProvider (
1465+ { scheme : Schemes . Comment } ,
1466+ {
1467+ async provideDocumentPasteEdits ( document , ranges , dataTransfer , _context , token ) {
1468+ const files : { name : string ; getBytes : ( ) => Thenable < Uint8Array > } [ ] = [ ] ;
1469+ let counter = 0 ;
1470+ for ( const [ mime , item ] of dataTransfer ) {
1471+ const file = item . asFile ( ) ;
1472+ if ( ! file ) {
1473+ continue ;
1474+ }
1475+ const name = file . name || `pasted-file-${ ++ counter } ${ guessExtensionFromMime ( mime ) } ` ;
1476+ files . push ( { name, getBytes : ( ) => file . data ( ) } ) ;
1477+ }
1478+ if ( files . length === 0 || token . isCancellationRequested ) {
1479+ return ;
1480+ }
1481+
1482+ const potentialThread = findActiveHandler ( ) ?. commentController . activeCommentThread as vscode . CommentThread2 as GHPRCommentThread | undefined ;
1483+ if ( ! potentialThread ) {
1484+ return ;
1485+ }
1486+ const folderManager = reposManager . getManagerForFile ( potentialThread . uri ) ;
1487+ const githubRepository = folderManager ?. activePullRequest ?. githubRepository
1488+ ?? folderManager ?. gitHubRepositories [ 0 ] ;
1489+ if ( ! githubRepository ) {
1490+ return ;
1491+ }
1492+
1493+ const placeholders = placeholdersForNames ( files . map ( f => f . name ) ) ;
1494+ const placeholdersText = placeholders . map ( p => p . placeholder ) . join ( '\n' ) ;
1495+
1496+ const documentUri = document . uri . toString ( ) ;
1497+ const replacePlaceholder = async ( placeholder : string , replacement : string ) => {
1498+ const editor = vscode . window . visibleTextEditors . find ( e => e . document . uri . toString ( ) === documentUri ) ;
1499+ if ( ! editor ) {
1500+ return ;
1501+ }
1502+ const text = editor . document . getText ( ) ;
1503+ const idx = text . indexOf ( placeholder ) ;
1504+ if ( idx < 0 ) {
1505+ return ;
1506+ }
1507+ const start = editor . document . positionAt ( idx ) ;
1508+ const end = editor . document . positionAt ( idx + placeholder . length ) ;
1509+ await editor . edit ( editBuilder => {
1510+ editBuilder . replace ( new vscode . Range ( start , end ) , replacement ) ;
1511+ } ) ;
1512+ } ;
1513+
1514+ runPendingUploads (
1515+ githubRepository ,
1516+ files . map ( ( f , i ) => ( {
1517+ name : placeholders [ i ] . name ,
1518+ placeholder : placeholders [ i ] . placeholder ,
1519+ getBytes : f . getBytes ,
1520+ } ) ) ,
1521+ logId ,
1522+ ( placeholder , _name , markdown ) => replacePlaceholder ( placeholder , markdown ) ,
1523+ ( placeholder , name , error ) => {
1524+ vscode . window . showErrorMessage ( vscode . l10n . t ( 'Failed to upload {0}: {1}' , name , error ) ) ;
1525+ return replacePlaceholder ( placeholder , '' ) ;
1526+ } ,
1527+ ) ;
1528+
1529+ const edit = new vscode . DocumentPasteEdit (
1530+ placeholdersText ,
1531+ vscode . l10n . t ( 'Upload as GitHub attachment' ) ,
1532+ vscode . DocumentDropOrPasteEditKind . Empty . append ( 'github' , 'attachment' ) ,
1533+ ) ;
1534+ return [ edit ] ;
1535+ } ,
1536+ } ,
1537+ {
1538+ providedPasteEditKinds : [ vscode . DocumentDropOrPasteEditKind . Empty . append ( 'github' , 'attachment' ) ] ,
1539+ pasteMimeTypes : [ 'files' , 'image/*' ] ,
1540+ } ,
1541+ ) ,
1542+ ) ;
1543+
13821544 context . subscriptions . push (
13831545 vscode . commands . registerCommand ( 'pr.editComment' , async ( comment : GHPRComment | TemporaryComment ) => {
13841546 /* __GDPR__
0 commit comments