Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
obsidian-various-complements-plugin/show-hint.ts
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
661 lines (597 sloc)
18.4 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @ts-nocheck | |
// Because this code is originally javascript code. | |
// CodeMirror, copyright (c) by Marijn Haverbeke and others | |
// Distributed under an MIT license: https://codemirror.net/LICENSE | |
// declare global: DOMRect | |
"use strict"; | |
var HINT_ELEMENT_CLASS = "CodeMirror-hint"; | |
var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active"; | |
// This is the old interface, kept around for now to stay | |
// backwards-compatible. | |
CodeMirror.showHint = function (cm, getHints, options) { | |
if (!getHints) return cm.showHint(options); | |
if (options && options.async) getHints.async = true; | |
var newOpts = { hint: getHints }; | |
if (options) for (var prop in options) newOpts[prop] = options[prop]; | |
return cm.showHint(newOpts); | |
}; | |
CodeMirror.defineExtension("showHint", function (options) { | |
options = parseOptions(this, this.getCursor("start"), options); | |
var selections = this.listSelections(); | |
if (selections.length > 1) return; | |
// By default, don't allow completion when something is selected. | |
// A hint function can have a `supportsSelection` property to | |
// indicate that it can handle selections. | |
if (this.somethingSelected()) { | |
if (!options.hint.supportsSelection) return; | |
// Don't try with cross-line selections | |
for (var i = 0; i < selections.length; i++) | |
if (selections[i].head.line != selections[i].anchor.line) return; | |
} | |
if (this.state.completionActive) this.state.completionActive.close(); | |
var completion = (this.state.completionActive = new Completion( | |
this, | |
options | |
)); | |
if (!completion.options.hint) return; | |
CodeMirror.signal(this, "startCompletion", this); | |
completion.update(true); | |
}); | |
CodeMirror.defineExtension("closeHint", function () { | |
if (this.state.completionActive) this.state.completionActive.close(); | |
}); | |
function Completion(cm, options) { | |
this.cm = cm; | |
this.options = options; | |
this.widget = null; | |
this.debounce = 0; | |
this.tick = 0; | |
this.startPos = this.cm.getCursor("start"); | |
this.startLen = | |
this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length; | |
if (this.options.updateOnCursorActivity) { | |
var self = this; | |
cm.on( | |
"cursorActivity", | |
(this.activityFunc = function () { | |
self.cursorActivity(); | |
}) | |
); | |
} | |
} | |
var requestAnimationFrame = | |
window.requestAnimationFrame || | |
function (fn) { | |
return setTimeout(fn, 1000 / 60); | |
}; | |
var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout; | |
Completion.prototype = { | |
close: function () { | |
if (!this.active()) return; | |
this.cm.state.completionActive = null; | |
this.tick = null; | |
if (this.options.updateOnCursorActivity) { | |
this.cm.off("cursorActivity", this.activityFunc); | |
} | |
if (this.widget && this.data) CodeMirror.signal(this.data, "close"); | |
if (this.widget) this.widget.close(); | |
CodeMirror.signal(this.cm, "endCompletion", this.cm); | |
}, | |
active: function () { | |
return this.cm.state.completionActive == this; | |
}, | |
pick: function (data, i) { | |
var completion = data.list[i], | |
self = this; | |
this.cm.operation(function () { | |
if (completion.hint) completion.hint(self.cm, data, completion); | |
else | |
self.cm.replaceRange( | |
getText(completion), | |
completion.from || data.from, | |
completion.to || data.to, | |
"complete" | |
); | |
CodeMirror.signal(data, "pick", completion); | |
self.cm.scrollIntoView(); | |
}); | |
if (this.options.closeOnPick) { | |
this.close(); | |
} | |
}, | |
cursorActivity: function () { | |
if (this.debounce) { | |
cancelAnimationFrame(this.debounce); | |
this.debounce = 0; | |
} | |
var identStart = this.startPos; | |
if (this.data) { | |
identStart = this.data.from; | |
} | |
var pos = this.cm.getCursor(), | |
line = this.cm.getLine(pos.line); | |
if ( | |
pos.line != this.startPos.line || | |
line.length - pos.ch != this.startLen - this.startPos.ch || | |
pos.ch < identStart.ch || | |
this.cm.somethingSelected() || | |
!pos.ch || | |
this.options.closeCharacters.test(line.charAt(pos.ch - 1)) | |
) { | |
this.close(); | |
} else { | |
var self = this; | |
this.debounce = requestAnimationFrame(function () { | |
self.update(); | |
}); | |
if (this.widget) this.widget.disable(); | |
} | |
}, | |
update: function (first) { | |
if (this.tick == null) return; | |
var self = this, | |
myTick = ++this.tick; | |
fetchHints(this.options.hint, this.cm, this.options, function (data) { | |
if (self.tick == myTick) self.finishUpdate(data, first); | |
}); | |
}, | |
finishUpdate: function (data, first) { | |
if (this.data) CodeMirror.signal(this.data, "update"); | |
var picked = | |
(this.widget && this.widget.picked) || | |
(first && this.options.completeSingle); | |
if (this.widget) this.widget.close(); | |
this.data = data; | |
if (data && data.list.length) { | |
if (picked && data.list.length == 1) { | |
this.pick(data, 0); | |
} else { | |
this.widget = new Widget(this, data); | |
CodeMirror.signal(data, "shown"); | |
} | |
} | |
}, | |
}; | |
function parseOptions(cm, pos, options) { | |
var editor = cm.options.hintOptions; | |
var out = {}; | |
for (var prop in defaultOptions) out[prop] = defaultOptions[prop]; | |
if (editor) | |
for (var prop in editor) | |
if (editor[prop] !== undefined) out[prop] = editor[prop]; | |
if (options) | |
for (var prop in options) | |
if (options[prop] !== undefined) out[prop] = options[prop]; | |
if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos); | |
return out; | |
} | |
function getText(completion) { | |
if (typeof completion == "string") return completion; | |
else return completion.text; | |
} | |
function buildKeyMap(completion, handle) { | |
var baseMap = { | |
Up: function () { | |
handle.moveFocus(-1); | |
}, | |
Down: function () { | |
handle.moveFocus(1); | |
}, | |
PageUp: function () { | |
handle.moveFocus(-handle.menuSize() + 1, true); | |
}, | |
PageDown: function () { | |
handle.moveFocus(handle.menuSize() - 1, true); | |
}, | |
Home: function () { | |
handle.setFocus(0); | |
}, | |
End: function () { | |
handle.setFocus(handle.length - 1); | |
}, | |
Enter: handle.pick, | |
Tab: handle.pick, | |
Esc: handle.close, | |
}; | |
var mac = /Mac/.test(navigator.platform); | |
if (mac) { | |
baseMap["Ctrl-P"] = function () { | |
handle.moveFocus(-1); | |
}; | |
baseMap["Ctrl-N"] = function () { | |
handle.moveFocus(1); | |
}; | |
} | |
var custom = completion.options.customKeys; | |
var ourMap = custom ? {} : baseMap; | |
function addBinding(key, val) { | |
var bound; | |
if (typeof val != "string") | |
bound = function (cm) { | |
return val(cm, handle); | |
}; | |
// This mechanism is deprecated | |
else if (baseMap.hasOwnProperty(val)) bound = baseMap[val]; | |
else bound = val; | |
ourMap[key] = bound; | |
} | |
if (custom) | |
for (var key in custom) | |
if (custom.hasOwnProperty(key)) addBinding(key, custom[key]); | |
var extra = completion.options.extraKeys; | |
if (extra) | |
for (var key in extra) | |
if (extra.hasOwnProperty(key)) addBinding(key, extra[key]); | |
return ourMap; | |
} | |
function getHintElement(hintsElement, el) { | |
while (el && el != hintsElement) { | |
if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) | |
return el; | |
el = el.parentNode; | |
} | |
} | |
function Widget(completion, data) { | |
this.completion = completion; | |
this.data = data; | |
this.picked = false; | |
var widget = this, | |
cm = completion.cm; | |
var ownerDocument = cm.getInputField().ownerDocument; | |
var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow; | |
var hints = (this.hints = ownerDocument.createElement("ul")); | |
var theme = completion.cm.options.theme; | |
hints.className = "CodeMirror-hints " + theme; | |
this.selectedHint = data.selectedHint || 0; | |
var completions = data.list; | |
for (var i = 0; i < completions.length; ++i) { | |
var elt = hints.appendChild(ownerDocument.createElement("li")), | |
cur = completions[i]; | |
var className = | |
HINT_ELEMENT_CLASS + | |
(i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS); | |
if (cur.className != null) className = cur.className + " " + className; | |
elt.className = className; | |
if (cur.render) cur.render(elt, data, cur); | |
else | |
elt.appendChild( | |
ownerDocument.createTextNode(cur.displayText || getText(cur)) | |
); | |
elt.hintId = i; | |
} | |
var container = completion.options.container || ownerDocument.body; | |
var pos = cm.cursorCoords( | |
completion.options.alignWithWord ? data.from : null | |
); | |
var left = pos.left, | |
top = pos.bottom, | |
below = true; | |
var offsetLeft = 0, | |
offsetTop = 0; | |
if (container !== ownerDocument.body) { | |
// We offset the cursor position because left and top are relative to the offsetParent's top left corner. | |
var isContainerPositioned = | |
["absolute", "relative", "fixed"].indexOf( | |
parentWindow.getComputedStyle(container).position | |
) !== -1; | |
var offsetParent = isContainerPositioned | |
? container | |
: container.offsetParent; | |
var offsetParentPosition = offsetParent.getBoundingClientRect(); | |
var bodyPosition = ownerDocument.body.getBoundingClientRect(); | |
offsetLeft = | |
offsetParentPosition.left - bodyPosition.left - offsetParent.scrollLeft; | |
offsetTop = | |
offsetParentPosition.top - bodyPosition.top - offsetParent.scrollTop; | |
} | |
hints.style.left = left - offsetLeft + "px"; | |
hints.style.top = top - offsetTop + "px"; | |
// If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. | |
var winW = | |
parentWindow.innerWidth || | |
Math.max( | |
ownerDocument.body.offsetWidth, | |
ownerDocument.documentElement.offsetWidth | |
); | |
var winH = | |
parentWindow.innerHeight || | |
Math.max( | |
ownerDocument.body.offsetHeight, | |
ownerDocument.documentElement.offsetHeight | |
); | |
container.appendChild(hints); | |
var box = completion.options.moveOnOverlap | |
? hints.getBoundingClientRect() | |
: new DOMRect(); | |
var scrolls = completion.options.paddingForScrollbar | |
? hints.scrollHeight > hints.clientHeight + 1 | |
: false; | |
// Compute in the timeout to avoid reflow on init | |
var startScroll; | |
setTimeout(function () { | |
startScroll = cm.getScrollInfo(); | |
}); | |
var overlapY = box.bottom - winH; | |
if (overlapY > 0) { | |
var height = box.bottom - box.top, | |
curTop = pos.top - (pos.bottom - box.top); | |
if (curTop - height > 0) { | |
// Fits above cursor | |
hints.style.top = (top = pos.top - height - offsetTop) + "px"; | |
below = false; | |
} else if (height > winH) { | |
hints.style.height = winH - 5 + "px"; | |
hints.style.top = (top = pos.bottom - box.top - offsetTop) + "px"; | |
var cursor = cm.getCursor(); | |
if (data.from.ch != cursor.ch) { | |
pos = cm.cursorCoords(cursor); | |
hints.style.left = (left = pos.left - offsetLeft) + "px"; | |
box = hints.getBoundingClientRect(); | |
} | |
} | |
} | |
var overlapX = box.right - winW; | |
if (overlapX > 0) { | |
if (box.right - box.left > winW) { | |
hints.style.width = winW - 5 + "px"; | |
overlapX -= box.right - box.left - winW; | |
} | |
hints.style.left = (left = pos.left - overlapX - offsetLeft) + "px"; | |
} | |
if (scrolls) | |
for (var node = hints.firstChild; node; node = node.nextSibling) | |
node.style.paddingRight = cm.display.nativeBarWidth + "px"; | |
cm.addKeyMap( | |
(this.keyMap = buildKeyMap(completion, { | |
moveFocus: function (n, avoidWrap) { | |
widget.changeActive(widget.selectedHint + n, avoidWrap); | |
}, | |
setFocus: function (n) { | |
widget.changeActive(n); | |
}, | |
menuSize: function () { | |
return widget.screenAmount(); | |
}, | |
length: completions.length, | |
close: function () { | |
completion.close(); | |
}, | |
pick: function () { | |
widget.pick(); | |
}, | |
data: data, | |
})) | |
); | |
if (completion.options.closeOnUnfocus) { | |
var closingOnBlur; | |
cm.on( | |
"blur", | |
(this.onBlur = function () { | |
closingOnBlur = setTimeout(function () { | |
completion.close(); | |
}, 100); | |
}) | |
); | |
cm.on( | |
"focus", | |
(this.onFocus = function () { | |
clearTimeout(closingOnBlur); | |
}) | |
); | |
} | |
cm.on( | |
"scroll", | |
(this.onScroll = function () { | |
var curScroll = cm.getScrollInfo(), | |
editor = cm.getWrapperElement().getBoundingClientRect(); | |
var newTop = top + startScroll.top - curScroll.top; | |
var point = | |
newTop - | |
(parentWindow.pageYOffset || | |
(ownerDocument.documentElement || ownerDocument.body).scrollTop); | |
if (!below) point += hints.offsetHeight; | |
if (point <= editor.top || point >= editor.bottom) | |
return completion.close(); | |
hints.style.top = newTop + "px"; | |
hints.style.left = left + startScroll.left - curScroll.left + "px"; | |
}) | |
); | |
CodeMirror.on(hints, "dblclick", function (e) { | |
var t = getHintElement(hints, e.target || e.srcElement); | |
if (t && t.hintId != null) { | |
widget.changeActive(t.hintId); | |
widget.pick(); | |
} | |
}); | |
CodeMirror.on(hints, "click", function (e) { | |
var t = getHintElement(hints, e.target || e.srcElement); | |
if (t && t.hintId != null) { | |
widget.changeActive(t.hintId); | |
if (completion.options.completeOnSingleClick) widget.pick(); | |
} | |
}); | |
CodeMirror.on(hints, "mousedown", function () { | |
setTimeout(function () { | |
cm.focus(); | |
}, 20); | |
}); | |
// The first hint doesn't need to be scrolled to on init | |
var selectedHintRange = this.getSelectedHintRange(); | |
if (selectedHintRange.from !== 0 || selectedHintRange.to !== 0) { | |
this.scrollToActive(); | |
} | |
CodeMirror.signal( | |
data, | |
"select", | |
completions[this.selectedHint], | |
hints.childNodes[this.selectedHint] | |
); | |
return true; | |
} | |
Widget.prototype = { | |
close: function () { | |
if (this.completion.widget != this) return; | |
this.completion.widget = null; | |
this.hints.parentNode.removeChild(this.hints); | |
this.completion.cm.removeKeyMap(this.keyMap); | |
var cm = this.completion.cm; | |
if (this.completion.options.closeOnUnfocus) { | |
cm.off("blur", this.onBlur); | |
cm.off("focus", this.onFocus); | |
} | |
cm.off("scroll", this.onScroll); | |
}, | |
disable: function () { | |
this.completion.cm.removeKeyMap(this.keyMap); | |
var widget = this; | |
this.keyMap = { | |
Enter: function () { | |
widget.picked = true; | |
}, | |
}; | |
this.completion.cm.addKeyMap(this.keyMap); | |
}, | |
pick: function () { | |
this.completion.pick(this.data, this.selectedHint); | |
}, | |
changeActive: function (i, avoidWrap) { | |
if (i >= this.data.list.length) | |
i = avoidWrap ? this.data.list.length - 1 : 0; | |
else if (i < 0) i = avoidWrap ? 0 : this.data.list.length - 1; | |
if (this.selectedHint == i) return; | |
var node = this.hints.childNodes[this.selectedHint]; | |
if (node) | |
node.className = node.className.replace( | |
" " + ACTIVE_HINT_ELEMENT_CLASS, | |
"" | |
); | |
node = this.hints.childNodes[(this.selectedHint = i)]; | |
node.className += " " + ACTIVE_HINT_ELEMENT_CLASS; | |
this.scrollToActive(); | |
CodeMirror.signal( | |
this.data, | |
"select", | |
this.data.list[this.selectedHint], | |
node | |
); | |
}, | |
scrollToActive: function () { | |
var selectedHintRange = this.getSelectedHintRange(); | |
var node1 = this.hints.childNodes[selectedHintRange.from]; | |
var node2 = this.hints.childNodes[selectedHintRange.to]; | |
var firstNode = this.hints.firstChild; | |
if (node1.offsetTop < this.hints.scrollTop) | |
this.hints.scrollTop = node1.offsetTop - firstNode.offsetTop; | |
else if ( | |
node2.offsetTop + node2.offsetHeight > | |
this.hints.scrollTop + this.hints.clientHeight | |
) | |
this.hints.scrollTop = | |
node2.offsetTop + | |
node2.offsetHeight - | |
this.hints.clientHeight + | |
firstNode.offsetTop; | |
}, | |
screenAmount: function () { | |
return ( | |
Math.floor( | |
this.hints.clientHeight / this.hints.firstChild.offsetHeight | |
) || 1 | |
); | |
}, | |
getSelectedHintRange: function () { | |
var margin = this.completion.options.scrollMargin || 0; | |
return { | |
from: Math.max(0, this.selectedHint - margin), | |
to: Math.min(this.data.list.length - 1, this.selectedHint + margin), | |
}; | |
}, | |
}; | |
function applicableHelpers(cm, helpers) { | |
if (!cm.somethingSelected()) return helpers; | |
var result = []; | |
for (var i = 0; i < helpers.length; i++) | |
if (helpers[i].supportsSelection) result.push(helpers[i]); | |
return result; | |
} | |
function fetchHints(hint, cm, options, callback) { | |
if (hint.async) { | |
hint(cm, callback, options); | |
} else { | |
var result = hint(cm, options); | |
if (result && result.then) result.then(callback); | |
else callback(result); | |
} | |
} | |
function resolveAutoHints(cm, pos) { | |
var helpers = cm.getHelpers(pos, "hint"), | |
words; | |
if (helpers.length) { | |
var resolved = function (cm, callback, options) { | |
var app = applicableHelpers(cm, helpers); | |
function run(i) { | |
if (i == app.length) return callback(null); | |
fetchHints(app[i], cm, options, function (result) { | |
if (result && result.list.length > 0) callback(result); | |
else run(i + 1); | |
}); | |
} | |
run(0); | |
}; | |
resolved.async = true; | |
resolved.supportsSelection = true; | |
return resolved; | |
} else if ((words = cm.getHelper(cm.getCursor(), "hintWords"))) { | |
return function (cm) { | |
return CodeMirror.hint.fromList(cm, { words: words }); | |
}; | |
} else if (CodeMirror.hint.anyword) { | |
return function (cm, options) { | |
return CodeMirror.hint.anyword(cm, options); | |
}; | |
} else { | |
return function () {}; | |
} | |
} | |
CodeMirror.registerHelper("hint", "auto", { | |
resolve: resolveAutoHints, | |
}); | |
CodeMirror.registerHelper("hint", "fromList", function (cm, options) { | |
var cur = cm.getCursor(), | |
token = cm.getTokenAt(cur); | |
var term, | |
from = CodeMirror.Pos(cur.line, token.start), | |
to = cur; | |
if ( | |
token.start < cur.ch && | |
/\w/.test(token.string.charAt(cur.ch - token.start - 1)) | |
) { | |
term = token.string.substr(0, cur.ch - token.start); | |
} else { | |
term = ""; | |
from = cur; | |
} | |
var found = []; | |
for (var i = 0; i < options.words.length; i++) { | |
var word = options.words[i]; | |
if (word.slice(0, term.length) == term) found.push(word); | |
} | |
if (found.length) return { list: found, from: from, to: to }; | |
}); | |
CodeMirror.commands.autocomplete = CodeMirror.showHint; | |
var defaultOptions = { | |
hint: CodeMirror.hint.auto, | |
completeSingle: true, | |
alignWithWord: true, | |
closeCharacters: /[\s()\[\]{};:>,]/, | |
closeOnPick: true, | |
closeOnUnfocus: true, | |
updateOnCursorActivity: true, | |
completeOnSingleClick: true, | |
container: null, | |
customKeys: null, | |
extraKeys: null, | |
paddingForScrollbar: true, | |
moveOnOverlap: true, | |
}; | |
CodeMirror.defineOption("hintOptions", null); |