atom-languageclient
Version:
Integrate Language Servers with Atom
440 lines • 75 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.grammarScopeToAutoCompleteSelector = void 0;
const convert_1 = require("../convert");
const Utils = require("../utils");
const zadeh_1 = require("zadeh");
const languageclient_1 = require("../languageclient");
const apply_edit_adapter_1 = require("./apply-edit-adapter");
const atom_1 = require("atom");
class PossiblyResolvedCompletionItem {
constructor(completionItem, isResolved) {
this.completionItem = completionItem;
this.isResolved = isResolved;
}
}
/** Public: Adapts the language server protocol "textDocument/completion" to the Atom AutoComplete+ package. */
class AutocompleteAdapter {
constructor() {
this._suggestionCache = new WeakMap();
this._cancellationTokens = new WeakMap();
}
static canAdapt(serverCapabilities) {
return serverCapabilities.completionProvider != null;
}
static canResolve(serverCapabilities) {
return (serverCapabilities.completionProvider != null && serverCapabilities.completionProvider.resolveProvider === true);
}
/**
* Public: Obtain suggestion list for AutoComplete+ by querying the language server using the `textDocument/completion` request.
*
* @param server An {ActiveServer} pointing to the language server to query.
* @param request The {atom$AutocompleteRequest} to satisfy.
* @param onDidConvertCompletionItem An optional function that takes a {CompletionItem}, an
* {atom$AutocompleteSuggestion} and a {atom$AutocompleteRequest} allowing you to adjust converted items.
* @param shouldReplace The behavior of suggestion acceptance (see {ShouldReplace}).
* @returns A {Promise} of an {Array} of {atom$AutocompleteSuggestion}s containing the AutoComplete+ suggestions to display.
*/
getSuggestions(server, request, onDidConvertCompletionItem, minimumWordLength, shouldReplace = false) {
return __awaiter(this, void 0, void 0, function* () {
const triggerChars = server.capabilities.completionProvider != null
? server.capabilities.completionProvider.triggerCharacters || []
: [];
// triggerOnly is true if we have just typed in a trigger character, and is false if we
// have typed additional characters following a trigger character.
const [triggerChar, triggerOnly] = AutocompleteAdapter.getTriggerCharacter(request, triggerChars);
if (!this.shouldTrigger(request, triggerChar, minimumWordLength || 0)) {
return [];
}
// Get the suggestions either from the cache or by calling the language server
const suggestions = yield this.getOrBuildSuggestions(server, request, triggerChar, triggerOnly, shouldReplace, onDidConvertCompletionItem);
// We must update the replacement prefix as characters are added and removed
const cache = this._suggestionCache.get(server);
const replacementPrefix = request.editor.getTextInBufferRange([
[cache.triggerPoint.row, cache.triggerPoint.column + cache.triggerChar.length],
request.bufferPosition,
]);
for (const suggestion of suggestions) {
if (suggestion.customReplacmentPrefix) {
// having this property means a custom range was provided
const len = replacementPrefix.length;
const preReplacementPrefix = suggestion.customReplacmentPrefix +
replacementPrefix.substring(len + cache.originalBufferPoint.column - request.bufferPosition.column, len);
// we cannot replace text after the cursor with the current autocomplete-plus API
// so we will simply ignore it for now
suggestion.replacementPrefix = preReplacementPrefix;
}
else {
suggestion.replacementPrefix = replacementPrefix;
}
}
const filtered = !(request.prefix === "" || (triggerChar !== "" && triggerOnly));
if (filtered) {
// filter the suggestions who have `filterText` property
const validSuggestions = suggestions.filter((sgs) => typeof sgs.filterText === "string");
// TODO use `ObjectArrayFilterer.setCandidate` in `_suggestionCache` to avoid creating `ObjectArrayFilterer` every time from scratch
const objFilterer = new zadeh_1.ObjectArrayFilterer(validSuggestions, "filterText");
// zadeh returns an array of the selected `Suggestions`
return objFilterer.filter(request.prefix);
}
else {
return suggestions;
}
});
}
shouldTrigger(request, triggerChar, minWordLength) {
return (request.activatedManually || triggerChar !== "" || minWordLength <= 0 || request.prefix.length >= minWordLength);
}
getOrBuildSuggestions(server, request, triggerChar, triggerOnly, shouldReplace, onDidConvertCompletionItem) {
return __awaiter(this, void 0, void 0, function* () {
const cache = this._suggestionCache.get(server);
const triggerColumn = triggerChar !== "" && triggerOnly
? request.bufferPosition.column - triggerChar.length
: request.bufferPosition.column - request.prefix.length - triggerChar.length;
const triggerPoint = new atom_1.Point(request.bufferPosition.row, triggerColumn);
// Do we have complete cached suggestions that are still valid for this request?
if (cache &&
!cache.isIncomplete &&
cache.triggerChar === triggerChar &&
cache.triggerPoint.isEqual(triggerPoint) &&
cache.originalBufferPoint.isLessThanOrEqual(request.bufferPosition)) {
return Array.from(cache.suggestionMap.keys());
}
// Our cached suggestions can't be used so obtain new ones from the language server
const completions = yield Utils.doWithCancellationToken(server.connection, this._cancellationTokens, (cancellationToken) => server.connection.completion(AutocompleteAdapter.createCompletionParams(request, triggerChar, triggerOnly), cancellationToken));
// spec guarantees all edits are on the same line, so we only need to check the columns
const triggerColumns = [triggerPoint.column, request.bufferPosition.column];
// Setup the cache for subsequent filtered results
const isComplete = completions === null || Array.isArray(completions) || !completions.isIncomplete;
const suggestionMap = this.completionItemsToSuggestions(completions, request, triggerColumns, shouldReplace, onDidConvertCompletionItem);
this._suggestionCache.set(server, {
isIncomplete: !isComplete,
triggerChar,
triggerPoint,
originalBufferPoint: request.bufferPosition,
suggestionMap,
});
return Array.from(suggestionMap.keys());
});
}
/**
* Public: Obtain a complete version of a suggestion with additional information the language server can provide by
* way of the `completionItem/resolve` request.
*
* @param server An {ActiveServer} pointing to the language server to query.
* @param suggestion An {atom$AutocompleteSuggestion} suggestion that should be resolved.
* @param request An {Object} with the AutoComplete+ request to satisfy.
* @param onDidConvertCompletionItem An optional function that takes a {CompletionItem}, an
* {atom$AutocompleteSuggestion} and a {atom$AutocompleteRequest} allowing you to adjust converted items.
* @returns A {Promise} of an {atom$AutocompleteSuggestion} with the resolved AutoComplete+ suggestion.
*/
completeSuggestion(server, suggestion, request, onDidConvertCompletionItem) {
return __awaiter(this, void 0, void 0, function* () {
const cache = this._suggestionCache.get(server);
if (cache) {
const possiblyResolvedCompletionItem = cache.suggestionMap.get(suggestion);
if (possiblyResolvedCompletionItem != null && !possiblyResolvedCompletionItem.isResolved) {
const resolvedCompletionItem = yield server.connection.completionItemResolve(possiblyResolvedCompletionItem.completionItem);
if (resolvedCompletionItem != null) {
AutocompleteAdapter.resolveSuggestion(resolvedCompletionItem, suggestion, request, onDidConvertCompletionItem);
possiblyResolvedCompletionItem.isResolved = true;
}
}
}
return suggestion;
});
}
static resolveSuggestion(resolvedCompletionItem, suggestion, request, onDidConvertCompletionItem) {
// only the `documentation` and `detail` properties may change when resolving
AutocompleteAdapter.applyDetailsToSuggestion(resolvedCompletionItem, suggestion);
if (onDidConvertCompletionItem != null) {
onDidConvertCompletionItem(resolvedCompletionItem, suggestion, request);
}
}
/**
* Public: Get the trigger character that caused the autocomplete (if any). This is required because AutoComplete-plus
* does not have trigger characters. Although the terminology is 'character' we treat them as variable length strings
* as this will almost certainly change in the future to support '->' etc.
*
* @param request An {Array} of {atom$AutocompleteSuggestion}s to locate the prefix, editor, bufferPosition etc.
* @param triggerChars The {Array} of {string}s that can be trigger characters.
* @returns A [{string}, boolean] where the string is the matching trigger character or an empty string if one was not
* matched, and the boolean is true if the trigger character is in request.prefix, and false if it is in the word
* before request.prefix. The boolean return value has no meaning if the string return value is an empty string.
*/
static getTriggerCharacter(request, triggerChars) {
// AutoComplete-Plus considers text after a symbol to be a new trigger. So we should look backward
// from the current cursor position to see if one is there and thus simulate it.
const buffer = request.editor.getBuffer();
const cursor = request.bufferPosition;
const prefixStartColumn = cursor.column - request.prefix.length;
for (const triggerChar of triggerChars) {
if (request.prefix.endsWith(triggerChar)) {
return [triggerChar, true];
}
if (prefixStartColumn >= triggerChar.length) {
// Far enough along a line to fit the trigger char
const start = new atom_1.Point(cursor.row, prefixStartColumn - triggerChar.length);
const possibleTrigger = buffer.getTextInRange([start, [cursor.row, prefixStartColumn]]);
if (possibleTrigger === triggerChar) {
// The text before our trigger is a trigger char!
return [triggerChar, false];
}
}
}
// There was no explicit trigger char
return ["", false];
}
/**
* Public: Create TextDocumentPositionParams to be sent to the language server based on the editor and position from
* the AutoCompleteRequest.
*
* @param request The {atom$AutocompleteRequest} to obtain the editor from.
* @param triggerPoint The {atom$Point} where the trigger started.
* @returns A {string} containing the prefix including the trigger character.
*/
static getPrefixWithTrigger(request, triggerPoint) {
return request.editor.getBuffer().getTextInRange([[triggerPoint.row, triggerPoint.column], request.bufferPosition]);
}
/**
* Public: Create {CompletionParams} to be sent to the language server based on the editor and position from the
* Autocomplete request etc.
*
* @param request The {atom$AutocompleteRequest} containing the request details.
* @param triggerCharacter The {string} containing the trigger character (empty if none).
* @param triggerOnly A {boolean} representing whether this completion is triggered right after a trigger character.
* @returns A {CompletionParams} with the keys:
*
* - `textDocument` the language server protocol textDocument identification.
* - `position` the position within the text document to display completion request for.
* - `context` containing the trigger character and kind.
*/
static createCompletionParams(request, triggerCharacter, triggerOnly) {
return {
textDocument: convert_1.default.editorToTextDocumentIdentifier(request.editor),
position: convert_1.default.pointToPosition(request.bufferPosition),
context: AutocompleteAdapter.createCompletionContext(triggerCharacter, triggerOnly),
};
}
/**
* Public: Create {CompletionContext} to be sent to the language server based on the trigger character.
*
* @param triggerCharacter The {string} containing the trigger character or '' if none.
* @param triggerOnly A {boolean} representing whether this completion is triggered right after a trigger character.
* @returns An {CompletionContext} that specifies the triggerKind and the triggerCharacter if there is one.
*/
static createCompletionContext(triggerCharacter, triggerOnly) {
if (triggerCharacter === "") {
return { triggerKind: languageclient_1.CompletionTriggerKind.Invoked };
}
else {
return triggerOnly
? { triggerKind: languageclient_1.CompletionTriggerKind.TriggerCharacter, triggerCharacter }
: { triggerKind: languageclient_1.CompletionTriggerKind.TriggerForIncompleteCompletions, triggerCharacter };
}
}
/**
* Public: Convert a language server protocol CompletionItem array or CompletionList to an array of ordered
* AutoComplete+ suggestions.
*
* @param completionItems An {Array} of {CompletionItem} objects or a {CompletionList} containing completion items to
* be converted.
* @param request The {atom$AutocompleteRequest} to satisfy.
* @param shouldReplace The behavior of suggestion acceptance (see {ShouldReplace}).
* @param onDidConvertCompletionItem A function that takes a {CompletionItem}, an {atom$AutocompleteSuggestion} and a
* {atom$AutocompleteRequest} allowing you to adjust converted items.
* @returns A {Map} of AutoComplete+ suggestions ordered by the CompletionItems sortText.
*/
completionItemsToSuggestions(completionItems, request, triggerColumns, shouldReplace, onDidConvertCompletionItem) {
const completionsArray = Array.isArray(completionItems)
? completionItems
: (completionItems && completionItems.items) || [];
return new Map(completionsArray
.sort((a, b) => (a.sortText || a.label).localeCompare(b.sortText || b.label))
.map((s) => [
AutocompleteAdapter.completionItemToSuggestion(s, {}, request, triggerColumns, shouldReplace, onDidConvertCompletionItem),
new PossiblyResolvedCompletionItem(s, false),
]));
}
/**
* Public: Convert a language server protocol CompletionItem to an AutoComplete+ suggestion.
*
* @param item An {CompletionItem} containing a completion item to be converted.
* @param suggestion A {atom$AutocompleteSuggestion} to have the conversion applied to.
* @param request The {atom$AutocompleteRequest} to satisfy.
* @param shouldReplace The behavior of suggestion acceptance (see {ShouldReplace}).
* @param onDidConvertCompletionItem A function that takes a {CompletionItem}, an {atom$AutocompleteSuggestion} and a
* {atom$AutocompleteRequest} allowing you to adjust converted items.
* @returns The {atom$AutocompleteSuggestion} passed in as suggestion with the conversion applied.
*/
static completionItemToSuggestion(item, suggestion, request, triggerColumns, shouldReplace, onDidConvertCompletionItem) {
AutocompleteAdapter.applyCompletionItemToSuggestion(item, suggestion);
AutocompleteAdapter.applyTextEditToSuggestion(item.textEdit, request.editor, triggerColumns, request.bufferPosition, suggestion, shouldReplace);
AutocompleteAdapter.applySnippetToSuggestion(item, suggestion);
if (onDidConvertCompletionItem != null) {
onDidConvertCompletionItem(item, suggestion, request);
}
return suggestion;
}
/**
* Public: Convert the primary parts of a language server protocol CompletionItem to an AutoComplete+ suggestion.
*
* @param item An {CompletionItem} containing the completion items to be merged into.
* @param suggestion The {Suggestion} to merge the conversion into.
* @returns The {Suggestion} with details added from the {CompletionItem}.
*/
static applyCompletionItemToSuggestion(item, suggestion) {
suggestion.text = item.insertText || item.label;
suggestion.filterText = item.filterText || item.label;
suggestion.displayText = item.label;
suggestion.type = AutocompleteAdapter.completionKindToSuggestionType(item.kind);
AutocompleteAdapter.applyDetailsToSuggestion(item, suggestion);
suggestion.completionItem = item;
}
static applyDetailsToSuggestion(item, suggestion) {
suggestion.rightLabel = item.detail;
// Older format, can't know what it is so assign to both and hope for best
if (typeof item.documentation === "string") {
suggestion.descriptionMarkdown = item.documentation;
suggestion.description = item.documentation;
}
if (item.documentation != null && typeof item.documentation === "object") {
// Newer format specifies the kind of documentation, assign appropriately
if (item.documentation.kind === "markdown") {
suggestion.descriptionMarkdown = item.documentation.value;
}
else {
suggestion.description = item.documentation.value;
}
}
}
/**
* Public: Applies the textEdit part of a language server protocol CompletionItem to an AutoComplete+ Suggestion via
* the replacementPrefix and text properties.
*
* @param textEdit A {TextEdit} from a CompletionItem to apply.
* @param editor An Atom {TextEditor} used to obtain the necessary text replacement.
* @param suggestion An {atom$AutocompleteSuggestion} to set the replacementPrefix and text properties of.
* @param shouldReplace The behavior of suggestion acceptance (see {ShouldReplace}).
*/
static applyTextEditToSuggestion(textEdit, editor, triggerColumns, originalBufferPosition, suggestion, shouldReplace) {
if (!textEdit) {
return;
}
let range;
if ("range" in textEdit) {
range = textEdit.range;
}
else if (shouldReplace) {
range = textEdit.replace;
}
else {
range = textEdit.insert;
}
if (range.start.character !== triggerColumns[0]) {
const atomRange = convert_1.default.lsRangeToAtomRange(range);
suggestion.customReplacmentPrefix = editor.getTextInBufferRange([atomRange.start, originalBufferPosition]);
}
suggestion.text = textEdit.newText;
}
/**
* Handle additional text edits after a suggestion insert, e.g. `additionalTextEdits`.
*
* `additionalTextEdits` are An optional array of additional text edits that are applied when selecting this
* completion. Edits must not overlap (including the same insert position) with the main edit nor with themselves.
*
* Additional text edits should be used to change text unrelated to the current cursor position (for example adding an
* import statement at the top of the file if the completion item will insert an unqualified type).
*/
static applyAdditionalTextEdits(event) {
var _a;
const suggestion = event.suggestion;
const additionalEdits = (_a = suggestion.completionItem) === null || _a === void 0 ? void 0 : _a.additionalTextEdits;
const buffer = event.editor.getBuffer();
apply_edit_adapter_1.default.applyEdits(buffer, convert_1.default.convertLsTextEdits(additionalEdits));
buffer.groupLastChanges();
}
/**
* Public: Adds a snippet to the suggestion if the CompletionItem contains snippet-formatted text
*
* @param item An {CompletionItem} containing the completion items to be merged into.
* @param suggestion The {atom$AutocompleteSuggestion} to merge the conversion into.
*/
static applySnippetToSuggestion(item, suggestion) {
if (item.insertTextFormat === languageclient_1.InsertTextFormat.Snippet) {
suggestion.snippet = item.textEdit != null ? item.textEdit.newText : item.insertText || item.label;
}
}
/**
* Public: Obtain the textual suggestion type required by AutoComplete+ that most closely maps to the numeric
* completion kind supplies by the language server.
*
* @param kind A {Number} that represents the suggestion kind to be converted.
* @returns A {String} containing the AutoComplete+ suggestion type equivalent to the given completion kind.
*/
static completionKindToSuggestionType(kind) {
switch (kind) {
case languageclient_1.CompletionItemKind.Constant:
return "constant";
case languageclient_1.CompletionItemKind.Method:
return "method";
case languageclient_1.CompletionItemKind.Function:
case languageclient_1.CompletionItemKind.Constructor:
return "function";
case languageclient_1.CompletionItemKind.Field:
case languageclient_1.CompletionItemKind.Property:
return "property";
case languageclient_1.CompletionItemKind.Variable:
return "variable";
case languageclient_1.CompletionItemKind.Class:
return "class";
case languageclient_1.CompletionItemKind.Struct:
case languageclient_1.CompletionItemKind.TypeParameter:
return "type";
case languageclient_1.CompletionItemKind.Operator:
return "selector";
case languageclient_1.CompletionItemKind.Interface:
return "mixin";
case languageclient_1.CompletionItemKind.Module:
return "module";
case languageclient_1.CompletionItemKind.Unit:
return "builtin";
case languageclient_1.CompletionItemKind.Enum:
case languageclient_1.CompletionItemKind.EnumMember:
return "enum";
case languageclient_1.CompletionItemKind.Keyword:
return "keyword";
case languageclient_1.CompletionItemKind.Snippet:
return "snippet";
case languageclient_1.CompletionItemKind.File:
case languageclient_1.CompletionItemKind.Folder:
return "import";
case languageclient_1.CompletionItemKind.Reference:
return "require";
default:
return "value";
}
}
}
exports.default = AutocompleteAdapter;
/**
* Normalizes the given grammar scope for autoComplete package so it always starts with `.` Based on
* https://github.com/atom/autocomplete-plus/wiki/Autocomplete-Providers
*
* @param grammarScope Such as 'source.python' or '.source.python'
* @returns The normalized grammarScope such as `.source.python`
*/
function grammarScopeToAutoCompleteSelector(grammarScope) {
return grammarScope.includes(".") && grammarScope[0] !== "." ? `.${grammarScope}` : grammarScope;
}
exports.grammarScopeToAutoCompleteSelector = grammarScopeToAutoCompleteSelector;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0b2NvbXBsZXRlLWFkYXB0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9saWIvYWRhcHRlcnMvYXV0b2NvbXBsZXRlLWFkYXB0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBQUEsd0NBQWdDO0FBQ2hDLGtDQUFpQztBQUdqQyxpQ0FBMkM7QUFDM0Msc0RBYTBCO0FBQzFCLDZEQUFtRDtBQUNuRCwrQkFBd0M7QUFtQ3hDLE1BQU0sOEJBQThCO0lBQ2xDLFlBQW1CLGNBQThCLEVBQVMsVUFBbUI7UUFBMUQsbUJBQWMsR0FBZCxjQUFjLENBQWdCO1FBQVMsZUFBVSxHQUFWLFVBQVUsQ0FBUztJQUFHLENBQUM7Q0FDbEY7QUFFRCwrR0FBK0c7QUFDL0csTUFBcUIsbUJBQW1CO0lBQXhDO1FBV1UscUJBQWdCLEdBQWdELElBQUksT0FBTyxFQUFFLENBQUE7UUFDN0Usd0JBQW1CLEdBQStELElBQUksT0FBTyxFQUFFLENBQUE7SUEwZnpHLENBQUM7SUFyZ0JRLE1BQU0sQ0FBQyxRQUFRLENBQUMsa0JBQXNDO1FBQzNELE9BQU8sa0JBQWtCLENBQUMsa0JBQWtCLElBQUksSUFBSSxDQUFBO0lBQ3RELENBQUM7SUFFTSxNQUFNLENBQUMsVUFBVSxDQUFDLGtCQUFzQztRQUM3RCxPQUFPLENBQ0wsa0JBQWtCLENBQUMsa0JBQWtCLElBQUksSUFBSSxJQUFJLGtCQUFrQixDQUFDLGtCQUFrQixDQUFDLGVBQWUsS0FBSyxJQUFJLENBQ2hILENBQUE7SUFDSCxDQUFDO0lBS0Q7Ozs7Ozs7OztPQVNHO0lBQ1UsY0FBYyxDQUN6QixNQUFvQixFQUNwQixPQUFxQyxFQUNyQywwQkFBbUQsRUFDbkQsaUJBQTBCLEVBQzFCLGdCQUErQixLQUFLOztZQUVwQyxNQUFNLFlBQVksR0FDaEIsTUFBTSxDQUFDLFlBQVksQ0FBQyxrQkFBa0IsSUFBSSxJQUFJO2dCQUM1QyxDQUFDLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FB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QUNsQixLQUFLLG1DQUFrQixDQUFDLE9BQU87Z0JBQzdCLE9BQU8sU0FBUyxDQUFBO1lBQ2xCLEtBQUssbUNBQWtCLENBQUMsSUFBSSxDQUFDO1lBQzdCLEtBQUssbUNBQWtCLENBQUMsTUFBTTtnQkFDNUIsT0FBTyxRQUFRLENBQUE7WUFDakIsS0FBSyxtQ0FBa0IsQ0FBQyxTQUFTO2dCQUMvQixPQUFPLFNBQVMsQ0FBQTtZQUNsQjtnQkFDRSxPQUFPLE9BQU8sQ0FBQTtTQUNqQjtJQUNILENBQUM7Q0FDRjtBQXRnQkQsc0NBc2dCQztBQUVEOzs7Ozs7R0FNRztBQUNILFNBQWdCLGtDQUFrQyxDQUFDLFlBQW9CO0lBQ3JFLE9BQU8sWUFBWSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxZQUFZLENBQUMsQ0FBQyxDQUFDLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQyxJQUFJLFlBQVksRUFBRSxDQUFDLENBQUMsQ0FBQyxZQUFZLENBQUE7QUFDbEcsQ0FBQztBQUZELGdGQUVDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IENvbnZlcnQgZnJvbSBcIi4uL2NvbnZlcnRcIlxuaW1wb3J0ICogYXMgVXRpbHMgZnJvbSBcIi4uL3V0aWxzXCJcbmltcG9ydCB7IENhbmNlbGxhdGlvblRva2VuU291cmNlIH0gZnJvbSBcInZzY29kZS1qc29ucnBjXCJcbmltcG9ydCB7IEFjdGl2ZVNlcnZlciB9IGZyb20gXCIuLi9zZXJ2ZXItbWFuYWdlclwiXG5pbXBvcnQgeyBPYmplY3RBcnJheUZpbHRlcmVyIH0gZnJvbSBcInphZGVoXCJcbmltcG9ydCB7XG4gIENvbXBsZXRpb25Db250ZXh0LFxuICBDb21wbGV0aW9uSXRlbSxcbiAgQ29tcGxldGlvbkl0ZW1LaW5kLFxuICBDb21wbGV0aW9uTGlzdCxcbiAgQ29tcGxldGlvblBhcmFtcyxcbiAgQ29tcGxldGlvblRyaWdnZXJLaW5kLFxuICBJbnNlcnRUZXh0Rm9ybWF0LFxuICBJbnNlcnRSZXBsYWNlRWRpdCxcbiAgTGFuZ3VhZ2VDbGllbnRDb25uZWN0aW9uLFxuICBSYW5nZSxcbiAgU2VydmVyQ2FwYWJpbGl0aWVzLFxuICBUZXh0RWRpdCxcbn0gZnJvbSBcIi4uL2xhbmd1YWdlY2xpZW50XCJcbmltcG9ydCBBcHBseUVkaXRBZGFwdGVyIGZyb20gXCIuL2FwcGx5LWVkaXQtYWRhcHRlclwiXG5pbXBvcnQgeyBQb2ludCwgVGV4dEVkaXRvciB9IGZyb20gXCJhdG9tXCJcbmltcG9ydCAqIGFzIGFjIGZyb20gXCJhdG9tL2F1dG9jb21wbGV0ZS1wbHVzXCJcbmltcG9ydCB7IFN1Z2dlc3Rpb24sIFRleHRTdWdnZXN0aW9uLCBTbmlwcGV0U3VnZ2VzdGlvbiwgU3VnZ2VzdGlvbkJhc2UgfSBmcm9tIFwiLi4vdHlwZXMvYXV0b2NvbXBsZXRlLWV4dGVuZGVkXCJcblxuLyoqXG4gKiBEZWZpbmVzIHRoZSBiZWhhdmlvciBvZiBzdWdnZXN0aW9uIGFjY2VwdGFuY2UuIEFzc3VtZSB5b3UgaGF2ZSBcImNvbnN8b2xlXCIgaW4gdGhlIGVkaXRvciAoIGB8YCBpcyB0aGUgY3Vyc29yIHBvc2l0aW9uKVxuICogYW5kIHRoZSBhdXRvY29tcGxldGUgc3VnZ2VzdGlvbiBpcyBgY29uc3RgLlxuICpcbiAqIC0gSWYgYGZhbHNlYCAtPiB0aGUgZWRpdHMgYXJlIGluc2VydGVkIDogY29uc3R8b2xlXG4gKiAtIElmIGB0cnVlYGAgLT4gdGhlIGVkaXRzIGFyZSByZXBsYWNlZDogY29uc3R8XG4gKi9cbnR5cGUgU2hvdWxkUmVwbGFjZSA9IGJvb2xlYW5cblxuLyoqXG4gKiBIb2xkcyBhIGxpc3Qgb2Ygc3VnZ2VzdGlvbnMgZ2VuZXJhdGVkIGZyb20gdGhlIENvbXBsZXRpb25JdGVtW10gbGlzdCBzZW50IGJ5IHRoZSBzZXJ2ZXIsIGFzIHdlbGwgYXMgbWV0YWRhdGEgYWJvdXRcbiAqIHRoZSBjb250ZXh0IGl0IHdhcyBjb2xsZWN0ZWQgaW5cbiAqL1xuaW50ZXJmYWNlIFN1Z2dlc3Rpb25DYWNoZUVudHJ5IHtcbiAgLyoqIElmIGB0cnVlYCwgdGhlIHNlcnZlciB3aWxsIHNlbmQgYSBsaXN0IG9mIHN1Z2dlc3Rpb25zIHRvIHJlcGxhY2UgdGhpcyBvbmUgKi9cbiAgaXNJbmNvbXBsZXRlOiBib29sZWFuXG4gIC8qKiBUaGUgcG9pbnQgbGVmdCBvZiB0aGUgZmlyc3QgY2hhcmFjdGVyIGluIHRoZSBvcmlnaW5hbCBwcmVmaXggc2VudCB0byB0aGUgc2VydmVyICovXG4gIHRyaWdnZXJQb2ludDogUG9pbnRcbiAgLyoqIFRoZSBwb2ludCByaWdodCBvZiB0aGUgbGFzdCBjaGFyYWN0ZXIgaW4gdGhlIG9yaWdpbmFsIHByZWZpeCBzZW50IHRvIHRoZSBzZXJ2ZXIgKi9cbiAgb3JpZ2luYWxCdWZmZXJQb2ludDogUG9pbnRcbiAgLyoqIFRoZSB0cmlnZ2VyIHN0cmluZyB0aGF0IGNhdXNlZCB0aGUgYXV0b2NvbXBsZXRlIChpZiBhbnkpICovXG4gIHRyaWdnZXJDaGFyOiBzdHJpbmdcbiAgc3VnZ2VzdGlvbk1hcDogTWFwPFN1Z2dlc3Rpb24sIFBvc3NpYmx5UmVzb2x2ZWRDb21wbGV0aW9uSXRlbT5cbn1cblxudHlwZSBDb21wbGV0aW9uSXRlbUFkanVzdGVyID0gKFxuICBpdGVtOiBDb21wbGV0aW9uSXRlbSxcbiAgc3VnZ2VzdGlvbjogYWMuQW55U3VnZ2VzdGlvbixcbiAgcmVxdWVzdDogYWMuU3VnZ2VzdGlvbnNSZXF1ZXN0ZWRFdmVudFxuKSA9PiB2b2lkXG5cbmNsYXNzIFBvc3NpYmx5UmVzb2x2ZWRDb21wbGV0aW9uSXRlbSB7XG4gIGNvbnN0cnVjdG9yKHB1YmxpYyBjb21wbGV0aW9uSXRlbTogQ29tcGxldGlvbkl0ZW0sIHB1YmxpYyBpc1Jlc29sdmVkOiBib29sZWFuKSB7fVxufVxuXG4vKiogUHVibGljOiBBZGFwdHMgdGhlIGxhbmd1YWdlIHNlcnZlciBwcm90b2NvbCBcInRleHREb2N1bWVudC9jb21wbGV0aW9uXCIgdG8gdGhlIEF0b20gQXV0b0NvbXBsZXRlKyBwYWNrYWdlLiAqL1xuZXhwb3J0IGRlZmF1bHQgY2xhc3MgQXV0b2NvbXBsZXRlQWRhcHRlciB7XG4gIHB1YmxpYyBzdGF0aWMgY2FuQWRhcHQoc2VydmVyQ2FwYWJpbGl0aWVzOiBTZXJ2ZXJDYXBhYmlsaXRpZXMpOiBib29sZWFuIHtcbiAgICByZXR1cm4gc2VydmVyQ2FwYWJpbGl0aWVzLmNvbXBsZXRpb25Qcm92aWRlciAhPSBudWxsXG4gIH1cblxuICBwdWJsaWMgc3RhdGljIGNhblJlc29sdmUoc2VydmVyQ2FwYWJpbGl0aWVzOiBTZXJ2ZXJDYXBhYmlsaXRpZXMpOiBib29sZWFuIHtcbiAgICByZXR1cm4gKFxuICAgICAgc2VydmVyQ2FwYWJpbGl0aWVzLmNvbXBsZXRpb25Qcm92aWRlciAhPSBudWxsICYmIHNlcnZlckNhcGFiaWxpdGllcy5jb21wbGV0aW9uUHJvdmlkZXIucmVzb2x2ZVByb3ZpZGVyID09PSB0cnVlXG4gICAgKVxuICB9XG5cbiAgcHJpdmF0ZSBfc3VnZ2VzdGlvbkNhY2hlOiBXZWFrTWFwPEFjdGl2ZVNlcnZlciwgU3VnZ2VzdGlvbkNhY2hlRW50cnk+ID0gbmV3IFdlYWtNYXAoKVxuICBwcml2YXRlIF9jYW5jZWxsYXRpb25Ub2tlbnM6IFdlYWtNYXA8TGFuZ3VhZ2VDbGllbnRDb25uZWN0aW9uLCBDYW5jZWxsYXRpb25Ub2tlblNvdXJjZT4gPSBuZXcgV2Vha01hcCgpXG5cbiAgLyoqXG4gICAqIFB1YmxpYzogT2J0YWluIHN1Z2dlc3Rpb24gbGlzdCBmb3IgQXV0b0NvbXBsZXRlKyBieSBxdWVyeWluZyB0aGUgbGFuZ3VhZ2Ugc2VydmVyIHVzaW5nIHRoZSBgdGV4dERvY3VtZW50L2NvbXBsZXRpb25gIHJlcXVlc3QuXG4gICAqXG4gICAqIEBwYXJhbSBzZXJ2ZXIgQW4ge0FjdGl2ZVNlcnZlcn0gcG9pbnRpbmcgdG8gdGhlIGxhbmd1YWdlIHNlcnZlciB0byBxdWVyeS5cbiAgICogQHBhcmFtIHJlcXVlc3QgVGhlIHthdG9tJEF1dG9jb21wbGV0ZVJlcXVlc3R9IHRvIHNhdGlzZnkuXG4gICAqIEBwYXJhbSBvbkRpZENvbnZlcnRDb21wbGV0aW9uSXRlbSBBbiBvcHRpb25hbCBmdW5jdGlvbiB0aGF0IHRha2VzIGEge0NvbXBsZXRpb25JdGVtfSwgYW5cbiAgICogICB7YXRvbSRBdXRvY29tcGxldGVTdWdnZXN0aW9ufSBhbmQgYSB7YXRvbSRBdXRvY29tcGxldGVSZXF1ZXN0fSBhbGxvd2luZyB5b3UgdG8gYWRqdXN0IGNvbnZlcnRlZCBpdGVtcy5cbiAgICogQHBhcmFtIHNob3VsZFJlcGxhY2UgVGhlIGJlaGF2aW9yIG9mIHN1Z2dlc3Rpb24gYWNjZXB0YW5jZSAoc2VlIHtTaG91bGRSZXBsYWNlfSkuXG4gICAqIEByZXR1cm5zIEEge1Byb21pc2V9IG9mIGFuIHtBcnJheX0gb2Yge2F0b20kQXV0b2NvbXBsZXRlU3VnZ2VzdGlvbn1zIGNvbnRhaW5pbmcgdGhlIEF1dG9Db21wbGV0ZSsgc3VnZ2VzdGlvbnMgdG8gZGlzcGxheS5cbiAgICovXG4gIHB1YmxpYyBhc3luYyBnZXRTdWdnZXN0aW9ucyhcbiAgICBzZXJ2ZXI6IEFjdGl2ZVNlcnZlcixcbiAgICByZXF1ZXN0OiBhYy5TdWdnZXN0aW9uc1JlcXVlc3RlZEV2ZW50LFxuICAgIG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtPzogQ29tcGxldGlvbkl0ZW1BZGp1c3RlcixcbiAgICBtaW5pbXVtV29yZExlbmd0aD86IG51bWJlcixcbiAgICBzaG91bGRSZXBsYWNlOiBTaG91bGRSZXBsYWNlID0gZmFsc2VcbiAgKTogUHJvbWlzZTxhYy5BbnlTdWdnZXN0aW9uW10+IHtcbiAgICBjb25zdCB0cmlnZ2VyQ2hhcnMgPVxuICAgICAgc2VydmVyLmNhcGFiaWxpdGllcy5jb21wbGV0aW9uUHJvdmlkZXIgIT0gbnVsbFxuICAgICAgICA/IHNlcnZlci5jYXBhYmlsaXRpZXMuY29tcGxldGlvblByb3ZpZGVyLnRyaWdnZXJDaGFyYWN0ZXJzIHx8IFtdXG4gICAgICAgIDogW11cblxuICAgIC8vIHRyaWdnZXJPbmx5IGlzIHRydWUgaWYgd2UgaGF2ZSBqdXN0IHR5cGVkIGluIGEgdHJpZ2dlciBjaGFyYWN0ZXIsIGFuZCBpcyBmYWxzZSBpZiB3ZVxuICAgIC8vIGhhdmUgdHlwZWQgYWRkaXRpb25hbCBjaGFyYWN0ZXJzIGZvbGxvd2luZyBhIHRyaWdnZXIgY2hhcmFjdGVyLlxuICAgIGNvbnN0IFt0cmlnZ2VyQ2hhciwgdHJpZ2dlck9ubHldID0gQXV0b2NvbXBsZXRlQWRhcHRlci5nZXRUcmlnZ2VyQ2hhcmFjdGVyKHJlcXVlc3QsIHRyaWdnZXJDaGFycylcblxuICAgIGlmICghdGhpcy5zaG91bGRUcmlnZ2VyKHJlcXVlc3QsIHRyaWdnZXJDaGFyLCBtaW5pbXVtV29yZExlbmd0aCB8fCAwKSkge1xuICAgICAgcmV0dXJuIFtdXG4gICAgfVxuXG4gICAgLy8gR2V0IHRoZSBzdWdnZXN0aW9ucyBlaXRoZXIgZnJvbSB0aGUgY2FjaGUgb3IgYnkgY2FsbGluZyB0aGUgbGFuZ3VhZ2Ugc2VydmVyXG4gICAgY29uc3Qgc3VnZ2VzdGlvbnMgPSBhd2FpdCB0aGlzLmdldE9yQnVpbGRTdWdnZXN0aW9ucyhcbiAgICAgIHNlcnZlcixcbiAgICAgIHJlcXVlc3QsXG4gICAgICB0cmlnZ2VyQ2hhcixcbiAgICAgIHRyaWdnZXJPbmx5LFxuICAgICAgc2hvdWxkUmVwbGFjZSxcbiAgICAgIG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtXG4gICAgKVxuXG4gICAgLy8gV2UgbXVzdCB1cGRhdGUgdGhlIHJlcGxhY2VtZW50IHByZWZpeCBhcyBjaGFyYWN0ZXJzIGFyZSBhZGRlZCBhbmQgcmVtb3ZlZFxuICAgIGNvbnN0IGNhY2hlID0gdGhpcy5fc3VnZ2VzdGlvbkNhY2hlLmdldChzZXJ2ZXIpIVxuICAgIGNvbnN0IHJlcGxhY2VtZW50UHJlZml4ID0gcmVxdWVzdC5lZGl0b3IuZ2V0VGV4dEluQnVmZmVyUmFuZ2UoW1xuICAgICAgW2NhY2hlLnRyaWdnZXJQb2ludC5yb3csIGNhY2hlLnRyaWdnZXJQb2ludC5jb2x1bW4gKyBjYWNoZS50cmlnZ2VyQ2hhci5sZW5ndGhdLFxuICAgICAgcmVxdWVzdC5idWZmZXJQb3NpdGlvbixcbiAgICBdKVxuICAgIGZvciAoY29uc3Qgc3VnZ2VzdGlvbiBvZiBzdWdnZXN0aW9ucykge1xuICAgICAgaWYgKHN1Z2dlc3Rpb24uY3VzdG9tUmVwbGFjbWVudFByZWZpeCkge1xuICAgICAgICAvLyBoYXZpbmcgdGhpcyBwcm9wZXJ0eSBtZWFucyBhIGN1c3RvbSByYW5nZSB3YXMgcHJvdmlkZWRcbiAgICAgICAgY29uc3QgbGVuID0gcmVwbGFjZW1lbnRQcmVmaXgubGVuZ3RoXG4gICAgICAgIGNvbnN0IHByZVJlcGxhY2VtZW50UHJlZml4ID1cbiAgICAgICAgICBzdWdnZXN0aW9uLmN1c3RvbVJlcGxhY21lbnRQcmVmaXggK1xuICAgICAgICAgIHJlcGxhY2VtZW50UHJlZml4LnN1YnN0cmluZyhsZW4gKyBjYWNoZS5vcmlnaW5hbEJ1ZmZlclBvaW50LmNvbHVtbiAtIHJlcXVlc3QuYnVmZmVyUG9zaXRpb24uY29sdW1uLCBsZW4pXG4gICAgICAgIC8vIHdlIGNhbm5vdCByZXBsYWNlIHRleHQgYWZ0ZXIgdGhlIGN1cnNvciB3aXRoIHRoZSBjdXJyZW50IGF1dG9jb21wbGV0ZS1wbHVzIEFQSVxuICAgICAgICAvLyBzbyB3ZSB3aWxsIHNpbXBseSBpZ25vcmUgaXQgZm9yIG5vd1xuICAgICAgICBzdWdnZXN0aW9uLnJlcGxhY2VtZW50UHJlZml4ID0gcHJlUmVwbGFjZW1lbnRQcmVmaXhcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHN1Z2dlc3Rpb24ucmVwbGFjZW1lbnRQcmVmaXggPSByZXBsYWNlbWVudFByZWZpeFxuICAgICAgfVxuICAgIH1cblxuICAgIGNvbnN0IGZpbHRlcmVkID0gIShyZXF1ZXN0LnByZWZpeCA9PT0gXCJcIiB8fCAodHJpZ2dlckNoYXIgIT09IFwiXCIgJiYgdHJpZ2dlck9ubHkpKVxuICAgIGlmIChmaWx0ZXJlZCkge1xuICAgICAgLy8gZmlsdGVyIHRoZSBzdWdnZXN0aW9ucyB3aG8gaGF2ZSBgZmlsdGVyVGV4dGAgcHJvcGVydHlcbiAgICAgIGNvbnN0IHZhbGlkU3VnZ2VzdGlvbnMgPSBzdWdnZXN0aW9ucy5maWx0ZXIoKHNncykgPT4gdHlwZW9mIHNncy5maWx0ZXJUZXh0ID09PSBcInN0cmluZ1wiKSBhcyBTdWdnZXN0aW9uW10gJlxuICAgICAgICB7IGZpbHRlclRleHQ6IHN0cmluZyB9W11cbiAgICAgIC8vIFRPRE8gdXNlIGBPYmplY3RBcnJheUZpbHRlcmVyLnNldENhbmRpZGF0ZWAgaW4gYF9zdWdnZXN0aW9uQ2FjaGVgIHRvIGF2b2lkIGNyZWF0aW5nIGBPYmplY3RBcnJheUZpbHRlcmVyYCBldmVyeSB0aW1lIGZyb20gc2NyYXRjaFxuICAgICAgY29uc3Qgb2JqRmlsdGVyZXIgPSBuZXcgT2JqZWN0QXJyYXlGaWx0ZXJlcih2YWxpZFN1Z2dlc3Rpb25zLCBcImZpbHRlclRleHRcIilcbiAgICAgIC8vIHphZGVoIHJldHVybnMgYW4gYXJyYXkgb2YgdGhlIHNlbGVjdGVkIGBTdWdnZXN0aW9uc2BcbiAgICAgIHJldHVybiBvYmpGaWx0ZXJlci5maWx0ZXIocmVxdWVzdC5wcmVmaXgpIGFzIGFueSBhcyBTdWdnZXN0aW9uW11cbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIHN1Z2dlc3Rpb25zXG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBzaG91bGRUcmlnZ2VyKHJlcXVlc3Q6IGFjLlN1Z2dlc3Rpb25zUmVxdWVzdGVkRXZlbnQsIHRyaWdnZXJDaGFyOiBzdHJpbmcsIG1pbldvcmRMZW5ndGg6IG51bWJlcik6IGJvb2xlYW4ge1xuICAgIHJldHVybiAoXG4gICAgICByZXF1ZXN0LmFjdGl2YXRlZE1hbnVhbGx5IHx8IHRyaWdnZXJDaGFyICE9PSBcIlwiIHx8IG1pbldvcmRMZW5ndGggPD0gMCB8fCByZXF1ZXN0LnByZWZpeC5sZW5ndGggPj0gbWluV29yZExlbmd0aFxuICAgIClcbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgZ2V0T3JCdWlsZFN1Z2dlc3Rpb25zKFxuICAgIHNlcnZlcjogQWN0aXZlU2VydmVyLFxuICAgIHJlcXVlc3Q6IGFjLlN1Z2dlc3Rpb25zUmVxdWVzdGVkRXZlbnQsXG4gICAgdHJpZ2dlckNoYXI6IHN0cmluZyxcbiAgICB0cmlnZ2VyT25seTogYm9vbGVhbixcbiAgICBzaG91bGRSZXBsYWNlOiBTaG91bGRSZXBsYWNlLFxuICAgIG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtPzogQ29tcGxldGlvbkl0ZW1BZGp1c3RlclxuICApOiBQcm9taXNlPFN1Z2dlc3Rpb25bXT4ge1xuICAgIGNvbnN0IGNhY2hlID0gdGhpcy5fc3VnZ2VzdGlvbkNhY2hlLmdldChzZXJ2ZXIpXG5cbiAgICBjb25zdCB0cmlnZ2VyQ29sdW1uID1cbiAgICAgIHRyaWdnZXJDaGFyICE9PSBcIlwiICYmIHRyaWdnZXJPbmx5XG4gICAgICAgID8gcmVxdWVzdC5idWZmZXJQb3NpdGlvbi5jb2x1bW4gLSB0cmlnZ2VyQ2hhci5sZW5ndGhcbiAgICAgICAgOiByZXF1ZXN0LmJ1ZmZlclBvc2l0aW9uLmNvbHVtbiAtIHJlcXVlc3QucHJlZml4Lmxlbmd0aCAtIHRyaWdnZXJDaGFyLmxlbmd0aFxuICAgIGNvbnN0IHRyaWdnZXJQb2ludCA9IG5ldyBQb2ludChyZXF1ZXN0LmJ1ZmZlclBvc2l0aW9uLnJvdywgdHJpZ2dlckNvbHVtbilcblxuICAgIC8vIERvIHdlIGhhdmUgY29tcGxldGUgY2FjaGVkIHN1Z2dlc3Rpb25zIHRoYXQgYXJlIHN0aWxsI