mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-27 05:06:42 +00:00
fixed fuzzy. added proper boost.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
parent
fdc5814e66
commit
8aa08cbf6e
@ -42,34 +42,36 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
|||||||
const { fqbn } = selectedBoard;
|
const { fqbn } = selectedBoard;
|
||||||
if (fqbn) {
|
if (fqbn) {
|
||||||
const { configOptions, programmers, selectedProgrammer } = await this.boardsDataStore.getData(fqbn);
|
const { configOptions, programmers, selectedProgrammer } = await this.boardsDataStore.getData(fqbn);
|
||||||
const boardsConfigMenuPath = [...ArduinoMenus.TOOLS, 'z01_boardsConfig']; // `z_` is for ordering.
|
if (configOptions.length) {
|
||||||
for (const { label, option, values } of configOptions.sort(ConfigOption.LABEL_COMPARATOR)) {
|
const boardsConfigMenuPath = [...ArduinoMenus.TOOLS, 'z01_boardsConfig']; // `z_` is for ordering.
|
||||||
const menuPath = [...boardsConfigMenuPath, `${option}`];
|
for (const { label, option, values } of configOptions.sort(ConfigOption.LABEL_COMPARATOR)) {
|
||||||
const commands = new Map<string, Disposable & { label: string }>()
|
const menuPath = [...boardsConfigMenuPath, `${option}`];
|
||||||
for (const value of values) {
|
const commands = new Map<string, Disposable & { label: string }>()
|
||||||
const id = `${fqbn}-${option}--${value.value}`;
|
for (const value of values) {
|
||||||
const command = { id };
|
const id = `${fqbn}-${option}--${value.value}`;
|
||||||
const selectedValue = value.value;
|
const command = { id };
|
||||||
const handler = {
|
const selectedValue = value.value;
|
||||||
execute: () => this.boardsDataStore.selectConfigOption({ fqbn, option, selectedValue }),
|
const handler = {
|
||||||
isToggled: () => value.selected
|
execute: () => this.boardsDataStore.selectConfigOption({ fqbn, option, selectedValue }),
|
||||||
};
|
isToggled: () => value.selected
|
||||||
commands.set(id, Object.assign(this.commandRegistry.registerCommand(command, handler), { label: value.label }));
|
};
|
||||||
|
commands.set(id, Object.assign(this.commandRegistry.registerCommand(command, handler), { label: value.label }));
|
||||||
|
}
|
||||||
|
this.menuRegistry.registerSubmenu(menuPath, label);
|
||||||
|
this.toDisposeOnBoardChange.pushAll([
|
||||||
|
...commands.values(),
|
||||||
|
Disposable.create(() => this.unregisterSubmenu(menuPath)), // We cannot dispose submenu entries: https://github.com/eclipse-theia/theia/issues/7299
|
||||||
|
...Array.from(commands.keys()).map((commandId, i) => {
|
||||||
|
const { label } = commands.get(commandId)!;
|
||||||
|
this.menuRegistry.registerMenuAction(menuPath, { commandId, order: `${i}`, label });
|
||||||
|
return Disposable.create(() => this.menuRegistry.unregisterMenuAction(commandId));
|
||||||
|
})
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
this.menuRegistry.registerSubmenu(menuPath, label);
|
|
||||||
this.toDisposeOnBoardChange.pushAll([
|
|
||||||
...commands.values(),
|
|
||||||
Disposable.create(() => this.unregisterSubmenu(menuPath)), // We cannot dispose submenu entries: https://github.com/eclipse-theia/theia/issues/7299
|
|
||||||
...Array.from(commands.keys()).map((commandId, i) => {
|
|
||||||
const { label } = commands.get(commandId)!;
|
|
||||||
this.menuRegistry.registerMenuAction(menuPath, { commandId, order: `${i}`, label });
|
|
||||||
return Disposable.create(() => this.menuRegistry.unregisterMenuAction(commandId));
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
if (programmers.length) {
|
if (programmers.length) {
|
||||||
const programmersMenuPath = [...ArduinoMenus.TOOLS, 'z02_programmers'];
|
const programmersMenuPath = [...ArduinoMenus.TOOLS, 'z02_programmers'];
|
||||||
const label = selectedProgrammer ? `Programmer: ${selectedProgrammer.name}` : 'Programmer'
|
const label = selectedProgrammer ? `Programmer: "${selectedProgrammer.name}"` : 'Programmer'
|
||||||
this.menuRegistry.registerSubmenu(programmersMenuPath, label);
|
this.menuRegistry.registerSubmenu(programmersMenuPath, label);
|
||||||
for (const programmer of programmers) {
|
for (const programmer of programmers) {
|
||||||
const { id, name } = programmer;
|
const { id, name } = programmer;
|
||||||
|
@ -8,6 +8,12 @@ import { RecursiveRequired } from '../../common/types';
|
|||||||
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, Board, Port, BoardUninstalledEvent, BoardsService } from '../../common/protocol';
|
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, Board, Port, BoardUninstalledEvent, BoardsService } from '../../common/protocol';
|
||||||
import { BoardsConfig } from './boards-config';
|
import { BoardsConfig } from './boards-config';
|
||||||
import { naturalCompare } from '../../common/utils';
|
import { naturalCompare } from '../../common/utils';
|
||||||
|
import { compareAnything } from '../theia/monaco/comparers';
|
||||||
|
|
||||||
|
interface BoardMatch {
|
||||||
|
readonly board: Board & Readonly<{ packageName: string }>;
|
||||||
|
readonly matches: monaco.filters.IMatch[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApplicationContribution {
|
export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApplicationContribution {
|
||||||
@ -168,13 +174,28 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
|||||||
const coresFilter = !!cores && cores.length
|
const coresFilter = !!cores && cores.length
|
||||||
? ((toFilter: { packageName: string }) => cores.some(core => core === toFilter.packageName))
|
? ((toFilter: { packageName: string }) => cores.some(core => core === toFilter.packageName))
|
||||||
: () => true;
|
: () => true;
|
||||||
const fuzzyFilter = !!query
|
if (!query) {
|
||||||
? ((toFilter: Board) => !!monaco.filters.matchesFuzzy(query, toFilter.name, true))
|
return boards.filter(coresFilter).sort(Board.compare);
|
||||||
: () => true
|
}
|
||||||
return boards
|
const toMatch = ((toFilter: Board & { packageName: string }) => (({ board: toFilter, matches: monaco.filters.matchesFuzzy(query, toFilter.name, true) })));
|
||||||
.filter(coresFilter)
|
const compareEntries = (left: BoardMatch, right: BoardMatch, lookFor: string) => {
|
||||||
.filter(fuzzyFilter)
|
const leftMatches = left.matches || [];
|
||||||
.sort(Board.compare)
|
const rightMatches = right.matches || [];
|
||||||
|
if (leftMatches.length && !rightMatches.length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!leftMatches.length && rightMatches.length) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (leftMatches.length === 0 && rightMatches.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const leftLabel = left.board.name.replace(/\r?\n/g, ' ');
|
||||||
|
const rightLabel = right.board.name.replace(/\r?\n/g, ' ');
|
||||||
|
return compareAnything(leftLabel, rightLabel, lookFor);
|
||||||
|
}
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
return boards.filter(coresFilter).map(toMatch).sort((left, right) => compareEntries(left, right, normalizedQuery)).map(({ board }) => board);
|
||||||
}
|
}
|
||||||
|
|
||||||
get boardsConfig(): BoardsConfig.Config {
|
get boardsConfig(): BoardsConfig.Config {
|
||||||
|
@ -219,7 +219,7 @@ ${value}
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async run(commandId: string): Promise<any> {
|
protected async run(commandId: string): Promise<any> {
|
||||||
const editor = await this.current();
|
const editor = await this.current(); // TODO: this should be the active monaco editor and not Theia editor. e.g: Output
|
||||||
if (editor) {
|
if (editor) {
|
||||||
const action = editor.getControl().getAction(commandId);
|
const action = editor.getControl().getAction(commandId);
|
||||||
if (action) {
|
if (action) {
|
||||||
|
367
arduino-ide-extension/src/browser/theia/monaco/comparers.ts
Normal file
367
arduino-ide-extension/src/browser/theia/monaco/comparers.ts
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
// Copied from https://github.com/microsoft/vscode/blob/724c307bf35646ac549a8533a255c51b63fea5c7/src/vs/base/common/comparers.ts
|
||||||
|
// We cannot customize the monaco loader for Theia: https://github.com/eclipse-theia/theia/issues/8220
|
||||||
|
|
||||||
|
import { isWindows } from '@theia/core/lib/common/os';
|
||||||
|
|
||||||
|
const sep = (isWindows ? '\\' : '/');
|
||||||
|
interface IDisposable {
|
||||||
|
dispose(): void;
|
||||||
|
}
|
||||||
|
interface IdleDeadline {
|
||||||
|
readonly didTimeout: boolean;
|
||||||
|
timeRemaining(): number;
|
||||||
|
}
|
||||||
|
let runWhenIdle: (callback: (idle: IdleDeadline) => void, timeout?: number) => IDisposable;
|
||||||
|
declare function requestIdleCallback(callback: (args: IdleDeadline) => void, options?: { timeout: number }): number;
|
||||||
|
declare function cancelIdleCallback(handle: number): void;
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
if (typeof requestIdleCallback !== 'function' || typeof cancelIdleCallback !== 'function') {
|
||||||
|
const dummyIdle: IdleDeadline = Object.freeze({
|
||||||
|
didTimeout: true,
|
||||||
|
timeRemaining() { return 15; }
|
||||||
|
});
|
||||||
|
runWhenIdle = (runner) => {
|
||||||
|
const handle = setTimeout(() => runner(dummyIdle));
|
||||||
|
let disposed = false;
|
||||||
|
return {
|
||||||
|
dispose() {
|
||||||
|
if (disposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
disposed = true;
|
||||||
|
clearTimeout(handle);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
runWhenIdle = (runner, timeout?) => {
|
||||||
|
const handle: number = requestIdleCallback(runner, typeof timeout === 'number' ? { timeout } : undefined);
|
||||||
|
let disposed = false;
|
||||||
|
return {
|
||||||
|
dispose() {
|
||||||
|
if (disposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
disposed = true;
|
||||||
|
cancelIdleCallback(handle);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of the "idle-until-urgent"-strategy as introduced
|
||||||
|
* here: https://philipwalton.com/articles/idle-until-urgent/
|
||||||
|
*/
|
||||||
|
class IdleValue<T> {
|
||||||
|
|
||||||
|
private readonly _executor: () => void;
|
||||||
|
private readonly _handle: IDisposable;
|
||||||
|
|
||||||
|
private _didRun: boolean = false;
|
||||||
|
private _value?: T;
|
||||||
|
private _error: any;
|
||||||
|
|
||||||
|
constructor(executor: () => T) {
|
||||||
|
this._executor = () => {
|
||||||
|
try {
|
||||||
|
this._value = executor();
|
||||||
|
} catch (err) {
|
||||||
|
this._error = err;
|
||||||
|
} finally {
|
||||||
|
this._didRun = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._handle = runWhenIdle(() => this._executor());
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this._handle.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): T {
|
||||||
|
if (!this._didRun) {
|
||||||
|
this._handle.dispose();
|
||||||
|
this._executor();
|
||||||
|
}
|
||||||
|
if (this._error) {
|
||||||
|
throw this._error;
|
||||||
|
}
|
||||||
|
return this._value!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When comparing large numbers of strings, such as in sorting large arrays, is better for
|
||||||
|
// performance to create an Intl.Collator object and use the function provided by its compare
|
||||||
|
// property than it is to use String.prototype.localeCompare()
|
||||||
|
|
||||||
|
// A collator with numeric sorting enabled, and no sensitivity to case or to accents
|
||||||
|
const intlFileNameCollatorBaseNumeric: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }> = new IdleValue(() => {
|
||||||
|
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
|
||||||
|
return {
|
||||||
|
collator: collator,
|
||||||
|
collatorIsNumeric: collator.resolvedOptions().numeric
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// A collator with numeric sorting enabled.
|
||||||
|
const intlFileNameCollatorNumeric: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => {
|
||||||
|
const collator = new Intl.Collator(undefined, { numeric: true });
|
||||||
|
return {
|
||||||
|
collator: collator
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// A collator with numeric sorting enabled, and sensitivity to accents and diacritics but not case.
|
||||||
|
const intlFileNameCollatorNumericCaseInsenstive: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => {
|
||||||
|
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'accent' });
|
||||||
|
return {
|
||||||
|
collator: collator
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
|
||||||
|
const a = one || '';
|
||||||
|
const b = other || '';
|
||||||
|
const result = intlFileNameCollatorBaseNumeric.value.collator.compare(a, b);
|
||||||
|
|
||||||
|
// Using the numeric option in the collator will
|
||||||
|
// make compare(`foo1`, `foo01`) === 0. We must disambiguate.
|
||||||
|
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && a !== b) {
|
||||||
|
return a < b ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
|
||||||
|
|
||||||
|
export function noIntlCompareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
|
||||||
|
if (!caseSensitive) {
|
||||||
|
one = one && one.toLowerCase();
|
||||||
|
other = other && other.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
const [oneName, oneExtension] = extractNameAndExtension(one);
|
||||||
|
const [otherName, otherExtension] = extractNameAndExtension(other);
|
||||||
|
|
||||||
|
if (oneName !== otherName) {
|
||||||
|
return oneName < otherName ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oneExtension === otherExtension) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return oneExtension < otherExtension ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compareFileExtensions(one: string | null, other: string | null): number {
|
||||||
|
const [oneName, oneExtension] = extractNameAndExtension(one);
|
||||||
|
const [otherName, otherExtension] = extractNameAndExtension(other);
|
||||||
|
|
||||||
|
let result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneExtension, otherExtension);
|
||||||
|
|
||||||
|
if (result === 0) {
|
||||||
|
// Using the numeric option in the collator will
|
||||||
|
// make compare(`foo1`, `foo01`) === 0. We must disambiguate.
|
||||||
|
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && oneExtension !== otherExtension) {
|
||||||
|
return oneExtension < otherExtension ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extensions are equal, compare filenames
|
||||||
|
result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneName, otherName);
|
||||||
|
|
||||||
|
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && oneName !== otherName) {
|
||||||
|
return oneName < otherName ? -1 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
|
||||||
|
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
// Check for extension differences, ignoring differences in case and comparing numbers numerically.
|
||||||
|
result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension);
|
||||||
|
if (result !== 0) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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>);
|
||||||
|
|
||||||
|
let result: [string, string] = [(match && match[1]) || '', (match && match[3]) || ''];
|
||||||
|
|
||||||
|
// if the dotfilesAsNames option is selected, treat an empty filename with an extension,
|
||||||
|
// or a filename that starts with a dot, as a dotfile name
|
||||||
|
if (dotfilesAsNames && (!result[0] && result[1] || result[0] && result[0].charAt(0) === '.')) {
|
||||||
|
result = [result[0] + '.' + result[1], ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareAndDisambiguateByLength(collator: Intl.Collator, one: string, other: string) {
|
||||||
|
// Check for differences
|
||||||
|
let result = collator.compare(one, other);
|
||||||
|
if (result !== 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In a numeric comparison, `foo1` and `foo01` will compare as equivalent.
|
||||||
|
// Disambiguate by sorting the shorter string first.
|
||||||
|
if (one.length !== other.length) {
|
||||||
|
return one.length < other.length ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function comparePathComponents(one: string, other: string, caseSensitive = false): number {
|
||||||
|
if (!caseSensitive) {
|
||||||
|
one = one && one.toLowerCase();
|
||||||
|
other = other && other.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (one === other) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return one < other ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function comparePaths(one: string, other: string, caseSensitive = false): number {
|
||||||
|
const oneParts = one.split(sep);
|
||||||
|
const otherParts = other.split(sep);
|
||||||
|
|
||||||
|
const lastOne = oneParts.length - 1;
|
||||||
|
const lastOther = otherParts.length - 1;
|
||||||
|
let endOne: boolean, endOther: boolean;
|
||||||
|
|
||||||
|
for (let i = 0; ; i++) {
|
||||||
|
endOne = lastOne === i;
|
||||||
|
endOther = lastOther === i;
|
||||||
|
|
||||||
|
if (endOne && endOther) {
|
||||||
|
return compareFileNames(oneParts[i], otherParts[i], caseSensitive);
|
||||||
|
} else if (endOne) {
|
||||||
|
return -1;
|
||||||
|
} else if (endOther) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = comparePathComponents(oneParts[i], otherParts[i], caseSensitive);
|
||||||
|
|
||||||
|
if (result !== 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compareAnything(one: string, other: string, lookFor: string): number {
|
||||||
|
const elementAName = one.toLowerCase();
|
||||||
|
const elementBName = other.toLowerCase();
|
||||||
|
|
||||||
|
// Sort prefix matches over non prefix matches
|
||||||
|
const prefixCompare = compareByPrefix(one, other, lookFor);
|
||||||
|
if (prefixCompare) {
|
||||||
|
return prefixCompare;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort suffix matches over non suffix matches
|
||||||
|
const elementASuffixMatch = elementAName.endsWith(lookFor);
|
||||||
|
const elementBSuffixMatch = elementBName.endsWith(lookFor);
|
||||||
|
if (elementASuffixMatch !== elementBSuffixMatch) {
|
||||||
|
return elementASuffixMatch ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Understand file names
|
||||||
|
const r = compareFileNames(elementAName, elementBName);
|
||||||
|
if (r !== 0) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare by name
|
||||||
|
return elementAName.localeCompare(elementBName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compareByPrefix(one: string, other: string, lookFor: string): number {
|
||||||
|
const elementAName = one.toLowerCase();
|
||||||
|
const elementBName = other.toLowerCase();
|
||||||
|
|
||||||
|
// Sort prefix matches over non prefix matches
|
||||||
|
const elementAPrefixMatch = elementAName.startsWith(lookFor);
|
||||||
|
const elementBPrefixMatch = elementBName.startsWith(lookFor);
|
||||||
|
if (elementAPrefixMatch !== elementBPrefixMatch) {
|
||||||
|
return elementAPrefixMatch ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same prefix: Sort shorter matches to the top to have those on top that match more precisely
|
||||||
|
else if (elementAPrefixMatch && elementBPrefixMatch) {
|
||||||
|
if (elementAName.length < elementBName.length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elementAName.length > elementBName.length) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -276,6 +276,7 @@ export interface Programmer {
|
|||||||
readonly id: string;
|
readonly id: string;
|
||||||
}
|
}
|
||||||
export namespace Programmer {
|
export namespace Programmer {
|
||||||
|
|
||||||
export function equals(left: Programmer | undefined, right: Programmer | undefined): boolean {
|
export function equals(left: Programmer | undefined, right: Programmer | undefined): boolean {
|
||||||
if (!left) {
|
if (!left) {
|
||||||
return !right;
|
return !right;
|
||||||
@ -285,6 +286,15 @@ export namespace Programmer {
|
|||||||
}
|
}
|
||||||
return left.id === right.id && left.name === right.name && left.platform === right.platform;
|
return left.id === right.id && left.name === right.name && left.platform === right.platform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toString({ id, platform }: Programmer): string {
|
||||||
|
const [vendor,] = platform.split('@');
|
||||||
|
if (!vendor) {
|
||||||
|
throw new Error(`Could not extract vendor from platform: '${platform}'.`);
|
||||||
|
}
|
||||||
|
return `${vendor}:${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Board {
|
export namespace Board {
|
||||||
@ -357,5 +367,4 @@ export namespace Board {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { inject, injectable, postConstruct } from 'inversify';
|
|||||||
import { FileSystem } from '@theia/filesystem/lib/common/filesystem';
|
import { FileSystem } from '@theia/filesystem/lib/common/filesystem';
|
||||||
import { CoreService, CoreServiceClient } from '../common/protocol/core-service';
|
import { CoreService, CoreServiceClient } from '../common/protocol/core-service';
|
||||||
import { CompileReq, CompileResp } from './cli-protocol/commands/compile_pb';
|
import { CompileReq, CompileResp } from './cli-protocol/commands/compile_pb';
|
||||||
import { BoardsService } from '../common/protocol/boards-service';
|
import { BoardsService, Programmer } from '../common/protocol/boards-service';
|
||||||
import { CoreClientProvider } from './core-client-provider';
|
import { CoreClientProvider } from './core-client-provider';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { ToolOutputServiceServer } from '../common/protocol/tool-output-service';
|
import { ToolOutputServiceServer } from '../common/protocol/tool-output-service';
|
||||||
@ -62,7 +62,7 @@ export class CoreServiceImpl implements CoreService {
|
|||||||
compilerReq.setVerbose(true);
|
compilerReq.setVerbose(true);
|
||||||
compilerReq.setQuiet(false);
|
compilerReq.setQuiet(false);
|
||||||
if (options.programmer) {
|
if (options.programmer) {
|
||||||
compilerReq.setProgrammer(options.programmer.id);
|
compilerReq.setProgrammer(Programmer.toString(options.programmer));
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = client.compile(compilerReq);
|
const result = client.compile(compilerReq);
|
||||||
@ -108,7 +108,7 @@ export class CoreServiceImpl implements CoreService {
|
|||||||
uploadReq.setFqbn(fqbn);
|
uploadReq.setFqbn(fqbn);
|
||||||
uploadReq.setPort(options.port);
|
uploadReq.setPort(options.port);
|
||||||
if (options.programmer) {
|
if (options.programmer) {
|
||||||
uploadReq.setProgrammer(options.programmer.id);
|
uploadReq.setProgrammer(Programmer.toString(options.programmer));
|
||||||
}
|
}
|
||||||
const result = client.upload(uploadReq);
|
const result = client.upload(uploadReq);
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ export class SketchesServiceImpl implements SketchesService, BackendApplicationC
|
|||||||
&& fs.lstatSync(filePath).isFile()) {
|
&& fs.lstatSync(filePath).isFile()) {
|
||||||
const uri = FileUri.create(filePath).toString();
|
const uri = FileUri.create(filePath).toString();
|
||||||
if (fileName === basename + '.ino') {
|
if (fileName === basename + '.ino') {
|
||||||
uris.unshift(uri);
|
uris.unshift(uri); // The sketch file is the first.
|
||||||
} else {
|
} else {
|
||||||
uris.push(uri);
|
uris.push(uri);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user