452 lines
80 KiB
JavaScript
452 lines
80 KiB
JavaScript
|
/*
|
|||
|
THIS IS A GENERATED/BUNDLED FILE BY ROLLUP
|
|||
|
if you want to view the source visit the plugins github repository
|
|||
|
*/
|
|||
|
|
|||
|
'use strict';
|
|||
|
|
|||
|
var obsidian = require('obsidian');
|
|||
|
|
|||
|
/******************************************************************************
|
|||
|
Copyright (c) Microsoft Corporation.
|
|||
|
|
|||
|
Permission to use, copy, modify, and/or distribute this software for any
|
|||
|
purpose with or without fee is hereby granted.
|
|||
|
|
|||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|||
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|||
|
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|||
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|||
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|||
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|||
|
PERFORMANCE OF THIS SOFTWARE.
|
|||
|
***************************************************************************** */
|
|||
|
|
|||
|
function __awaiter(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());
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
class TextSnippets extends obsidian.Plugin {
|
|||
|
onInit() { }
|
|||
|
onload() {
|
|||
|
return __awaiter(this, void 0, void 0, function* () {
|
|||
|
console.log("Loading snippets plugin");
|
|||
|
yield this.loadSettings();
|
|||
|
this.addSettingTab(new TextSnippetsSettingsTab(this.app, this));
|
|||
|
//expected warning
|
|||
|
var isLegacy = this.app.vault.config.legacyEditor;
|
|||
|
if (!isLegacy != this.settings.isWYSIWYG) {
|
|||
|
this.settings.isWYSIWYG = !isLegacy;
|
|||
|
yield this.saveSettings();
|
|||
|
}
|
|||
|
this.addCommand({
|
|||
|
id: "text-snippets",
|
|||
|
name: "Run snippet replacement",
|
|||
|
callback: () => this.SnippetOnTrigger(),
|
|||
|
hotkeys: [{
|
|||
|
modifiers: ["Mod"],
|
|||
|
key: "tab"
|
|||
|
}],
|
|||
|
});
|
|||
|
this.cmEditors = [];
|
|||
|
this.registerCodeMirror((cm) => {
|
|||
|
this.cmEditors.push(cm);
|
|||
|
// the callback has to be called through another function in order for 'this' to work
|
|||
|
cm.on('keydown', (cm, event) => this.handleKeyDown(cm, event));
|
|||
|
});
|
|||
|
});
|
|||
|
}
|
|||
|
onunload() {
|
|||
|
return __awaiter(this, void 0, void 0, function* () {
|
|||
|
console.log("Unloading text snippet plugin");
|
|||
|
this.cmEditors = [];
|
|||
|
this.registerCodeMirror((cm) => {
|
|||
|
this.cmEditors.push(cm);
|
|||
|
// the callback has to be called through another function in order for 'this' to work
|
|||
|
cm.off('keydown', (cm, event) => this.handleKeyDown(cm, event));
|
|||
|
});
|
|||
|
});
|
|||
|
}
|
|||
|
loadSettings() {
|
|||
|
return __awaiter(this, void 0, void 0, function* () {
|
|||
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData());
|
|||
|
});
|
|||
|
}
|
|||
|
saveSettings() {
|
|||
|
return __awaiter(this, void 0, void 0, function* () {
|
|||
|
yield this.saveData(this.settings);
|
|||
|
});
|
|||
|
}
|
|||
|
UpdateSplit(newlineSymbol) {
|
|||
|
var nlSymb = newlineSymbol;
|
|||
|
var nlSymb = nlSymb.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
|||
|
var rg = '(?<!' + nlSymb + ')\\n';
|
|||
|
const regex = new RegExp(rg);
|
|||
|
var splited = this.settings.snippets_file.split(regex);
|
|||
|
splited = splited.filter(item => item);
|
|||
|
this.settings.snippets = splited;
|
|||
|
}
|
|||
|
getSelectedText(editor) {
|
|||
|
if (editor.somethingSelected()) {
|
|||
|
return editor.getSelection();
|
|||
|
}
|
|||
|
else {
|
|||
|
var wordBoundaries = this.getWordBoundaries(editor);
|
|||
|
editor.getDoc().setSelection(wordBoundaries.start, wordBoundaries.end);
|
|||
|
return editor.getSelection();
|
|||
|
}
|
|||
|
}
|
|||
|
isWord(c) {
|
|||
|
//if character is not a whiespace or a delimiter
|
|||
|
var notWord = ' \t\n\r\v' + this.settings.wordDelimiters;
|
|||
|
if (notWord.indexOf(c) <= -1) {
|
|||
|
return true;
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
SnippetsWordAt(cm, pos) {
|
|||
|
var start = pos.ch, end = start, line = cm.getLine(pos.line);
|
|||
|
while (start && this.isWord(line.charAt(start - 1)))
|
|||
|
--start;
|
|||
|
while (end < line.length && this.isWord(line.charAt(end)))
|
|||
|
++end;
|
|||
|
var fr = { line: pos.line, ch: start };
|
|||
|
var t = { line: pos.line, ch: end };
|
|||
|
return { from: fr, to: t, word: line.slice(start, end) };
|
|||
|
}
|
|||
|
getWordBoundaries(editor) {
|
|||
|
var cursor = editor.getCursor();
|
|||
|
var line = cursor.line;
|
|||
|
cursor.ch;
|
|||
|
var word = this.SnippetsWordAt(editor, cursor);
|
|||
|
var wordStart = word.from.ch;
|
|||
|
var wordEnd = word.to.ch;
|
|||
|
return {
|
|||
|
start: {
|
|||
|
line: line,
|
|||
|
ch: wordStart
|
|||
|
},
|
|||
|
end: {
|
|||
|
line: line,
|
|||
|
ch: wordEnd
|
|||
|
},
|
|||
|
};
|
|||
|
}
|
|||
|
findSnippet(editor, cursorOrig, cursor) {
|
|||
|
var selectedText = this.getSelectedText(editor);
|
|||
|
Array.from(this.settings.wordDelimiters);
|
|||
|
'' + selectedText.split(' ').join('');
|
|||
|
var newStr = "";
|
|||
|
// without this finds next stop everywhere in file
|
|||
|
// if (selectedWoSpaces == '' || (selectedWoSpaces.length > 0 && wordDelimiters.indexOf(selectedWoSpaces[0]) >= 0 && cursorOrig.ch == cursor.ch)) {
|
|||
|
// editor.execCommand('goWordLeft');
|
|||
|
// editor.execCommand('goWordLeft');
|
|||
|
// selectedText = this.getSelectedText(editor);
|
|||
|
// var cursor = editor.getCursor('from');
|
|||
|
// }
|
|||
|
var i;
|
|||
|
var snippets = this.settings.snippets;
|
|||
|
for (i in snippets) {
|
|||
|
var snippet = snippets[i].split(' : ');
|
|||
|
if (selectedText == snippet[0]) {
|
|||
|
newStr = snippet[1];
|
|||
|
}
|
|||
|
}
|
|||
|
return newStr;
|
|||
|
}
|
|||
|
calculateCursorEndPos(nStr, cursor, endPosition) {
|
|||
|
var nlSymb = this.settings.newlineSymbol;
|
|||
|
var endSymbol = this.settings.endSymbol;
|
|||
|
var stopSymbol = this.settings.stopSymbol;
|
|||
|
var newStr = nStr.split('\n').join('');
|
|||
|
if (newStr.indexOf(stopSymbol) == -1) {
|
|||
|
var rawEnd = newStr.indexOf(endSymbol);
|
|||
|
if (rawEnd == -1)
|
|||
|
rawEnd = newStr.length;
|
|||
|
var lastNl = newStr.substring(0, rawEnd).lastIndexOf(nlSymb);
|
|||
|
if (lastNl != -1)
|
|||
|
var endPosIndex = rawEnd - lastNl - nlSymb.length - cursor.ch;
|
|||
|
else
|
|||
|
var endPosIndex = rawEnd;
|
|||
|
}
|
|||
|
else {
|
|||
|
var endPosIndex = 0;
|
|||
|
}
|
|||
|
nlSymb = nlSymb.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); //no special symbols in nlSymb
|
|||
|
var rg = nlSymb + '\\n' + '|' + nlSymb;
|
|||
|
const regex = new RegExp(rg);
|
|||
|
const regexF = new RegExp(rg, 'g');
|
|||
|
var nlinesCount = (newStr.substring(0, rawEnd).match(regexF) || []).length;
|
|||
|
endPosition.nlinesCount = nlinesCount;
|
|||
|
endPosition.position = endPosIndex;
|
|||
|
newStr = newStr.split(regex).join('\n');
|
|||
|
newStr = newStr.replace(endSymbol, '');
|
|||
|
return newStr;
|
|||
|
}
|
|||
|
insertSnippet(key = '', snippetStartpos = { ch: -1, line: -1 }) {
|
|||
|
let activeLeaf = this.app.workspace.activeLeaf;
|
|||
|
let editor = activeLeaf.view.sourceMode.cmEditor;
|
|||
|
// let editor = activeLeaf.view.editor;
|
|||
|
var cursorOrig = editor.getCursor();
|
|||
|
var wasSelection = editor.somethingSelected();
|
|||
|
var cursor = editor.getCursor('from');
|
|||
|
if (wasSelection) {
|
|||
|
({ start: cursor, end: editor.getCursor('to') });
|
|||
|
}
|
|||
|
else {
|
|||
|
this.getWordBoundaries(editor);
|
|||
|
}
|
|||
|
var stopSymbol = this.settings.stopSymbol;
|
|||
|
var pasteSymbol = this.settings.pasteSymbol;
|
|||
|
var stopFound = false;
|
|||
|
var newStr = "";
|
|||
|
newStr = this.findSnippet(editor, cursorOrig, cursor);
|
|||
|
cursor = editor.getCursor('from');
|
|||
|
//proceed Tab and Spacebar
|
|||
|
var endCursor = editor.getCursor('to');
|
|||
|
if (newStr == "" ||
|
|||
|
(key == 'Space' && (cursorOrig.ch != endCursor.ch || cursorOrig.line != endCursor.line))) {
|
|||
|
if (wasSelection == false) {
|
|||
|
editor.getDoc().setSelection(cursorOrig, cursorOrig);
|
|||
|
}
|
|||
|
if (key == 'Space')
|
|||
|
return false;
|
|||
|
if (newStr == "") {
|
|||
|
editor.setCursor(cursorOrig);
|
|||
|
return this.nextStop();
|
|||
|
}
|
|||
|
}
|
|||
|
//find end position
|
|||
|
var endPosition = { nlinesCount: 0, position: 0 };
|
|||
|
newStr = this.calculateCursorEndPos(newStr, cursor, endPosition);
|
|||
|
if (newStr.indexOf(stopSymbol) != -1)
|
|||
|
stopFound = true;
|
|||
|
if (newStr.indexOf(pasteSymbol) != -1)
|
|||
|
snippetStartpos = cursor;
|
|||
|
editor.replaceSelection(newStr);
|
|||
|
if (stopFound) {
|
|||
|
editor.setCursor({
|
|||
|
line: cursor.line,
|
|||
|
ch: cursor.ch
|
|||
|
});
|
|||
|
return this.nextStop();
|
|||
|
}
|
|||
|
else {
|
|||
|
editor.setCursor({
|
|||
|
line: cursor.line + endPosition.nlinesCount,
|
|||
|
ch: cursor.ch + endPosition.position
|
|||
|
});
|
|||
|
}
|
|||
|
editor.focus();
|
|||
|
return true;
|
|||
|
}
|
|||
|
adjustCursor(editor, cursor, newStr, oldStr) {
|
|||
|
var cursorOffset = newStr.length - oldStr.length;
|
|||
|
this.adjustCursorOffset(editor, cursor, cursorOffset);
|
|||
|
}
|
|||
|
adjustCursorOffset(editor, cursor, cursorOffset) {
|
|||
|
editor.setCursor({
|
|||
|
line: cursor.line,
|
|||
|
ch: cursor.ch + cursorOffset
|
|||
|
});
|
|||
|
}
|
|||
|
handleKeyDown(cm, event) {
|
|||
|
if ((event.key == 'Tab' && this.settings.useTab) || (event.code == 'Space' && this.settings.useSpace)) {
|
|||
|
this.SnippetOnTrigger(event.code, true);
|
|||
|
}
|
|||
|
}
|
|||
|
SnippetOnTrigger(key = '', preventDef = false) {
|
|||
|
let activeLeaf = this.app.workspace.activeLeaf;
|
|||
|
let cm = activeLeaf.view.sourceMode.cmEditor;
|
|||
|
var cursorSt = cm.getCursor();
|
|||
|
if (this.insertSnippet(key, cursorSt)) {
|
|||
|
if (preventDef) {
|
|||
|
event.preventDefault();
|
|||
|
if (this.settings.isWYSIWYG && key == 'Tab') {
|
|||
|
// delete '\t' in Live preview
|
|||
|
var search = cm.searchCursor('\t', cursorSt);
|
|||
|
if (search.findPrevious()) {
|
|||
|
search.replace('');
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if (cursorSt.ch >= 0 && cursorSt.line >= 0) { //paste text from clipboard
|
|||
|
cm.getCursor();
|
|||
|
navigator.clipboard.readText().then((clipText) => {
|
|||
|
if (this.settings.isWYSIWYG == false) {
|
|||
|
var search = cm.getSearchCursor(this.settings.pasteSymbol, cursorSt);
|
|||
|
}
|
|||
|
else {
|
|||
|
var search = cm.searchCursor(this.settings.pasteSymbol, cursorSt);
|
|||
|
}
|
|||
|
if (search.findNext()) {
|
|||
|
search.replace(clipText);
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
return this.nextStop();
|
|||
|
}
|
|||
|
nextStop() {
|
|||
|
let activeLeaf = this.app.workspace.activeLeaf;
|
|||
|
let cm = activeLeaf.view.sourceMode.cmEditor;
|
|||
|
if (this.settings.isWYSIWYG == false) {
|
|||
|
var search = cm.getSearchCursor(this.settings.stopSymbol, cm.getCursor());
|
|||
|
}
|
|||
|
else {
|
|||
|
var search = cm.searchCursor(this.settings.stopSymbol, cm.getCursor());
|
|||
|
}
|
|||
|
if (search.findNext()) {
|
|||
|
search.replace("");
|
|||
|
if (this.settings.isWYSIWYG == false) {
|
|||
|
cm.setCursor(search.from());
|
|||
|
}
|
|||
|
else {
|
|||
|
cm.setCursor(search.current().from);
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
else if (this.settings.useTab) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
const DEFAULT_SETTINGS = {
|
|||
|
snippets_file: "snippets : It is an obsidian plugin, that replaces your selected text.",
|
|||
|
snippets: ["snippets : It is an obsidian plugin, that replaces your selected text."],
|
|||
|
endSymbol: '$end$',
|
|||
|
newlineSymbol: '$nl$',
|
|||
|
stopSymbol: "$tb$",
|
|||
|
pasteSymbol: "$pst$",
|
|||
|
useTab: true,
|
|||
|
useSpace: false,
|
|||
|
wordDelimiters: "$()[]{}<>,.!?;:\'\"\\/",
|
|||
|
isWYSIWYG: false,
|
|||
|
};
|
|||
|
class TextSnippetsSettingsTab extends obsidian.PluginSettingTab {
|
|||
|
constructor(app, plugin) {
|
|||
|
super(app, plugin);
|
|||
|
this.plugin = plugin;
|
|||
|
}
|
|||
|
display() {
|
|||
|
let { containerEl } = this;
|
|||
|
containerEl.empty();
|
|||
|
containerEl.createEl('h2', { text: 'Text Snippets - Settings' });
|
|||
|
new obsidian.Setting(containerEl)
|
|||
|
.setName("Snippets")
|
|||
|
.setDesc("Type here your snippets in format 'snippet : result', one per line. Empty lines will be ignored. Ctrl+Tab to replace (hotkey can be changed).")
|
|||
|
.setClass("text-snippets-class")
|
|||
|
.addTextArea((text) => text
|
|||
|
.setPlaceholder("before : after")
|
|||
|
.setValue(this.plugin.settings.snippets_file)
|
|||
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|||
|
this.plugin.settings.snippets_file = value;
|
|||
|
this.plugin.UpdateSplit(this.plugin.settings.newlineSymbol);
|
|||
|
yield this.plugin.saveSettings();
|
|||
|
})));
|
|||
|
new obsidian.Setting(containerEl)
|
|||
|
.setName("Cursor end position mark")
|
|||
|
.setDesc("Places the cursor to the mark position after inserting a snippet (default: $end$).\nMark does not appear anywhere within the snippet. Do not use together with Stop Symbol.")
|
|||
|
.setClass("text-snippets-cursor")
|
|||
|
.addTextArea((text) => text
|
|||
|
.setPlaceholder("$end$")
|
|||
|
.setValue(this.plugin.settings.endSymbol)
|
|||
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|||
|
if (value == '') {
|
|||
|
value = '$end$';
|
|||
|
}
|
|||
|
this.plugin.settings.endSymbol = value;
|
|||
|
yield this.plugin.saveSettings();
|
|||
|
})));
|
|||
|
new obsidian.Setting(containerEl)
|
|||
|
.setName("Newline mark")
|
|||
|
.setDesc("Ignores newline after mark, replace it with a newline character after expanding (default: $nl$).\nNecessary to write before every line break in multiline snippets.")
|
|||
|
.setClass("text-snippets-newline")
|
|||
|
.addTextArea((text) => text
|
|||
|
.setPlaceholder("$nl$")
|
|||
|
.setValue(this.plugin.settings.newlineSymbol)
|
|||
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|||
|
if (value == '') {
|
|||
|
value = '$nl$';
|
|||
|
}
|
|||
|
this.plugin.settings.newlineSymbol = value;
|
|||
|
this.plugin.UpdateSplit(value);
|
|||
|
yield this.plugin.saveSettings();
|
|||
|
})));
|
|||
|
new obsidian.Setting(containerEl)
|
|||
|
.setName('Stop Symbol')
|
|||
|
.setDesc('Symbol to jump to when command is called.')
|
|||
|
.setClass("text-snippets-tabstops")
|
|||
|
.addTextArea((text) => text
|
|||
|
.setPlaceholder('')
|
|||
|
.setValue(this.plugin.settings.stopSymbol)
|
|||
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|||
|
if (value == '') {
|
|||
|
value = '$tb$';
|
|||
|
}
|
|||
|
this.plugin.settings.stopSymbol = value;
|
|||
|
yield this.plugin.saveSettings();
|
|||
|
})));
|
|||
|
new obsidian.Setting(containerEl)
|
|||
|
.setName('Clipboard paste Symbol')
|
|||
|
.setDesc('Symbol to be replaced with clipboard content.')
|
|||
|
.setClass("text-snippets-tabstops")
|
|||
|
.addTextArea((text) => text
|
|||
|
.setPlaceholder('')
|
|||
|
.setValue(this.plugin.settings.pasteSymbol)
|
|||
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|||
|
if (value == '') {
|
|||
|
value = '$pst$';
|
|||
|
}
|
|||
|
this.plugin.settings.pasteSymbol = value;
|
|||
|
yield this.plugin.saveSettings();
|
|||
|
})));
|
|||
|
new obsidian.Setting(containerEl)
|
|||
|
.setName("Expand on Tab")
|
|||
|
.setDesc("Use the Tab key as the trigger.")
|
|||
|
.addToggle(toggle => toggle.setValue(this.plugin.settings.useTab)
|
|||
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|||
|
this.plugin.settings.useTab = !this.plugin.settings.useTab;
|
|||
|
yield this.plugin.saveSettings();
|
|||
|
})));
|
|||
|
new obsidian.Setting(containerEl)
|
|||
|
.setName("Expand on Space")
|
|||
|
.setDesc("Use the Space bar button as the trigger.")
|
|||
|
.addToggle(toggle => toggle.setValue(this.plugin.settings.useSpace)
|
|||
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|||
|
this.plugin.settings.useSpace = !this.plugin.settings.useSpace;
|
|||
|
yield this.plugin.saveSettings();
|
|||
|
})));
|
|||
|
new obsidian.Setting(containerEl)
|
|||
|
.setName("Live Preview Mode")
|
|||
|
.setDesc("Toggle manually if not correct. You should restart plugin after changing this option.")
|
|||
|
.addToggle(toggle => toggle.setValue(this.plugin.settings.isWYSIWYG)
|
|||
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|||
|
this.plugin.settings.isWYSIWYG = !this.plugin.settings.isWYSIWYG;
|
|||
|
yield this.plugin.saveSettings();
|
|||
|
})));
|
|||
|
new obsidian.Setting(containerEl)
|
|||
|
.setName('Word delimiters')
|
|||
|
.setDesc('Сharacters for specifying the boundary between separate words.')
|
|||
|
.setClass("text-snippets-delimiter")
|
|||
|
.addTextArea((text) => text
|
|||
|
.setPlaceholder('')
|
|||
|
.setValue(this.plugin.settings.wordDelimiters)
|
|||
|
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
|||
|
this.plugin.settings.wordDelimiters = value;
|
|||
|
yield this.plugin.saveSettings();
|
|||
|
})));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
module.exports = TextSnippets;
|
|||
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsibm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsIm1haW4udHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKlxyXG5Db3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi5cclxuXHJcblBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueVxyXG5wdXJwb3NlIHdpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQuXHJcblxyXG5USEUgU09GVFdBUkUgSVMgUFJPVklERUQgXCJBUyBJU1wiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUyBXSVRIXHJcblJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWVxyXG5BTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SIEFOWSBTUEVDSUFMLCBESVJFQ1QsXHJcbklORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMgV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTVxyXG5MT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUlxyXG5PVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SXHJcblBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuXHJcbioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqICovXHJcbi8qIGdsb2JhbCBSZWZsZWN0LCBQcm9taXNlICovXHJcblxyXG52YXIgZXh0ZW5kU3RhdGljcyA9IGZ1bmN0aW9uKGQsIGIpIHtcclxuICAgIGV4dGVuZFN0YXRpY3MgPSBPYmplY3Quc2V0UHJvdG90eXBlT2YgfHxcclxuICAgICAgICAoeyBfX3Byb3RvX186IFtdIH0gaW5zdGFuY2VvZiBBcnJheSAmJiBmdW5jdGlvbiAoZCwgYikgeyBkLl9fcHJvdG9fXyA9IGI7IH0pIHx8XHJcbiAgICAgICAgZnVuY3Rpb24gKGQsIGIpIHsgZm9yICh2YXIgcCBpbiBiKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKGIsIHApKSBkW3BdID0gYltwXTsgfTtcclxuICAgIHJldHVybiBleHRlbmRTdGF0aWNzKGQsIGIpO1xyXG59O1xyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fZXh0ZW5kcyhkLCBiKSB7XHJcbiAgICBpZiAodHlwZW9mIGIgIT09IFwiZnVuY3Rpb25cIiAmJiBiICE9PSBudWxsKVxyXG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJDbGFzcyBleHRlbmRzIHZhbHVlIFwiICsgU3RyaW5nKGIpICsgXCIgaXMgbm90IGEgY29uc3RydWN0b3Igb3IgbnVsbFwiKTtcclxuICAgIGV4dGVuZFN0YXRpY3MoZCwgYik7XHJcbiAgICBmdW5jdGlvbiBfXygpIHsgdGhpcy5jb25zdHJ1Y3RvciA9IGQ7IH1cclxuICAgIGQucHJvdG90eXBlID0gYiA9PT0gbnVsbCA/IE9iamVjdC5jcmVhdGUoYikgOiAoX18ucHJvdG90eXBlID0gYi5wcm90b3R5cGUsIG5ldyBfXygpKTtcclxufVxyXG5cclxuZXhwb3J0IHZhciBfX2Fzc2lnbiA9IGZ1bmN0aW9uKCkge1xyXG4gICAgX19hc3NpZ24gPSBPYmplY3QuYXNzaWduIHx8IGZ1bmN0aW9uIF9fYXNzaWduKHQpIHtcclxuICAgICAgICBmb3IgKHZhciBzLCBpID0gMSwgbiA9IGFyZ3VtZW50cy5sZW5ndGg7IGkgPCBuOyBpKyspIHtcclxuICAgICAgICAgICAgcyA9IGFyZ3VtZW50c1tpXTtcclxuICAgICAgICAgICAgZm9yICh2YXIgcCBpbiBzKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHMsIHApKSB0W3BdID0gc1twXTtcclxuICAgICAgICB9XHJcbiAgICAgICAgcmV0dXJuIHQ7XHJcbiAgICB9XHJcbiAgICByZXR1cm4gX19hc3NpZ24uYXBwbHkodGhpcywgYXJndW1lbnRzKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fcmVzdChzLCBlKSB7XHJcbiAgICB2YXIgdCA9IHt9O1xyXG4gICAgZm9yICh2YXIgcCBpbiBzKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHMsIHApICYmIGUuaW5kZXhPZihwKSA8IDApXHJcbiAgICAgICAgdFtwXSA9IHNbcF07XHJcbiAgICBpZiAocyAhPSBudWxsICYmIHR5cGVvZiBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzID09PSBcImZ1bmN0aW9uXCIpXHJcbiAgICAgICAgZm9yICh2YXIgaSA9IDAsIHAgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzKHMpOyBpIDwgcC5sZW5ndGg7IGkrKykge1xyXG4gICAgICAgICAgICBpZiAoZS5pbmRleE9mKHBbaV0pIDwgMCAmJiBPYmplY3QucHJvdG90eXBlLnByb3BlcnR5SXNFbnVtZXJhYmxlLmNhbGwocywgcFtpXSkpXHJcbiAgICAgICAgICAgICAgICB0W3BbaV1dID0gc1twW2ldXTtcclxuICAgICAgICB9XHJcbiAgICByZXR1cm4gdDtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fZGVjb3JhdGUoZGVjb3JhdG9ycywgdGFyZ2V0LCBrZXksIGRlc2MpIHtcclxuICAgIHZhciBjID0gYXJndW1lbnRzLmxlbmd0aCwgciA9IGMgPCAzID8gdGFyZ2V0IDogZGVzYyA9PT0gbnVsbCA/IGRlc2MgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKHRhcmdldCwga2V5KSA6IGRlc2MsIGQ7XHJcbiAgICBpZiAodHlwZW9mIFJlZmxlY3QgPT09IFwib2JqZWN0XCIgJiYgdHlwZW9mIFJlZmxlY3QuZGVjb3JhdGUgPT09IFwiZnVuY3Rpb25cIikgciA9IFJlZmxlY3QuZGVjb3JhdGUoZGVjb3JhdG9ycywgdGFyZ2V0LCBrZXksIGRlc2MpO1xyXG4gICAgZWxzZSBmb3IgKHZhciBpID0gZGVjb3JhdG9ycy5sZW5ndGggLSAxOyBpID49IDA7IGktLSkgaWYgKGQgPSBkZWNvcmF0b3JzW2ldKSByID0gKGMgPCAzID8gZChyKSA
|