UNPKG

atom-languageclient

Version:
440 lines 75 kB
"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