vendor/ui/js/src/components/blob.js in dolt-0.17.0 vs vendor/ui/js/src/components/blob.js in dolt-0.18.0

- old
+ new

@@ -1,39 +1,154 @@ /*global cull, dome*/ +this.gts = this.gts || {}; + +/** + * Utilities for working with syntax highligted blobs. Assumes the markup for + * blobs generated by Dolt. Exports three functions: + * + * * trackFocus + * Sets up events to highlight lines on mouseover, and allows the user to + * highlight regions + * * regionFromUrl + * Parses the URL and finds line number or region to highlight + * * highlightRegion + * Highlights a region + * + * The following functions all assume that a blob is contained in some root + * element that represents each line in the file as an li element. + * + * A "region" is an array of two numbers, denoting the first and last line of + * a consecutive region. Both boundaries are inclusive. Regions start at 1 (not + * 0). + * [1, 2] - The region consisting of the first and second line, inclusive + * [1, 1] - The first line + * [10, 14] - The region of lines 10, 11, 12, 13 and 14 + */ this.gts.blob = (function () { - function highlightLineOnFocus(element, start, end) { + /** + * Accepts the root element of a blob and a region, and adds the "region" + * class name to all li elements in the region. + */ + function highlightRegion(element, region) { + // Line numbers are 1-based, but ElementLists are 0-indexed, + // offset region accordingly + var start = region[0] - 1; + var end = region[1] - 1; + var lines = element.getElementsByTagName("li"); + + cull.doall(function (li, num) { + var inRegion = start <= num && num <= end; + dome.cn[inRegion ? "add" : "rm"]("region", li); + }, element.getElementsByTagName("li")); + } + + /** + * Given an li element representing a line, return which line number it + * represents. + */ + function getLineNum(element) { + while (element && element.tagName !== "LI") { + element = element.parentNode; + } + var lineNum = element && element.className.match(/L(\d+)/)[1]; + if (!lineNum) { return; } + return parseInt(lineNum, 10) + 1; + } + + /** + * Set the active region. Marks the region and updates the URL with the + * new region as an anchor ("#L<start>-<end>"). + */ + function setRegion(element, region, redirect) { + var regionStr = region[0]; + regionStr += region[1] !== region[0] ? "-" + region[1] : ""; + redirect("#L" + regionStr); + highlightRegion(element, region); + } + + /** + * Given two numbers, return a region where the lowest number is the start + * and the highest number is the end. The end is optional, in which case + * the [start, start] region is returned. + */ + function orderedRegion(start, end) { + end = typeof end === "number" ? end : start; + return [Math.min(start, end), Math.max(start, end)]; + } + + /** + * Track selected region through clicks on lines. + * + * Clicking a line highlights it by setting the region to that line + * (e.g [n, n]). + * + * Holding shift while clicking a line makes a region starting at the + * previously clicked line and ending at the line being clicked. Example: + * + * Click on line 4. Press shift, click line 9. Lines 4, 5, 6, 7, 8 and 9 + * will be highlighted. While holding shift, click line 1. Lines 1, 2, 3 and + * 4 will be highlighted. Release shift, click line 5, line 5 (and no + * others) will be highlighted. + */ + function trackSelectedRegion(element, redirect) { + var shift = false, anchor; + var events = []; + + dome.on(document.body, "keydown", function (e) { + shift = e.which === 16; + }); + dome.on(document.body, "keyup", function (e) { + shift = e.which !== 16; + }); + + dome.on(element, "click", function (e) { + var region, currLine = getLineNum(e.target); + if (!shift || !anchor) { anchor = e.target; } + + if (anchor && shift) { + region = orderedRegion(getLineNum(anchor), currLine); + e.preventDefault(); + } else { + region = [currLine, currLine]; + } + + setRegion(element, region, redirect); + }); + } + + /** + * Track focus on lines by highlighting lines as the cursor hovers them, and + * by highlighting (and selecting) regions as lines are clicked (see the + * above function) + */ + function trackFocus(element, redirect) { var highlight = function () { dome.cn.add("focus", this); }; var dim = function () { dome.cn.rm("focus", this); }; cull.doall(function (li) { dome.on(li, "mouseenter", highlight); dome.on(li, "mouseleave", dim); }, element.getElementsByTagName("li")); + + trackSelectedRegion(element, redirect); } + /** + * Regions can be specified over the URL by using the #L<start>[-<end>] + * syntax. This function extracts it. Examples: + * + * /some/url#L1 - [1, 1] + * /some/url#L1-2 - [1, 2] + * /some/url#l12-2 - [2, 12] (ordered) + */ function regionFromUrl(url) { var matches = url.match(/\#l(\d+)(?:-(\d+))?/i); if (!matches) { return; } - var start = Math.min(matches[2] || matches[1], matches[1]); - var end = Math.max(matches[2] || matches[1], matches[1]); - return [start, end]; + return orderedRegion(matches[1], matches[2] && Number(matches[2])); } - function highlightRegion(element, region) { - // Line numbers are 1-based, but ElementLists are 0-indexed, - // offset region accordingly - var start = region[0] - 1; - var end = region[1] - 1; - var lines = element.getElementsByTagName("li"); - - cull.doall(function (li, num) { - var inRegion = start <= num && num <= end; - dome.cn[inRegion ? "add" : "rm"]("region", li); - }, element.getElementsByTagName("li")); - } - return { - highlightLineOnFocus: highlightLineOnFocus, + trackFocus: trackFocus, regionFromUrl: regionFromUrl, highlightRegion: highlightRegion }; }());