提交 95749c86 编写于 作者: B Benjamin Pasero

Merge branch 'master' into ben/more-testing

...@@ -7,7 +7,7 @@ import * as assert from 'assert'; ...@@ -7,7 +7,7 @@ import * as assert from 'assert';
import 'mocha'; import 'mocha';
import * as os from 'os'; import * as os from 'os';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { closeAllEditors, delay, disposeAll, flakySuite } from '../utils'; import { closeAllEditors, delay, disposeAll } from '../utils';
const webviewId = 'myWebview'; const webviewId = 'myWebview';
...@@ -17,7 +17,7 @@ function workspaceFile(...segments: string[]) { ...@@ -17,7 +17,7 @@ function workspaceFile(...segments: string[]) {
const testDocument = workspaceFile('bower.json'); const testDocument = workspaceFile('bower.json');
flakySuite('vscode API - webview', () => { suite.skip('vscode API - webview', () => {
const disposables: vscode.Disposable[] = []; const disposables: vscode.Disposable[] = [];
function _register<T extends vscode.Disposable>(disposable: T) { function _register<T extends vscode.Disposable>(disposable: T) {
...@@ -212,7 +212,7 @@ flakySuite('vscode API - webview', () => { ...@@ -212,7 +212,7 @@ flakySuite('vscode API - webview', () => {
assert.strictEqual(Math.round(secondResponse.value), 100); assert.strictEqual(Math.round(secondResponse.value), 100);
}); });
test('webviews with retainContextWhenHidden should be able to receive messages while hidden', async () => { test('webviews with retainContextWhenHidden should be able to recive messages while hidden', async () => {
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true })); const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true }));
const ready = getMesssage(webview); const ready = getMesssage(webview);
......
...@@ -3,25 +3,9 @@ ...@@ -3,25 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Suite } from 'mocha';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { TestFS } from './memfs'; import { TestFS } from './memfs';
import * as assert from 'assert';
export function flakySuite(title: string, fn: (this: Suite) => void): Suite {
return suite(title, function () {
// Flaky suites need retries and timeout to complete
// e.g. because they access the file system which can
// be unreliable depending on the environment.
this.retries(3);
this.timeout(1000 * 20);
// Invoke suite ensuring that `this` is
// properly wired in.
fn.call(this);
});
}
export function rndName() { export function rndName() {
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10); return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10);
......
...@@ -605,7 +605,6 @@ suite('BackupMainService', () => { ...@@ -605,7 +605,6 @@ suite('BackupMainService', () => {
}); });
suite('getWorkspaceHash', () => { suite('getWorkspaceHash', () => {
(platform.isLinux ? test.skip : test)('should ignore case on Windows and Mac', () => { (platform.isLinux ? test.skip : test)('should ignore case on Windows and Mac', () => {
if (platform.isMacintosh) { if (platform.isMacintosh) {
assert.equal(service.getFolderHash(URI.file('/foo')), service.getFolderHash(URI.file('/FOO'))); assert.equal(service.getFolderHash(URI.file('/foo')), service.getFolderHash(URI.file('/FOO')));
......
...@@ -407,7 +407,7 @@ suite('Disk File Service', function () { ...@@ -407,7 +407,7 @@ suite('Disk File Service', function () {
assert.equal(r2.name, 'deep'); assert.equal(r2.name, 'deep');
}); });
(isWindows /* symlinks not reliable on windows */ ? test.skip : test)('resolve - folder symbolic link', async () => { (isWindows /* symlinks are not reliable on windows */ ? test.skip : test)('resolve - folder symbolic link', async () => {
const link = URI.file(join(testDir, 'deep-link')); const link = URI.file(join(testDir, 'deep-link'));
await symlink(join(testDir, 'deep'), link.fsPath); await symlink(join(testDir, 'deep'), link.fsPath);
...@@ -417,7 +417,7 @@ suite('Disk File Service', function () { ...@@ -417,7 +417,7 @@ suite('Disk File Service', function () {
assert.equal(resolved.isSymbolicLink, true); assert.equal(resolved.isSymbolicLink, true);
}); });
(isWindows /* symlinks not reliable on windows */ ? test.skip : test)('resolve - file symbolic link', async () => { (isWindows /* symlinks are not reliable on windows */ ? test.skip : test)('resolve - file symbolic link', async () => {
const link = URI.file(join(testDir, 'lorem.txt-linked')); const link = URI.file(join(testDir, 'lorem.txt-linked'));
await symlink(join(testDir, 'lorem.txt'), link.fsPath); await symlink(join(testDir, 'lorem.txt'), link.fsPath);
...@@ -426,7 +426,7 @@ suite('Disk File Service', function () { ...@@ -426,7 +426,7 @@ suite('Disk File Service', function () {
assert.equal(resolved.isSymbolicLink, true); assert.equal(resolved.isSymbolicLink, true);
}); });
(isWindows /* symlinks not reliable on windows */ ? test.skip : test)('resolve - symbolic link pointing to non-existing file does not break', async () => { (isWindows /* symlinks are not reliable on windows */ ? test.skip : test)('resolve - symbolic link pointing to non-existing file does not break', async () => {
await symlink(join(testDir, 'foo'), join(testDir, 'bar')); await symlink(join(testDir, 'foo'), join(testDir, 'bar'));
const resolved = await service.resolve(URI.file(testDir)); const resolved = await service.resolve(URI.file(testDir));
...@@ -475,7 +475,7 @@ suite('Disk File Service', function () { ...@@ -475,7 +475,7 @@ suite('Disk File Service', function () {
assert.equal((<FileOperationError>error).fileOperationResult, FileOperationResult.FILE_NOT_FOUND); assert.equal((<FileOperationError>error).fileOperationResult, FileOperationResult.FILE_NOT_FOUND);
} }
(isWindows /* symlinks not reliable on windows */ ? test.skip : test)('deleteFile - symbolic link (exists)', async () => { (isWindows /* symlinks are not reliable on windows */ ? test.skip : test)('deleteFile - symbolic link (exists)', async () => {
const target = URI.file(join(testDir, 'lorem.txt')); const target = URI.file(join(testDir, 'lorem.txt'));
const link = URI.file(join(testDir, 'lorem.txt-linked')); const link = URI.file(join(testDir, 'lorem.txt-linked'));
await symlink(target.fsPath, link.fsPath); await symlink(target.fsPath, link.fsPath);
...@@ -497,7 +497,7 @@ suite('Disk File Service', function () { ...@@ -497,7 +497,7 @@ suite('Disk File Service', function () {
assert.equal(existsSync(target.fsPath), true); // target the link pointed to is never deleted assert.equal(existsSync(target.fsPath), true); // target the link pointed to is never deleted
}); });
(isWindows /* symlinks not reliable on windows */ ? test.skip : test)('deleteFile - symbolic link (pointing to non-existing file)', async () => { (isWindows /* symlinks are not reliable on windows */ ? test.skip : test)('deleteFile - symbolic link (pointing to non-existing file)', async () => {
const target = URI.file(join(testDir, 'foo')); const target = URI.file(join(testDir, 'foo'));
const link = URI.file(join(testDir, 'bar')); const link = URI.file(join(testDir, 'bar'));
await symlink(target.fsPath, link.fsPath); await symlink(target.fsPath, link.fsPath);
......
...@@ -1362,7 +1362,88 @@ export class TabsTitleControl extends TitleControl { ...@@ -1362,7 +1362,88 @@ export class TabsTitleControl extends TitleControl {
} }
private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: ITitleControlDimensions): void { private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: ITitleControlDimensions): void {
const [tabsAndActionsContainer, tabsContainer, tabsScrollbar, editorToolbarContainer] = assertAllDefined(this.tabsAndActionsContainer, this.tabsContainer, this.tabsScrollbar, this.editorToolbarContainer);
// Always first layout tabs with wrapping support even if wrapping
// is disabled. The result indicates if tabs wrap and if not, we
// need to proceed with the layout without wrapping because even
// if wrapping is enabled in settings, there are cases where
// wrapping is disabled (e.g. due to space constraints)
const tabsWrapMultiLine = this.doLayoutTabsWrapping(dimensions);
if (!tabsWrapMultiLine) {
this.doLayoutTabsNonWrapping(activeTab, activeIndex);
}
}
private doLayoutTabsWrapping(dimensions: ITitleControlDimensions): boolean {
const [tabsAndActionsContainer, tabsContainer, editorToolbarContainer, tabsScrollbar] = assertAllDefined(this.tabsAndActionsContainer, this.tabsContainer, this.editorToolbarContainer, this.tabsScrollbar);
// Handle wrapping tabs according to setting:
// - enabled: only add class if tabs wrap and don't exceed available dimensions
// - disabled: remove class and margin-right variable
const didTabsWrapMultiLine = tabsAndActionsContainer.classList.contains('wrapping');
let tabsWrapMultiLine = didTabsWrapMultiLine;
function updateTabsWrapping(enabled: boolean): void {
tabsWrapMultiLine = enabled;
// Toggle the `wrapped` class to enable wrapping
tabsAndActionsContainer.classList.toggle('wrapping', tabsWrapMultiLine);
// 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');
}
// Setting enabled: selectively enable wrapping if possible
if (this.accessor.partOptions.wrapTabs) {
const visibleTabsWidth = tabsContainer.offsetWidth;
const allTabsWidth = tabsContainer.scrollWidth;
// If tabs wrap or should start to wrap (when width exceeds visible width)
// we must trigger `updateWrapping` to set the `last-tab-margin-right`
// accordingly based on the number of actions. The margin is important to
// properly position the last tab apart from the actions
if (tabsWrapMultiLine || allTabsWidth > visibleTabsWidth) {
updateTabsWrapping(true);
}
// Tabs wrap multiline: remove wrapping under certain size constraint conditions
if (tabsWrapMultiLine) {
const lastTab = this.getLastTab();
if (
(tabsContainer.offsetHeight > dimensions.available.height) || // if height exceeds available height
(allTabsWidth === visibleTabsWidth && tabsContainer.offsetHeight === TabsTitleControl.TAB_HEIGHT) || // if wrapping is not needed anymore
(lastTab && lastTab.offsetWidth > (dimensions.available.width - editorToolbarContainer.offsetWidth)) // if editor actions occupy too much space
) {
updateTabsWrapping(false);
}
}
}
// Setting disabled: remove CSS traces only if tabs did wrap
else if (didTabsWrapMultiLine) {
updateTabsWrapping(false);
}
// If we transitioned from non-wrapping to wrapping, we need
// to update the scrollbar to have an equal `width` and
// `scrollWidth`. Otherwise a scrollbar would appear which is
// never desired when wrapping.
if (tabsWrapMultiLine && !didTabsWrapMultiLine) {
const visibleTabsWidth = tabsContainer.offsetWidth;
tabsScrollbar.setScrollDimensions({
width: visibleTabsWidth,
scrollWidth: visibleTabsWidth
});
}
return tabsWrapMultiLine;
}
private doLayoutTabsNonWrapping(activeTab: HTMLElement, activeIndex: number): void {
const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar);
// //
// Synopsis // Synopsis
...@@ -1380,7 +1461,7 @@ export class TabsTitleControl extends TitleControl { ...@@ -1380,7 +1461,7 @@ export class TabsTitleControl extends TitleControl {
// [-- Sticky Tabs Width --] // [-- Sticky Tabs Width --]
// //
const visibleTabsContainerWidth = tabsContainer.offsetWidth; const visibleTabsWidth = tabsContainer.offsetWidth;
const allTabsWidth = tabsContainer.scrollWidth; const allTabsWidth = tabsContainer.scrollWidth;
// Compute width of sticky tabs depending on pinned tab sizing // Compute width of sticky tabs depending on pinned tab sizing
...@@ -1409,77 +1490,17 @@ export class TabsTitleControl extends TitleControl { ...@@ -1409,77 +1490,17 @@ export class TabsTitleControl extends TitleControl {
// Special case: we have sticky tabs but the available space for showing tabs // Special case: we have sticky tabs but the available space for showing tabs
// is little enough that we need to disable sticky tabs sticky positioning // is little enough that we need to disable sticky tabs sticky positioning
// so that tabs can be scrolled at naturally. // so that tabs can be scrolled at naturally.
let availableTabsContainerWidth = visibleTabsContainerWidth - stickyTabsWidth; let availableTabsContainerWidth = visibleTabsWidth - stickyTabsWidth;
if (this.group.stickyCount > 0 && availableTabsContainerWidth < TabsTitleControl.TAB_WIDTH.fit) { if (this.group.stickyCount > 0 && availableTabsContainerWidth < TabsTitleControl.TAB_WIDTH.fit) {
tabsContainer.classList.add('disable-sticky-tabs'); tabsContainer.classList.add('disable-sticky-tabs');
availableTabsContainerWidth = visibleTabsContainerWidth; availableTabsContainerWidth = visibleTabsWidth;
stickyTabsWidth = 0; stickyTabsWidth = 0;
activeTabPositionStatic = false; activeTabPositionStatic = false;
} else { } else {
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 dimensions
// - disabled: remove class
if (this.accessor.partOptions.wrapTabs) {
const oldTabsWrapMultiLine = tabsAndActionsContainer.classList.contains('wrapping');
// 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
let tabsWrapMultiLine = oldTabsWrapMultiLine;
if (!tabsWrapMultiLine && allTabsWidth > visibleTabsContainerWidth) {
tabsAndActionsContainer.classList.add('wrapping');
tabsWrapMultiLine = true;
}
// Tabs wrap multiline: remove wrapping if height exceeds available height
if (tabsWrapMultiLine && tabsContainer.offsetHeight > dimensions.available.height) {
tabsAndActionsContainer.classList.remove('wrapping');
tabsWrapMultiLine = false;
}
// 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;
}
// 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');
// We need to disable wrapping also in case the last tab requires more
// width than what is available accounting for the available width and
// the editor toolbar.
// Workaround for https://github.com/microsoft/vscode/issues/113926
// An overall better fix is to make the editor toolbar a "fake" last tab
// so that it requires the space it needs, potentially wrapping to the
// next line (see https://github.com/microsoft/vscode/issues/113801)
const lastTab = this.getLastTab();
if (tabsWrapMultiLine && lastTab && lastTab.offsetWidth > (dimensions.available.width - editorToolbarContainer.offsetWidth)) {
tabsAndActionsContainer.classList.remove('wrapping');
tabsWrapMultiLine = false;
tabsContainer.style.setProperty('--last-tab-margin-right', '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 (oldTabsWrapMultiLine && !tabsWrapMultiLine) {
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;
...@@ -1490,7 +1511,7 @@ export class TabsTitleControl extends TitleControl { ...@@ -1490,7 +1511,7 @@ export class TabsTitleControl extends TitleControl {
// Update scrollbar // Update scrollbar
tabsScrollbar.setScrollDimensions({ tabsScrollbar.setScrollDimensions({
width: visibleTabsContainerWidth, width: visibleTabsWidth,
scrollWidth: allTabsWidth scrollWidth: allTabsWidth
}); });
......
...@@ -351,7 +351,7 @@ suite('Encoding', () => { ...@@ -351,7 +351,7 @@ suite('Encoding', () => {
assert.equal(content.length, 65537); assert.equal(content.length, 65537);
}); });
(isWindows /* TODO@bpasero why does this fail on windows? */ ? test.skip : test)('toDecodeStream - some stream (UTF-8 issue #102202)', async function () { (isWindows /* TODO@bpasero why does this fail on windows */ ? test.skip : test)('toDecodeStream - some stream (UTF-8 issue #102202)', async function () {
const path = getPathFromAmdModule(require, './fixtures/issue_102202.txt'); const path = getPathFromAmdModule(require, './fixtures/issue_102202.txt');
const source = streamToBufferReadableStream(fs.createReadStream(path)); const source = streamToBufferReadableStream(fs.createReadStream(path));
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册