Enhanced data explorer text pad

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

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

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

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

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

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

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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(", "));
    }
})();