提交 3c4de451 编写于 作者: B Benjamin Pasero

ux - distinguish folders from workspaces when opening (#77718)

上级 4f4ba928
......@@ -6,7 +6,7 @@
import { localize } from 'vs/nls';
import { coalesce } from 'vs/base/common/arrays';
import { IStateService } from 'vs/platform/state/node/state';
import { app, JumpListCategory } from 'electron';
import { app, JumpListCategory, JumpListItem } from 'electron';
import { ILogService } from 'vs/platform/log/common/log';
import { getBaseLabel, getPathLabel, splitName } from 'vs/base/common/labels';
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
......@@ -350,34 +350,40 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
this.removeRecentlyOpened(toRemove);
// Add entries
jumpList.push({
type: 'custom',
name: localize('recentFolders', "Recent Workspaces"),
items: coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(recent => {
const workspace = isRecentWorkspace(recent) ? recent.workspace : recent.folderUri;
const title = recent.label ? splitName(recent.label).name : this.getSimpleWorkspaceLabel(workspace, this.environmentService.untitledWorkspacesHome);
let description;
let args;
if (isSingleFolderWorkspaceIdentifier(workspace)) {
description = localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(dirname(workspace), this.environmentService));
args = `--folder-uri "${workspace.toString()}"`;
} else {
description = localize('workspaceDesc', "{0} {1}", getBaseLabel(workspace.configPath), getPathLabel(dirname(workspace.configPath), this.environmentService));
args = `--file-uri "${workspace.configPath.toString()}"`;
}
let hasWorkspaces = false;
const items: JumpListItem[] = coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(recent => {
const workspace = isRecentWorkspace(recent) ? recent.workspace : recent.folderUri;
const title = recent.label ? splitName(recent.label).name : this.getSimpleWorkspaceLabel(workspace, this.environmentService.untitledWorkspacesHome);
let description;
let args;
if (isSingleFolderWorkspaceIdentifier(workspace)) {
description = localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(dirname(workspace), this.environmentService));
args = `--folder-uri "${workspace.toString()}"`;
} else {
hasWorkspaces = true;
description = localize('workspaceDesc', "{0} {1}", getBaseLabel(workspace.configPath), getPathLabel(dirname(workspace.configPath), this.environmentService));
args = `--file-uri "${workspace.configPath.toString()}"`;
}
return {
type: 'task',
title: title.substr(0, 255), // Windows seems to be picky around the length of entries
description: description.substr(0, 255), // (see https://github.com/microsoft/vscode/issues/111177)
program: process.execPath,
args,
iconPath: 'explorer.exe', // simulate folder icon
iconIndex: 0
};
}))
});
return {
type: 'task',
title: title.substr(0, 255), // Windows seems to be picky around the length of entries
description: description.substr(0, 255), // (see https://github.com/microsoft/vscode/issues/111177)
program: process.execPath,
args,
iconPath: 'explorer.exe', // simulate folder icon
iconIndex: 0
};
}));
if (items.length > 0) {
jumpList.push({
type: 'custom',
name: hasWorkspaces ? localize('recentFoldersAndWorkspaces', "Recent Folders & Workspaces") : localize('recentFolders', "Recent Folders"),
items
});
}
}
// Recent
......
......@@ -49,12 +49,17 @@ abstract class BaseOpenRecentAction extends Action {
tooltip: nls.localize('remove', "Remove from Recently Opened")
};
private readonly dirtyRecentlyOpened: IQuickInputButton = {
private readonly dirtyRecentlyOpenedFolder: IQuickInputButton = {
iconClass: 'dirty-workspace ' + Codicon.closeDirty.classNames,
tooltip: nls.localize('dirtyRecentlyOpened', "Workspace With Dirty Files"),
tooltip: nls.localize('dirtyRecentlyOpenedFolder', "Folder With Unsaved Files"),
alwaysVisible: true
};
private readonly dirtyRecentlyOpenedWorkspace: IQuickInputButton = {
...this.dirtyRecentlyOpenedFolder,
tooltip: nls.localize('dirtyRecentlyOpenedWorkspace', "Workspace With Unsaved Files"),
};
constructor(
id: string,
label: string,
......@@ -77,7 +82,9 @@ abstract class BaseOpenRecentAction extends Action {
const recentlyOpened = await this.workspacesService.getRecentlyOpened();
const dirtyWorkspacesAndFolders = await this.workspacesService.getDirtyWorkspaces();
// Identify all folders and workspaces with dirty files
let hasWorkspaces = false;
// Identify all folders and workspaces with unsaved files
const dirtyFolders = new ResourceMap<boolean>();
const dirtyWorkspaces = new ResourceMap<IWorkspaceIdentifier>();
for (const dirtyWorkspace of dirtyWorkspacesAndFolders) {
......@@ -85,6 +92,7 @@ abstract class BaseOpenRecentAction extends Action {
dirtyFolders.set(dirtyWorkspace, true);
} else {
dirtyWorkspaces.set(dirtyWorkspace.configPath, dirtyWorkspace);
hasWorkspaces = true;
}
}
......@@ -96,6 +104,7 @@ abstract class BaseOpenRecentAction extends Action {
recentFolders.set(recent.folderUri, true);
} else {
recentWorkspaces.set(recent.workspace.configPath, recent.workspace);
hasWorkspaces = true;
}
}
......@@ -124,7 +133,7 @@ abstract class BaseOpenRecentAction extends Action {
let keyMods: IKeyMods | undefined;
const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('workspaces', "workspaces") };
const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: hasWorkspaces ? nls.localize('workspacesAndFolders', "folders & workspaces") : nls.localize('folders', "folders") };
const fileSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('files', "files") };
const picks = [workspaceSeparator, ...workspacePicks, fileSeparator, ...filePicks];
......@@ -143,13 +152,14 @@ abstract class BaseOpenRecentAction extends Action {
context.removeItem();
}
// Dirty Workspace
else if (context.button === this.dirtyRecentlyOpened) {
// Dirty Folder/Workspace
else if (context.button === this.dirtyRecentlyOpenedFolder || context.button === this.dirtyRecentlyOpenedWorkspace) {
const isDirtyWorkspace = context.button === this.dirtyRecentlyOpenedWorkspace;
const result = await this.dialogService.confirm({
type: 'question',
title: nls.localize('dirtyWorkspace', "Workspace with Dirty Files"),
message: nls.localize('dirtyWorkspaceConfirm', "Do you want to open the workspace to review the dirty files?"),
detail: nls.localize('dirtyWorkspaceConfirmDetail', "Workspaces with dirty files cannot be removed until all dirty files have been saved or reverted.")
title: isDirtyWorkspace ? nls.localize('dirtyWorkspace', "Workspace with Unsaved Files") : nls.localize('dirtyFolder', "Folder with Unsaved Files"),
message: isDirtyWorkspace ? nls.localize('dirtyWorkspaceConfirm', "Do you want to open the workspace to review the unsaved files?") : nls.localize('dirtyFolderConfirm', "Do you want to open the folder to review the unsaved files?"),
detail: isDirtyWorkspace ? nls.localize('dirtyWorkspaceConfirmDetail', "Workspaces with unsaved files cannot be removed until all unsaved files have been saved or reverted.") : nls.localize('dirtyFolderConfirmDetail', "Folders with unsaved files cannot be removed until all unsaved files have been saved or reverted.")
});
if (result.confirmed) {
......@@ -170,6 +180,7 @@ abstract class BaseOpenRecentAction extends Action {
let iconClasses: string[];
let fullLabel: string | undefined;
let resource: URI | undefined;
let isWorkspace = false;
// Folder
if (isRecentFolder(recent)) {
......@@ -185,6 +196,7 @@ abstract class BaseOpenRecentAction extends Action {
iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.ROOT_FOLDER);
openable = { workspaceUri: resource };
fullLabel = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true });
isWorkspace = true;
}
// File
......@@ -200,9 +212,9 @@ abstract class BaseOpenRecentAction extends Action {
return {
iconClasses,
label: name,
ariaLabel: isDirty ? nls.localize('recentDirtyAriaLabel', "{0}, dirty workspace", name) : name,
ariaLabel: isDirty ? isWorkspace ? nls.localize('recentDirtyWorkspaceAriaLabel', "{0}, workspace with unsaved changes", name) : nls.localize('recentDirtyFolderAriaLabel', "{0}, folder with unsaved changes", name) : name,
description: parentPath,
buttons: isDirty ? [this.dirtyRecentlyOpened] : [this.removeFromRecentlyOpened],
buttons: isDirty ? [isWorkspace ? this.dirtyRecentlyOpenedWorkspace : this.dirtyRecentlyOpenedFolder] : [this.removeFromRecentlyOpened],
openable,
resource
};
......
......@@ -114,7 +114,7 @@ export class CloseWorkspaceAction extends Action {
async run(): Promise<void> {
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
this.notificationService.info(nls.localize('noWorkspaceOpened', "There is currently no workspace opened in this instance to close."));
this.notificationService.info(nls.localize('noWorkspaceOrFolderOpened', "There is currently no workspace or folder opened in this instance to close."));
return;
}
......
......@@ -330,8 +330,8 @@ import { isStandalone } from 'vs/base/browser/browser';
nls.localize('activeFolderLong', "`\${activeFolderLong}`: the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder)."),
nls.localize('folderName', "`\${folderName}`: name of the workspace folder the file is contained in (e.g. myFolder)."),
nls.localize('folderPath', "`\${folderPath}`: file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)."),
nls.localize('rootName', "`\${rootName}`: name of the workspace (e.g. myFolder or myWorkspace)."),
nls.localize('rootPath', "`\${rootPath}`: file path of the workspace (e.g. /Users/Development/myWorkspace)."),
nls.localize('rootName', "`\${rootName}`: name of the opened workspace or folder (e.g. myFolder or myWorkspace)."),
nls.localize('rootPath', "`\${rootPath}`: file path of the opened workspace or folder (e.g. /Users/Development/myWorkspace)."),
nls.localize('appName', "`\${appName}`: e.g. VS Code."),
nls.localize('remoteName', "`\${remoteName}`: e.g. SSH"),
nls.localize('dirty', "`\${dirty}`: a dirty indicator if the active editor is dirty."),
......
......@@ -339,7 +339,7 @@ export const STATUS_BAR_FOREGROUND = registerColor('statusBar.foreground', {
dark: '#FFFFFF',
light: '#FFFFFF',
hc: '#FFFFFF'
}, nls.localize('statusBarForeground', "Status bar foreground color when a workspace is opened. The status bar is shown in the bottom of the window."));
}, nls.localize('statusBarForeground', "Status bar foreground color when a workspace or folder is opened. The status bar is shown in the bottom of the window."));
export const STATUS_BAR_NO_FOLDER_FOREGROUND = registerColor('statusBar.noFolderForeground', {
dark: STATUS_BAR_FOREGROUND,
......@@ -351,7 +351,7 @@ export const STATUS_BAR_BACKGROUND = registerColor('statusBar.background', {
dark: '#007ACC',
light: '#007ACC',
hc: null
}, nls.localize('statusBarBackground', "Status bar background color when a workspace is opened. The status bar is shown in the bottom of the window."));
}, nls.localize('statusBarBackground', "Status bar background color when a workspace or folder is opened. The status bar is shown in the bottom of the window."));
export const STATUS_BAR_NO_FOLDER_BACKGROUND = registerColor('statusBar.noFolderBackground', {
dark: '#68217A',
......
......@@ -198,8 +198,8 @@ const hotExitConfiguration: IConfigurationPropertySchema = platform.isNative ?
'default': HotExitConfiguration.ON_EXIT,
'markdownEnumDescriptions': [
nls.localize('hotExit.off', 'Disable hot exit. A prompt will show when attempting to close a window with dirty files.'),
nls.localize('hotExit.onExit', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu). All windows without folders opened will be restored upon next launch. A list of workspaces with unsaved files can be accessed via `File > Open Recent > More...`'),
nls.localize('hotExit.onExitAndWindowClose', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu), and also for any window with a folder opened regardless of whether it\'s the last window. All windows without folders opened will be restored upon next launch. A list of workspaces with unsaved files can be accessed via `File > Open Recent > More...`')
nls.localize('hotExit.onExit', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu). All windows without folders opened will be restored upon next launch. A list of previously opened windows with unsaved files can be accessed via `File > Open Recent > More...`'),
nls.localize('hotExit.onExitAndWindowClose', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu), and also for any window with a folder opened regardless of whether it\'s the last window. All windows without folders opened will be restored upon next launch. A list of previously opened windows with unsaved files can be accessed via `File > Open Recent > More...`')
],
'description': nls.localize('hotExit', "Controls whether unsaved files are remembered between sessions, allowing the save prompt when exiting the editor to be skipped.", HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE)
} : {
......
......@@ -89,7 +89,7 @@ export class WorkspaceWatcher extends Disposable {
if (msg.indexOf('ENOSPC') >= 0) {
this.notificationService.prompt(
Severity.Warning,
localize('enospcError', "Unable to watch for file changes in this large workspace. Please follow the instructions link to resolve this issue."),
localize('enospcError', "Unable to watch for file changes in this large workspace folder. Please follow the instructions link to resolve this issue."),
[{
label: localize('learnMore', "Instructions"),
run: () => this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?linkid=867693'))
......
......@@ -840,7 +840,7 @@ configurationRegistry.registerConfiguration({
'search.seedOnFocus': {
type: 'boolean',
default: false,
description: nls.localize('search.seedOnFocus', "Update workspace search query to the editor's selected text when focusing the search view. This happens either on click or when triggering the `workbench.views.search.focus` command.")
description: nls.localize('search.seedOnFocus', "Update the search query to the editor's selected text when focusing the search view. This happens either on click or when triggering the `workbench.views.search.focus` command.")
},
'search.searchOnTypeDebouncePeriod': {
type: 'number',
......
......@@ -378,7 +378,7 @@ export class QueryBuilder {
});
} else {
// No root folder with name
const searchPathNotFoundError = nls.localize('search.noWorkspaceWithName', "No folder in workspace with name: {0}", searchPathRoot);
const searchPathNotFoundError = nls.localize('search.noWorkspaceWithName', "Workspace folder does not exist: {0}", searchPathRoot);
throw new Error(searchPathNotFoundError);
}
} else {
......
......@@ -1977,7 +1977,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
await this.computeTasksForSingleConfig(workspaceFolder, configuration.config, runSource, custom, customizedTasks.byIdentifier, TaskConfig.TaskConfigSource.WorkspaceFile);
const engine = configuration.config ? TaskConfig.ExecutionEngine.from(configuration.config) : ExecutionEngine.Terminal;
if (engine === ExecutionEngine.Process) {
this.notificationService.warn(nls.localize('TaskSystem.versionWorkspaceFile', 'Only tasks version 2.0.0 permitted in .codeworkspace.'));
this.notificationService.warn(nls.localize('TaskSystem.versionWorkspaceFile', 'Only tasks version 2.0.0 permitted in workspace configuration files.'));
return this.emptyWorkspaceTaskResults(workspaceFolder);
}
return { workspaceFolder, set: { tasks: custom }, configurations: customizedTasks, hasErrors: configuration.hasParseErrors };
......
......@@ -32,7 +32,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.none' }, "Start without an editor."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the Welcome page (default)."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.readme' }, "Open the README when opening a folder that contains one, fallback to 'welcomePage' otherwise."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.newUntitledFile' }, "Open a new untitled file (only applies when opening an empty workspace)."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.newUntitledFile' }, "Open a new untitled file (only applies when opening an empty window)."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePageInEmptyWorkbench' }, "Open the Welcome page when opening an empty workbench."),],
...(product.quality !== 'stable'
? [localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.gettingStarted' }, "Open the Getting Started page (experimental).")]
......
......@@ -246,7 +246,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
jsonEditor;
if (!this.workspaceSettingsResource) {
this.notificationService.info(nls.localize('openFolderFirst', "Open a folder first to create workspace settings"));
this.notificationService.info(nls.localize('openFolderFirst', "Open a folder or workspace first to create workspace or folder settings."));
return Promise.reject(null);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册