提交 89868703 编写于 作者: J Johannes Rieken

Merge branch 'master' into joh/toc

...@@ -7744,7 +7744,7 @@ declare module 'vscode' { ...@@ -7744,7 +7744,7 @@ declare module 'vscode' {
* your extension should first check to see if any backups exist for the resource. If there is a backup, your * your extension should first check to see if any backups exist for the resource. If there is a backup, your
* extension should load the file contents from there instead of from the resource in the workspace. * extension should load the file contents from there instead of from the resource in the workspace.
* *
* `backup` is triggered approximately one second after the the user stops editing the document. If the user * `backup` is triggered approximately one second after the user stops editing the document. If the user
* rapidly edits the document, `backup` will not be invoked until the editing stops. * rapidly edits the document, `backup` will not be invoked until the editing stops.
* *
* `backup` is not invoked when `auto save` is enabled (since auto save already persists the resource). * `backup` is not invoked when `auto save` is enabled (since auto save already persists the resource).
......
...@@ -110,7 +110,7 @@ const viewDescriptor: IJSONSchema = { ...@@ -110,7 +110,7 @@ const viewDescriptor: IJSONSchema = {
defaultSnippets: [{ body: { id: '${1:id}', name: '${2:name}' } }], defaultSnippets: [{ body: { id: '${1:id}', name: '${2:name}' } }],
properties: { properties: {
type: { type: {
markdownDescription: localize('vscode.extension.contributes.view.type', "Type of the the view. This can either be `tree` for a tree view based view or `webview` for a webview based view. The default is `tree`."), markdownDescription: localize('vscode.extension.contributes.view.type', "Type of the view. This can either be `tree` for a tree view based view or `webview` for a webview based view. The default is `tree`."),
type: 'string', type: 'string',
enum: [ enum: [
'tree', 'tree',
......
...@@ -103,7 +103,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { ...@@ -103,7 +103,7 @@ export class ExtHostCommands implements ExtHostCommandsShape {
const internalArgs = apiCommand.args.map((arg, i) => { const internalArgs = apiCommand.args.map((arg, i) => {
if (!arg.validate(apiArgs[i])) { if (!arg.validate(apiArgs[i])) {
throw new Error(`Invalid argument '${arg.name}' when running '${apiCommand.id}', receieved: ${apiArgs[i]}`); throw new Error(`Invalid argument '${arg.name}' when running '${apiCommand.id}', received: ${apiArgs[i]}`);
} }
return arg.convert(apiArgs[i]); return arg.convert(apiArgs[i]);
}); });
......
...@@ -38,10 +38,7 @@ ...@@ -38,10 +38,7 @@
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container { .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container {
display: flex; display: flex;
} position: relative; /* position tabs border bottom or editor actions (when tabs wrap) relative to this container */
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.tabs-border-bottom {
position: relative;
} }
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.tabs-border-bottom::after { .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.tabs-border-bottom::after {
...@@ -77,6 +74,13 @@ ...@@ -77,6 +74,13 @@
overflow: scroll !important; overflow: scroll !important;
} }
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container {
/* Enable wrapping via flex layout and dynamic height */
height: auto;
flex-wrap: wrap;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container::-webkit-scrollbar { .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container::-webkit-scrollbar {
display: none; /* Chrome + Safari: hide scrollbar */ display: none; /* Chrome + Safari: hide scrollbar */
} }
...@@ -93,6 +97,10 @@ ...@@ -93,6 +97,10 @@
padding-left: 10px; padding-left: 10px;
} }
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab:last-child {
margin-right: var(--last-tab-margin-right); /* when tabs wrap, we need a margin away from the absolute positioned editor actions */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-right, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-right,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-off:not(.sticky-compact) { .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-off:not(.sticky-compact) {
padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab actions is not left (unless sticky-compact) */ padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab actions is not left (unless sticky-compact) */
...@@ -105,6 +113,10 @@ ...@@ -105,6 +113,10 @@
flex-shrink: 0; flex-shrink: 0;
} }
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab.sizing-fit {
flex-grow: 1; /* grow the tabs to fill each row for a more homogeneous look when tabs wrap */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink { .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink {
min-width: 80px; min-width: 80px;
flex-basis: 0; /* all tabs are even */ flex-basis: 0; /* all tabs are even */
...@@ -149,9 +161,7 @@ ...@@ -149,9 +161,7 @@
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-compact, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-compact,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-fit.sticky-shrink, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-fit.sticky-shrink,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-shrink { .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-shrink {
position: static; /** disable sticky positions for sticky compact/shrink tabs if the available space is too little */
/** Disable sticky positions for sticky compact/shrink tabs if the available space is too little */
position: static;
} }
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-left .action-label { .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-left .action-label {
...@@ -163,7 +173,7 @@ ...@@ -163,7 +173,7 @@
content: ''; content: '';
display: flex; display: flex;
flex: 0; flex: 0;
width: 5px; /* Reserve space to hide tab fade when close button is left or off (fixes https://github.com/microsoft/vscode/issues/45728) */ width: 5px; /* reserve space to hide tab fade when close button is left or off (fixes https://github.com/microsoft/vscode/issues/45728) */
} }
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-left { .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-left {
...@@ -276,7 +286,7 @@ ...@@ -276,7 +286,7 @@
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions { .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions {
flex: 0; flex: 0;
overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink to make more room... */ overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink to make more room */
} }
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.tab-actions-right.sizing-shrink > .tab-actions, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.tab-actions-right.sizing-shrink > .tab-actions,
...@@ -366,6 +376,14 @@ ...@@ -366,6 +376,14 @@
height: 35px; height: 35px;
} }
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .editor-actions {
/* When tabs are wrapped, position the editor actions at the end of the very last row */
position: absolute;
bottom: 0;
right: 0;
}
/* Breadcrumbs */ /* Breadcrumbs */
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control { .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control {
......
...@@ -91,7 +91,7 @@ export class TabsTitleControl extends TitleControl { ...@@ -91,7 +91,7 @@ export class TabsTitleControl extends TitleControl {
private tabActionBars: ActionBar[] = []; private tabActionBars: ActionBar[] = [];
private tabDisposables: IDisposable[] = []; private tabDisposables: IDisposable[] = [];
private dimensions: ITitleControlDimensions = { private dimensions: ITitleControlDimensions & { used?: Dimension } = {
container: Dimension.None, container: Dimension.None,
available: Dimension.None available: Dimension.None
}; };
...@@ -571,6 +571,7 @@ export class TabsTitleControl extends TitleControl { ...@@ -571,6 +571,7 @@ export class TabsTitleControl extends TitleControl {
oldOptions.showIcons !== newOptions.showIcons || oldOptions.showIcons !== newOptions.showIcons ||
oldOptions.hasIcons !== newOptions.hasIcons || oldOptions.hasIcons !== newOptions.hasIcons ||
oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs || oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs ||
oldOptions.wrapTabs !== newOptions.wrapTabs ||
!equals(oldOptions.decorations, newOptions.decorations) !equals(oldOptions.decorations, newOptions.decorations)
) { ) {
this.redraw(); this.redraw();
...@@ -1274,19 +1275,30 @@ export class TabsTitleControl extends TitleControl { ...@@ -1274,19 +1275,30 @@ export class TabsTitleControl extends TitleControl {
} }
getDimensions(): IEditorGroupTitleDimensions { getDimensions(): IEditorGroupTitleDimensions {
let height = TabsTitleControl.TAB_HEIGHT; let height: number;
// Wrap: we need to ask `offsetHeight` to get
// the real height of the title area with wrapping.
if (this.accessor.partOptions.wrapTabs && this.tabsAndActionsContainer?.classList.contains('wrapping')) {
height = this.tabsAndActionsContainer.offsetHeight;
} else {
height = TabsTitleControl.TAB_HEIGHT;
}
const offset = height;
// Account for breadcrumbs if visible
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
height += BreadcrumbsControl.HEIGHT; // Account for breadcrumbs if visible height += BreadcrumbsControl.HEIGHT; // Account for breadcrumbs if visible
} }
return { return { height, offset };
height,
offset: TabsTitleControl.TAB_HEIGHT
};
} }
layout(dimensions: ITitleControlDimensions): Dimension { layout(dimensions: ITitleControlDimensions): Dimension {
this.dimensions = dimensions;
// Remember dimensions that we get
Object.assign(this.dimensions, dimensions);
// The layout of tabs can be an expensive operation because we access DOM properties // The layout of tabs can be an expensive operation because we access DOM properties
// that can result in the browser doing a full page layout to validate them. To buffer // that can result in the browser doing a full page layout to validate them. To buffer
...@@ -1303,17 +1315,33 @@ export class TabsTitleControl extends TitleControl { ...@@ -1303,17 +1315,33 @@ export class TabsTitleControl extends TitleControl {
} }
private doLayout(dimensions: ITitleControlDimensions): void { private doLayout(dimensions: ITitleControlDimensions): void {
// Only layout if we have valid tab index and dimensions
const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined; const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined;
if (!activeTabAndIndex || dimensions.container === Dimension.None || dimensions.available === Dimension.None) { if (activeTabAndIndex && dimensions.container !== Dimension.None && dimensions.available !== Dimension.None) {
return; // nothing to do if not editor opened or we got no dimensions yet
// Breadcrumbs
this.doLayoutBreadcrumbs(dimensions);
// Tabs
const [activeTab, activeIndex] = activeTabAndIndex;
this.doLayoutTabs(activeTab, activeIndex, dimensions);
} }
// Breadcrumbs // Compute new dimension of tabs title control and remember it for future usages
this.doLayoutBreadcrumbs(dimensions); const oldDimension = this.dimensions.used;
const newDimension = this.dimensions.used = new Dimension(dimensions.container.width, this.getDimensions().height);
// Tabs // In case the height of the title control changed from before
const [activeTab, activeIndex] = activeTabAndIndex; // (currently only possible if tabs are set to wrap), we need
this.doLayoutTabs(activeTab, activeIndex); // to signal this to the outside via a `relayout` call so that
// e.g. the editor control can be adjusted accordingly.
if (
this.accessor.partOptions.wrapTabs &&
oldDimension && oldDimension.height !== newDimension.height
) {
this.group.relayout();
}
} }
private doLayoutBreadcrumbs(dimensions: ITitleControlDimensions): void { private doLayoutBreadcrumbs(dimensions: ITitleControlDimensions): void {
...@@ -1322,8 +1350,8 @@ export class TabsTitleControl extends TitleControl { ...@@ -1322,8 +1350,8 @@ export class TabsTitleControl extends TitleControl {
} }
} }
private doLayoutTabs(activeTab: HTMLElement, activeIndex: number): void { private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: ITitleControlDimensions): void {
const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar); const [tabsAndActionsContainer, tabsContainer, tabsScrollbar, editorToolbarContainer] = assertAllDefined(this.tabsAndActionsContainer, this.tabsContainer, this.tabsScrollbar, this.editorToolbarContainer);
// //
// Synopsis // Synopsis
...@@ -1381,6 +1409,55 @@ export class TabsTitleControl extends TitleControl { ...@@ -1381,6 +1409,55 @@ export class TabsTitleControl extends TitleControl {
tabsContainer.classList.remove('disable-sticky-tabs'); tabsContainer.classList.remove('disable-sticky-tabs');
} }
// Handle wrapping tabs according to setting:
// - enabled: only add class if tabs wrap and don't exceed available height
// - disabled: remove class
if (this.accessor.partOptions.wrapTabs) {
let tabsWrapMultiLine = tabsAndActionsContainer.classList.contains('wrapping');
let updateScrollbar = false;
// Tabs do not wrap multiline: add wrapping if tabs exceed the tabs container width
// and the height of the tabs container does not exceed the maximum
if (!tabsWrapMultiLine && allTabsWidth > visibleTabsContainerWidth) {
tabsAndActionsContainer.classList.add('wrapping');
tabsWrapMultiLine = true;
}
// Tabs wrap multiline: remove wrapping if height exceeds available height
// or the maximum allowed height
if (tabsWrapMultiLine && tabsContainer.offsetHeight > dimensions.available.height) {
tabsAndActionsContainer.classList.remove('wrapping');
tabsWrapMultiLine = false;
updateScrollbar = true;
}
// If we do not exceed the tabs container width, we cannot simply remove
// the wrap class because by wrapping tabs, they reduce their size
// and we would otherwise constantly add and remove the class. As such
// we need to check if the height of the tabs container is back to normal
// and then remove the wrap class.
if (tabsWrapMultiLine && allTabsWidth === visibleTabsContainerWidth && tabsContainer.offsetHeight === TabsTitleControl.TAB_HEIGHT) {
tabsAndActionsContainer.classList.remove('wrapping');
tabsWrapMultiLine = false;
updateScrollbar = true;
}
// Update `last-tab-margin-right` CSS variable to account for the absolute
// positioned editor actions container when tabs wrap. The margin needs to
// be the width of the editor actions container to avoid screen cheese.
tabsContainer.style.setProperty('--last-tab-margin-right', tabsWrapMultiLine ? `${editorToolbarContainer.offsetWidth}px` : '0');
// When tabs change from wrapping back to normal, we need to indicate this
// to the scrollbar so that revealing the active tab functions properly.
if (updateScrollbar) {
tabsScrollbar.setScrollPosition({
scrollLeft: tabsContainer.scrollLeft
});
}
} else {
tabsAndActionsContainer.classList.remove('wrapping');
}
let activeTabPosX: number | undefined; let activeTabPosX: number | undefined;
let activeTabWidth: number | undefined; let activeTabWidth: number | undefined;
...@@ -1567,11 +1644,23 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = ...@@ -1567,11 +1644,23 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
// Add border between tabs and breadcrumbs in high contrast mode. // Add border between tabs and breadcrumbs in high contrast mode.
if (theme.type === ColorScheme.HIGH_CONTRAST) { if (theme.type === ColorScheme.HIGH_CONTRAST) {
const borderColor = (theme.getColor(TAB_BORDER) || theme.getColor(contrastBorder)); const borderColor = (theme.getColor(TAB_BORDER) || theme.getColor(contrastBorder));
if (borderColor) {
collector.addRule(`
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container {
border-bottom: 1px solid ${borderColor};
}
`);
}
}
// Add bottom border to tabs when wrapping
const borderColor = theme.getColor(TAB_BORDER);
if (borderColor) {
collector.addRule(` collector.addRule(`
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container { .monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab {
border-bottom: 1px solid ${borderColor}; border-bottom: 1px solid ${borderColor};
} }
`); `);
} }
// Styling with Outline color (e.g. high contrast theme) // Styling with Outline color (e.g. high contrast theme)
......
...@@ -33,6 +33,11 @@ import { isStandalone } from 'vs/base/browser/browser'; ...@@ -33,6 +33,11 @@ import { isStandalone } from 'vs/base/browser/browser';
'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), 'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."),
'default': true 'default': true
}, },
'workbench.editor.wrapTabs': {
'type': 'boolean',
'description': nls.localize('wrapTabs', "Controls whether tabs should be wrapped over multiple lines when exceeding available space or wether a scrollbar should appear instead."),
'default': false
},
'workbench.editor.scrollToSwitchTabs': { 'workbench.editor.scrollToSwitchTabs': {
'type': 'boolean', 'type': 'boolean',
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration. This value is ignored when `#workbench.editor.showTabs#` is disabled."), 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration. This value is ignored when `#workbench.editor.showTabs#` is disabled."),
......
...@@ -1265,6 +1265,7 @@ export interface IWorkbenchEditorConfiguration { ...@@ -1265,6 +1265,7 @@ export interface IWorkbenchEditorConfiguration {
interface IEditorPartConfiguration { interface IEditorPartConfiguration {
showTabs?: boolean; showTabs?: boolean;
wrapTabs?: boolean;
scrollToSwitchTabs?: boolean; scrollToSwitchTabs?: boolean;
highlightModifiedTabs?: boolean; highlightModifiedTabs?: boolean;
tabCloseButton?: 'left' | 'right' | 'off'; tabCloseButton?: 'left' | 'right' | 'off';
......
...@@ -389,7 +389,7 @@ export class ReplAccessibilityProvider implements IListAccessibilityProvider<IRe ...@@ -389,7 +389,7 @@ export class ReplAccessibilityProvider implements IListAccessibilityProvider<IRe
return localize('replVariableAriaLabel', "Variable {0}, value {1}", element.name, element.value); return localize('replVariableAriaLabel', "Variable {0}, value {1}", element.name, element.value);
} }
if (element instanceof SimpleReplElement || element instanceof ReplEvaluationInput || element instanceof ReplEvaluationResult) { if (element instanceof SimpleReplElement || element instanceof ReplEvaluationInput || element instanceof ReplEvaluationResult) {
return element.value + (element instanceof SimpleReplElement && element.count > 1 ? localize({ key: 'occurred', comment: ['Front will the the value of the debug console element. Placeholder will be replaced by a number which represents occurrance count.'] }, return element.value + (element instanceof SimpleReplElement && element.count > 1 ? localize({ key: 'occurred', comment: ['Front will the value of the debug console element. Placeholder will be replaced by a number which represents occurrance count.'] },
", occured {0} times", element.count) : ''); ", occured {0} times", element.count) : '');
} }
if (element instanceof RawObjectReplElement) { if (element instanceof RawObjectReplElement) {
......
...@@ -30,8 +30,12 @@ export class SimpleReplElement implements IReplElement { ...@@ -30,8 +30,12 @@ export class SimpleReplElement implements IReplElement {
) { } ) { }
toString(): string { toString(): string {
let valueRespectCount = this.value;
if (this._count > 1) {
valueRespectCount = valueRespectCount + ('\n' + this.value).repeat(this._count - 1);
}
const sourceStr = this.sourceData ? ` ${this.sourceData.source.name}` : ''; const sourceStr = this.sourceData ? ` ${this.sourceData.source.name}` : '';
return this.value + sourceStr; return valueRespectCount + sourceStr;
} }
getId(): string { getId(): string {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册