From 953e452f705a9e4af080d09825c7f5b49a0c9001 Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 11 Sep 2014 05:00:49 +0400 Subject: [PATCH] add doc tooltip --- lib/ace/autocomplete.js | 89 ++++++++++++++++++++++++++++++++++- lib/ace/editor.js | 8 ++-- lib/ace/ext/language_tools.js | 12 ++++- lib/ace/keyboard/textinput.js | 8 ++-- 4 files changed, 106 insertions(+), 11 deletions(-) diff --git a/lib/ace/autocomplete.js b/lib/ace/autocomplete.js index 78015bd45f9..b4bac72b309 100644 --- a/lib/ace/autocomplete.js +++ b/lib/ace/autocomplete.js @@ -36,6 +36,7 @@ var AcePopup = require("./autocomplete/popup").AcePopup; var util = require("./autocomplete/util"); var event = require("./lib/event"); var lang = require("./lib/lang"); +var dom = require("./lib/dom"); var snippetManager = require("./snippets").snippetManager; var Autocomplete = function() { @@ -52,6 +53,8 @@ var Autocomplete = function() { this.changeTimer = lang.delayedCall(function() { this.updateCompletions(true); }.bind(this)); + + this.tooltipTimer = lang.delayedCall(this.updateDocTooltip.bind(this), 50); }; (function() { @@ -64,6 +67,13 @@ var Autocomplete = function() { e.stop(); }.bind(this)); this.popup.focus = this.editor.focus.bind(this.editor); + this.popup.on("select", this.tooltipTimer.bind(null, null)); + this.popup.on("changeHoverMarker", this.tooltipTimer.bind(null, null)); + return this.popup; + }; + + this.getPopup = function() { + return this.popup || this.$init(); }; this.openPopup = function(editor, prefix, keepPopupPosition) { @@ -101,6 +111,7 @@ var Autocomplete = function() { this.editor.off("mousedown", this.mousedownListener); this.editor.off("mousewheel", this.mousewheelListener); this.changeTimer.cancel(); + this.hideDocTooltip(); if (this.popup && this.popup.isOpen) { this.gatherCompletionsId += 1; @@ -124,12 +135,17 @@ var Autocomplete = function() { this.detach(); }; - this.blurListener = function() { + this.blurListener = function(e) { // we have to check if activeElement is a child of popup because // on IE preventDefault doesn't stop scrollbar from being focussed var el = document.activeElement; - if (el != this.editor.textInput.getElement() && el.parentNode != this.popup.container) + var text = this.editor.textInput.getElement() + if (el != text && el.parentNode != this.popup.container + && el != this.tooltipNode && e.relatedTarget != this.tooltipNode + && e.relatedTarget != text + ) { this.detach(); + } }; this.mousedownListener = function(e) { @@ -178,6 +194,7 @@ var Autocomplete = function() { this.detach(); }; + this.commands = { "Up": function(editor) { editor.completer.goTo("up"); }, "Down": function(editor) { editor.completer.goTo("down"); }, @@ -310,6 +327,74 @@ var Autocomplete = function() { this.cancelContextMenu = function() { this.editor.$mouseHandler.cancelContextMenu(); }; + + this.updateDocTooltip = function() { + var popup = this.popup; + var all = popup.data; + var selected = all && (all[popup.getHoveredRow()] || all[popup.getRow()]); + var doc = null; + if (!selected || !this.editor || !this.popup.isOpen) + return this.hideDocTooltip(); + this.editor.completers.some(function(completer) { + if (completer.getDocTooltip) + doc = completer.getDocTooltip(selected); + return doc; + }); + if (!doc) + doc = selected; + + if (typeof doc == "string") + doc = {tooltipText: doc} + if (!doc || !(doc.docHTML || doc.docText)) + return this.hideDocTooltip(); + this.showDocTooltip(doc); + }; + + this.showDocTooltip = function(item) { + if (!this.tooltipNode) { + this.tooltipNode = dom.createElement("pre"); + this.tooltipNode.className = "ace_tooltip ace_doc-tooltip"; + this.tooltipNode.style.margin = 0; + this.tooltipNode.style.pointerEvents = "auto"; + this.tooltipNode.tabIndex = -1; + this.tooltipNode.onblur = this.blurListener.bind(this); + } + + var tooltipNode = this.tooltipNode; + if (item.docHTML) { + tooltipNode.innerHTML = item.docHTML; + } else if (item.docText) { + tooltipNode.textContent = item.docText; + } + + if (!tooltipNode.parentNode) + document.body.appendChild(tooltipNode); + var popup = this.popup; + var rect = popup.container.getBoundingClientRect(); + tooltipNode.style.top = popup.container.style.top; + tooltipNode.style.bottom = popup.container.style.bottom; + + if (window.innerWidth - rect.right < 320) { + tooltipNode.style.right = window.innerWidth - rect.left + "px"; + tooltipNode.style.left = ""; + } else { + tooltipNode.style.left = (rect.right + 1) + "px"; + tooltipNode.style.right = ""; + } + // tooltipNode.style.height = rect.height + "px"; + tooltipNode.style.display = "block"; + }; + + this.hideDocTooltip = function() { + this.tooltipTimer.cancel(); + if (!this.tooltipNode) return; + var el = this.tooltipNode; + if (!this.editor.isFocused() && document.activeElement == el) + this.editor.focus(); + this.tooltipNode = null; + if (el.parentNode) + el.parentNode.removeChild(el); + }; }).call(Autocomplete.prototype); diff --git a/lib/ace/editor.js b/lib/ace/editor.js index f2b9744c966..95dee68d9f9 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -662,13 +662,13 @@ var Editor = function(renderer, session) { * * **/ - this.onFocus = function() { + this.onFocus = function(e) { if (this.$isFocused) return; this.$isFocused = true; this.renderer.showCursor(); this.renderer.visualizeFocus(); - this._emit("focus"); + this._emit("focus", e); }; /** @@ -677,13 +677,13 @@ var Editor = function(renderer, session) { * * **/ - this.onBlur = function() { + this.onBlur = function(e) { if (!this.$isFocused) return; this.$isFocused = false; this.renderer.hideCursor(); this.renderer.visualizeBlur(); - this._emit("blur"); + this._emit("blur", e); }; this.$cursorChange = function() { diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index d8b2a1dd663..3b4dfd7ad5f 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -34,6 +34,7 @@ define(function(require, exports, module) { var snippetManager = require("../snippets").snippetManager; var Autocomplete = require("../autocomplete").Autocomplete; var config = require("../config"); +var lang = require("../lib/lang"); var util = require("../autocomplete/util"); var textCompleter = require("../autocomplete/text_completer"); @@ -59,11 +60,20 @@ var snippetCompleter = { completions.push({ caption: caption, snippet: s.content, - meta: s.tabTrigger && !s.name ? s.tabTrigger + "\u21E5 " : "snippet" + meta: s.tabTrigger && !s.name ? s.tabTrigger + "\u21E5 " : "snippet", + type: "snippet" }); } }, this); callback(null, completions); + }, + getDocTooltip: function(item) { + if (item.type == "snippet" && !item.docHTML) { + item.docHTML = [ + "", lang.escapeHTML(item.caption), "", "
", + lang.escapeHTML(item.snippet) + ].join(""); + } } }; diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index d22930243ac..1e6b057e01d 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -66,13 +66,13 @@ var TextInput = function(parentNode, host) { // ie9 throws error if document.activeElement is accessed too soon try { var isFocused = document.activeElement === text; } catch(e) {} - event.addListener(text, "blur", function() { - host.onBlur(); + event.addListener(text, "blur", function(e) { + host.onBlur(e); isFocused = false; }); - event.addListener(text, "focus", function() { + event.addListener(text, "focus", function(e) { isFocused = true; - host.onFocus(); + host.onFocus(e); resetSelection(); }); this.focus = function() { text.focus(); };