diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 6fa7b59575f..9d9d9c07e16 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -789,18 +789,26 @@ define(function (require, exports, module) { }; /** - * Returns true if pos is between start and end (inclusive at both ends) + * Returns true if pos is between start and end (INclusive at start; EXclusive at end by default, + * but overridable via the endInclusive flag). * @param {{line:number, ch:number}} pos * @param {{line:number, ch:number}} start * @param {{line:number, ch:number}} end + * @param {boolean} endInclusive * */ - Editor.prototype.posWithinRange = function (pos, start, end) { - var startIndex = this.indexFromPos(start), - endIndex = this.indexFromPos(end), - posIndex = this.indexFromPos(pos); - - return posIndex >= startIndex && posIndex <= endIndex; + Editor.prototype.posWithinRange = function (pos, start, end, endInclusive) { + if (start.line <= pos.line && end.line >= pos.line) { + if (endInclusive) { + return (start.line < pos.line || start.ch <= pos.ch) && // inclusive + (end.line > pos.line || end.ch >= pos.ch); // inclusive + } else { + return (start.line < pos.line || start.ch <= pos.ch) && // inclusive + (end.line > pos.line || end.ch > pos.ch); // exclusive + } + + } + return false; }; /** @@ -1336,7 +1344,7 @@ define(function (require, exports, module) { _setEditorOption(value, cmOption); _prefs.setValue(prefName, value); } - + /** * Sets whether to use tab characters (vs. spaces) when inserting new text. Affects all Editors. * @param {boolean} value diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index 77add7dad92..8357c869fb2 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -326,7 +326,7 @@ define(function (require, exports, module) { // If we are in a selection starting and ending in invalid tokens and with no content (not considering spaces), // find if we are inside a block-comment. } else if (startCtx.token.className === null && endCtx.token.className === null && - !editor.posWithinRange(ctx.pos, startCtx.pos, endCtx.pos)) { + !editor.posWithinRange(ctx.pos, startCtx.pos, endCtx.pos, true)) { result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, startCtx); // We found a comment, find the start and end and check if the selection is inside the block-comment. @@ -334,7 +334,7 @@ define(function (require, exports, module) { prefixPos = _findCommentStart(startCtx, prefixExp); suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); - if (prefixPos !== null && suffix !== null && !editor.posWithinRange(sel.start, prefixPos, suffixPos)) { + if (prefixPos !== null && suffix !== null && !editor.posWithinRange(sel.start, prefixPos, suffixPos, true)) { canComment = true; } } else { @@ -366,7 +366,7 @@ define(function (require, exports, module) { // Search if there is another comment in the selection. Do nothing if there is one. if (!canComment && !invalidComment && !lineUncomment && suffixPos) { var start = {line: suffixPos.line, ch: suffixPos.ch + suffix.length + 1}; - if (editor.posWithinRange(start, sel.start, sel.end)) { + if (editor.posWithinRange(start, sel.start, sel.end, true)) { // Start searching at the next token, if there is one. result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && _findNextBlockComment(ctx, sel.end, prefixExp); diff --git a/src/extensions/default/HoverPreview/main.js b/src/extensions/default/HoverPreview/main.js index a8f7aa90e22..1c4787f4a0c 100644 --- a/src/extensions/default/HoverPreview/main.js +++ b/src/extensions/default/HoverPreview/main.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define, brackets, $, PathUtils, CodeMirror */ +/*global define, brackets, $, window, PathUtils, CodeMirror */ define(function (require, exports, module) { "use strict"; @@ -41,32 +41,70 @@ define(function (require, exports, module) { var defaultPrefs = { enabled: true }, enabled, // Only show preview if true prefs = null, // Preferences - previewMark, // CodeMirror marker highlighting the preview text $previewContainer, // Preview container $previewContent, // Preview content holder - currentImagePreviewContent = "", // Current image preview content, or "" if no content is showing. - lastPreviewedColorOrGradient = "", // Color/gradient value of last previewed. - lastPreviewedImagePath = ""; // Image path of last previewed. + lastPos; // Last line/ch pos processed by handleMouseMove // Constants var CMD_ENABLE_HOVER_PREVIEW = "view.enableHoverPreview", + HOVER_DELAY = 350, // Time (ms) mouse must remain over a provider's matched text before popover appears POSITION_OFFSET = 38, // Distance between the bottom of the line and the bottom of the preview container POINTER_LEFT_OFFSET = 17, // Half of the pointer width, used to find the center of the pointer POINTER_TOP_OFFSET = 7, // Pointer height, used to shift popover above pointer POSITION_BELOW_OFFSET = 16; // Amount to adjust to top position when the preview bubble is below the text + /** + * There are three states for this var: + * 1. If null, there is no provider result for the given mouse position. + * 2. If non-null, and visible==true, there is a popover currently showing. + * 3. If non-null, but visible==false, there is a provider result but it has not been shown yet because + * we're waiting for HOVER_DELAY, which is tracked by hoverTimer. The state changes to visible==true as + * soon as hoverTimer fires. If the mouse moves before then, the popover will never become visible. + * + * @type {{ + * visible: boolean, + * editor: !Editor, + * hoverTimer: number, - setTimeout() token + * start: !{line, ch}, - start of matched text range + * end: !{line, ch}, - end of matched text range + * content: !string, - HTML content to display in popover + * onShow: ?function():void, - called once popover content added to the DOM (may never be called) + * xpos: number, - x of center of popover + * ytop: number, - y of top of matched text (when popover placed above text, normally) + * ybot: number, - y of bottom of matched text (when popover moved below text, avoiding window top) + * marker: ?CodeMirror.TextMarker - only set once visible==true + * }} + */ + var popoverState = null; + + + + // Popover widget management ---------------------------------------------- + + /** + * Cancels whatever popoverState was currently pending and sets it back to null. If the popover was visible, + * hides it; if the popover was invisible and still pending, cancels hoverTimer so it will never be shown. + */ function hidePreview() { - if (previewMark) { - previewMark.clear(); - previewMark = null; + if (!popoverState) { + return; + } + + if (popoverState.visible) { + popoverState.marker.clear(); + + $previewContent.empty(); + $previewContainer.hide(); + + } else { + window.clearTimeout(popoverState.hoverTimer); } - $previewContent.empty(); - $previewContainer.hide(); - currentImagePreviewContent = ""; + + popoverState = null; } - function positionPreview(xpos, ypos, ybot) { - var top = ypos - $previewContainer.height() - POSITION_OFFSET; + function positionPreview(xpos, ytop, ybot) { + var top = ytop - $previewContainer.height() - POSITION_OFFSET; if (top < 0) { $previewContainer.removeClass("preview-bubble-above"); @@ -86,11 +124,29 @@ define(function (require, exports, module) { } } - function showPreview(content, xpos, ypos, ybot) { - hidePreview(); - $previewContent.append(content); + /** + * Changes the current hidden popoverState to visible, showing it in the UI and highlighting + * its matching text in the editor. + */ + function showPreview() { + + var cm = popoverState.editor._codeMirror; + popoverState.marker = cm.markText( + popoverState.start, + popoverState.end, + {className: "hover-preview-highlight"} + ); + + $previewContent.append(popoverState.content); $previewContainer.show(); - positionPreview(xpos, ypos, ybot); + + popoverState.visible = true; + + if (popoverState.onShow) { + popoverState.onShow(); + } + + positionPreview(popoverState.xpos, popoverState.ytop, popoverState.ybot); } function divContainsMouse($div, event) { @@ -101,12 +157,13 @@ define(function (require, exports, module) { event.clientY >= offset.top && event.clientY <= offset.top + $div.height()); } - + + + // Color & gradient preview provider -------------------------------------- + function colorAndGradientPreviewProvider(editor, pos, token, line) { var cm = editor._codeMirror; - lastPreviewedColorOrGradient = ""; - // Check for gradient var gradientRegEx = /-webkit-gradient\([^;]*;?|(-moz-|-ms-|-o-|-webkit-|\s)(linear-gradient\([^;]*);?|(-moz-|-ms-|-o-|-webkit-)(radial-gradient\([^;]*);?/, gradientMatch = line.match(gradientRegEx), @@ -149,11 +206,8 @@ define(function (require, exports, module) { while (match) { if (pos.ch >= match.index && pos.ch <= match.index + match[0].length) { - lastPreviewedColorOrGradient = (colorValue || match[0]); - var preview = "
" + - "
" + - "
" + + var previewCSS = prefix + (colorValue || match[0]); + var preview = "
" + "
"; var startPos = {line: pos.line, ch: match.index}, endPos = {line: pos.line, ch: match.index + match[0].length}, @@ -161,25 +215,29 @@ define(function (require, exports, module) { xPos; xPos = (cm.charCoords(endPos).left - startCoords.left) / 2 + startCoords.left; - showPreview(preview, xPos, startCoords.top, startCoords.bottom); - previewMark = cm.markText( - startPos, - endPos, - {className: "hover-preview-highlight"} - ); - return true; + + return { + start: startPos, + end: endPos, + content: preview, + xpos: xPos, + ytop: startCoords.top, + ybot: startCoords.bottom, + _previewCSS: previewCSS + }; } match = colorRegEx.exec(line); } - return false; + return null; } + + // Image preview provider ------------------------------------------------- + function imagePreviewProvider(editor, pos, token, line) { var cm = editor._codeMirror; - lastPreviewedImagePath = ""; - // Check for image name var urlRegEx = /url\(([^\)]*)\)/, tokenString, @@ -219,54 +277,78 @@ define(function (require, exports, module) { var imgPreview = "
" + " " + "
"; - if (imgPreview !== currentImagePreviewContent) { - var coord = cm.charCoords(sPos); - var xpos = (cm.charCoords(ePos).left - coord.left) / 2 + coord.left; - var ypos = coord.top; - var ybot = coord.bottom; - showPreview(imgPreview, xpos, ypos, ybot); - + var coord = cm.charCoords(sPos); + var xpos = (cm.charCoords(ePos).left - coord.left) / 2 + coord.left; + + var showHandler = function () { // Hide the preview container until the image is loaded. $previewContainer.hide(); - $previewContainer.find("img").on("load", function () { + + $previewContainer.find(".image-preview > img").on("load", function () { $previewContent .append("
" + this.naturalWidth + " x " + this.naturalHeight + " pixels" + "
" ); $previewContainer.show(); - positionPreview(xpos, ypos, ybot); + positionPreview(popoverState.xpos, popoverState.ytop, popoverState.ybot); }); - previewMark = cm.markText( - sPos, - ePos, - {className: "hover-preview-highlight"} - ); - currentImagePreviewContent = imgPreview; - lastPreviewedImagePath = imgPath; - } - return true; + }; + + return { + start: sPos, + end: ePos, + content: imgPreview, + onShow: showHandler, + xpos: xpos, + ytop: coord.top, + ybot: coord.bottom, + _imgPath: imgPath + }; } } } - return false; + return null; } - function queryPreviewProviders(editor, pos, token, line) { + + // Preview hide/show logic ------------------------------------------------ + + /** + * Returns a 'ready for use' popover state object: + * { visible: false, editor, start, end, content, ?onShow, xpos, ytop, ybot } + * Lacks only hoverTimer (supplied by handleMouseMove()) and marker (supplied by showPreview()). + */ + function queryPreviewProviders(editor, pos, token) { + + var line = editor.document.getLine(pos.line); // FUTURE: Support plugin providers. For now we just hard-code... - if (!colorAndGradientPreviewProvider(editor, pos, token, line) && - !imagePreviewProvider(editor, pos, token, line)) { - hidePreview(); + var popover = colorAndGradientPreviewProvider(editor, pos, token, line) || + imagePreviewProvider(editor, pos, token, line); + + if (popover) { + // Providers return just { start, end, content, ?onShow, xpos, ytop, ybot } + $.extend(popover, { visible: false, editor: editor }); + + return popover; } + return null; } + function handleMouseMove(event) { if (!enabled) { return; } + if (event.which) { + // Button is down - don't show popovers while dragging + hidePreview(); + return; + } + // Figure out which editor we are over var fullEditor = EditorManager.getCurrentFullEditor(); @@ -297,25 +379,53 @@ define(function (require, exports, module) { } if (editor && editor._codeMirror) { + // Find char mouse is over var cm = editor._codeMirror; var pos = cm.coordsChar({left: event.clientX, top: event.clientY}); + + if (lastPos && lastPos.line === pos.line && lastPos.ch === pos.ch) { + return; // bail if mouse is on same char as last event + } + lastPos = pos; + + var showImmediately = false; + + // Is there a popover already visible or pending? + if (popoverState) { + if (editor.posWithinRange(pos, popoverState.start, popoverState.end)) { + // That one's still relevant - nothing more to do + return; + } else { + // That one doesn't cover this pos - hide it and query providers anew + showImmediately = popoverState.visible; + hidePreview(); + } + } + + // Query providers for a new popoverState var token = cm.getTokenAt(pos); - var line = cm.getLine(pos.line); + popoverState = queryPreviewProviders(editor, pos, token); + + if (popoverState) { + // We have a popover available - wait until we're ready to show it + if (showImmediately) { + showPreview(); + } else { + popoverState.hoverTimer = window.setTimeout(function () { + // Ready to show now (we'll never get here if mouse movement rendered this popover + // inapplicable first - hidePopover() cancels hoverTimer) + showPreview(); + }, HOVER_DELAY); + } + } - queryPreviewProviders(editor, pos, token, line); } else { + // Mouse not over any Editor - immediately hide popover hidePreview(); } } - function getLastPreviewedColorOrGradient() { - return lastPreviewedColorOrGradient; - } - function getLastPreviewedImagePath() { - return lastPreviewedImagePath; - } - // Menu command handlers function updateMenuItemCheckmark() { CommandManager.get(CMD_ENABLE_HOVER_PREVIEW).setChecked(enabled); @@ -326,6 +436,8 @@ define(function (require, exports, module) { enabled = _enabled; var editorHolder = $("#editor-holder")[0]; if (enabled) { + // Note: listening to "scroll" also catches text edits, which bubble a scroll event up from the hidden text area. This means + // we auto-hide on text edit, which is probably actually a good thing. editorHolder.addEventListener("mousemove", handleMouseMove, true); editorHolder.addEventListener("scroll", hidePreview, true); } else { @@ -367,8 +479,10 @@ define(function (require, exports, module) { }); // For unit testing - exports._colorAndGradientPreviewProvider = colorAndGradientPreviewProvider; - exports._imagePreviewProvider = imagePreviewProvider; - exports._getLastPreviewedColorOrGradient = getLastPreviewedColorOrGradient; - exports._getLastPreviewedImagePath = getLastPreviewedImagePath; + exports._queryPreviewProviders = queryPreviewProviders; + exports._forceShow = function (popover) { + hidePreview(); + popoverState = popover; + showPreview(); + }; }); diff --git a/src/extensions/default/HoverPreview/unittests.js b/src/extensions/default/HoverPreview/unittests.js index 26ec3b4aeda..41269846681 100644 --- a/src/extensions/default/HoverPreview/unittests.js +++ b/src/extensions/default/HoverPreview/unittests.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, describe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, $, brackets, waitsForDone */ +/*global define, describe, it, expect, beforeEach, afterEach, waitsFor, runs, $, brackets, waitsForDone */ define(function (require, exports, module) { "use strict"; @@ -63,42 +63,38 @@ define(function (require, exports, module) { } }); - function hoverOn(lineNum, columnNum, isImage) { + function getPopoverAtPos(lineNum, columnNum) { var cm = editor._codeMirror, pos = { line: lineNum, ch: columnNum }, - token, - line; + token; editor.setCursorPos(pos); token = cm.getTokenAt(pos); - line = cm.getLine(pos.line); - - if (isImage) { - HoverPreview._imagePreviewProvider(editor, pos, token, line); - } else { - HoverPreview._colorAndGradientPreviewProvider(editor, pos, token, line); - } + + return HoverPreview._queryPreviewProviders(editor, pos, token); } - function checkColorAtPos(curColor, line, ch) { - var colorInPreview; - - hoverOn(line, ch, false); - colorInPreview = HoverPreview._getLastPreviewedColorOrGradient(); - expect(colorInPreview).toBe(curColor); + function expectNoPreviewAtPos(line, ch) { + var popoverInfo = getPopoverAtPos(line, ch); + expect(popoverInfo).toBeFalsy(); + } + + function checkColorAtPos(expectedColor, line, ch) { + var popoverInfo = getPopoverAtPos(line, ch); + expect(popoverInfo._previewCSS).toBe(expectedColor); } - function checkGradientAtPos(curGradient, line, ch) { + function checkGradientAtPos(expectedGradient, line, ch) { // Just call checkColorAtPos since both have the same function calls. - checkColorAtPos(curGradient, line, ch); + checkColorAtPos(expectedGradient, line, ch); } - function checkImagePathAtPos(curImagePath, line, ch) { - var imagePath; + function checkImagePathAtPos(expectedPathEnding, line, ch) { + var popoverInfo = getPopoverAtPos(line, ch), + imagePath = popoverInfo._imgPath; - hoverOn(line, ch, true); - imagePath = HoverPreview._getLastPreviewedImagePath(); - expect(imagePath.indexOf(curImagePath)).toBeGreaterThan(-1); + // Just check end of path - local drive location prefix unimportant + expect(imagePath.substr(imagePath.length - expectedPathEnding.length)).toBe(expectedPathEnding); } describe("Hover preview colors", function () { @@ -111,8 +107,8 @@ define(function (require, exports, module) { it("should NOT show preview of color on words start with #", function () { runs(function () { - checkColorAtPos("", 7, 7); // cursor on #invalid_hex - checkColorAtPos("", 8, 15); // cursor on #web + expectNoPreviewAtPos(7, 7); // cursor on #invalid_hex + expectNoPreviewAtPos(8, 15); // cursor on #web }); }); @@ -130,16 +126,15 @@ define(function (require, exports, module) { // rgba with percentage values checkColorAtPos("rgba(100%, 0%, 0%, 0.5)", 18, 32); checkColorAtPos("rgba(80%, 50%, 50%, 1)", 20, 33); - // This is not working yet - //checkColorAtPos("rgba(50%, 75%, 25%, 1.0)", 21, 23); + //checkColorAtPos("rgba(50%, 75%, 25%, 1.0)", 21, 23); // TODO (#3454): not working yet }); }); it("should NOT show preview of unsupported rgb/rgba colors", function () { runs(function () { - checkColorAtPos("", 25, 14); // cursor on rgb(300, 0, 0) - checkColorAtPos("", 26, 15); // cursor on rgb(0, 95.5, 0) - checkColorAtPos("", 27, 15); // cursor on rgba(-0, 0, 0, 0.5) + expectNoPreviewAtPos(25, 14); // cursor on rgb(300, 0, 0) + expectNoPreviewAtPos(26, 15); // cursor on rgb(0, 95.5, 0) + expectNoPreviewAtPos(27, 15); // cursor on rgba(-0, 0, 0, 0.5) }); }); @@ -153,10 +148,10 @@ define(function (require, exports, module) { it("should NOT show preview of unsupported hsl/hsla colors", function () { runs(function () { - checkColorAtPos("", 37, 24); // cursor on hsl(390, 100%, 50%) - checkColorAtPos("", 38, 25); // cursor on hsla(90, 100%, 50%, 2) - checkColorAtPos("", 39, 24); // cursor on hsla(0, 200%, 50%, 0.5) - checkColorAtPos("", 40, 25); // cursor on hsla(0.0, 100%, 50%, .5) + expectNoPreviewAtPos(37, 24); // cursor on hsl(390, 100%, 50%) + expectNoPreviewAtPos(38, 25); // cursor on hsla(90, 100%, 50%, 2) + expectNoPreviewAtPos(39, 24); // cursor on hsla(0, 200%, 50%, 0.5) + expectNoPreviewAtPos(40, 25); // cursor on hsla(0.0, 100%, 50%, .5) }); }); @@ -175,11 +170,11 @@ define(function (require, exports, module) { it("should NOT show preview of colors with invalid names", function () { runs(function () { - checkColorAtPos("", 72, 15); // cursor on redsox - checkColorAtPos("", 73, 16); // cursor on pinky + expectNoPreviewAtPos(72, 15); // cursor on redsox + expectNoPreviewAtPos(73, 16); // cursor on pinky // issue #3445 -// checkColorAtPos("", 74, 16); // cursor on blue in hyphenated word blue-chesse -// checkColorAtPos("", 75, 18); // cursor on white in hyphenated word @bc-white +// expectNoPreviewAtPos(74, 16); // cursor on blue in hyphenated word blue-chesse +// expectNoPreviewAtPos(75, 18); // cursor on white in hyphenated word @bc-white }); }); }); @@ -187,69 +182,94 @@ define(function (require, exports, module) { describe("Hover preview gradients", function () { it("Should show linear gradient preview for those with vendor prefix", function () { runs(function () { - var expectedGradient1 = "linear-gradient(top, #d2dfed 0%, #c8d7eb 26%, #bed0ea 51%, #a6c0e3 51%, #afc7e8 62%, #bad0ef 75%, #99b5db 88%, #799bc8 100%)", - expectedGradient2 = "linear-gradient(top, #d2dfed 0%,#c8d7eb 26%,#bed0ea 51%,#a6c0e3 51%,#afc7e8 62%,#bad0ef 75%,#99b5db 88%,#799bc8 100%)"; + var expectedGradient1 = "-webkit-linear-gradient(top, #d2dfed 0%, #c8d7eb 26%, #bed0ea 51%, #a6c0e3 51%, #afc7e8 62%, #bad0ef 75%, #99b5db 88%, #799bc8 100%)", + expectedGradient2 = "-webkit-gradient(linear, left top, left bottom, color-stop(0%,#d2dfed), color-stop(26%,#c8d7eb), color-stop(51%,#bed0ea), color-stop(51%,#a6c0e3), color-stop(62%,#afc7e8), color-stop(75%,#bad0ef), color-stop(88%,#99b5db), color-stop(100%,#799bc8));", + expectedGradient3 = "-webkit-linear-gradient(top, #d2dfed 0%,#c8d7eb 26%,#bed0ea 51%,#a6c0e3 51%,#afc7e8 62%,#bad0ef 75%,#99b5db 88%,#799bc8 100%)"; checkGradientAtPos(expectedGradient1, 80, 36); // -moz- prefix gets stripped - checkGradientAtPos("-webkit-gradient(linear, left top, left bottom, color-stop(0%,#d2dfed), color-stop(26%,#c8d7eb), color-stop(51%,#bed0ea), color-stop(51%,#a6c0e3), color-stop(62%,#afc7e8), color-stop(75%,#bad0ef), color-stop(88%,#99b5db), color-stop(100%,#799bc8));", - 81, 36); // Old webkit syntax - checkGradientAtPos(expectedGradient2, 82, 36); // -webkit- prefix gets stripped - checkGradientAtPos(expectedGradient2, 83, 36); // -o- prefix gets stripped - checkGradientAtPos(expectedGradient2, 84, 36); // -ms- prefix gets stripped + checkGradientAtPos(expectedGradient2, 81, 36); // Old webkit syntax + checkGradientAtPos(expectedGradient3, 82, 36); // -webkit- prefix gets stripped + checkGradientAtPos(expectedGradient3, 83, 36); // -o- prefix gets stripped + checkGradientAtPos(expectedGradient3, 84, 36); // -ms- prefix gets stripped }); }); it("Should show linear gradient preview for those with w3c standard syntax (no prefix)", function () { runs(function () { - // Keyword "to" not supported until Brackets upgrades to Chrome 26 - //checkGradientAtPos("linear-gradient(to right, #333, #CCC)", 98, 50); - checkGradientAtPos("linear-gradient(#333, #CCC)", 99, 50); - //checkGradientAtPos("linear-gradient(to bottom right, #333, #CCC)", 100, 50); - checkGradientAtPos("linear-gradient(135deg, #333, #CCC)", 101, 50); + checkGradientAtPos("-webkit-linear-gradient(#333, #CCC)", 99, 50); + checkGradientAtPos("-webkit-linear-gradient(135deg, #333, #CCC)", 101, 50); + + // TODO (#3458): Keyword "to" not supported until Brackets upgrades to Chrome 26 + //checkGradientAtPos("-webkit-linear-gradient(to right, #333, #CCC)", 98, 50); + //checkGradientAtPos("-webkit-linear-gradient(to bottom right, #333, #CCC)", 100, 50); + expectNoPreviewAtPos(98, 50); + expectNoPreviewAtPos(100, 50); // multiple colors - checkGradientAtPos("linear-gradient(#333, #CCC, #333)", 104, 50); - checkGradientAtPos("linear-gradient(#333 0%, #CCC 33%, #333 100%)", 105, 50); - checkGradientAtPos("linear-gradient(yellow, blue 20%, #0f0)", 106, 50); + checkGradientAtPos("-webkit-linear-gradient(#333, #CCC, #333)", 104, 50); + checkGradientAtPos("-webkit-linear-gradient(#333 0%, #CCC 33%, #333 100%)", 105, 50); + checkGradientAtPos("-webkit-linear-gradient(yellow, blue 20%, #0f0)", 106, 50); }); }); it("Should show radial gradient preview for those with vendor prefix syntax", function () { runs(function () { var expectedGradient1 = "-webkit-gradient(radial, center center, 0, center center, 141, from(black), to(white), color-stop(25%, blue), color-stop(40%, green), color-stop(60%, red), color-stop(80%, purple));", - expectedGradient2 = "radial-gradient(center center, circle contain, black 0%, blue 25%, green 40%, red 60%, purple 80%, white 100%)"; + expectedGradient2 = "-webkit-radial-gradient(center center, circle contain, black 0%, blue 25%, green 40%, red 60%, purple 80%, white 100%)"; checkGradientAtPos(expectedGradient1, 110, 93); // old webkit syntax - checkGradientAtPos(expectedGradient2, 111, 36); // -webkit- prefix gets stripped + checkGradientAtPos(expectedGradient2, 111, 36); // -webkit- prefix preserved checkGradientAtPos(expectedGradient2, 112, 36); // -moz- prefix gets stripped checkGradientAtPos(expectedGradient2, 113, 36); // -ms- prefix gets stripped checkGradientAtPos(expectedGradient2, 114, 36); // -0- prefix gets stripped }); }); - xit("Should show radial gradient preview for those with w3c standard syntax (no prefix)", function () { + it("Should show radial gradient preview for those with w3c standard syntax (no prefix)", function () { runs(function () { - checkGradientAtPos("radial-gradient(yellow, green)", 118, 40); + // TODO (#3458): support new W3C syntax +// checkGradientAtPos("-webkit-radial-gradient(yellow, green)", 118, 35); +// checkGradientAtPos("-webkit-radial-gradient(yellow, green)", 118, 40); + + // For now the color stops are just previewed in isolation + expectNoPreviewAtPos(118, 35); + checkColorAtPos("yellow", 118, 40); }); }); - xit("Should show repeating linear gradient preview", function () { + it("Should show repeating linear gradient preview", function () { runs(function () { - checkGradientAtPos("radial-gradient(yellow, green)", 122, 50); - checkGradientAtPos("radial-gradient(yellow, green)", 123, 50); - checkGradientAtPos("radial-gradient(yellow, green)", 124, 50); + // TODO (#3458): support repeat +// checkGradientAtPos("repeating-linear-gradient(red, blue 20px, red 40px)", 122, 50); +// checkGradientAtPos("repeating-linear-gradient(red 0px, white 0px, blue 0px)", 123, 50); +// checkGradientAtPos("repeating-linear-gradient(red 0px, white .1px, blue .2px)", 124, 50); + + // For now the color stops are just previewed in isolation + expectNoPreviewAtPos(122, 35); + expectNoPreviewAtPos(123, 35); + expectNoPreviewAtPos(124, 35); + checkColorAtPos("red", 122, 50); }); }); - xit("Should show repeating radial gradient preview", function () { + it("Should show repeating radial gradient preview", function () { runs(function () { - checkGradientAtPos("repeating-radial-gradient(circle closest-side at 20px 30px, red, yellow, green 100%, yellow 150%, red 200%)", 128, 40); - checkGradientAtPos("repeating-radial-gradient(red, blue 20px, red 40px)", 129, 40); + // TODO (#3458): support repeat +// checkGradientAtPos("repeating-radial-gradient(circle closest-side at 20px 30px, red, yellow, green 100%, yellow 150%, red 200%)", 128, 40); +// checkGradientAtPos("repeating-radial-gradient(red, blue 20px, red 40px)", 129, 40); + + expectNoPreviewAtPos(128, 40); + expectNoPreviewAtPos(129, 40); }); }); }); - describe("Hover preview positioning", function () { - + describe("Hover preview display", function () { + + function showPopoverAtPos(line, ch) { + var popoverInfo = getPopoverAtPos(line, ch); + HoverPreview._forceShow(popoverInfo); + } + function getBounds(object) { return { left: object.offset().left, @@ -274,17 +294,17 @@ define(function (require, exports, module) { }); } - it("popover is not clipped", function () { + it("popover is positioned within window bounds", function () { var $popover = testWindow.$("#hover-preview-container"); expect($popover.length).toEqual(1); - + runs(function () { // Popover should be below item - hoverOn(3, 12, false); + showPopoverAtPos(3, 12); expect(boundsInsideWindow($popover)).toBeTruthy(); // Popover should above item - hoverOn(20, 33, false); + showPopoverAtPos(20, 33); expect(boundsInsideWindow($popover)).toBeTruthy(); }); @@ -298,7 +318,7 @@ define(function (require, exports, module) { // Issue #3447 - fixes both of the following tests /* // Popover should be inside right edge - hoverOn(81, 36, false); + showPopoverAtPos(81, 36); expect(boundsInsideWindow($popover)).toBeTruthy(); */ @@ -308,7 +328,7 @@ define(function (require, exports, module) { scrollY = editor._codeMirror.defaultTextHeight() * 190; editor.setScrollPos(scrollX, scrollY); // Scroll right - hoverOn(82, 136, false); + showPopoverAtPos(82, 136); expect(boundsInsideWindow($popover)).toBeTruthy(); */ @@ -316,6 +336,16 @@ define(function (require, exports, module) { toggleOption(Commands.TOGGLE_WORD_WRAP, "Toggle word-wrap"); }); }); + + it("highlight matched text when popover shown", function () { + showPopoverAtPos(4, 14); + var markers = editor._codeMirror.findMarksAt({line: 4, ch: 14}); + expect(markers.length).toBe(1); + var range = markers[0].find(); + expect(range.from.ch).toBe(11); + expect(range.to.ch).toBe(18); + }); + }); describe("Hover preview images", function () { @@ -350,4 +380,4 @@ define(function (require, exports, module) { }); }); }); -}); \ No newline at end of file +});