Enhanced data explorer text pad

Adds shift + enter, tab, shift + tab, ctrl+k/c, ctrl+k/u support to cosmos data explorer textarea.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Enhanced data explorer text pad
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Adds shift + enter, tab, shift + tab, ctrl+k/c, ctrl+k/u support to cosmos data explorer textarea.
// @author       You
// @match        https://cosmos.azure.com/explorer.html*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=azure.com
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    class LineCollection {
        constructor(multilineString) {
            let segments = multilineString.split(newLine);
            let lines = [];
            let startIndex = 0;
            let endIndex = 0;
            for(let i=0; i<segments.length; i++) {
                let segment = segments[i];
                endIndex = startIndex + segment.length;
                lines.push(new Line(segment, startIndex, endIndex, this)); //NOTE: new line character \n is not included in line..-=

                startIndex = endIndex + newLine.length;
            }
            this.lines = lines;
        }
        getLineByIndex(index){
            return this.lines.find(function(line){ return line.startIndex <= index && line.endIndex >= index; });
        }
        toString(addLineNumbers) {
            return this.lines.map(function(line, i){
                return addLineNumbers ?
                    `${i.toString().padStart(2, " ")}: ${line.value} [${line.startIndex.toString().padStart(3," ")}-${line.endIndex.toString().padEnd(3," ")}]` :
                    line.value;
            }).join(newLine);
        }
    }
    class Line {
        constructor(lineText, startIndex, endIndex, collection) {
            this.value = lineText;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.collection = collection;
        }
        isContainedBy(spanStart, spanEnd) {
            return this.startIndex >= spanStart && this.endIndex <= spanEnd;
        }
        isInterceptedBy(spanStart, spanEnd) {
            return (spanStart > this.startIndex && spanStart <= this.endIndex) ||
                (spanEnd >= this.startIndex && spanEnd < this.endIndex);
        }
        charsBeforeIndexMatchRegex(index, regex) {
            if(index < this.startIndex) throw new Error("Index must be >= startIndex.");
            if(index > this.endIndex) throw new Error("Index must be <= endIndex.");
            let piece = this.value.substring(0, index - this.startIndex);
            return piece.length ? piece.match(regex) : true;
        }
        insert(index, value) {
            if(index < this.startIndex) throw new Error("Index must be >= startIndex.");
            if(index > this.endIndex) throw new Error("Index must be <= endIndex.");
            if(this.collection) {
                let lineFound = false;
                for(let i=0; i< this.collection.lines.length; i++){
                    let line = this.collection.lines[i];
                    if(lineFound) {
                        line.startIndex += value.length;
                        line.endIndex += value.length;
                    }
                    else if(line == this) {
                        lineFound = true;
                        let piece1 = line.value.substring(0, index - line.startIndex);
                        let piece2 = line.value.substring(index - line.startIndex);
                        line.value = `${piece1}${value}${piece2}`;
                        line.endIndex += value.length;
                    }
                }
            }
            else throw new Error("Line is not part of a LineCollection. This can result in unexpected behavior.");
        }
        extract(index, length) {
            if(index < this.startIndex) throw new Error("Index must be >= startIndex.");
            if(index > this.endIndex) throw new Error("Index must be <= endIndex.");
            if(index + length > this.endIndex) throw new Error("Index + length must be <= endIndex.");
            if(this.collection) {
                let lineFound = false;
                for(let i=0; i< this.collection.lines.length; i++){
                    let line = this.collection.lines[i];
                    if(lineFound) {
                        line.startIndex -= length;
                        line.endIndex -= length;
                    }
                    else if(line == this) {
                        lineFound = true;
                        let piece1 = line.value.substring(0, index - this.startIndex);
                        let piece2 = line.value.substring((index - this.startIndex) + length);
                        line.value = `${piece1}${piece2}`;
                        line.endIndex -= length;
                    }
                }
            }
            else throw new Error("Line is not part of a LineCollection. This can result in unexpected behavior.");
        }
        substr(index, length) {
            if(index < this.startIndex) throw new Error("Index must be >= startIndex.");
            if(index > this.endIndex) throw new Error("Index must be <= endIndex.");
            if(index + length > this.endIndex) throw new Error("Index + length must be <= endIndex.");
            return this.value.substr(index - this.startIndex, length);
        }
    }

    let comment = "//";
    let newLine = "\n";
    let tab = "    ";
    let textarea = null;
    let previousKeyDownEvent = null;
    (function registerKeyDownHandler (){
        try{
            textarea = document.querySelector("textarea#input:not(.registered-keyDownHandler)");
        }catch(ex){}
        if(textarea) {
            console.log("found textarea..");
            textarea.classList.add("registered-keyDownHandler");
            textarea.addEventListener("keydown", keyDownHandler);
        }
        else setTimeout(registerKeyDownHandler, 50);
    })();

    function keyDownHandler(e){
        //printEvent(previousKeyDownEvent, e);
        let haultEvent = false;
        let start = e.target.selectionStart;
        let end = e.target.selectionEnd;
        let value = e.target.value;
        if(
            e.key == "Control" || //Ctrl
            e.keyCode == 75 && e.ctrlKey //Ctrl+K
        ) {
            haultEvent = true;
        }
        else if(e.keyCode == 8 && start == end) { //backspace and no selected text
            let lineStartIndex = indexOfLineStart(value, start);
            if(lineStartIndex > 0 && lineStartIndex < start){ //line starts with spaces
                haultEvent = true;
                let spaceCountToRemove = (start - lineStartIndex) % tab.length;
                if(spaceCountToRemove == 0) spaceCountToRemove = tab.length;
                let text = value.substring(0, start - spaceCountToRemove) + value.substring(end, value.length);
                setReactBoundElementValue(e.target, text);
                e.target.selectionStart = start + tab.length;
                e.target.selectionEnd = start + tab.length;
            }
        }
        else if(e.keyCode == 13 && e.shiftKey) { //Shft+Enter
            haultEvent = true;
            let text = value.substring(0, start) + newLine + value.substring(end, value.length);
            setReactBoundElementValue(e.target, text);
            e.target.selectionStart = start + newLine.length;
            e.target.selectionEnd = start + newLine.length;
        }
        else if(e.keyCode == 9 && !e.shiftKey) { // [Tab]
            haultEvent = true;
            if(start == end){
                let collection = new LineCollection(value);
                let line = collection.getLineByIndex(start);
                line.insert(start, tab)
                let text = collection.toString();
                setReactBoundElementValue(textarea, text);
                    textarea.selectionStart = start + tab.length;
                    textarea.selectionEnd = end + tab.length;
            }
            else tryAddLinePrefix(e.target, tab);
        }
        else if(e.keyCode == 9 && e.shiftKey) { // [Shft] + [Tab]
            haultEvent = true;
            if(start == end){
                let collection = new LineCollection(value);
                let line = collection.getLineByIndex(start);
                if(line.substr(start - tab.length, tab.length) == tab) { //chars preceding cursor index are tab
                    line.extract(start - tab.length, tab.length);
                    let text = collection.toString();
                    setReactBoundElementValue(textarea, text);
                    textarea.selectionStart = start - tab.length;
                    textarea.selectionEnd = end - tab.length;
                }
            }
            else tryRemoveLinePrefix(e.target, tab);
        }
        else if( //Ctrl+K -> Ctrl+C
            e.keyCode == 67 && e.ctrlKey && //Ctrl+C
            previousKeyDownEvent.keyCode == 75 && previousKeyDownEvent.ctrlKey //Ctrl+K
        ) {
            haultEvent = true;
            tryAddLinePrefix(e.target, comment);
        }
        else if( //Ctrl+K -> Ctrl+U
            e.keyCode == 85 && e.ctrlKey && //Ctrl+U
            previousKeyDownEvent.keyCode == 75 && previousKeyDownEvent.ctrlKey //Ctrl+K
        ) {
            haultEvent = true;
            tryRemoveLinePrefix(e.target, comment);
        }
        if(haultEvent){
            printEvent(previousKeyDownEvent, e);
            e.preventDefault();
            e.stopPropagation();
        }
        previousKeyDownEvent = e;
    }

    function tryAddLinePrefix(textarea, prefix){
        let start = textarea.selectionStart;
        let end = textarea.selectionEnd;
        let value = textarea.value;
        let firstSelectedLine = true;
        let collection = new LineCollection(value);
        for(let i = 0; i<collection.lines.length; i++) {
            let line = collection.lines[i];
            let intercepted = line.isInterceptedBy(start, end);
            if(intercepted || line.isContainedBy(start, end)) {
                line.insert(line.startIndex, prefix);
                if(start == end || (intercepted && firstSelectedLine)) start += prefix.length;
                end += prefix.length;
                firstSelectedLine = false;
            }
        }
        let text = collection.toString();
        setReactBoundElementValue(textarea, text);
        textarea.selectionStart = start;
        textarea.selectionEnd = end;
    }

    function tryRemoveLinePrefix(textarea, prefix){
        let start = textarea.selectionStart;
        let end = textarea.selectionEnd;
        let value = textarea.value;
        let firstSelectedLine = true;
        let collection = new LineCollection(value);
        for(let i = 0; i<collection.lines.length; i++) {
            let line = collection.lines[i];
            let intercepted = line.isInterceptedBy(start, end);
            if(intercepted || line.isContainedBy(start, end)) {
                if(line.value.indexOf(prefix) == 0) { //starts with prefix characters e.g. //, <spaces>, etc
                    line.extract(line.startIndex, prefix.length);
                    if(start == end || (intercepted && firstSelectedLine)) start -= prefix.length;
                    end -= prefix.length;
                }
                firstSelectedLine = false;
            }
        }
        let text = collection.toString();
        setReactBoundElementValue(textarea, text);
        textarea.selectionStart = start;
        textarea.selectionEnd = end;
    }

    //index of the begining of the line with the cursor (assuming only whitespace characters exist prior to the cursor), else -1
    function indexOfLineStart(line, selectionStartIndex){
        let preSelectionText = line.substring(0, selectionStartIndex);
        let lineStartIndex = preSelectionText.lastIndexOf(newLine);
        if(lineStartIndex < 0) lineStartIndex = 0;
        let lineStartText = preSelectionText.substring(lineStartIndex, preSelectionText.length);
        return lineStartText.match(/^ *$/g) ? lineStartIndex : -1
    }

    function setReactBoundElementValue(element, value) {
        const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set;
        const prototype = Object.getPrototypeOf(element);
        const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;
        if (valueSetter && valueSetter !== prototypeValueSetter) {
            prototypeValueSetter.call(element, value);
        } else {
            valueSetter.call(element, value);
        }
        element.dispatchEvent(new Event('input', { bubbles: true }));
    }

    function printEvent(/*params*/ events) {
        let sb = [];
        for(let i = 0; i < arguments.length; i++) {
            let e = arguments[i];
            if(e) {
                let ctrl = e.ctrlKey ? "Ctrl+" : "";
                let alt = e.altKey ? "Alt+" : "";
                let shift = e.shiftKey ? "Shft+": "";
                let char = e.key ?? e.keyCode;
                if(char == "Control") char = "..";
                sb.push(`${e.type}: ${ctrl}${alt}${shift}${char}`);
            }
            else sb.push(`event #${i} is null`);
        }
        console.log(sb.join(", "));
    }
})();