diff --git a/src/keyboard/gutter_handler_test.js b/src/keyboard/gutter_handler_test.js index 4dc8fd6570..49476fe0d4 100644 --- a/src/keyboard/gutter_handler_test.js +++ b/src/keyboard/gutter_handler_test.js @@ -149,11 +149,11 @@ module.exports = { // Click annotation. emit(keys["enter"]); - + setTimeout(function() { // Check annotation is rendered. editor.renderer.$loop._flush(); - var tooltip = editor.container.querySelector(".ace_tooltip"); + var tooltip = editor.container.querySelector(".ace_gutter-tooltip"); assert.ok(/error test/.test(tooltip.textContent)); // Press escape to dismiss the tooltip. @@ -198,7 +198,7 @@ module.exports = { setTimeout(function() { // Check annotation is rendered. editor.renderer.$loop._flush(); - var tooltip = editor.container.querySelector(".ace_tooltip"); + var tooltip = editor.container.querySelector(".ace_gutter-tooltip"); assert.ok(/error test/.test(tooltip.textContent)); // Press escape to dismiss the tooltip. @@ -214,7 +214,7 @@ module.exports = { setTimeout(function() { // Check annotation is rendered. editor.renderer.$loop._flush(); - var tooltip = editor.container.querySelector(".ace_tooltip"); + var tooltip = editor.container.querySelector(".ace_gutter-tooltip"); assert.ok(/warning test/.test(tooltip.textContent)); // Press escape to dismiss the tooltip. diff --git a/src/layer/gutter.js b/src/layer/gutter.js index 0b0e922f52..213e5dcf19 100644 --- a/src/layer/gutter.js +++ b/src/layer/gutter.js @@ -6,6 +6,7 @@ var dom = require("../lib/dom"); var oop = require("../lib/oop"); var lang = require("../lib/lang"); +const {GUTTER_HOVER_TOOLTIP_ID} = require("../mouse/default_gutter_handler"); var EventEmitter = require("../lib/event_emitter").EventEmitter; var Lines = require("./lines").Lines; var nls = require("../config").nls; @@ -524,6 +525,7 @@ class Gutter{ annotationNode.setAttribute("aria-label", ariaLabel); annotationNode.setAttribute("tabindex", "-1"); annotationNode.setAttribute("role", "button"); + annotationNode.setAttribute("aria-describedby", GUTTER_HOVER_TOOLTIP_ID); } else { dom.setStyle(annotationNode.style, "display", "none"); diff --git a/src/mouse/default_gutter_handler.js b/src/mouse/default_gutter_handler.js index 3f68604013..9006d542f1 100644 --- a/src/mouse/default_gutter_handler.js +++ b/src/mouse/default_gutter_handler.js @@ -8,6 +8,14 @@ var Tooltip = require("../tooltip").Tooltip; var nls = require("../config").nls; var lang = require("../lib/lang"); +const GUTTER_TOOLTIP_LEFT_OFFSET = 5; +const GUTTER_TOOLTIP_TOP_OFFSET = 3; +const GUTTER_HOVER_TOOLTIP_ID = "ace_gutter_hover_tooltip"; +exports.GUTTER_HOVER_TOOLTIP_ID = GUTTER_HOVER_TOOLTIP_ID; +exports.GUTTER_TOOLTIP_LEFT_OFFSET = GUTTER_TOOLTIP_LEFT_OFFSET; +exports.GUTTER_TOOLTIP_TOP_OFFSET = GUTTER_TOOLTIP_TOP_OFFSET; + + /** * @param {MouseHandler} mouseHandler * @this {MouseHandler} @@ -15,7 +23,7 @@ var lang = require("../lib/lang"); function GutterHandler(mouseHandler) { var editor = mouseHandler.editor; var gutter = editor.renderer.$gutterLayer; - var tooltip = new GutterTooltip(editor); + var tooltip = new GutterTooltip(editor, true); mouseHandler.editor.setDefaultHandler("guttermousedown", function(e) { if (!editor.isFocused() || e.getButton() != 0) @@ -61,6 +69,8 @@ function GutterHandler(mouseHandler) { return; editor.on("mousewheel", hideTooltip); + editor.on("changeSession", hideTooltip); + window.addEventListener("keydown", hideTooltip, true); if (mouseHandler.$tooltipFollowsMouse) { moveTooltip(mouseEvent); @@ -71,20 +81,28 @@ function GutterHandler(mouseHandler) { var gutterElement = gutterCell.element.querySelector(".ace_gutter_annotation"); var rect = gutterElement.getBoundingClientRect(); var style = tooltip.getElement().style; - style.left = rect.right + "px"; - style.top = rect.bottom + "px"; + style.left = (rect.right - GUTTER_TOOLTIP_LEFT_OFFSET) + "px"; + style.top = (rect.bottom - GUTTER_TOOLTIP_TOP_OFFSET) + "px"; } else { moveTooltip(mouseEvent); } } } - function hideTooltip() { + function hideTooltip(e) { + // dont close tooltip in case the user wants to copy text from it (Ctrl/Meta + C) + if (e && e.type === "keydown" && (e.ctrlKey || e.metaKey)) + return; + // dont close tooltip if mouse is on tooltip + if (e && e.type === "mouseout" && (!e.relatedTarget || tooltip.getElement().contains(e.relatedTarget))) + return; if (tooltipTimeout) tooltipTimeout = clearTimeout(tooltipTimeout); if (tooltip.isOpen) { tooltip.hideTooltip(); editor.off("mousewheel", hideTooltip); + editor.off("changeSession", hideTooltip); + window.removeEventListener("keydown", hideTooltip, true); } } @@ -107,34 +125,47 @@ function GutterHandler(mouseHandler) { tooltipTimeout = null; if (mouseEvent && !mouseHandler.isMousePressed) showTooltip(); - else - hideTooltip(); }, 50); }); event.addListener(editor.renderer.$gutter, "mouseout", function(e) { mouseEvent = null; - if (!tooltip.isOpen || tooltipTimeout) + if (!tooltip.isOpen) return; tooltipTimeout = setTimeout(function() { tooltipTimeout = null; - hideTooltip(); + hideTooltip(e); }, 50); }, editor); - - editor.on("changeSession", hideTooltip); - editor.on("input", hideTooltip); } exports.GutterHandler = GutterHandler; class GutterTooltip extends Tooltip { - constructor(editor) { + constructor(editor, isHover = false) { super(editor.container); this.editor = editor; /**@type {Number | Undefined}*/ this.visibleTooltipRow; + var el = this.getElement(); + el.style.pointerEvents = "auto"; + el.role = "tooltip"; + if (isHover) { + el.id = GUTTER_HOVER_TOOLTIP_ID; + this.onMouseOut = this.onMouseOut.bind(this); + el.addEventListener("mouseout", this.onMouseOut); + } + } + + // handler needed to hide tooltip after mouse hovers from tooltip to editor + onMouseOut(e) { + if (!this.isOpen) return; + + if (!e.relatedTarget || this.getElement().contains(e.relatedTarget)) return; + + if (e && e.currentTarget.contains(e.relatedTarget)) return; + this.hideTooltip(); } setPosition(x, y) { diff --git a/src/mouse/default_gutter_handler_test.js b/src/mouse/default_gutter_handler_test.js index 64a5ff355c..2d8a2b31f4 100644 --- a/src/mouse/default_gutter_handler_test.js +++ b/src/mouse/default_gutter_handler_test.js @@ -11,6 +11,8 @@ var Editor = require("../editor").Editor; var Mode = require("../mode/java").Mode; var VirtualRenderer = require("../virtual_renderer").VirtualRenderer; var assert = require("../test/assertions"); +var user = require("../test/user"); +const {GUTTER_TOOLTIP_LEFT_OFFSET, GUTTER_TOOLTIP_TOP_OFFSET} = require("./default_gutter_handler"); var MouseEvent = function(type, opts){ var e = document.createEvent("MouseEvents"); e.initMouseEvent(/click|wheel/.test(type) ? type : "mouse" + type, @@ -36,8 +38,7 @@ module.exports = { editor = this.editor; next(); }, - - "test: gutter error tooltip" : function() { + "test: gutter error tooltip" : function(done) { var editor = this.editor; var value = ""; @@ -56,11 +57,12 @@ module.exports = { // Wait for the tooltip to appear after its timeout. setTimeout(function() { editor.renderer.$loop._flush(); - var tooltip = editor.container.querySelector(".ace_tooltip"); + var tooltip = editor.container.querySelector(".ace_gutter-tooltip"); assert.ok(/error test/.test(tooltip.textContent)); - }, 100); + done(); + }, 100); }, - "test: gutter security tooltip" : function() { + "test: gutter security tooltip" : function(done) { var editor = this.editor; var value = ""; @@ -79,11 +81,12 @@ module.exports = { // Wait for the tooltip to appear after its timeout. setTimeout(function() { editor.renderer.$loop._flush(); - var tooltip = editor.container.querySelector(".ace_tooltip"); + var tooltip = editor.container.querySelector(".ace_gutter-tooltip"); assert.ok(/security finding test/.test(tooltip.textContent)); - }, 100); + done(); + }, 100); }, - "test: gutter warning tooltip" : function() { + "test: gutter warning tooltip" : function(done) { var editor = this.editor; var value = ""; @@ -102,11 +105,12 @@ module.exports = { // Wait for the tooltip to appear after its timeout. setTimeout(function() { editor.renderer.$loop._flush(); - var tooltip = editor.container.querySelector(".ace_tooltip"); + var tooltip = editor.container.querySelector(".ace_gutter-tooltip"); assert.ok(/warning test/.test(tooltip.textContent)); - }, 100); + done(); + }, 100); }, - "test: gutter info tooltip" : function() { + "test: gutter info tooltip" : function(done) { var editor = this.editor; var value = ""; @@ -125,11 +129,12 @@ module.exports = { // Wait for the tooltip to appear after its timeout. setTimeout(function() { editor.renderer.$loop._flush(); - var tooltip = editor.container.querySelector(".ace_tooltip"); + var tooltip = editor.container.querySelector(".ace_gutter-tooltip"); assert.ok(/info test/.test(tooltip.textContent)); - }, 100); + done(); + }, 100); }, - "test: gutter hint tooltip" : function() { + "test: gutter hint tooltip" : function(done) { var editor = this.editor; var value = ""; @@ -148,9 +153,10 @@ module.exports = { // Wait for the tooltip to appear after its timeout. setTimeout(function() { editor.renderer.$loop._flush(); - var tooltip = editor.container.querySelector(".ace_tooltip"); + var tooltip = editor.container.querySelector(".ace_gutter-tooltip"); assert.ok(/suggestion test/.test(tooltip.textContent)); - }, 100); + done(); + }, 100); }, "test: gutter svg icons" : function() { var editor = this.editor; @@ -169,7 +175,7 @@ module.exports = { var annotation = line.children[2].firstChild; assert.ok(/ace_icon_svg/.test(annotation.className)); }, - "test: error show up in fold" : function() { + "test: error show up in fold" : function(done) { var editor = this.editor; var value = "x {" + "\n".repeat(50) + "}"; value = value.repeat(50); @@ -200,11 +206,12 @@ module.exports = { // Wait for the tooltip to appear after its timeout. setTimeout(function() { editor.renderer.$loop._flush(); - var tooltip = editor.container.querySelector(".ace_tooltip"); + var tooltip = editor.container.querySelector(".ace_gutter-tooltip"); assert.ok(/error in folded/.test(tooltip.textContent)); - }, 100); + done(); + }, 50); }, - "test: security show up in fold" : function() { + "test: security show up in fold" : function(done) { var editor = this.editor; var value = "x {" + "\n".repeat(50) + "}"; value = value.repeat(50); @@ -235,11 +242,12 @@ module.exports = { // Wait for the tooltip to appear after its timeout. setTimeout(function() { editor.renderer.$loop._flush(); - var tooltip = editor.container.querySelector(".ace_tooltip"); + var tooltip = editor.container.querySelector(".ace_gutter-tooltip"); assert.ok(/security finding in folded/.test(tooltip.textContent)); - }, 100); + done(); + }, 100); }, - "test: warning show up in fold" : function() { + "test: warning show up in fold" : function(done) { var editor = this.editor; var value = "x {" + "\n".repeat(50) + "}"; value = value.repeat(50); @@ -270,9 +278,10 @@ module.exports = { // Wait for the tooltip to appear after its timeout. setTimeout(function() { editor.renderer.$loop._flush(); - var tooltip = editor.container.querySelector(".ace_tooltip"); + var tooltip = editor.container.querySelector(".ace_gutter-tooltip"); assert.ok(/warning in folded/.test(tooltip.textContent)); - }, 100); + done(); + }, 100); }, "test: info not show up in fold" : function() { var editor = this.editor; @@ -396,12 +405,12 @@ module.exports = { // Wait for the tooltip to appear after its timeout. setTimeout(function() { editor.renderer.$loop._flush(); - var tooltip = editor.container.querySelector(".ace_tooltip"); + var tooltip = editor.container.querySelector(".ace_gutter-tooltip"); assert.ok(/error test/.test(tooltip.textContent)); - assert.equal(tooltip.style.left, `${rect.right}px`); - assert.equal(tooltip.style.top, `${rect.bottom}px`); + assert.equal(tooltip.style.left, `${rect.right - GUTTER_TOOLTIP_LEFT_OFFSET}px`); + assert.equal(tooltip.style.top, `${rect.bottom - GUTTER_TOOLTIP_TOP_OFFSET}px`); done(); - }, 100); + }, 100); }, "test: gutter tooltip should properly display special characters (\" ' & <)" : function(done) { var editor = this.editor; @@ -422,12 +431,39 @@ module.exports = { // Wait for the tooltip to appear after its timeout. setTimeout(function() { editor.renderer.$loop._flush(); - var tooltip = editor.container.querySelector(".ace_tooltip"); + var tooltip = editor.container.querySelector(".ace_gutter-tooltip"); assert.ok(/special characters " ' &