diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/parts/search/browser/searchActions.ts index 94a6efb8886bf5f7fba446a30083b180409f9e5e..8d421e4391b3881fcfbc7f054497ac9580c3ec17 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/parts/search/browser/searchActions.ts @@ -18,7 +18,7 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi import { ResolvedKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { OS } from 'vs/base/common/platform'; +import { OS, isWindows } from 'vs/base/common/platform'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { VIEW_ID } from 'vs/platform/search/common/search'; @@ -635,10 +635,62 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { } } +function fileMatchUriToString(fileMatch: FileMatch): string { + const resource = fileMatch.resource(); + return resource.scheme === Schemas.file ? getPathLabel(resource) : resource.toString(); +} + export const copyPathCommand: ICommandHandler = (accessor, fileMatch: FileMatch) => { const clipboardService = accessor.get(IClipboardService); - const resource = fileMatch.resource(); - const text = resource.scheme === Schemas.file ? getPathLabel(resource) : resource.toString(); + const text = fileMatchUriToString(fileMatch); clipboardService.writeText(text); }; + +function matchToString(match: Match): string { + return `${match.range().startLineNumber},${match.range().startColumn}: ${match.text()}`; +} + +const lineDelimiter = isWindows ? '\r\n' : '\n'; +function fileMatchToString(fileMatch: FileMatch, maxMatches: number): { text: string, count: number } { + const matchTextRows = fileMatch.matches() + .slice(0, maxMatches) + .map(matchToString) + .map(matchText => ' ' + matchText); + + return { + text: `${fileMatchUriToString(fileMatch)}${lineDelimiter}${matchTextRows.join(lineDelimiter)}`, + count: matchTextRows.length + }; +} + +function folderMatchToString(folderMatch: FolderMatch, maxMatches: number): string { + const fileResults: string[] = []; + let numMatches = 0; + + for (let i = 0; i < folderMatch.fileCount() && numMatches < maxMatches; i++) { + const fileResult = fileMatchToString(folderMatch.matches()[i], maxMatches - numMatches); + numMatches += fileResult.count; + fileResults.push(fileResult.text); + } + + return fileResults.join(lineDelimiter + lineDelimiter); +} + +const maxClipboardMatches = 1e4; +export const copyMatchCommand: ICommandHandler = (accessor, match: RenderableMatch) => { + const clipboardService = accessor.get(IClipboardService); + + let text: string; + if (match instanceof Match) { + text = matchToString(match); + } else if (match instanceof FileMatch) { + text = fileMatchToString(match, maxClipboardMatches).text; + } else if (match instanceof FolderMatch) { + text = folderMatchToString(match, maxClipboardMatches); + } + + if (text) { + clipboardService.writeText(text); + } +}; diff --git a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts index 1715c84ce87a46d6c85e22a2a92b1e5d726bf588..8443056e9c30cedc0dd1fff3fd2a3aaf2637917b 100644 --- a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts @@ -53,7 +53,7 @@ import { getMultiSelectedResources } from 'vs/workbench/parts/files/browser/file import { Schemas } from 'vs/base/common/network'; import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { openSearchView, getSearchView, ReplaceAllInFolderAction, ReplaceAllAction, CloseReplaceAction, FocusNextInputAction, FocusPreviousInputAction, FocusNextSearchResultAction, FocusPreviousSearchResultAction, ReplaceInFilesAction, FindInFilesAction, FocusActiveEditorCommand, toggleCaseSensitiveCommand, ShowNextSearchTermAction, ShowPreviousSearchTermAction, toggleRegexCommand, ShowPreviousSearchIncludeAction, ShowNextSearchIncludeAction, CollapseDeepestExpandedLevelAction, toggleWholeWordCommand, RemoveAction, ReplaceAction, ClearSearchResultsAction, copyPathCommand } from 'vs/workbench/parts/search/browser/searchActions'; +import { openSearchView, getSearchView, ReplaceAllInFolderAction, ReplaceAllAction, CloseReplaceAction, FocusNextInputAction, FocusPreviousInputAction, FocusNextSearchResultAction, FocusPreviousSearchResultAction, ReplaceInFilesAction, FindInFilesAction, FocusActiveEditorCommand, toggleCaseSensitiveCommand, ShowNextSearchTermAction, ShowPreviousSearchTermAction, toggleRegexCommand, ShowPreviousSearchIncludeAction, ShowNextSearchIncludeAction, CollapseDeepestExpandedLevelAction, toggleWholeWordCommand, RemoveAction, ReplaceAction, ClearSearchResultsAction, copyPathCommand, copyMatchCommand } from 'vs/workbench/parts/search/browser/searchActions'; import { VIEW_ID, ISearchConfigurationProperties } from 'vs/platform/search/common/search'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -235,6 +235,24 @@ MenuRegistry.appendMenuItem(MenuId.SearchContext, { order: 2 }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: Constants.CopyMatchCommandId, + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: Constants.FileMatchOrMatchFocusKey, + primary: KeyMod.CtrlCmd | KeyCode.KEY_C, + handler: copyMatchCommand +}); + +MenuRegistry.appendMenuItem(MenuId.SearchContext, { + command: { + id: Constants.CopyMatchCommandId, + title: nls.localize('copyMatchLabel', "Copy") + }, + when: Constants.FileMatchOrMatchFocusKey, + group: 'search_2', + order: 3 +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: Constants.CopyPathCommandId, weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), @@ -252,8 +270,8 @@ MenuRegistry.appendMenuItem(MenuId.SearchContext, { title: nls.localize('copyPathLabel', "Copy Path") }, when: Constants.FileFocusKey, - group: 'search', - order: 3 + group: 'search_2', + order: 4 }); CommandsRegistry.registerCommand({ @@ -274,7 +292,7 @@ MenuRegistry.appendMenuItem(MenuId.SearchContext, { title: toggleSearchViewPositionLabel }, when: Constants.SearchViewVisibleKey, - group: 'search_2', + group: 'search_9', order: 1 });