提交 3522f213 编写于 作者: B Benjamin Pasero

Merge pull request #1069 from Microsoft/ben/quickopen

Allow to match on paths in quick open (fixes #1065)
......@@ -8,9 +8,13 @@ import WinJS = require('vs/base/common/winjs.base');
import Types = require('vs/base/common/types');
import URI from 'vs/base/common/uri';
import Tree = require('vs/base/parts/tree/common/tree');
import { IQuickNavigateConfiguration, IModel, IDataSource, IFilter, IRenderer, IRunner, Mode } from './quickOpen';
import Filters = require('vs/base/common/filters');
import Strings = require('vs/base/common/strings');
import Paths = require('vs/base/common/paths');
import {IQuickNavigateConfiguration, IModel, IDataSource, IFilter, IRenderer, IRunner, Mode} from './quickOpen';
import ActionsRenderer = require('vs/base/parts/tree/browser/actionsRenderer');
import Actions = require('vs/base/common/actions');
import {compareAnything} from 'vs/base/common/comparers';
import ActionBar = require('vs/base/browser/ui/actionbar/actionbar');
import TreeDefaults = require('vs/base/parts/tree/browser/treeDefaults');
import HighlightedLabel = require('vs/base/browser/ui/highlightedlabel/highlightedLabel');
......@@ -47,6 +51,13 @@ export class QuickOpenEntry {
return this.id;
}
/**
* The prefix to show in front of the label if any
*/
public getPrefix(): string {
return null;
}
/**
* The label of the entry to identify it from others in the list
*/
......@@ -121,6 +132,74 @@ export class QuickOpenEntry {
public run(mode: Mode, context: IContext): boolean {
return false;
}
/**
* A good default sort implementation for quick open entries
*/
public static compare(elementA: QuickOpenEntry, elementB: QuickOpenEntry, lookFor: string): number {
// Normalize
if (lookFor) {
lookFor = Strings.stripWildcards(lookFor).toLowerCase();
}
// Give matches with label highlights higher priority over
// those with only description highlights
const labelHighlightsA = elementA.getHighlights()[0] || [];
const labelHighlightsB = elementB.getHighlights()[0] || [];
if (labelHighlightsA.length && !labelHighlightsB.length) {
return -1;
} else if (!labelHighlightsA.length && labelHighlightsB.length) {
return 1;
}
// Sort by name/path
let nameA = elementA.getLabel();
let nameB = elementB.getLabel();
if (nameA === nameB) {
let resourceA = elementA.getResource();
let resourceB = elementB.getResource();
if (resourceA && resourceB) {
nameA = elementA.getResource().fsPath;
nameB = elementB.getResource().fsPath;
}
}
return compareAnything(nameA, nameB, lookFor);
}
public static highlight(entry: QuickOpenEntry, lookFor: string): { labelHighlights: IHighlight[], descriptionHighlights: IHighlight[] } {
let labelHighlights: IHighlight[] = [];
let descriptionHighlights: IHighlight[] = [];
// Highlight file aware
if (entry.getResource()) {
// Highlight only inside label
if (lookFor.indexOf(Paths.nativeSep) < 0) {
labelHighlights = Filters.matchesFuzzy(lookFor, entry.getLabel());
}
// Highlight in label and description
else {
descriptionHighlights = Filters.matchesFuzzy(Strings.trim(lookFor, Paths.nativeSep), entry.getDescription());
// If we have no highlights, assume that the match is split among name and parent folder
if (!descriptionHighlights || !descriptionHighlights.length) {
labelHighlights = Filters.matchesFuzzy(Paths.basename(lookFor), entry.getLabel());
descriptionHighlights = Filters.matchesFuzzy(Strings.trim(Paths.dirname(lookFor), Paths.nativeSep), entry.getDescription());
}
}
}
// Highlight by label otherwise
else {
labelHighlights = Filters.matchesFuzzy(lookFor, entry.getLabel());
}
return { labelHighlights, descriptionHighlights };
}
}
export class QuickOpenEntryItem extends QuickOpenEntry {
......@@ -175,6 +254,10 @@ export class QuickOpenEntryGroup extends QuickOpenEntry {
this.withBorder = showBorder;
}
public getPrefix(): string {
return this.entry ? this.entry.getPrefix() : super.getPrefix();
}
public getLabel(): string {
return this.entry ? this.entry.getLabel() : super.getLabel();
}
......@@ -265,6 +348,7 @@ class NoActionProvider implements ActionsRenderer.IActionProvider {
export interface IQuickOpenEntryTemplateData {
container: HTMLElement;
icon: HTMLSpanElement;
prefix: HTMLSpanElement;
label: HighlightedLabel.HighlightedLabel;
meta: HTMLSpanElement;
description: HighlightedLabel.HighlightedLabel;
......@@ -346,6 +430,10 @@ class Renderer implements IRenderer<QuickOpenEntry> {
let icon = document.createElement('span');
entry.appendChild(icon);
// Prefix
let prefix = document.createElement('span');
entry.appendChild(prefix);
// Label
let label = new HighlightedLabel.HighlightedLabel(entry);
......@@ -363,6 +451,7 @@ class Renderer implements IRenderer<QuickOpenEntry> {
return {
container: container,
icon: icon,
prefix: prefix,
label: label,
meta: meta,
description: description,
......@@ -424,7 +513,10 @@ class Renderer implements IRenderer<QuickOpenEntry> {
let iconClass = entry.getIcon() ? ('quick-open-entry-icon ' + entry.getIcon()) : '';
data.icon.className = iconClass;
// Label
// Prefix
let prefix = entry.getPrefix() || '';
data.prefix.textContent = prefix;
let labelHighlights = highlights[0];
data.label.set(entry.getLabel() || '', labelHighlights || []);
......
......@@ -8,7 +8,9 @@ import {Registry} from 'vs/platform/platform';
import filters = require('vs/base/common/filters');
import strings = require('vs/base/common/strings');
import types = require('vs/base/common/types');
import paths = require('vs/base/common/paths');
import URI from 'vs/base/common/uri';
import labels = require('vs/base/common/labels');
import {EventType} from 'vs/base/common/events';
import comparers = require('vs/base/common/comparers');
import {Mode, IContext} from 'vs/base/parts/quickopen/browser/quickOpen';
......@@ -31,7 +33,8 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry {
editorService: IWorkbenchEditorService,
private contextService: IWorkspaceContextService,
input: EditorInput,
highlights: IHighlight[],
labelHighlights: IHighlight[],
descriptionHighlights: IHighlight[],
model: EditorHistoryModel
) {
super(editorService);
......@@ -49,19 +52,23 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry {
}
}
this.setHighlights(highlights);
this.setHighlights(labelHighlights, descriptionHighlights);
}
public clone(highlights: IHighlight[]): EditorHistoryEntry {
return new EditorHistoryEntry(this.editorService, this.contextService, this.input, highlights, this.model);
public clone(labelHighlights: IHighlight[], descriptionHighlights?: IHighlight[]): EditorHistoryEntry {
return new EditorHistoryEntry(this.editorService, this.contextService, this.input, labelHighlights, descriptionHighlights, this.model);
}
public getLabel(): string {
public getPrefix(): string {
let status = this.input.getStatus();
if (status && status.decoration) {
return status.decoration + ' ' + this.input.getName();
return `${status.decoration} `;
}
return void 0;
}
public getLabel(): string {
return this.input.getName();
}
......@@ -130,7 +137,7 @@ export class EditorHistoryModel extends QuickOpenModel {
// Remove any existing entry and add to the beginning
this.remove(entry);
this.entries.unshift(new EditorHistoryEntry(this.editorService, this.contextService, entry, null, this));
this.entries.unshift(new EditorHistoryEntry(this.editorService, this.contextService, entry, null, null, this));
// Respect max entries setting
if (this.entries.length > MAX_ENTRIES) {
......@@ -204,38 +211,27 @@ export class EditorHistoryModel extends QuickOpenModel {
public getResults(searchValue: string): QuickOpenEntry[] {
searchValue = searchValue.trim();
const searchInPath = searchValue.indexOf(paths.nativeSep) >= 0;
let results: QuickOpenEntry[] = [];
for (let i = 0; i < this.entries.length; i++) {
let entry = this.entries[i];
let entry = <EditorHistoryEntry>this.entries[i];
if (!entry.getResource()) {
continue; //For now, only support to match on inputs that provide resource information
}
let highlights = filters.matchesFuzzy(searchValue, (<EditorHistoryEntry>entry).getInput().getName());
if (highlights) {
results.push((<EditorHistoryEntry>entry).clone(highlights));
// Check if this entry is a match for the search value
let targetToMatch = searchInPath ? labels.getPathLabel(entry.getResource(), this.contextService) : entry.getLabel();
if (!filters.matchesFuzzy(searchValue, targetToMatch)) {
continue;
}
}
// If user is searching, use the same sorting that is used for other quick open handlers
if (searchValue) {
let normalizedSearchValue = strings.stripWildcards(searchValue.toLowerCase());
return results.sort((elementA:EditorHistoryEntry, elementB:EditorHistoryEntry) => {
let nameA = elementA.getInput().getName();
let nameB = elementB.getInput().getName();
if (nameA === nameB) {
nameA = elementA.getResource().fsPath;
nameB = elementB.getResource().fsPath;
}
return comparers.compareAnything(nameA, nameB, normalizedSearchValue);
});
// Apply highlights
const {labelHighlights, descriptionHighlights} = QuickOpenEntry.highlight(entry, searchValue);
results.push(entry.clone(labelHighlights, descriptionHighlights));
}
// Leave default "most recently used" order if user is not actually searching
return results;
// Sort
return results.sort((elementA: EditorHistoryEntry, elementB: EditorHistoryEntry) => QuickOpenEntry.compare(elementA, elementB, searchValue));
}
}
......@@ -10,7 +10,9 @@ import nls = require('vs/nls');
import {ThrottledDelayer} from 'vs/base/common/async';
import types = require('vs/base/common/types');
import strings = require('vs/base/common/strings');
import paths = require('vs/base/common/paths');
import filters = require('vs/base/common/filters');
import labels = require('vs/base/common/labels');
import {IRange} from 'vs/editor/common/editorCommon';
import {compareAnything} from 'vs/base/common/comparers';
import {IAutoFocus} from 'vs/base/parts/quickopen/browser/quickOpen';
......@@ -20,6 +22,7 @@ import {FileEntry, OpenFileHandler} from 'vs/workbench/parts/search/browser/open
import {OpenSymbolHandler as _OpenSymbolHandler} from 'vs/workbench/parts/search/browser/openSymbolHandler';
import {IMessageService, Severity} from 'vs/platform/message/common/message';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
// OpenSymbolHandler is used from an extension and must be in the main bundle file so it can load
export const OpenSymbolHandler = _OpenSymbolHandler
......@@ -42,6 +45,7 @@ export class OpenAnythingHandler extends QuickOpenHandler {
constructor(
@IMessageService private messageService: IMessageService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IInstantiationService instantiationService: IInstantiationService
) {
super();
......@@ -139,8 +143,7 @@ export class OpenAnythingHandler extends QuickOpenHandler {
let result = [...results[0].entries, ...results[1].entries];
// Sort
let normalizedSearchValue = strings.stripWildcards(searchValue.toLowerCase());
result.sort((elementA, elementB) => this.compareResults(elementA, elementB, normalizedSearchValue));
result.sort((elementA, elementB) => QuickOpenEntry.compare(elementA, elementB, searchValue));
// Apply Range
result.forEach((element) => {
......@@ -214,7 +217,13 @@ export class OpenAnythingHandler extends QuickOpenHandler {
// Find cache entries by prefix of search value
let cachedEntries: QuickOpenEntry[];
for (let previousSearch in this.resultsToSearchCache) {
// If we narrow down, we might be able to reuse the cached results
if (searchValue.indexOf(previousSearch) === 0) {
if (searchValue.indexOf(paths.nativeSep) >= 0 && previousSearch.indexOf(paths.nativeSep) < 0) {
continue; // since a path character widens the search for potential more matches, require it in previous search too
}
cachedEntries = this.resultsToSearchCache[previousSearch];
break;
}
......@@ -226,6 +235,7 @@ export class OpenAnythingHandler extends QuickOpenHandler {
// Pattern match on results and adjust highlights
let results: QuickOpenEntry[] = [];
const searchInPath = searchValue.indexOf(paths.nativeSep) >= 0;
for (let i = 0; i < cachedEntries.length; i++) {
let entry = cachedEntries[i];
......@@ -234,17 +244,21 @@ export class OpenAnythingHandler extends QuickOpenHandler {
continue;
}
// Check for pattern match
let highlights = filters.matchesFuzzy(searchValue, entry.getLabel());
if (highlights) {
entry.setHighlights(highlights);
results.push(entry);
// Check if this entry is a match for the search value
let targetToMatch = searchInPath ? labels.getPathLabel(entry.getResource(), this.contextService) : entry.getLabel();
if (!filters.matchesFuzzy(searchValue, targetToMatch)) {
continue;
}
// Apply highlights
const {labelHighlights, descriptionHighlights} = QuickOpenEntry.highlight(entry, searchValue);
entry.setHighlights(labelHighlights, descriptionHighlights);
results.push(entry);
}
// Sort
let normalizedSearchValue = strings.stripWildcards(searchValue.toLowerCase());
results.sort((elementA, elementB) => this.compareResults(elementA, elementB, normalizedSearchValue));
results.sort((elementA, elementB) => QuickOpenEntry.compare(elementA, elementB, searchValue));
// Apply Range
results.forEach((element) => {
......@@ -256,23 +270,6 @@ export class OpenAnythingHandler extends QuickOpenHandler {
return results;
}
private compareResults(elementA:QuickOpenEntry, elementB:QuickOpenEntry, searchValue: string): number {
let nameA = elementA.getLabel();
let nameB = elementB.getLabel();
if (nameA === nameB) {
let resourceA = elementA.getResource();
let resourceB = elementB.getResource();
if (resourceA && resourceB) {
nameA = elementA.getResource().fsPath;
nameB = elementB.getResource().fsPath;
}
}
return compareAnything(nameA, nameB, searchValue);
}
public getGroupLabel(): string {
return nls.localize('fileAndTypeResults', "file and symbol results");
}
......
......@@ -17,7 +17,6 @@ import {QuickOpenEntry, QuickOpenModel, IHighlight} from 'vs/base/parts/quickope
import filters = require('vs/base/common/filters');
import comparers = require('vs/base/common/comparers');
import {QuickOpenHandler, EditorQuickOpenEntry} from 'vs/workbench/browser/quickopen';
import {FileMatch, SearchResult} from 'vs/workbench/parts/search/common/searchModel';
import {QueryBuilder} from 'vs/workbench/parts/search/common/searchQuery';
import {ITextFileService} from 'vs/workbench/parts/files/common/files';
import {EditorInput} from 'vs/workbench/common/editor';
......@@ -34,7 +33,10 @@ export class FileEntry extends EditorQuickOpenEntry {
private resource: URI;
private range: IRange;
constructor(name: string, resource: URI, highlights: IHighlight[],
constructor(
name: string,
description: string,
resource: URI,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IInstantiationService private instantiationService: IInstantiationService,
@IWorkspaceContextService contextService: IWorkspaceContextService
......@@ -43,8 +45,7 @@ export class FileEntry extends EditorQuickOpenEntry {
this.resource = resource;
this.name = name;
this.description = labels.getPathLabel(paths.dirname(this.resource.fsPath), contextService);
this.setHighlights(highlights);
this.description = description;
}
public getLabel(): string {
......@@ -136,47 +137,31 @@ export class OpenFileHandler extends QuickOpenHandler {
let query: IQueryOptions = { filePattern: searchValue, rootResources: rootResources };
return this.queryBuilder.file(query).then((query) => this.searchService.search(query)).then((complete) => {
let searchResult = this.instantiationService.createInstance(SearchResult, null);
searchResult.append(complete.results);
// Sort (standalone only)
let matches = searchResult.matches();
if (this.isStandalone) {
matches = matches.sort((elementA, elementB) => this.sort(elementA, elementB, searchValue.toLowerCase()));
}
// Highlight
let results: QuickOpenEntry[] = [];
for (let i = 0; i < matches.length; i++) {
let fileMatch = matches[i];
let highlights = filters.matchesFuzzy(searchValue, fileMatch.name());
for (let i = 0; i < complete.results.length; i++) {
let fileMatch = complete.results[i];
results.push(this.instantiationService.createInstance(FileEntry, fileMatch.name(), fileMatch.resource(), highlights));
}
let label = paths.basename(fileMatch.resource.fsPath);
let description = labels.getPathLabel(paths.dirname(fileMatch.resource.fsPath), this.contextService);
return results;
});
}
let entry = this.instantiationService.createInstance(FileEntry, label, description, fileMatch.resource);
private sort(elementA: FileMatch, elementB: FileMatch, searchValue: string): number {
let elementAName = elementA.name().toLowerCase();
let elementBName = elementB.name().toLowerCase();
// Apply highlights
let {labelHighlights, descriptionHighlights} = QuickOpenEntry.highlight(entry, searchValue);
entry.setHighlights(labelHighlights, descriptionHighlights);
// Sort matches that have search value in beginning to the top
let elementAPrefixMatch = elementAName.indexOf(searchValue) === 0;
let elementBPrefixMatch = elementBName.indexOf(searchValue) === 0;
if (elementAPrefixMatch !== elementBPrefixMatch) {
return elementAPrefixMatch ? -1 : 1;
}
results.push(entry);
}
// Compare by name
let r = comparers.compareFileNames(elementAName, elementBName);
if (r !== 0) {
return r;
}
// Sort (standalone only)
if (this.isStandalone) {
results = results.sort((elementA, elementB) => QuickOpenEntry.compare(elementA, elementB, searchValue));
}
// Otherwise do full compare with path info
return strings.localeCompare(elementA.resource().fsPath, elementB.resource().fsPath);
return results;
});
}
public getGroupLabel(): string {
......
......@@ -140,10 +140,31 @@ actionBarRegistry.registerActionBarContributor(Scope.VIEWER, ExplorerViewerActio
'vs/workbench/parts/search/browser/openAnythingHandler',
'OpenAnythingHandler',
'',
env.isMacintosh ? nls.localize('openAnythingHandlerDescriptionMac', "Open Files and Symbols by Name") : nls.localize('openAnythingHandlerDescriptionWin', "Open Files and Symbols by Name")
nls.localize('openAnythingHandlerDescription', "Open Files and Symbols by Name")
)
);
(<IQuickOpenRegistry>Registry.as(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
'vs/workbench/parts/search/browser/openAnythingHandler',
'OpenSymbolHandler',
ALL_SYMBOLS_PREFIX,
[
{
prefix: ALL_SYMBOLS_PREFIX,
needsEditor: false,
description: nls.localize('openSymbolDescriptionNormal', "Open Symbol By Name")
}
]
)
);
// Actions
const registry = <IWorkbenchActionRegistry>Registry.as(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllSymbolsAction, ACTION_ID, ACTION_LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.KEY_T
}));
// Configuration
const configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
......@@ -178,24 +199,4 @@ configurationRegistry.registerConfiguration({
}
}
}
});
const registry = <IWorkbenchActionRegistry>Registry.as(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllSymbolsAction, ACTION_ID, ACTION_LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.KEY_T
}));
(<IQuickOpenRegistry>Registry.as(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
'vs/workbench/parts/search/browser/openAnythingHandler',
'OpenSymbolHandler',
ALL_SYMBOLS_PREFIX,
[
{
prefix: ALL_SYMBOLS_PREFIX,
needsEditor: false,
description: env.isMacintosh ? nls.localize('openSymbolDescriptionNormalMac', "Open Symbol By Name") : nls.localize('openSymbolDescriptionNormalWin', "Open Symbol By Name")
}
]
)
);
\ No newline at end of file
});
\ No newline at end of file
......@@ -31,6 +31,7 @@ export class FileWalker {
private isLimitHit: boolean;
private resultCount: number;
private isCanceled: boolean;
private searchInPath: boolean;
private walkedPaths: { [path: string]: boolean; };
......@@ -41,6 +42,12 @@ export class FileWalker {
this.includePattern = config.includePattern;
this.maxResults = config.maxResults || null;
this.walkedPaths = Object.create(null);
// Normalize file patterns to forward slashs
if (this.filePattern.indexOf(paths.sep) >= 0) {
this.filePattern = strings.replaceAll(this.filePattern, '\\', '/');
this.searchInPath = true;
}
}
private resetState(): void {
......@@ -81,7 +88,7 @@ export class FileWalker {
}
// Check for match on file pattern and include pattern
if (this.isFilePatternMatch(paths.basename(absolutePath)) && (!this.includePattern || glob.match(this.includePattern, absolutePath))) {
if (this.isFilePatternMatch(paths.basename(absolutePath), absolutePath) && (!this.includePattern || glob.match(this.includePattern, absolutePath))) {
this.resultCount++;
if (this.maxResults && this.resultCount > this.maxResults) {
......@@ -156,7 +163,7 @@ export class FileWalker {
if ((<any>error).code === FileWalker.ENOTDIR && !this.isCanceled && !this.isLimitHit) {
// Check for match on file pattern and include pattern
if (this.isFilePatternMatch(file) && (!this.includePattern || glob.match(this.includePattern, relativeFilePath, children))) {
if (this.isFilePatternMatch(file, relativeFilePath) && (!this.includePattern || glob.match(this.includePattern, relativeFilePath, children))) {
this.resultCount++;
if (this.maxResults && this.resultCount > this.maxResults) {
......@@ -183,11 +190,11 @@ export class FileWalker {
});
}
private isFilePatternMatch(path: string): boolean {
private isFilePatternMatch(name: string, path: string): boolean {
// Check for search pattern
if (this.filePattern) {
const res = filters.matchesFuzzy(this.filePattern, path);
const res = filters.matchesFuzzy(this.filePattern, this.searchInPath ? path : name);
return !!res && res.length > 0;
}
......
......@@ -9,6 +9,7 @@ import path = require('path');
import assert = require('assert');
import uri from 'vs/base/common/uri';
import {join, normalize} from 'vs/base/common/paths';
import {LineMatch} from 'vs/platform/search/common/search';
import {FileWalker, Engine as FileSearchEngine} from 'vs/workbench/services/search/node/fileSearch';
......@@ -47,6 +48,24 @@ suite('Search', () => {
});
});
test('Files: examples/com*', function(done: () => void) {
let engine = new FileSearchEngine({
rootPaths: [require.toUrl('./fixtures')],
filePattern: normalize(join('examples', 'com*'), true)
});
let count = 0;
engine.search((result) => {
if (result) {
count++;
}
}, () => { }, (error) => {
assert.ok(!error);
assert.equal(count, 1);
done();
});
});
test('Files: *.js (Files as roots)', function(done: () => void) {
let engine = new FileSearchEngine({
rootPaths: [require.toUrl('./fixtures/examples/company.js'), require.toUrl('./fixtures/examples/small.js')],
......
......@@ -43,7 +43,7 @@ suite('Workbench QuickOpen', () => {
let model = new EditorHistoryModel(editorService, null, contextService);
let input1 = inst.createInstance(StringEditorInput, "name1", 'description', "value1", "text/plain", false);
let entry1 = new EditorHistoryEntry(editorService, contextService, input1, null, model);
let entry1 = new EditorHistoryEntry(editorService, contextService, input1, null, null, model);
assert.equal(input1.getName(), entry1.getLabel());
assert.equal(input1.getDescription(), entry1.getDescription());
......@@ -63,7 +63,7 @@ suite('Workbench QuickOpen', () => {
let input2 = inst.createInstance(StringEditorInput, "name2", 'description', "value2", "text/plain", false);
(<any>input2).getResource = () => "path";
let entry2 = new EditorHistoryEntry(editorService, contextService, input2, null, model);
let entry2 = new EditorHistoryEntry(editorService, contextService, input2, null, null, model);
assert.equal(entry2.getResource(), "path");
assert(!entry1.matches(entry2.getInput()));
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册