From 85969a4dc11ed7133fa7ac803c4465e5c8503392 Mon Sep 17 00:00:00 2001 From: danielfrankcom Date: Sat, 12 May 2018 14:20:47 -0700 Subject: [PATCH 01/37] Rewrite of ANSI code handling method --- .../parts/debug/browser/media/repl.css | 49 ++--- .../debug/electron-browser/replViewer.ts | 200 +++++++++--------- 2 files changed, 130 insertions(+), 119 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/media/repl.css b/src/vs/workbench/parts/debug/browser/media/repl.css index 057a3253ee1..15d41d443c9 100644 --- a/src/vs/workbench/parts/debug/browser/media/repl.css +++ b/src/vs/workbench/parts/debug/browser/media/repl.css @@ -165,32 +165,33 @@ .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-bold { font-weight: bold; } .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-underline { text-decoration: underline; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code30, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code90 { color: gray; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code31, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code91 { color: #BE1717; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code32, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code92 { color: #338A2F; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code33, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code93 { color: #BEB817; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code34, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code94 { color: darkblue; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code35, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code95 { color: darkmagenta; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code36, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code96 { color: darkcyan; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code37, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code97 { color: #BDBDBD; } +/* Regular and bright color codes are currently treated the same. */ +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-30, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-90 { color: gray; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-31, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-91 { color: #BE1717; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-32, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-92 { color: #338A2F; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-33, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-93 { color: #BEB817; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-34, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-94 { color: darkblue; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-35, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-95 { color: darkmagenta; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-36, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-96 { color: darkcyan; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-37, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-97 { color: #BDBDBD; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code30, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code90 { color: #A0A0A0; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code31, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code91 { color: #A74747; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code32, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code92 { color: #348F34; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code33, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code93 { color: #5F4C29; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code34, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code94 { color: #6286BB; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code35, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code95 { color: #914191; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code36, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code96 { color: #218D8D; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code37, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code97 { color: #707070; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-30, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-90 { color: #A0A0A0; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-31, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-91 { color: #A74747; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-32, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-92 { color: #348F34; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-33, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-93 { color: #5F4C29; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-34, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-94 { color: #6286BB; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-35, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-95 { color: #914191; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-36, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-96 { color: #218D8D; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-37, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-97 { color: #707070; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code30, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code90 { color: gray; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code31, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code91 { color: #A74747; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code32, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code92 { color: #348F34; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code33, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code93 { color: #5F4C29; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code34, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code94 { color: #6286BB; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code35, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code95 { color: #914191; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code36, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code96 { color: #218D8D; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code37, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code97 { color: #707070; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-30, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-90 { color: gray; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-31, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-91 { color: #A74747; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-32, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-92 { color: #348F34; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-33, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-93 { color: #5F4C29; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-34, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-94 { color: #6286BB; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-35, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-95 { color: #914191; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-36, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-96 { color: #218D8D; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-37, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-97 { color: #707070; } /* Links */ .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression a { diff --git a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts index b285d27ddea..571e040eee4 100644 --- a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts @@ -253,14 +253,7 @@ export class ReplExpressionsRenderer implements IRenderer { // Reset classes to clear ansi decorations since templates are reused templateData.value.className = 'value'; let result = this.handleANSIOutput(element.value); - if (typeof result === 'string') { - renderExpressionValue(result, templateData.value, { - preserveWhitespace: true, - showHover: false - }); - } else { - templateData.value.appendChild(result); - } + templateData.value.appendChild(result); dom.addClass(templateData.value, (element.severity === severity.Warning) ? 'warn' : (element.severity === severity.Error) ? 'error' : 'info'); templateData.source.textContent = element.sourceData ? `${element.sourceData.source.name}:${element.sourceData.lineNumber}` : ''; @@ -292,113 +285,130 @@ export class ReplExpressionsRenderer implements IRenderer { } } - private handleANSIOutput(text: string): HTMLElement | string { - let tokensContainer: HTMLSpanElement; - let currentToken: HTMLSpanElement; + /** + * @param text The content to stylize. + * @returns An {@link HTMLSpanElement} that contains the potentially stylized text. + */ + private handleANSIOutput(text: string): HTMLSpanElement { + + const root: HTMLSpanElement = document.createElement('span'); + const textLength: number = text.length; + + let styleNames: string[] = []; + let currentPos: number = 0; let buffer: string = ''; - for (let i = 0, len = text.length; i < len; i++) { + while (currentPos < textLength) { - // start of ANSI escape sequence (see http://ascii-table.com/ansi-escape-sequences.php) - if (text.charCodeAt(i) === 27) { - let index = i; - let chr = (++index < len ? text.charAt(index) : null); - let codes = []; - if (chr && chr === '[') { - let code: string = null; - while (chr !== 'm' && codes.length <= 7) { - chr = (++index < len ? text.charAt(index) : null); + // Potentially an ANSI escape sequence. + // See http://ascii-table.com/ansi-escape-sequences.php & https://en.wikipedia.org/wiki/ANSI_escape_code + if (text.charCodeAt(currentPos) === 27 && text.charAt(currentPos + 1) === '[') { - if (chr && chr >= '0' && chr <= '9') { - code = chr; - chr = (++index < len ? text.charAt(index) : null); - } + const startPos: number = currentPos; + currentPos += 2; // Ignore 'Esc[' as it's in every sequence. - if (chr && chr >= '0' && chr <= '9') { - code += chr; - chr = (++index < len ? text.charAt(index) : null); - } + let ansiSequence: string = ''; + let sequenceFound: boolean = false; - if (code === null) { - code = '0'; - } + while (currentPos < textLength) { + const char: string = text.charAt(currentPos); + ansiSequence += char; - codes.push(code); + currentPos++; + + if (char.match(/^[ABCDHIJKfhmpsu]$/)) { + sequenceFound = true; + break; } - if (chr === 'm') { // set text color/mode. - code = null; - // only respect text-foreground ranges and ignore the values for "black" & "white" because those - // only make sense in combination with text-background ranges which we currently not support - let token = document.createElement('span'); - token.className = ''; - while (codes.length > 0) { - code = codes.pop(); - let parsedMode = parseInt(code, 10); - if (token.className.length > 0) { - token.className += ' '; - } - if ((parsedMode >= 30 && parsedMode <= 37) || (parsedMode >= 90 && parsedMode <= 97)) { - token.className += 'code' + parsedMode; - } else if (parsedMode === 1) { - token.className += 'code-bold'; - } else if (parsedMode === 4) { - token.className += 'code-underline'; - } - } - - // we need a tokens container now - if (!tokensContainer) { - tokensContainer = document.createElement('span'); - } - - // flush text buffer if we have any - if (buffer) { - this.insert(this.linkDetector.handleLinks(buffer), currentToken || tokensContainer); - buffer = ''; - } - - currentToken = token; - - // get child until deepest nested node is found - let childPointer: Node = tokensContainer; - while (childPointer.hasChildNodes() && childPointer.firstChild.nodeName !== '#text') { - childPointer = childPointer.firstChild; - } - childPointer.appendChild(token); - - i = index; - } } + + if (sequenceFound) { + + // Flush buffer with previous styles. + this.appendStylizedStringToContainer(root, buffer, styleNames); + buffer = ''; + + /* + * Certain ranges that are matched here do not contain real graphics rendition sequences. For + * the sake of having a simpler expression, they have been included anyway. + */ + if (ansiSequence.match(/^(?:[39][0-7]|[0-8]|39)(?:;(?:[39][0-7]|[0-8]|39))*;?m$/)) { + + const styleCodes: number[] = ansiSequence.slice(0, -1) // Remove final 'm' character. + .split(';') // Separate style codes. + .filter(elem => elem !== '') // Filter empty elems as '34;m' -> ['34', '']. + .map(elem => parseInt(elem, 10)); // Convert to numbers. + + for (let code of styleCodes) { + if (code === 0) { + styleNames = []; + } else if (code === 1) { + styleNames.push('code-bold'); + } else if (code === 4) { + styleNames.push('code-underline'); + } else if ((code >= 30 && code <= 37) || (code >= 90 && code <= 97)) { + styleNames.push('code-fg-' + code); + } else if (code === 39) { + styleNames = styleNames.filter(style => !style.match(/^code-fg-\d+$/)); + } + } + + } else if (ansiSequence.match(/^.*[ABCDHIJKfhmpsu]$/)) { + // Unsupported sequence so simply hide it. + } else { + sequenceFound = false; + } + + } + + if (sequenceFound === false) { + /* + * Reached end of text without ending the escape sequence, + * or given sequence is currently unsupported. In either + * case, treat sequence as regular text. + */ + currentPos = startPos + 1; + } + + } else { + buffer += text.charAt(currentPos); + currentPos++; } - // normal text - else { - buffer += text[i]; - } } - // flush remaining text buffer if we have any + // Flush remaining text buffer if not empty. if (buffer) { - let res = this.linkDetector.handleLinks(buffer); - if (typeof res !== 'string' || currentToken) { - if (!tokensContainer) { - tokensContainer = document.createElement('span'); - } - - this.insert(res, currentToken || tokensContainer); - } + this.appendStylizedStringToContainer(root, buffer, styleNames); } - return tokensContainer || buffer; + return root; + } - private insert(arg: HTMLElement | string, target: HTMLElement): void { - if (typeof arg === 'string') { - target.textContent = arg; - } else { - target.appendChild(arg); + /** + * @param root The {@link HTMLElement} to append the content to. + * @param stringContent The text content to be appended. + * @param cssClasses The list of CSS styles to apply to the text content. + */ + private appendStylizedStringToContainer(root: HTMLElement, stringContent: string, cssClasses: string[]): void { + if (!root || !stringContent) { + return; } + + const content: string | HTMLElement = this.linkDetector.handleLinks(stringContent); + let container: HTMLElement; + + if (typeof content === 'string') { + container = document.createElement('span'); + container.textContent = content; + } else { + container = content; + } + + container.className = cssClasses.join(' '); + root.appendChild(container); } public disposeTemplate(tree: ITree, templateId: string, templateData: any): void { From da4cfffd559e25785d2b3d7f156a33fdd337b702 Mon Sep 17 00:00:00 2001 From: danielfrankcom Date: Mon, 14 May 2018 21:07:23 -0700 Subject: [PATCH 02/37] replViewer changes per code review --- .../workbench/parts/debug/electron-browser/replViewer.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts index 571e040eee4..c1151555844 100644 --- a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts @@ -316,6 +316,7 @@ export class ReplExpressionsRenderer implements IRenderer { currentPos++; + // Look for a known sequence terminating character. if (char.match(/^[ABCDHIJKfhmpsu]$/)) { sequenceFound = true; break; @@ -350,14 +351,13 @@ export class ReplExpressionsRenderer implements IRenderer { } else if ((code >= 30 && code <= 37) || (code >= 90 && code <= 97)) { styleNames.push('code-fg-' + code); } else if (code === 39) { + // Remove all foreground colour codes styleNames = styleNames.filter(style => !style.match(/^code-fg-\d+$/)); } } - } else if (ansiSequence.match(/^.*[ABCDHIJKfhmpsu]$/)) { - // Unsupported sequence so simply hide it. } else { - sequenceFound = false; + // Unsupported sequence so simply hide it. } } @@ -397,7 +397,7 @@ export class ReplExpressionsRenderer implements IRenderer { return; } - const content: string | HTMLElement = this.linkDetector.handleLinks(stringContent); + const content = this.linkDetector.handleLinks(stringContent); let container: HTMLElement; if (typeof content === 'string') { From a6153b62d2929c782f91b2a4ca1ac4eeae4583b8 Mon Sep 17 00:00:00 2001 From: danielfrankcom Date: Mon, 21 May 2018 12:31:07 -0700 Subject: [PATCH 03/37] fg -> foreground in repl.css for naming clarity --- .../parts/debug/browser/media/repl.css | 48 +++++++++---------- .../debug/electron-browser/replViewer.ts | 4 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/media/repl.css b/src/vs/workbench/parts/debug/browser/media/repl.css index 15d41d443c9..aaa22e8f9bf 100644 --- a/src/vs/workbench/parts/debug/browser/media/repl.css +++ b/src/vs/workbench/parts/debug/browser/media/repl.css @@ -166,32 +166,32 @@ .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-underline { text-decoration: underline; } /* Regular and bright color codes are currently treated the same. */ -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-30, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-90 { color: gray; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-31, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-91 { color: #BE1717; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-32, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-92 { color: #338A2F; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-33, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-93 { color: #BEB817; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-34, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-94 { color: darkblue; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-35, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-95 { color: darkmagenta; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-36, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-96 { color: darkcyan; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-37, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-97 { color: #BDBDBD; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-30, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-90 { color: gray; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-31, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-91 { color: #BE1717; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-32, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-92 { color: #338A2F; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-33, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-93 { color: #BEB817; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-34, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-94 { color: darkblue; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-35, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-95 { color: darkmagenta; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-36, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-96 { color: darkcyan; } +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-37, .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-97 { color: #BDBDBD; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-30, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-90 { color: #A0A0A0; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-31, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-91 { color: #A74747; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-32, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-92 { color: #348F34; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-33, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-93 { color: #5F4C29; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-34, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-94 { color: #6286BB; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-35, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-95 { color: #914191; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-36, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-96 { color: #218D8D; } -.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-37, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-97 { color: #707070; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-30, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-90 { color: #A0A0A0; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-31, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-91 { color: #A74747; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-32, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-92 { color: #348F34; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-33, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-93 { color: #5F4C29; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-34, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-94 { color: #6286BB; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-35, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-95 { color: #914191; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-36, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-96 { color: #218D8D; } +.vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-37, .vs-dark .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-97 { color: #707070; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-30, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-90 { color: gray; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-31, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-91 { color: #A74747; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-32, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-92 { color: #348F34; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-33, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-93 { color: #5F4C29; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-34, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-94 { color: #6286BB; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-35, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-95 { color: #914191; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-36, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-96 { color: #218D8D; } -.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-37, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-fg-97 { color: #707070; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-30, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-90 { color: gray; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-31, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-91 { color: #A74747; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-32, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-92 { color: #348F34; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-33, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-93 { color: #5F4C29; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-34, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-94 { color: #6286BB; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-35, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-95 { color: #914191; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-36, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-96 { color: #218D8D; } +.hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-37, .hc-black .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression .code-foreground-97 { color: #707070; } /* Links */ .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row > .content > .output.expression a { diff --git a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts index c1151555844..247d37167cf 100644 --- a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts @@ -349,10 +349,10 @@ export class ReplExpressionsRenderer implements IRenderer { } else if (code === 4) { styleNames.push('code-underline'); } else if ((code >= 30 && code <= 37) || (code >= 90 && code <= 97)) { - styleNames.push('code-fg-' + code); + styleNames.push('code-foreground-' + code); } else if (code === 39) { // Remove all foreground colour codes - styleNames = styleNames.filter(style => !style.match(/^code-fg-\d+$/)); + styleNames = styleNames.filter(style => !style.match(/^code-foreground-\d+$/)); } } From 7b15718ca271ebde87280a4c6ac87562ddce51d3 Mon Sep 17 00:00:00 2001 From: danielfrankcom Date: Mon, 21 May 2018 12:33:22 -0700 Subject: [PATCH 04/37] Extracted appendStylizedStringToContainer function from replViewer.ts --- .../parts/debug/browser/debugANSIHandling.ts | 31 +++++++++++++++++++ .../debug/electron-browser/replViewer.ts | 30 +++--------------- 2 files changed, 35 insertions(+), 26 deletions(-) create mode 100644 src/vs/workbench/parts/debug/browser/debugANSIHandling.ts diff --git a/src/vs/workbench/parts/debug/browser/debugANSIHandling.ts b/src/vs/workbench/parts/debug/browser/debugANSIHandling.ts new file mode 100644 index 00000000000..1c0345057cd --- /dev/null +++ b/src/vs/workbench/parts/debug/browser/debugANSIHandling.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LinkDetector } from 'vs/workbench/parts/debug/browser/linkDetector'; + +/** + * @param root The {@link HTMLElement} to append the content to. + * @param stringContent The text content to be appended. + * @param cssClasses The list of CSS styles to apply to the text content. + * @param linkDetector The {@link LinkDetector} responsible for generating links from {@param stringContent}. + */ +export function appendStylizedStringToContainer(root: HTMLElement, stringContent: string, cssClasses: string[], linkDetector: LinkDetector): void { + if (!root || !stringContent) { + return; + } + + const content = linkDetector.handleLinks(stringContent); + let container: HTMLElement; + + if (typeof content === 'string') { + container = document.createElement('span'); + container.textContent = content; + } else { + container = content; + } + + container.className = cssClasses.join(' '); + root.appendChild(container); +} \ No newline at end of file diff --git a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts index 247d37167cf..8d29ddcf65f 100644 --- a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts @@ -23,6 +23,7 @@ import { CopyAction, CopyAllAction } from 'vs/workbench/parts/debug/electron-bro import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LinkDetector } from 'vs/workbench/parts/debug/browser/linkDetector'; +import { appendStylizedStringToContainer } from 'vs/workbench/parts/debug/browser/debugANSIHandling'; const $ = dom.$; @@ -327,7 +328,8 @@ export class ReplExpressionsRenderer implements IRenderer { if (sequenceFound) { // Flush buffer with previous styles. - this.appendStylizedStringToContainer(root, buffer, styleNames); + appendStylizedStringToContainer(root, buffer, styleNames, this.linkDetector); + buffer = ''; /* @@ -380,37 +382,13 @@ export class ReplExpressionsRenderer implements IRenderer { // Flush remaining text buffer if not empty. if (buffer) { - this.appendStylizedStringToContainer(root, buffer, styleNames); + appendStylizedStringToContainer(root, buffer, styleNames, this.linkDetector); } return root; } - /** - * @param root The {@link HTMLElement} to append the content to. - * @param stringContent The text content to be appended. - * @param cssClasses The list of CSS styles to apply to the text content. - */ - private appendStylizedStringToContainer(root: HTMLElement, stringContent: string, cssClasses: string[]): void { - if (!root || !stringContent) { - return; - } - - const content = this.linkDetector.handleLinks(stringContent); - let container: HTMLElement; - - if (typeof content === 'string') { - container = document.createElement('span'); - container.textContent = content; - } else { - container = content; - } - - container.className = cssClasses.join(' '); - root.appendChild(container); - } - public disposeTemplate(tree: ITree, templateId: string, templateData: any): void { if (templateData.toDispose) { lifecycle.dispose(templateData.toDispose); From 7b3869fb935d6bfc8324fdad0c8b736554ca8e5a Mon Sep 17 00:00:00 2001 From: danielfrankcom Date: Mon, 21 May 2018 12:38:31 -0700 Subject: [PATCH 05/37] Extracted handleANSIOutput function from replViewer.ts --- .../parts/debug/browser/debugANSIHandling.ts | 103 +++++++++++++++++ .../debug/electron-browser/replViewer.ts | 107 +----------------- 2 files changed, 105 insertions(+), 105 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/debugANSIHandling.ts b/src/vs/workbench/parts/debug/browser/debugANSIHandling.ts index 1c0345057cd..fc5e15e4466 100644 --- a/src/vs/workbench/parts/debug/browser/debugANSIHandling.ts +++ b/src/vs/workbench/parts/debug/browser/debugANSIHandling.ts @@ -5,6 +5,109 @@ import { LinkDetector } from 'vs/workbench/parts/debug/browser/linkDetector'; +/** + * @param text The content to stylize. + * @returns An {@link HTMLSpanElement} that contains the potentially stylized text. + */ +export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTMLSpanElement { + + const root: HTMLSpanElement = document.createElement('span'); + const textLength: number = text.length; + + let styleNames: string[] = []; + let currentPos: number = 0; + let buffer: string = ''; + + while (currentPos < textLength) { + + // Potentially an ANSI escape sequence. + // See http://ascii-table.com/ansi-escape-sequences.php & https://en.wikipedia.org/wiki/ANSI_escape_code + if (text.charCodeAt(currentPos) === 27 && text.charAt(currentPos + 1) === '[') { + + const startPos: number = currentPos; + currentPos += 2; // Ignore 'Esc[' as it's in every sequence. + + let ansiSequence: string = ''; + let sequenceFound: boolean = false; + + while (currentPos < textLength) { + const char: string = text.charAt(currentPos); + ansiSequence += char; + + currentPos++; + + // Look for a known sequence terminating character. + if (char.match(/^[ABCDHIJKfhmpsu]$/)) { + sequenceFound = true; + break; + } + + } + + if (sequenceFound) { + + // Flush buffer with previous styles. + appendStylizedStringToContainer(root, buffer, styleNames, linkDetector); + + buffer = ''; + + /* + * Certain ranges that are matched here do not contain real graphics rendition sequences. For + * the sake of having a simpler expression, they have been included anyway. + */ + if (ansiSequence.match(/^(?:[39][0-7]|[0-8]|39)(?:;(?:[39][0-7]|[0-8]|39))*;?m$/)) { + + const styleCodes: number[] = ansiSequence.slice(0, -1) // Remove final 'm' character. + .split(';') // Separate style codes. + .filter(elem => elem !== '') // Filter empty elems as '34;m' -> ['34', '']. + .map(elem => parseInt(elem, 10)); // Convert to numbers. + + for (let code of styleCodes) { + if (code === 0) { + styleNames = []; + } else if (code === 1) { + styleNames.push('code-bold'); + } else if (code === 4) { + styleNames.push('code-underline'); + } else if ((code >= 30 && code <= 37) || (code >= 90 && code <= 97)) { + styleNames.push('code-foreground-' + code); + } else if (code === 39) { + // Remove all foreground colour codes + styleNames = styleNames.filter(style => !style.match(/^code-foreground-\d+$/)); + } + } + + } else { + // Unsupported sequence so simply hide it. + } + + } + + if (sequenceFound === false) { + /* + * Reached end of text without ending the escape sequence, + * or given sequence is currently unsupported. In either + * case, treat sequence as regular text. + */ + currentPos = startPos + 1; + } + + } else { + buffer += text.charAt(currentPos); + currentPos++; + } + + } + + // Flush remaining text buffer if not empty. + if (buffer) { + appendStylizedStringToContainer(root, buffer, styleNames, linkDetector); + } + + return root; + +} + /** * @param root The {@link HTMLElement} to append the content to. * @param stringContent The text content to be appended. diff --git a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts index 8d29ddcf65f..cb44dfeb164 100644 --- a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts @@ -23,7 +23,7 @@ import { CopyAction, CopyAllAction } from 'vs/workbench/parts/debug/electron-bro import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LinkDetector } from 'vs/workbench/parts/debug/browser/linkDetector'; -import { appendStylizedStringToContainer } from 'vs/workbench/parts/debug/browser/debugANSIHandling'; +import { handleANSIOutput } from 'vs/workbench/parts/debug/browser/debugANSIHandling'; const $ = dom.$; @@ -253,7 +253,7 @@ export class ReplExpressionsRenderer implements IRenderer { dom.clearNode(templateData.value); // Reset classes to clear ansi decorations since templates are reused templateData.value.className = 'value'; - let result = this.handleANSIOutput(element.value); + let result = handleANSIOutput(element.value, this.linkDetector); templateData.value.appendChild(result); dom.addClass(templateData.value, (element.severity === severity.Warning) ? 'warn' : (element.severity === severity.Error) ? 'error' : 'info'); @@ -286,109 +286,6 @@ export class ReplExpressionsRenderer implements IRenderer { } } - /** - * @param text The content to stylize. - * @returns An {@link HTMLSpanElement} that contains the potentially stylized text. - */ - private handleANSIOutput(text: string): HTMLSpanElement { - - const root: HTMLSpanElement = document.createElement('span'); - const textLength: number = text.length; - - let styleNames: string[] = []; - let currentPos: number = 0; - let buffer: string = ''; - - while (currentPos < textLength) { - - // Potentially an ANSI escape sequence. - // See http://ascii-table.com/ansi-escape-sequences.php & https://en.wikipedia.org/wiki/ANSI_escape_code - if (text.charCodeAt(currentPos) === 27 && text.charAt(currentPos + 1) === '[') { - - const startPos: number = currentPos; - currentPos += 2; // Ignore 'Esc[' as it's in every sequence. - - let ansiSequence: string = ''; - let sequenceFound: boolean = false; - - while (currentPos < textLength) { - const char: string = text.charAt(currentPos); - ansiSequence += char; - - currentPos++; - - // Look for a known sequence terminating character. - if (char.match(/^[ABCDHIJKfhmpsu]$/)) { - sequenceFound = true; - break; - } - - } - - if (sequenceFound) { - - // Flush buffer with previous styles. - appendStylizedStringToContainer(root, buffer, styleNames, this.linkDetector); - - buffer = ''; - - /* - * Certain ranges that are matched here do not contain real graphics rendition sequences. For - * the sake of having a simpler expression, they have been included anyway. - */ - if (ansiSequence.match(/^(?:[39][0-7]|[0-8]|39)(?:;(?:[39][0-7]|[0-8]|39))*;?m$/)) { - - const styleCodes: number[] = ansiSequence.slice(0, -1) // Remove final 'm' character. - .split(';') // Separate style codes. - .filter(elem => elem !== '') // Filter empty elems as '34;m' -> ['34', '']. - .map(elem => parseInt(elem, 10)); // Convert to numbers. - - for (let code of styleCodes) { - if (code === 0) { - styleNames = []; - } else if (code === 1) { - styleNames.push('code-bold'); - } else if (code === 4) { - styleNames.push('code-underline'); - } else if ((code >= 30 && code <= 37) || (code >= 90 && code <= 97)) { - styleNames.push('code-foreground-' + code); - } else if (code === 39) { - // Remove all foreground colour codes - styleNames = styleNames.filter(style => !style.match(/^code-foreground-\d+$/)); - } - } - - } else { - // Unsupported sequence so simply hide it. - } - - } - - if (sequenceFound === false) { - /* - * Reached end of text without ending the escape sequence, - * or given sequence is currently unsupported. In either - * case, treat sequence as regular text. - */ - currentPos = startPos + 1; - } - - } else { - buffer += text.charAt(currentPos); - currentPos++; - } - - } - - // Flush remaining text buffer if not empty. - if (buffer) { - appendStylizedStringToContainer(root, buffer, styleNames, this.linkDetector); - } - - return root; - - } - public disposeTemplate(tree: ITree, templateId: string, templateData: any): void { if (templateData.toDispose) { lifecycle.dispose(templateData.toDispose); From 0788183a00203a7fe6075d5539c9ed4a7ac0df22 Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Fri, 25 May 2018 15:32:22 -0700 Subject: [PATCH 06/37] Option to just restart when locale matches lang pack Fixes #50492 --- .../electron-browser/localizations.contribution.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts index 7e9a9f3ffe4..7f47a692864 100644 --- a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts +++ b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts @@ -71,15 +71,17 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo if (!this.storageService.getBoolean(donotAskUpdateKey) && e.local && e.operation === InstallOperation.Install && e.local.manifest.contributes && e.local.manifest.contributes.localizations && e.local.manifest.contributes.localizations.length) { const locale = e.local.manifest.contributes.localizations[0].languageId; if (platform.language !== locale) { + const updateAndRestart = platform.locale !== locale; this.notificationService.prompt( Severity.Info, - localize('updateLocale', "Would you like to change VS Code's UI language to {0} and restart?", e.local.manifest.contributes.localizations[0].languageName || e.local.manifest.contributes.localizations[0].languageId), + updateAndRestart ? localize('updateLocale', "Would you like to change VS Code's UI language to {0} and restart?", e.local.manifest.contributes.localizations[0].languageName || e.local.manifest.contributes.localizations[0].languageId) + : localize('activateLanguagePack', "Would you like to restart VS Code to activate the language pack that was just installed?"), [{ label: localize('yes', "Yes"), run: () => { const file = URI.file(join(this.environmentService.appSettingsHome, 'locale.json')); - this.jsonEditingService.write(file, { key: 'locale', value: locale }, true) - .then(() => this.windowsService.relaunch({}), e => this.notificationService.error(e)); + const updatePromise = updateAndRestart ? this.jsonEditingService.write(file, { key: 'locale', value: locale }, true) : TPromise.as(null); + updatePromise.then(() => this.windowsService.relaunch({}), e => this.notificationService.error(e)); } }, { label: localize('no', "No"), From 680989bf42b49144bf92870c9671768c3f6ce590 Mon Sep 17 00:00:00 2001 From: danielfrankcom Date: Sun, 27 May 2018 14:49:26 -0700 Subject: [PATCH 07/37] Simplifying ANSI logic when no sequence found --- .../parts/debug/browser/debugANSIHandling.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/debugANSIHandling.ts b/src/vs/workbench/parts/debug/browser/debugANSIHandling.ts index fc5e15e4466..07fe64ce7a6 100644 --- a/src/vs/workbench/parts/debug/browser/debugANSIHandling.ts +++ b/src/vs/workbench/parts/debug/browser/debugANSIHandling.ts @@ -20,6 +20,8 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML while (currentPos < textLength) { + let sequenceFound: boolean = false; + // Potentially an ANSI escape sequence. // See http://ascii-table.com/ansi-escape-sequences.php & https://en.wikipedia.org/wiki/ANSI_escape_code if (text.charCodeAt(currentPos) === 27 && text.charAt(currentPos + 1) === '[') { @@ -28,7 +30,6 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML currentPos += 2; // Ignore 'Esc[' as it's in every sequence. let ansiSequence: string = ''; - let sequenceFound: boolean = false; while (currentPos < textLength) { const char: string = text.charAt(currentPos); @@ -81,18 +82,13 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML // Unsupported sequence so simply hide it. } + } else { + currentPos = startPos; } - if (sequenceFound === false) { - /* - * Reached end of text without ending the escape sequence, - * or given sequence is currently unsupported. In either - * case, treat sequence as regular text. - */ - currentPos = startPos + 1; - } + } - } else { + if (sequenceFound === false) { buffer += text.charAt(currentPos); currentPos++; } From c1666244b099577010250f248e82577766652ccd Mon Sep 17 00:00:00 2001 From: danielfrankcom Date: Sun, 27 May 2018 14:49:50 -0700 Subject: [PATCH 08/37] Added unit tests for debug ANSI handling --- .../test/browser/debugANSIHandling.test.ts | 266 ++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 src/vs/workbench/parts/debug/test/browser/debugANSIHandling.test.ts diff --git a/src/vs/workbench/parts/debug/test/browser/debugANSIHandling.test.ts b/src/vs/workbench/parts/debug/test/browser/debugANSIHandling.test.ts new file mode 100644 index 00000000000..33ff66b0077 --- /dev/null +++ b/src/vs/workbench/parts/debug/test/browser/debugANSIHandling.test.ts @@ -0,0 +1,266 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as dom from 'vs/base/browser/dom'; +import { generateUuid } from 'vs/base/common/uuid'; +import { appendStylizedStringToContainer, handleANSIOutput } from 'vs/workbench/parts/debug/browser/debugANSIHandling'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; +import { LinkDetector } from 'vs/workbench/parts/debug/browser/linkDetector'; + +suite('Debug - ANSI Handling', () => { + + let linkDetector: LinkDetector; + + /** + * Instantiate a {@link LinkDetector} for use by the functions being tested. + */ + setup(() => { + const instantiationService: TestInstantiationService = workbenchInstantiationService(); + linkDetector = instantiationService.createInstance(LinkDetector); + }); + + // todo: function here + + test('appendStylizedStringToContainer', () => { + const root: HTMLSpanElement = document.createElement('span'); + let child: Node; + + assert.equal(0, root.children.length); + + appendStylizedStringToContainer(root, 'content1', ['class1', 'class2'], linkDetector); + appendStylizedStringToContainer(root, 'content2', ['class2', 'class3'], linkDetector); + + assert.equal(2, root.children.length); + + child = root.firstChild; + if (child instanceof HTMLSpanElement) { + assert.equal('content1', child.textContent); + assert(dom.hasClass(child, 'class1')); + assert(dom.hasClass(child, 'class2')); + } else { + assert.fail(); + } + + child = root.lastChild; + if (child instanceof HTMLSpanElement) { + assert.equal('content2', child.textContent); + assert(dom.hasClass(child, 'class2')); + assert(dom.hasClass(child, 'class3')); + } else { + assert.fail(); + } + }); + + /** + * Apply an ANSI sequence to {@link #getSequenceOutput}. + * + * @param sequence The ANSI sequence to stylize. + * @returns An {@link HTMLSpanElement} that contains the stylized text. + */ + function getSequenceOutput(sequence: string): HTMLSpanElement { + const root: HTMLSpanElement = handleANSIOutput(sequence, linkDetector); + assert.equal(1, root.children.length); + const child: Node = root.lastChild; + if (child instanceof HTMLSpanElement) { + return child; + } else { + assert.fail(); + return null; + } + } + + /** + * Assert that a given ANSI sequence maintains added content following the ANSI code, and that + * the provided {@param assertion} passes. + * + * @param sequence The ANSI sequence to verify. The provided sequence should contain ANSI codes + * only, and should not include actual text content as it is provided by this function. + * @param assertion The function used to verify the output. + */ + function assertSingleSequenceElement(sequence: string, assertion: (child: HTMLSpanElement) => void): void { + const child: HTMLSpanElement = getSequenceOutput(sequence + 'content'); + assert.equal('content', child.textContent); + assertion(child); + } + + test('Expected single sequence operation', () => { + + // Bold code + assertSingleSequenceElement('\x1b[1m', (child) => { + assert(dom.hasClass(child, 'code-bold')); + }); + + // Underline code + assertSingleSequenceElement('\x1b[4m', (child) => { + assert(dom.hasClass(child, 'code-underline')); + }); + + for (let i = 30; i <= 37; i++) { + const style: string = 'code-foreground-' + i; + + // Foreground colour codes + assertSingleSequenceElement('\x1b[' + i + 'm', (child) => { + assert(dom.hasClass(child, style)); + }); + + // Cancellation code removes colour code + assertSingleSequenceElement('\x1b[' + i + ';39m', (child) => { + assert(dom.hasClass(child, style) === false); + }); + } + + // Codes do not interfere + assertSingleSequenceElement('\x1b[1;4;30;31;32;33;34;35;36;37m', (child) => { + assert.equal(10, child.classList.length); + + assert(dom.hasClass(child, 'code-bold')); + assert(dom.hasClass(child, 'code-underline')); + for (let i = 30; i <= 37; i++) { + assert(dom.hasClass(child, 'code-foreground-' + i)); + } + }); + + // Duplicate codes do not change output + assertSingleSequenceElement('\x1b[1;1;4;1;4;4;1;4m', (child) => { + assert(dom.hasClass(child, 'code-bold')); + assert(dom.hasClass(child, 'code-underline')); + }); + + // Extra terminating semicolon does not change output + assertSingleSequenceElement('\x1b[1;4;m', (child) => { + assert(dom.hasClass(child, 'code-bold')); + assert(dom.hasClass(child, 'code-underline')); + }); + + // Cancellation code removes all codes + assertSingleSequenceElement('\x1b[1;4;30;31;32;33;34;35;36;37;0m', (child) => { + assert.equal(0, child.classList.length); + }); + + }); + + /** + * Assert that a given ANSI sequence produces the expected number of {@link HTMLSpanElement} children. For + * each child, run the provided assertion. + * + * @param sequence The ANSI sequence to verify. + * @param assertions A set of assertions to run on the resulting children. + */ + function assertMultipleSequenceElements(sequence: string, assertions: Array<(child: HTMLSpanElement) => void>): void { + const root: HTMLSpanElement = handleANSIOutput(sequence, linkDetector); + assert.equal(assertions.length, root.children.length); + for (let i = 0; i < assertions.length; i++) { + const child: Node = root.children[i]; + if (child instanceof HTMLSpanElement) { + assertions[i](child); + } else { + assert.fail(); + } + } + } + + test('Expected multiple sequence operation', () => { + + // Multiple codes affect the same text + assertSingleSequenceElement('\x1b[1m\x1b[4m\x1b[32m', (child) => { + assert(dom.hasClass(child, 'code-bold')); + assert(dom.hasClass(child, 'code-underline')); + assert(dom.hasClass(child, 'code-foreground-32')); + }); + + // Consecutive codes do not affect previous ones + assertMultipleSequenceElements('\x1b[1mbold\x1b[32mgreen\x1b[4munderline\x1b[0mnothing', [ + (bold) => { + assert.equal(1, bold.classList.length); + assert(dom.hasClass(bold, 'code-bold')); + }, + (green) => { + assert.equal(2, green.classList.length); + assert(dom.hasClass(green, 'code-bold')); + assert(dom.hasClass(green, 'code-foreground-32')); + }, + (underline) => { + assert.equal(3, underline.classList.length); + assert(dom.hasClass(underline, 'code-bold')); + assert(dom.hasClass(underline, 'code-foreground-32')); + assert(dom.hasClass(underline, 'code-underline')); + }, + (nothing) => { + assert.equal(0, nothing.classList.length); + }, + ]); + + }); + + /** + * Assert that the provided ANSI sequence exactly matches the text content of the resulting + * {@link HTMLSpanElement}. + * + * @param sequence The ANSI sequence to verify. + */ + function assertSequenceEqualToContent(sequence: string): void { + const child: HTMLSpanElement = getSequenceOutput(sequence); + assert(child.textContent === sequence); + } + + test('Invalid codes treated as regular text', () => { + + // Individual components of ANSI code start are printed + assertSequenceEqualToContent('\x1b'); + assertSequenceEqualToContent('['); + + // Unsupported sequence prints both characters + assertSequenceEqualToContent('\x1b['); + + // Random strings are displayed properly + for (let i = 0; i < 50; i++) { + const uuid: string = generateUuid(); + assertSequenceEqualToContent(uuid); + } + + }); + + /** + * Assert that a given ANSI sequence maintains added content following the ANSI code, and that + * the expression itself is thrown away. + * + * @param sequence The ANSI sequence to verify. The provided sequence should contain ANSI codes + * only, and should not include actual text content as it is provided by this function. + */ + function assertEmptyOutput(sequence: string) { + const child: HTMLSpanElement = getSequenceOutput(sequence + 'content'); + assert.equal('content', child.textContent); + assert.equal(0, child.classList.length); + } + + test('Empty sequence output', () => { + + const sequences: string[] = [ + // No colour codes + '', + '\x1b[;m', + '\x1b[1;;m', + '\x1b[m', + // Unsupported colour codes + '\x1b[30;40m', + '\x1b[100m' + ]; + + sequences.forEach(sequence => { + assertEmptyOutput(sequence); + }); + + // Check other possible ANSI terminators + const terminators: string[] = 'ABCDHIJKfhmpsu'.split(''); + + terminators.forEach(terminator => { + assertEmptyOutput('\x1b[content' + terminator); + }); + + }); + +}); \ No newline at end of file From c7f735b9ca9a2c849ae3991a789d531860287705 Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Mon, 28 May 2018 03:16:34 +0300 Subject: [PATCH 09/37] Remove unnecessary commented out code --- test/index.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/index.html b/test/index.html index 785f4e3abc0..384c987fed1 100644 --- a/test/index.html +++ b/test/index.html @@ -7,8 +7,6 @@
- From bf38abadf83d74017a935c8864e551b25454010f Mon Sep 17 00:00:00 2001 From: h-h-h-h Date: Mon, 28 May 2018 02:53:24 +0200 Subject: [PATCH 10/37] Edited the "editor.wrappingIndent" preference to allow the value "deepIndent". The preference value "indent" adds 1 tab to the indentation of continuation lines, additionally to the indentation of the line of text in memory. "deepIndent" adds 2 tabs. --- .../editor/common/config/commonEditorConfig.ts | 4 ++-- src/vs/editor/common/config/editorOptions.ts | 18 ++++++++++++------ .../characterHardWrappingLineMapper.ts | 10 ++++++++++ .../characterHardWrappingLineMapper.test.ts | 9 ++++++++- src/vs/monaco.d.ts | 8 ++++++-- 5 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index a5d84b4f7be..62f27b14835 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -353,9 +353,9 @@ const editorConfiguration: IConfigurationNode = { }, 'editor.wrappingIndent': { 'type': 'string', - 'enum': ['none', 'same', 'indent'], + 'enum': ['none', 'same', 'indent', 'deepIndent'], 'default': 'same', - 'description': nls.localize('wrappingIndent', "Controls the indentation of wrapped lines. Can be one of 'none', 'same' or 'indent'.") + 'description': nls.localize('wrappingIndent', "Controls the indentation of wrapped lines. Can be one of 'none', 'same', 'indent' or 'deepIndent'.") }, 'editor.mouseWheelScrollSensitivity': { 'type': 'number', diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index c7ea7ee336c..af780797a47 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -340,7 +340,7 @@ export interface IEditorOptions { */ wordWrapMinified?: boolean; /** - * Control indentation of wrapped lines. Can be: 'none', 'same' or 'indent'. + * Control indentation of wrapped lines. Can be: 'none', 'same', 'indent' or 'deepIndent'. * Defaults to 'same' in vscode and to 'none' in monaco-editor. */ wrappingIndent?: string; @@ -635,9 +635,13 @@ export enum WrappingIndent { */ Same = 1, /** - * Indent => wrapped lines get +1 indentation as the parent. + * Indent => wrapped lines get +1 indentation toward the parent. */ - Indent = 2 + Indent = 2, + /** + * DeepIndent => wrapped lines get +2 indentation toward the parent. + */ + DeepIndent = 3 } /** @@ -1477,10 +1481,12 @@ function _wrappingIndentFromString(wrappingIndent: string, defaultValue: Wrappin if (typeof wrappingIndent !== 'string') { return defaultValue; } - if (wrappingIndent === 'indent') { - return WrappingIndent.Indent; - } else if (wrappingIndent === 'same') { + if (wrappingIndent === 'same') { return WrappingIndent.Same; + } else if (wrappingIndent === 'indent') { + return WrappingIndent.Indent; + } else if (wrappingIndent === 'deepIndent') { + return WrappingIndent.DeepIndent; } else { return WrappingIndent.None; } diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index b3e04d5a0af..fffd50708af 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -92,14 +92,24 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor if (hardWrappingIndent !== WrappingIndent.None) { firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText); if (firstNonWhitespaceIndex !== -1) { + // Track existing indent wrappedTextIndent = lineText.substring(0, firstNonWhitespaceIndex); for (let i = 0; i < firstNonWhitespaceIndex; i++) { wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, lineText.charCodeAt(i) === CharCode.Tab, 1); } + + // Increase indent of continuation lines, if desired + let numberOfAdditionalTabs = 0; if (hardWrappingIndent === WrappingIndent.Indent) { + numberOfAdditionalTabs = 1; + } else if (hardWrappingIndent === WrappingIndent.DeepIndent) { + numberOfAdditionalTabs = 2; + } + for (let i = 0; i < numberOfAdditionalTabs; i++) { wrappedTextIndent += '\t'; wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, true, 1); } + // Force sticking to beginning of line if no character would fit except for the indentation if (wrappedTextIndentVisibleColumn + columnsForFullWidthChar > breakingColumn) { wrappedTextIndent = ''; diff --git a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts index fb9c6978e74..ccfe8d1dce1 100644 --- a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts +++ b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts @@ -10,7 +10,7 @@ import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewMod import { ILineMapperFactory, ILineMapping } from 'vs/editor/common/viewModel/splitLinesCollection'; function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAfter: number, annotatedText: string, wrappingIndent = WrappingIndent.None): ILineMapping { - + // Create version of `annotatedText` with line break markers removed let rawText = ''; let currentLineIndex = 0; let lineIndices: number[] = []; @@ -25,6 +25,7 @@ function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAf let mapper = factory.createLineMapping(rawText, tabSize, breakAfter, 2, wrappingIndent); + // Insert line break markers again, according to algorithm let actualAnnotatedText = ''; if (mapper) { let previousLineIndex = 0; @@ -114,4 +115,10 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { let mapper = assertLineMapping(factory, 4, 24, ' t h i s |i s |a l |o n |g l |i n |e', WrappingIndent.Indent); assert.equal(mapper.getWrappedLinesIndent(), ' \t'); }); + + test('CharacterHardWrappingLineMapper - WrappingIndent.DeepIndent', () => { + let factory = new CharacterHardWrappingLineMapperFactory('', ' ', ''); + let mapper = assertLineMapping(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent); + assert.equal(mapper.getWrappedLinesIndent(), ' \t\t'); + }); }); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 71a6444b1a4..2b42db1f6e9 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2679,7 +2679,7 @@ declare namespace monaco.editor { */ wordWrapMinified?: boolean; /** - * Control indentation of wrapped lines. Can be: 'none', 'same' or 'indent'. + * Control indentation of wrapped lines. Can be: 'none', 'same', 'indent' or 'deepIndent'. * Defaults to 'same' in vscode and to 'none' in monaco-editor. */ wrappingIndent?: string; @@ -2977,9 +2977,13 @@ declare namespace monaco.editor { */ Same = 1, /** - * Indent => wrapped lines get +1 indentation as the parent. + * Indent => wrapped lines get +1 indentation toward the parent. */ Indent = 2, + /** + * DeepIndent => wrapped lines get +2 indentation toward the parent. + */ + DeepIndent = 3, } /** From 3e06a9de547b52c634eeb917b52375dd42999ed6 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 28 May 2018 11:22:56 +0200 Subject: [PATCH 11/37] Fix getmac test (#48804) --- src/vs/base/test/node/id.test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/base/test/node/id.test.ts b/src/vs/base/test/node/id.test.ts index 0cddcf54249..85a58de60ff 100644 --- a/src/vs/base/test/node/id.test.ts +++ b/src/vs/base/test/node/id.test.ts @@ -5,13 +5,22 @@ 'use strict'; import * as assert from 'assert'; +import * as getmac from 'getmac'; import { getMachineId } from 'vs/base/node/id'; suite('ID', () => { test('getMachineId', function () { return getMachineId().then(id => { - assert.ok(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.test(id), `Expected a MAC address: ${id}`); + assert.ok(id); + }); + }); + + test('getMac', function () { + return new Promise((resolve, reject) => { + getmac.getMac((err, macAddress) => err ? reject(err) : resolve(macAddress)); + }).then(macAddress => { + assert.ok(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.test(macAddress), `Expected a MAC address, got: ${macAddress}`); }); }); }); \ No newline at end of file From 59bb06ea161a4e7e85c76dd7c8665d444fcf6ae2 Mon Sep 17 00:00:00 2001 From: Drew Diamantoukos Date: Mon, 28 May 2018 05:24:08 -0400 Subject: [PATCH 12/37] Cleaning up some typos in vscode.d.ts and vscode.proposed.d.ts (#50533) --- src/vs/vscode.d.ts | 62 ++++++++++++++++++------------------- src/vs/vscode.proposed.d.ts | 2 +- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 576cf4551cc..c1fb24d6854 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -205,7 +205,7 @@ declare module 'vscode' { /** * Get a word-range at the given position. By default words are defined by - * common separators, like space, -, _, etc. In addition, per languge custom + * common separators, like space, -, _, etc. In addition, per language custom * [word definitions](#LanguageConfiguration.wordPattern) can be defined. It * is also possible to provide a custom regular expression. * @@ -483,7 +483,7 @@ declare module 'vscode' { active: Position; /** - * Create a selection from two postions. + * Create a selection from two positions. * * @param anchor A position. * @param active A position. @@ -1038,7 +1038,7 @@ declare module 'vscode' { /** * Render options applied to the current decoration. For performance reasons, keep the - * number of decoration specific options small, and use decoration types whereever possible. + * number of decoration specific options small, and use decoration types wherever possible. */ renderOptions?: DecorationInstanceRenderOptions; } @@ -1119,7 +1119,7 @@ declare module 'vscode' { /** * Insert a [snippet](#SnippetString) and put the editor into snippet mode. "Snippet mode" - * means the editor adds placeholders and additionals cursors so that the user can complete + * means the editor adds placeholders and additional cursors so that the user can complete * or accept the snippet. * * @param snippet The snippet to insert in this edit. @@ -1310,7 +1310,7 @@ declare module 'vscode' { * [Uri.parse](#Uri.parse). * * @param skipEncoding Do not percentage-encode the result, defaults to `false`. Note that - * the `#` and `?` characters occuring in the path will always be encoded. + * the `#` and `?` characters occurring in the path will always be encoded. * @returns A string representation of this Uri. */ toString(skipEncoding?: boolean): string; @@ -1605,7 +1605,7 @@ declare module 'vscode' { * * * Note 1: A dialog can select files, folders, or both. This is not true for Windows * which enforces to open either files or folder, but *not both*. - * * Note 2: Explictly setting `canSelectFiles` and `canSelectFolders` to `false` is futile + * * Note 2: Explicitly setting `canSelectFiles` and `canSelectFolders` to `false` is futile * and the editor then silently adjusts the options to select files. */ export interface OpenDialogOptions { @@ -1855,7 +1855,7 @@ declare module 'vscode' { * to that type `T`. In addition, `null` and `undefined` can be returned - either directly or from a * thenable. * - * The snippets below are all valid implementions of the [`HoverProvider`](#HoverProvider): + * The snippets below are all valid implementations of the [`HoverProvider`](#HoverProvider): * * ```ts * let a: HoverProvider = { @@ -2181,7 +2181,7 @@ declare module 'vscode' { } /** - * The implemenetation provider interface defines the contract between extensions and + * The implementation provider interface defines the contract between extensions and * the go to implementation feature. */ export interface ImplementationProvider { @@ -2718,7 +2718,7 @@ declare module 'vscode' { * Builder-function that appends a tabstop (`$1`, `$2` etc) to * the [`value`](#SnippetString.value) of this snippet string. * - * @param number The number of this tabstop, defaults to an auto-incremet + * @param number The number of this tabstop, defaults to an auto-increment * value starting at 1. * @return This snippet string. */ @@ -2730,7 +2730,7 @@ declare module 'vscode' { * * @param value The value of this placeholder - either a string or a function * with which a nested snippet can be created. - * @param number The number of this tabstop, defaults to an auto-incremet + * @param number The number of this tabstop, defaults to an auto-increment * value starting at 1. * @return This snippet string. */ @@ -3001,7 +3001,7 @@ declare module 'vscode' { /** * A completion item represents a text snippet that is proposed to complete text that is being typed. * - * It is suffient to create a completion item from just a [label](#CompletionItem.label). In that + * It is sufficient to create a completion item from just a [label](#CompletionItem.label). In that * case the completion item will replace the [word](#TextDocument.getWordRangeAtPosition) * until the cursor with the given label or [insertText](#CompletionItem.insertText). Otherwise the * the given [edit](#CompletionItem.textEdit) is used. @@ -3187,7 +3187,7 @@ declare module 'vscode' { * Providers can delay the computation of the [`detail`](#CompletionItem.detail) * and [`documentation`](#CompletionItem.documentation) properties by implementing the * [`resolveCompletionItem`](#CompletionItemProvider.resolveCompletionItem)-function. However, properties that - * are needed for the inital sorting and filtering, like `sortText`, `filterText`, `insertText`, and `range`, must + * are needed for the initial sorting and filtering, like `sortText`, `filterText`, `insertText`, and `range`, must * not be changed during resolve. * * Providers are asked for completions either explicitly by a user gesture or -depending on the configuration- @@ -3267,7 +3267,7 @@ declare module 'vscode' { /** * Given a link fill in its [target](#DocumentLink.target). This method is called when an incomplete - * link is selected in the UI. Providers can implement this method and return incomple links + * link is selected in the UI. Providers can implement this method and return incomplete links * (without target) from the [`provideDocumentLinks`](#DocumentLinkProvider.provideDocumentLinks) method which * often helps to improve performance. * @@ -3307,7 +3307,7 @@ declare module 'vscode' { * * @param red The red component. * @param green The green component. - * @param blue The bluew component. + * @param blue The blue component. * @param alpha The alpha component. */ constructor(red: number, green: number, blue: number, alpha: number); @@ -3319,7 +3319,7 @@ declare module 'vscode' { export class ColorInformation { /** - * The range in the document where this color appers. + * The range in the document where this color appears. */ range: Range; @@ -3387,7 +3387,7 @@ declare module 'vscode' { * * @param document The document in which the command was invoked. * @param token A cancellation token. - * @return An array of [color informations](#ColorInformation) or a thenable that resolves to such. The lack of a result + * @return An array of [color information](#ColorInformation) or a thenable that resolves to such. The lack of a result * can be signaled by returning `undefined`, `null`, or an empty array. */ provideDocumentColors(document: TextDocument, token: CancellationToken): ProviderResult; @@ -3506,7 +3506,7 @@ declare module 'vscode' { */ export interface IndentationRule { /** - * If a line matches this pattern, then all the lines after it should be unindendented once (until another rule matches). + * If a line matches this pattern, then all the lines after it should be unindented once (until another rule matches). */ decreaseIndentPattern: RegExp; /** @@ -3737,7 +3737,7 @@ declare module 'vscode' { * The *effective* value (returned by [`get`](#WorkspaceConfiguration.get)) * is computed like this: `defaultValue` overwritten by `globalValue`, * `globalValue` overwritten by `workspaceValue`. `workspaceValue` overwritten by `workspaceFolderValue`. - * Refer to [Settings Inheritence](https://code.visualstudio.com/docs/getstarted/settings) + * Refer to [Settings Inheritance](https://code.visualstudio.com/docs/getstarted/settings) * for more information. * * *Note:* The configuration name must denote a leaf in the configuration tree @@ -3762,7 +3762,7 @@ declare module 'vscode' { * has no observable effect in that workspace, but in others. Setting a workspace value * in the presence of a more specific folder value has no observable effect for the resources * under respective [folder](#workspace.workspaceFolders), but in others. Refer to - * [Settings Inheritence](https://code.visualstudio.com/docs/getstarted/settings) for more information. + * [Settings Inheritance](https://code.visualstudio.com/docs/getstarted/settings) for more information. * * *Note 2:* To remove a configuration value use `undefined`, like so: `config.update('somekey', undefined)` * @@ -4614,7 +4614,7 @@ declare module 'vscode' { /** * Defines how an argument should be quoted if it contains - * spaces or unsuppoerted characters. + * spaces or unsupported characters. */ export enum ShellQuoting { @@ -5222,7 +5222,7 @@ declare module 'vscode' { * * @param oldUri The existing file. * @param newUri The new location. - * @param options Defines if existing files should be overwriten. + * @param options Defines if existing files should be overwritten. * @throws [`FileNotFound`](#FileSystemError.FileNotFound) when `oldUri` doesn't exist. * @throws [`FileNotFound`](#FileSystemError.FileNotFound) when parent of `newUri` doesn't exist, e.g. no mkdirp-logic required. * @throws [`FileExists`](#FileSystemError.FileExists) when `newUri` exists and when the `overwrite` option is not `true`. @@ -5297,7 +5297,7 @@ declare module 'vscode' { /** * Post a message to the webview content. * - * Messages are only develivered if the webview is visible. + * Messages are only delivered if the webview is visible. * * @param message Body of the message. */ @@ -5422,14 +5422,14 @@ declare module 'vscode' { */ interface WebviewPanelSerializer { /** - * Restore a webview panel from its seriailzed `state`. + * Restore a webview panel from its serialized `state`. * * Called when a serialized webview first becomes visible. * * @param webviewPanel Webview panel to restore. The serializer should take ownership of this panel. - * @param state Persisted state. This state comesfrom the value set inside the webview by `acquireVsCodeApi().setState`. + * @param state Persisted state. This state comes from the value set inside the webview by `acquireVsCodeApi().setState`. * - * @return Thanble indicating that the webview has been fully restored. + * @return Thenable indicating that the webview has been fully restored. */ deserializeWebviewPanel(webviewPanel: WebviewPanel, state: any): Thenable; } @@ -5981,7 +5981,7 @@ declare module 'vscode' { * * @param task A callback returning a promise. Progress increments can be reported with * the provided [progress](#Progress)-object. - * @return The thenable the task did rseturn. + * @return The thenable the task did return. */ export function withScmProgress(task: (progress: Progress) => Thenable): Thenable; @@ -6267,7 +6267,7 @@ declare module 'vscode' { Window = 10, /** - * Show progress as notifiation with an optional cancel button. Supports to show infinite and discrete progress. + * Show progress as notification with an optional cancel button. Supports to show infinite and discrete progress. */ Notification = 15 } @@ -6825,7 +6825,7 @@ declare module 'vscode' { * 1. When the `DocumentFilter` is empty (`{}`) the result is `0` * 2. When `scheme`, `language`, or `pattern` are defined but one doesn’t match, the result is `0` * 3. Matching against `*` gives a score of `5`, matching via equality or via a glob-pattern gives a score of `10` - * 4. The result is the maximun value of each match + * 4. The result is the maximum value of each match * * Samples: * ```js @@ -6864,7 +6864,7 @@ declare module 'vscode' { * all extensions but *not yet* from the task framework. * * @param resource A resource - * @returns An arrary of [diagnostics](#Diagnostic) objects or an empty array. + * @returns An array of [diagnostics](#Diagnostic) objects or an empty array. */ export function getDiagnostics(resource: Uri): Diagnostic[]; @@ -7631,7 +7631,7 @@ declare module 'vscode' { /** - * Register a [debug configuration provider](#DebugConfigurationProvider) for a specifc debug type. + * Register a [debug configuration provider](#DebugConfigurationProvider) for a specific debug type. * More than one provider can be registered for the same type. * * @param type The debug type for which the provider is registered. @@ -7724,7 +7724,7 @@ declare module 'vscode' { /** * Thenable is a common denominator between ES6 promises, Q, jquery.Deferred, WinJS.Promise, - * and others. This API makes no assumption about what promise libary is being used which + * and others. This API makes no assumption about what promise library is being used which * enables reusing existing code without migrating to a specific promise implementation. Still, * we recommend the use of native promises which are available in this editor. */ diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 5f102c6451c..25410e5480a 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -183,7 +183,7 @@ declare module 'vscode' { export interface DebugConfigurationProvider { /** - * This optional method is called just before a debug adapter is started to determine its excutable path and arguments. + * This optional method is called just before a debug adapter is started to determine its executable path and arguments. * Registering more than one debugAdapterExecutable for a type results in an error. * @param folder The workspace folder from which the configuration originates from or undefined for a folderless setup. * @param token A cancellation token. From 7ada1d70790771a7e4ecf04792eae4b837bc1d1a Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 28 May 2018 11:25:11 +0200 Subject: [PATCH 13/37] yarn.lock changes --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 7f0b53d77e7..d3bfde961b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3280,7 +3280,7 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -keytar@^4.2.1: +keytar@4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/keytar/-/keytar-4.2.1.tgz#8a06a6577fdf6373e0aa6b112277e63dec77fd12" dependencies: From f9e96a39173163217c8b7edec19e973d5f7f2fc1 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 28 May 2018 11:39:54 +0200 Subject: [PATCH 14/37] Fixes #50382: Align .mtku with .detected-link --- src/vs/editor/common/modes/supports/tokenization.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/modes/supports/tokenization.ts b/src/vs/editor/common/modes/supports/tokenization.ts index a155e27cf56..85685d0ff41 100644 --- a/src/vs/editor/common/modes/supports/tokenization.ts +++ b/src/vs/editor/common/modes/supports/tokenization.ts @@ -391,6 +391,6 @@ export function generateTokensCSSForColorMap(colorMap: Color[]): string { } rules.push('.mtki { font-style: italic; }'); rules.push('.mtkb { font-weight: bold; }'); - rules.push('.mtku { text-decoration: underline; }'); + rules.push('.mtku { text-decoration: underline; text-underline-position: under; }'); return rules.join('\n'); } From 1df3da0bd2b028e771c6a23b25e62927e0092c8d Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 28 May 2018 12:02:23 +0200 Subject: [PATCH 15/37] debug issues back to isidorn --- .github/classifier.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/classifier.yml b/.github/classifier.yml index 7fddcd73904..514910d63d2 100644 --- a/.github/classifier.yml +++ b/.github/classifier.yml @@ -10,7 +10,7 @@ color-picker: [], css-less-sass: [ aeschli ], debug: { - assignees: [ roblourens ], + assignees: [ isidorn ], assignLabel: false }, diff-editor: [], From 74ecb33d0d8985657495c8d87713a765cca219ec Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 28 May 2018 12:10:23 +0200 Subject: [PATCH 16/37] Hide interactive playground commands from command palette (fixes #49706) --- .../walkThrough.contribution.ts | 20 ++-- .../electron-browser/walkThroughActions.ts | 113 +++++++----------- 2 files changed, 48 insertions(+), 85 deletions(-) diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution.ts b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution.ts index 7de5dc572e2..335ba65618e 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution.ts +++ b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution.ts @@ -6,8 +6,8 @@ import { localize } from 'vs/nls'; import { WalkThroughInput } from 'vs/workbench/parts/welcome/walkThrough/node/walkThroughInput'; -import { WalkThroughPart, WALK_THROUGH_FOCUS } from 'vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart'; -import { WalkThroughArrowUpAction, WalkThroughArrowDownAction, WalkThroughPageUpAction, WalkThroughPageDownAction } from 'vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughActions'; +import { WalkThroughPart } from 'vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart'; +import { WalkThroughArrowUp, WalkThroughArrowDown, WalkThroughPageUp, WalkThroughPageDown } from 'vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughActions'; import { WalkThroughContentProvider, WalkThroughSnippetContentProvider } from 'vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider'; import { EditorWalkThroughAction, EditorWalkThroughInputFactory } from 'vs/workbench/parts/welcome/walkThrough/electron-browser/editor/editorWalkThrough'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -16,11 +16,9 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IEditorRegistry, Extensions as EditorExtensions, EditorDescriptor } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; Registry.as(EditorExtensions.Editors) .registerEditor(new EditorDescriptor( @@ -43,14 +41,10 @@ Registry.as(WorkbenchExtensions.Workbench) Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(WalkThroughSnippetContentProvider, LifecyclePhase.Starting); -Registry.as(Extensions.WorkbenchActions) - .registerWorkbenchAction(new SyncActionDescriptor(WalkThroughArrowUpAction, WalkThroughArrowUpAction.ID, WalkThroughArrowUpAction.LABEL, { primary: KeyCode.UpArrow }, ContextKeyExpr.and(WALK_THROUGH_FOCUS, EditorContextKeys.editorTextFocus.toNegated())), 'Interactive Playground: Scroll Up (Line)', localize('interactivePlayground', "Interactive Playground")); +KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughArrowUp); -Registry.as(Extensions.WorkbenchActions) - .registerWorkbenchAction(new SyncActionDescriptor(WalkThroughArrowDownAction, WalkThroughArrowDownAction.ID, WalkThroughArrowDownAction.LABEL, { primary: KeyCode.DownArrow }, ContextKeyExpr.and(WALK_THROUGH_FOCUS, EditorContextKeys.editorTextFocus.toNegated())), 'Interactive Playground: Scroll Down (Line)', localize('interactivePlayground', "Interactive Playground")); +KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughArrowDown); -Registry.as(Extensions.WorkbenchActions) - .registerWorkbenchAction(new SyncActionDescriptor(WalkThroughPageUpAction, WalkThroughPageUpAction.ID, WalkThroughPageUpAction.LABEL, { primary: KeyCode.PageUp }, ContextKeyExpr.and(WALK_THROUGH_FOCUS, EditorContextKeys.editorTextFocus.toNegated())), 'Interactive Playground: Scroll Up (Page)', localize('interactivePlayground', "Interactive Playground")); +KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughPageUp); -Registry.as(Extensions.WorkbenchActions) - .registerWorkbenchAction(new SyncActionDescriptor(WalkThroughPageDownAction, WalkThroughPageDownAction.ID, WalkThroughPageDownAction.LABEL, { primary: KeyCode.PageDown }, ContextKeyExpr.and(WALK_THROUGH_FOCUS, EditorContextKeys.editorTextFocus.toNegated())), 'Interactive Playground: Scroll Down (Page)', localize('interactivePlayground', "Interactive Playground")); +KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughPageDown); diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughActions.ts b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughActions.ts index 5e8592604bc..1d8f99c373e 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughActions.ts +++ b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughActions.ts @@ -4,96 +4,65 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { localize } from 'vs/nls'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { Action } from 'vs/base/common/actions'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { WalkThroughPart } from 'vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart'; +import { WalkThroughPart, WALK_THROUGH_FOCUS } from 'vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart'; +import { ICommandAndKeybindingRule, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeyCode } from 'vs/base/common/keyCodes'; -export class WalkThroughArrowUpAction extends Action { - - public static readonly ID = 'workbench.action.interactivePlayground.arrowUp'; - public static readonly LABEL = localize('editorWalkThrough.arrowUp', "Scroll Up (Line)"); - - constructor( - id: string, - label: string, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService - ) { - super(id, label); - } - - public run(): TPromise { - const editor = this.editorService.getActiveEditor(); +export const WalkThroughArrowUp: ICommandAndKeybindingRule = { + id: 'workbench.action.interactivePlayground.arrowUp', + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: ContextKeyExpr.and(WALK_THROUGH_FOCUS, EditorContextKeys.editorTextFocus.toNegated()), + primary: KeyCode.UpArrow, + handler: accessor => { + const editorService = accessor.get(IWorkbenchEditorService); + const editor = editorService.getActiveEditor(); if (editor instanceof WalkThroughPart) { editor.arrowUp(); } - return null; } -} +}; -export class WalkThroughArrowDownAction extends Action { - - public static readonly ID = 'workbench.action.interactivePlayground.arrowDown'; - public static readonly LABEL = localize('editorWalkThrough.arrowDown', "Scroll Down (Line)"); - - constructor( - id: string, - label: string, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService - ) { - super(id, label); - } - - public run(): TPromise { - const editor = this.editorService.getActiveEditor(); +export const WalkThroughArrowDown: ICommandAndKeybindingRule = { + id: 'workbench.action.interactivePlayground.arrowDown', + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: ContextKeyExpr.and(WALK_THROUGH_FOCUS, EditorContextKeys.editorTextFocus.toNegated()), + primary: KeyCode.DownArrow, + handler: accessor => { + const editorService = accessor.get(IWorkbenchEditorService); + const editor = editorService.getActiveEditor(); if (editor instanceof WalkThroughPart) { editor.arrowDown(); } - return null; } -} +}; -export class WalkThroughPageUpAction extends Action { - - public static readonly ID = 'workbench.action.interactivePlayground.pageUp'; - public static readonly LABEL = localize('editorWalkThrough.pageUp', "Scroll Up (Page)"); - - constructor( - id: string, - label: string, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService - ) { - super(id, label); - } - - public run(): TPromise { - const editor = this.editorService.getActiveEditor(); +export const WalkThroughPageUp: ICommandAndKeybindingRule = { + id: 'workbench.action.interactivePlayground.pageUp', + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: ContextKeyExpr.and(WALK_THROUGH_FOCUS, EditorContextKeys.editorTextFocus.toNegated()), + primary: KeyCode.PageUp, + handler: accessor => { + const editorService = accessor.get(IWorkbenchEditorService); + const editor = editorService.getActiveEditor(); if (editor instanceof WalkThroughPart) { editor.pageUp(); } - return null; } -} +}; -export class WalkThroughPageDownAction extends Action { - - public static readonly ID = 'workbench.action.interactivePlayground.pageDown'; - public static readonly LABEL = localize('editorWalkThrough.pageDown', "Scroll Down (Page)"); - - constructor( - id: string, - label: string, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService - ) { - super(id, label); - } - - public run(): TPromise { - const editor = this.editorService.getActiveEditor(); +export const WalkThroughPageDown: ICommandAndKeybindingRule = { + id: 'workbench.action.interactivePlayground.pageDown', + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: ContextKeyExpr.and(WALK_THROUGH_FOCUS, EditorContextKeys.editorTextFocus.toNegated()), + primary: KeyCode.PageDown, + handler: accessor => { + const editorService = accessor.get(IWorkbenchEditorService); + const editor = editorService.getActiveEditor(); if (editor instanceof WalkThroughPart) { editor.pageDown(); } - return null; } -} \ No newline at end of file +}; From 7a9c0801581a811c964afc922ca7ddfe99fd0b8a Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Mon, 28 May 2018 13:03:22 +0200 Subject: [PATCH 17/37] Fixes #43003: Cannot specify a global problemMatcher in tasks.json --- src/vs/workbench/parts/tasks/common/tasks.ts | 2 ++ .../electron-browser/task.contribution.ts | 2 +- .../parts/tasks/node/taskConfiguration.ts | 19 +++++++++++++++++-- .../electron-browser/configuration.test.ts | 3 ++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/tasks/common/tasks.ts b/src/vs/workbench/parts/tasks/common/tasks.ts index 47cf212ecb4..f1528ca95ce 100644 --- a/src/vs/workbench/parts/tasks/common/tasks.ts +++ b/src/vs/workbench/parts/tasks/common/tasks.ts @@ -431,6 +431,8 @@ export interface CustomTask extends CommonTask, ConfigurationProperties { identifier: string; + hasDefinedMatchers: boolean; + /** * The command configuration */ diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 76796568a07..1d18db327ba 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -847,7 +847,7 @@ class TaskService implements ITaskService { } if (CustomTask.is(task)) { let configProperties: TaskConfig.ConfigurationProperties = task._source.config.element; - return configProperties.problemMatcher === void 0; + return configProperties.problemMatcher === void 0 && !task.hasDefinedMatchers; } return false; } diff --git a/src/vs/workbench/parts/tasks/node/taskConfiguration.ts b/src/vs/workbench/parts/tasks/node/taskConfiguration.ts index c4fbf4293a6..0ad38de152a 100644 --- a/src/vs/workbench/parts/tasks/node/taskConfiguration.ts +++ b/src/vs/workbench/parts/tasks/node/taskConfiguration.ts @@ -1337,6 +1337,7 @@ namespace CustomTask { _label: taskName, name: taskName, identifier: taskName, + hasDefinedMatchers: false, command: undefined }; let configuration = ConfigurationProperties.from(external, context, false); @@ -1375,6 +1376,10 @@ namespace CustomTask { if (CommandConfiguration.hasCommand(task.command) || task.dependsOn === void 0) { task.command = CommandConfiguration.fillGlobals(task.command, globals.command, task.name); } + if (task.problemMatchers === void 0 && globals.problemMatcher !== void 0) { + task.problemMatchers = Objects.deepClone(globals.problemMatcher); + task.hasDefinedMatchers = true; + } // promptOnClose is inferred from isBackground if available if (task.promptOnClose === void 0 && task.isBackground === void 0 && globals.promptOnClose !== void 0) { task.promptOnClose = globals.promptOnClose; @@ -1405,7 +1410,8 @@ namespace CustomTask { type: 'custom', command: contributedTask.command, name: configuredProps.name || contributedTask.name, - identifier: configuredProps.identifier || contributedTask.identifier + identifier: configuredProps.identifier || contributedTask.identifier, + hasDefinedMatchers: false }; let resultConfigProps: Tasks.ConfigurationProperties = result; @@ -1430,6 +1436,10 @@ namespace CustomTask { result.command.presentation, contributedConfigProps.presentation); result.command.options = CommandOptions.fillProperties(result.command.options, contributedConfigProps.options); + if (contributedTask.hasDefinedMatchers === true) { + result.hasDefinedMatchers = true; + } + return result; } } @@ -1545,6 +1555,7 @@ namespace TaskParser { interface Globals { command?: Tasks.CommandConfiguration; + problemMatcher?: ProblemMatcher[]; promptOnClose?: boolean; suppressTaskName?: boolean; } @@ -1581,6 +1592,9 @@ namespace Globals { if (config.promptOnClose !== void 0) { result.promptOnClose = !!config.promptOnClose; } + if (config.problemMatcher) { + result.problemMatcher = ProblemMatcherConverter.from(config.problemMatcher, context); + } return result; } @@ -1830,7 +1844,8 @@ class ConfigurationParser { suppressTaskName: true }, isBackground: isBackground, - problemMatchers: matchers + problemMatchers: matchers, + hasDefinedMatchers: false, }; let value = GroupKind.from(fileConfig.group); if (value) { diff --git a/src/vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts b/src/vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts index 725807575b4..ff3f2ef5297 100644 --- a/src/vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts +++ b/src/vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts @@ -188,7 +188,8 @@ class CustomTaskBuilder { command: this.commandBuilder.result, isBackground: false, promptOnClose: true, - problemMatchers: [] + problemMatchers: [], + hasDefinedMatchers: false }; } From 0dd96174c27270eb03b8d998c434db98cf66de37 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Mon, 28 May 2018 13:11:32 +0200 Subject: [PATCH 18/37] cleanup integrated terminal support for debugging --- .../mainThreadDebugService.ts | 35 +++- src/vs/workbench/api/node/extHost.protocol.ts | 2 + .../workbench/api/node/extHostDebugService.ts | 10 +- .../debug/electron-browser/terminalSupport.ts | 191 +++--------------- .../workbench/parts/debug/node/terminals.ts | 158 +++++++++++++++ 5 files changed, 229 insertions(+), 167 deletions(-) diff --git a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts index af2d76abc49..8c8d095db84 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts @@ -6,7 +6,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import uri from 'vs/base/common/uri'; -import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IAdapterExecutable, ITerminalSettings, IDebugAdapter, IDebugAdapterProvider } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IAdapterExecutable, ITerminalSettings, IDebugAdapter, IDebugAdapterProvider, ITerminalLauncher } from 'vs/workbench/parts/debug/common/debug'; import { TPromise } from 'vs/base/common/winjs.base'; import { ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext, @@ -18,6 +18,8 @@ import { AbstractDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter import * as paths from 'vs/base/common/paths'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { convertToVSCPaths, convertToDAPaths } from 'vs/workbench/parts/debug/common/debugUtils'; +import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal'; +import { AbstractTerminalLauncher } from 'vs/workbench/parts/debug/electron-browser/terminalSupport'; @extHostNamedCustomer(MainContext.MainThreadDebugService) @@ -28,11 +30,13 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb private _breakpointEventsActive: boolean; private _debugAdapters: Map; private _debugAdaptersHandleCounter = 1; + private _terminalLauncher: ITerminalLauncher; constructor( extHostContext: IExtHostContext, - @IDebugService private debugService: IDebugService + @IDebugService private debugService: IDebugService, + @ITerminalService private terminalService: ITerminalService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDebugService); this._toDispose = []; @@ -73,7 +77,10 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb } runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { - return this._proxy.$runInTerminal(args, config); + if (!this._terminalLauncher) { + this._terminalLauncher = new ExtensionTerminalLauncher(this.terminalService, this._proxy); + } + return this._terminalLauncher.runInTerminal(args, config); } public dispose(): void { @@ -295,3 +302,25 @@ class ExtensionHostDebugAdapter extends AbstractDebugAdapter { return this._proxy.$stopDASession(this._handle); } } + +export class ExtensionTerminalLauncher extends AbstractTerminalLauncher { + + constructor( + @ITerminalService terminalService: ITerminalService, + private _proxy: ExtHostDebugServiceShape + ) { + super(terminalService); + } + + protected runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { + return this._proxy.$runInTerminal(args, config); + } + + protected isBusy(processId: number): TPromise { + return this._proxy.$isTerminalBusy(processId); + } + + protected prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { + return this._proxy.$prepareCommandForTerminal(args, config); + } +} diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 80202809e70..4f757687281 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -831,6 +831,8 @@ export interface ISourceMultiBreakpointDto { export interface ExtHostDebugServiceShape { $substituteVariables(folder: UriComponents | undefined, config: IConfig): TPromise; $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise; + $isTerminalBusy(processId: number): TPromise; + $prepareCommandForTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise; $startDASession(handle: number, debugType: string, adapterExecutableInfo: IAdapterExecutable | null, debugPort: number): TPromise; $stopDASession(handle: number): TPromise; $sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): TPromise; diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 5dfa0140e6a..a16c1525a5e 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -22,7 +22,7 @@ import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors'; import { IAdapterExecutable, ITerminalSettings, IDebuggerContribution, IConfig, IDebugAdapter } from 'vs/workbench/parts/debug/common/debug'; -import { getTerminalLauncher } from 'vs/workbench/parts/debug/node/terminals'; +import { getTerminalLauncher, hasChildprocesses, prepareCommand } from 'vs/workbench/parts/debug/node/terminals'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { VariableResolver } from 'vs/workbench/services/configurationResolver/node/variableResolver'; import { IStringDictionary } from 'vs/base/common/collections'; @@ -125,6 +125,14 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return void 0; } + public $isTerminalBusy(processId: number): TPromise { + return asWinJsPromise(token => hasChildprocesses(processId)); + } + + public $prepareCommandForTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { + return asWinJsPromise(token => prepareCommand(args, config)); + } + public $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): TPromise { if (!this._variableResolver) { this._variableResolver = new ExtHostVariableResolverService(this._workspace, this._editorsService, this._configurationService); diff --git a/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts b/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts index d6901928791..e9abd1de5f1 100644 --- a/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts +++ b/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts @@ -4,31 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as platform from 'vs/base/common/platform'; -import * as cp from 'child_process'; import { IDisposable } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { ITerminalService, ITerminalInstance } from 'vs/workbench/parts/terminal/common/terminal'; import { ITerminalService as IExternalTerminalService } from 'vs/workbench/parts/execution/common/execution'; import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug'; +import { hasChildprocesses, prepareCommand } from 'vs/workbench/parts/debug/node/terminals'; -const enum ShellType { cmd, powershell, bash } - -export class TerminalLauncher implements ITerminalLauncher { +export class AbstractTerminalLauncher implements ITerminalLauncher { private integratedTerminalInstance: ITerminalInstance; private terminalDisposedListener: IDisposable; - constructor( - @ITerminalService private terminalService: ITerminalService, - @IExternalTerminalService private nativeTerminalService: IExternalTerminalService - ) { + constructor(private terminalService: ITerminalService) { } - runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { + async runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { if (args.kind === 'external') { - return this.nativeTerminalService.runInTerminal(args.title, args.cwd, args.args, args.env || {}); + return this.runInExternalTerminal(args, config); } if (!this.terminalDisposedListener) { @@ -41,14 +35,14 @@ export class TerminalLauncher implements ITerminalLauncher { } let t = this.integratedTerminalInstance; - if ((t && this.isBusy(t)) || !t) { + if ((t && await this.isBusy(t.processId)) || !t) { t = this.terminalService.createTerminal({ name: args.title || nls.localize('debug.terminal.title', "debuggee") }); this.integratedTerminalInstance = t; } this.terminalService.setActiveInstance(t); this.terminalService.showPanel(true); - const command = this.prepareCommand(args, config); + const command = await this.prepareCommand(args, config); return new TPromise((resolve, error) => { setTimeout(_ => { @@ -58,158 +52,29 @@ export class TerminalLauncher implements ITerminalLauncher { }); } - private isBusy(t: ITerminalInstance): boolean { - if (t.processId) { - try { - // if shell has at least one child process, assume that shell is busy - if (platform.isWindows) { - const result = cp.spawnSync('wmic', ['process', 'get', 'ParentProcessId']); - if (result.stdout) { - const pids = result.stdout.toString().split('\r\n'); - if (!pids.some(p => parseInt(p) === t.processId)) { - return false; - } - } - } else { - const result = cp.spawnSync('/usr/bin/pgrep', ['-lP', String(t.processId)]); - if (result.stdout) { - const r = result.stdout.toString().trim(); - if (r.length === 0 || r.indexOf(' tmux') >= 0) { // ignore 'tmux'; see #43683 - return false; - } - } - } - } - catch (e) { - // silently ignore - } - } - // fall back to safe side - return true; + protected runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { + return void 0; } - private prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): string { + protected isBusy(processId: number): TPromise { + return TPromise.as(hasChildprocesses(processId)); + } - let shellType: ShellType; - - // get the shell configuration for the current platform - let shell: string; - const shell_config = config.integrated.shell; - if (platform.isWindows) { - shell = shell_config.windows; - shellType = ShellType.cmd; - } else if (platform.isLinux) { - shell = shell_config.linux; - shellType = ShellType.bash; - } else if (platform.isMacintosh) { - shell = shell_config.osx; - shellType = ShellType.bash; - } - - // try to determine the shell type - shell = shell.trim().toLowerCase(); - if (shell.indexOf('powershell') >= 0) { - shellType = ShellType.powershell; - } else if (shell.indexOf('cmd.exe') >= 0) { - shellType = ShellType.cmd; - } else if (shell.indexOf('bash') >= 0) { - shellType = ShellType.bash; - } else if (shell.indexOf('git\\bin\\bash.exe') >= 0) { - shellType = ShellType.bash; - } - - let quote: (s: string) => string; - let command = ''; - - switch (shellType) { - - case ShellType.powershell: - - quote = (s: string) => { - s = s.replace(/\'/g, '\'\''); - return `'${s}'`; - //return s.indexOf(' ') >= 0 || s.indexOf('\'') >= 0 || s.indexOf('"') >= 0 ? `'${s}'` : s; - }; - - if (args.cwd) { - command += `cd '${args.cwd}'; `; - } - if (args.env) { - for (let key in args.env) { - const value = args.env[key]; - if (value === null) { - command += `Remove-Item env:${key}; `; - } else { - command += `\${env:${key}}='${value}'; `; - } - } - } - if (args.args && args.args.length > 0) { - const cmd = quote(args.args.shift()); - command += (cmd[0] === '\'') ? `& ${cmd} ` : `${cmd} `; - for (let a of args.args) { - command += `${quote(a)} `; - } - } - break; - - case ShellType.cmd: - - quote = (s: string) => { - s = s.replace(/\"/g, '""'); - return (s.indexOf(' ') >= 0 || s.indexOf('"') >= 0) ? `"${s}"` : s; - }; - - if (args.cwd) { - command += `cd ${quote(args.cwd)} && `; - } - if (args.env) { - command += 'cmd /C "'; - for (let key in args.env) { - const value = args.env[key]; - if (value === null) { - command += `set "${key}=" && `; - } else { - command += `set "${key}=${args.env[key]}" && `; - } - } - } - for (let a of args.args) { - command += `${quote(a)} `; - } - if (args.env) { - command += '"'; - } - break; - - case ShellType.bash: - - quote = (s: string) => { - s = s.replace(/\"/g, '\\"'); - return (s.indexOf(' ') >= 0 || s.indexOf('\\') >= 0) ? `"${s}"` : s; - }; - - if (args.cwd) { - command += `cd ${quote(args.cwd)} ; `; - } - if (args.env) { - command += 'env'; - for (let key in args.env) { - const value = args.env[key]; - if (value === null) { - command += ` -u "${key}"`; - } else { - command += ` "${key}=${value}"`; - } - } - command += ' '; - } - for (let a of args.args) { - command += `${quote(a)} `; - } - break; - } - - return command; + protected prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { + return TPromise.as(prepareCommand(args, config)); + } +} + +export class TerminalLauncher extends AbstractTerminalLauncher { + + constructor( + @ITerminalService terminalService: ITerminalService, + @IExternalTerminalService private nativeTerminalService: IExternalTerminalService + ) { + super(terminalService); + } + + runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { + return this.nativeTerminalService.runInTerminal(args.title, args.cwd, args.args, args.env || {}); } } diff --git a/src/vs/workbench/parts/debug/node/terminals.ts b/src/vs/workbench/parts/debug/node/terminals.ts index c0cfca5db35..a46ea642248 100644 --- a/src/vs/workbench/parts/debug/node/terminals.ts +++ b/src/vs/workbench/parts/debug/node/terminals.ts @@ -258,3 +258,161 @@ function quote(args: string[]): string { } return r; } + + +export function hasChildprocesses(processId: number): boolean { + if (processId) { + try { + // if shell has at least one child process, assume that shell is busy + if (env.isWindows) { + const result = cp.spawnSync('wmic', ['process', 'get', 'ParentProcessId']); + if (result.stdout) { + const pids = result.stdout.toString().split('\r\n'); + if (!pids.some(p => parseInt(p) === processId)) { + return false; + } + } + } else { + const result = cp.spawnSync('/usr/bin/pgrep', ['-lP', String(processId)]); + if (result.stdout) { + const r = result.stdout.toString().trim(); + if (r.length === 0 || r.indexOf(' tmux') >= 0) { // ignore 'tmux'; see #43683 + return false; + } + } + } + } + catch (e) { + // silently ignore + } + } + // fall back to safe side + return true; +} + +const enum ShellType { cmd, powershell, bash } + +export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): string { + + let shellType: ShellType; + + // get the shell configuration for the current platform + let shell: string; + const shell_config = config.integrated.shell; + if (env.isWindows) { + shell = shell_config.windows; + shellType = ShellType.cmd; + } else if (env.isLinux) { + shell = shell_config.linux; + shellType = ShellType.bash; + } else if (env.isMacintosh) { + shell = shell_config.osx; + shellType = ShellType.bash; + } + + // try to determine the shell type + shell = shell.trim().toLowerCase(); + if (shell.indexOf('powershell') >= 0) { + shellType = ShellType.powershell; + } else if (shell.indexOf('cmd.exe') >= 0) { + shellType = ShellType.cmd; + } else if (shell.indexOf('bash') >= 0) { + shellType = ShellType.bash; + } else if (shell.indexOf('git\\bin\\bash.exe') >= 0) { + shellType = ShellType.bash; + } + + let quote: (s: string) => string; + let command = ''; + + switch (shellType) { + + case ShellType.powershell: + + quote = (s: string) => { + s = s.replace(/\'/g, '\'\''); + return `'${s}'`; + //return s.indexOf(' ') >= 0 || s.indexOf('\'') >= 0 || s.indexOf('"') >= 0 ? `'${s}'` : s; + }; + + if (args.cwd) { + command += `cd '${args.cwd}'; `; + } + if (args.env) { + for (let key in args.env) { + const value = args.env[key]; + if (value === null) { + command += `Remove-Item env:${key}; `; + } else { + command += `\${env:${key}}='${value}'; `; + } + } + } + if (args.args && args.args.length > 0) { + const cmd = quote(args.args.shift()); + command += (cmd[0] === '\'') ? `& ${cmd} ` : `${cmd} `; + for (let a of args.args) { + command += `${quote(a)} `; + } + } + break; + + case ShellType.cmd: + + quote = (s: string) => { + s = s.replace(/\"/g, '""'); + return (s.indexOf(' ') >= 0 || s.indexOf('"') >= 0) ? `"${s}"` : s; + }; + + if (args.cwd) { + command += `cd ${quote(args.cwd)} && `; + } + if (args.env) { + command += 'cmd /C "'; + for (let key in args.env) { + const value = args.env[key]; + if (value === null) { + command += `set "${key}=" && `; + } else { + command += `set "${key}=${args.env[key]}" && `; + } + } + } + for (let a of args.args) { + command += `${quote(a)} `; + } + if (args.env) { + command += '"'; + } + break; + + case ShellType.bash: + + quote = (s: string) => { + s = s.replace(/\"/g, '\\"'); + return (s.indexOf(' ') >= 0 || s.indexOf('\\') >= 0) ? `"${s}"` : s; + }; + + if (args.cwd) { + command += `cd ${quote(args.cwd)} ; `; + } + if (args.env) { + command += 'env'; + for (let key in args.env) { + const value = args.env[key]; + if (value === null) { + command += ` -u "${key}"`; + } else { + command += ` "${key}=${value}"`; + } + } + command += ' '; + } + for (let a of args.args) { + command += `${quote(a)} `; + } + break; + } + + return command; +} \ No newline at end of file From 19f2507f552b0f9682ae1600374fa20ec783b036 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 28 May 2018 14:33:57 +0200 Subject: [PATCH 19/37] #45663 Use core translations asset --- .../common/extensionManagement.ts | 2 + .../node/extensionGalleryService.ts | 20 ++ .../localizations.contribution.ts | 192 +++++++++--------- 3 files changed, 118 insertions(+), 96 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 8a6261c70e6..27024e977b9 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -149,6 +149,7 @@ export interface IGalleryExtensionAssets { icon: IGalleryExtensionAsset; license: IGalleryExtensionAsset; repository: IGalleryExtensionAsset; + coreTranslations: { [languageId: string]: IGalleryExtensionAsset }; } export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifier { @@ -262,6 +263,7 @@ export interface IExtensionGalleryService { getReadme(extension: IGalleryExtension): TPromise; getManifest(extension: IGalleryExtension): TPromise; getChangelog(extension: IGalleryExtension): TPromise; + getCoreTranslations(extension: IGalleryExtension, languageId: string): TPromise<{}>; loadCompatibleVersion(extension: IGalleryExtension): TPromise; loadAllDependencies(dependencies: IExtensionIdentifier[]): TPromise; getExtensionsReport(): TPromise; diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index b212007836e..767d01ad51d 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -201,6 +201,15 @@ function getStatistic(statistics: IRawGalleryExtensionStatistics[], name: string return result ? result.value : 0; } +function getCoreTranslationAssets(version: IRawGalleryExtensionVersion): { [languageId: string]: IGalleryExtensionAsset } { + const coreTranslationAssetPrefix = 'Microsoft.VisualStudio.Code.Translation.'; + const result = version.files.filter(f => f.assetType.indexOf(coreTranslationAssetPrefix) === 0); + return result.reduce((result, file) => { + result[file.assetType.substring(coreTranslationAssetPrefix.length)] = getVersionAsset(version, file.assetType); + return result; + }, {}); +} + function getVersionAsset(version: IRawGalleryExtensionVersion, type: string): IGalleryExtensionAsset { const result = version.files.filter(f => f.assetType === type)[0]; @@ -278,6 +287,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUr icon: getVersionAsset(version, AssetType.Icon), license: getVersionAsset(version, AssetType.License), repository: getVersionAsset(version, AssetType.Repository), + coreTranslations: getCoreTranslationAssets(version) }; return { @@ -516,6 +526,16 @@ export class ExtensionGalleryService implements IExtensionGalleryService { .then(JSON.parse); } + getCoreTranslations(extension: IGalleryExtension, languageId: string): TPromise<{}> { + const asset = extension.assets.coreTranslations[languageId]; + if (asset) { + return this.getAsset(asset) + .then(asText) + .then(JSON.parse); + } + return TPromise.as(null); + } + getChangelog(extension: IGalleryExtension): TPromise { return this.getAsset(extension.assets.changelog) .then(asText); diff --git a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts index 6bc4493acf9..4f37b2eda19 100644 --- a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts +++ b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts @@ -96,31 +96,39 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo } } + private migrateToMarketplaceLanguagePack(language: string): void { + this.isLanguageInstalled(language) + .then(installed => { + if (!installed) { + this.getLanguagePackExtension(language) + .then(extension => { + if (extension) { + this.notificationService.prompt(Severity.Warning, localize('install language pack', "In the near future, VS Code will only support language packs in the form of Marketplace extensions. Please install the '{0}' extension in order to continue to use the currently configured language. ", extension.displayName || extension.displayName), + [ + { label: localize('install', "Install"), run: () => this.installExtension(extension) }, + { label: localize('more information', "More Information..."), run: () => window.open('https://go.microsoft.com/fwlink/?linkid=872941') } + ]); + } + }); + } + }); + } + private checkAndInstall(): void { const language = platform.language; const locale = platform.locale; - if (language !== 'en' && language !== 'en_us') { - this.isLanguageInstalled(language) - .then(installed => { - if (!installed) { - this.getLanguagePackExtension(language) - .then(extension => { - if (extension) { - this.notificationService.prompt(Severity.Warning, localize('install language pack', "In the near future, VS Code will only support language packs in the form of Marketplace extensions. Please install the '{0}' extension in order to continue to use the currently configured language. ", extension.displayName || extension.displayName), - [ - { label: localize('install', "Install"), run: () => this.installExtension(extension) }, - { label: localize('more information', "More Information..."), run: () => window.open('https://go.microsoft.com/fwlink/?linkid=872941') } - ]); - } - }); - } - }); + const languagePackSuggestionIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/languagePackSuggestionIgnore', StorageScope.GLOBAL, '[]')); + + if (!this.galleryService.isEnabled()) { + return; + } + if (language !== 'en' && language !== 'en_us') { + this.migrateToMarketplaceLanguagePack(language); + return; + } + if (locale === 'en' || locale === 'en_us') { return; } - - const languagePackSuggestionIgnoreList = JSON.parse(this.storageService.get - ('extensionsAssistant/languagePackSuggestionIgnore', StorageScope.GLOBAL, '[]')); - if (language === locale || languagePackSuggestionIgnoreList.indexOf(language) > -1) { return; } @@ -146,86 +154,78 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo return; } - this.galleryService.getManifest(extensionToFetchTranslationsFrom).then(x => { - if (!x.contributes || !x.contributes.localizations) { - return; - } - const locContribution = x.contributes.localizations.filter(x => x.languageId.toLowerCase() === locale)[0]; - if (!locContribution) { - return; - } + this.galleryService.getCoreTranslations(extensionToFetchTranslationsFrom, locale) + .then(coreTranslation => { + const translations = { + ...minimumTranslatedStrings, + ...(coreTranslation ? coreTranslation['vs/platform/node/minimalTranslations'] : {}) + }; + const logUserReaction = (userReaction: string) => { + /* __GDPR__ + "languagePackSuggestion:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "language": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction, language }); + }; - const translations = { - ...minimumTranslatedStrings, - ...(locContribution.minimalTranslations || {}) - }; - - const logUserReaction = (userReaction: string) => { - /* __GDPR__ - "languagePackSuggestion:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "language": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction, language }); - }; - - const searchAction = { - label: translations['searchMarketplace'], - run: () => { - logUserReaction('search'); - this.viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search(`tag:lp-${locale}`); - viewlet.focus(); - }); - } - }; - - const installAction = { - label: translations['install'], - run: () => { - logUserReaction('install'); - this.installExtension(extensionToInstall); - } - }; - - const installAndRestartAction = { - label: translations['installAndRestart'], - run: () => { - logUserReaction('installAndRestart'); - this.installExtension(extensionToInstall).then(() => this.windowsService.relaunch({})); - } - }; - - const mainActions = extensionToInstall ? [installAndRestartAction, installAction] : [searchAction]; - const promptMessage = translations[extensionToInstall ? 'installAndRestartMessage' : 'showLanguagePackExtensions'] - .replace('{0}', locContribution.languageNameLocalized || locContribution.languageName || locale); - - this.notificationService.prompt( - Severity.Info, - promptMessage, - [...mainActions, - { - label: localize('neverAgain', "Don't Show Again"), - isSecondary: true, + const searchAction = { + label: translations['searchMarketplace'], run: () => { - languagePackSuggestionIgnoreList.push(language); - this.storageService.store( - 'extensionsAssistant/languagePackSuggestionIgnore', - JSON.stringify(languagePackSuggestionIgnoreList), - StorageScope.GLOBAL - ); - logUserReaction('neverShowAgain'); + logUserReaction('search'); + this.viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true) + .then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => { + viewlet.search(`tag:lp-${locale}`); + viewlet.focus(); + }); } - }], - () => { - logUserReaction('cancelled'); - } - ); + }; - }); + const installAction = { + label: translations['install'], + run: () => { + logUserReaction('install'); + this.installExtension(extensionToInstall); + } + }; + + const installAndRestartAction = { + label: translations['installAndRestart'], + run: () => { + logUserReaction('installAndRestart'); + this.installExtension(extensionToInstall).then(() => this.windowsService.relaunch({})); + } + }; + + const mainActions = extensionToInstall ? [installAndRestartAction, installAction] : [searchAction]; + const promptMessage = translations[extensionToInstall ? 'installAndRestartMessage' : 'showLanguagePackExtensions'] + .replace('{0}', locale); + + this.notificationService.prompt( + Severity.Info, + promptMessage, + [...mainActions, + { + label: localize('neverAgain', "Don't Show Again"), + isSecondary: true, + run: () => { + languagePackSuggestionIgnoreList.push(language); + this.storageService.store( + 'extensionsAssistant/languagePackSuggestionIgnore', + JSON.stringify(languagePackSuggestionIgnoreList), + StorageScope.GLOBAL + ); + logUserReaction('neverShowAgain'); + } + }], + () => { + logUserReaction('cancelled'); + } + ); + + }); }); }); From 83cb1d7799a8d3b37fded8258ecb4588d95dc8f0 Mon Sep 17 00:00:00 2001 From: Erich Gamma Date: Mon, 28 May 2018 14:41:08 +0200 Subject: [PATCH 20/37] Support to run npm install from a package node --- extensions/npm/package.json | 10 +++++++++- extensions/npm/package.nls.json | 3 ++- extensions/npm/src/npmView.ts | 19 ++++++++++++++++++- extensions/npm/src/tasks.ts | 2 +- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/extensions/npm/package.json b/extensions/npm/package.json index a57db1747a0..b86114ae816 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -63,6 +63,10 @@ "command": "npm.openScript", "title": "%command.openScript%" }, + { + "command": "npm.runInstall", + "title": "%command.runInstall%" + }, { "command": "npm.refresh", "title": "%command.refresh%", @@ -84,9 +88,13 @@ { "command": "npm.openScript", "when": "view == npm && viewItem == packageJSON", - "group": "navigation" + "group": "navigation@1" }, { + "command": "npm.runInstall", + "when": "view == npm && viewItem == packageJSON", + "group": "navigation@2" + }, { "command": "npm.openScript", "when": "view == npm && viewItem == script", "group": "navigation@1" diff --git a/extensions/npm/package.nls.json b/extensions/npm/package.nls.json index dedd7af6161..ccc6aa3114c 100644 --- a/extensions/npm/package.nls.json +++ b/extensions/npm/package.nls.json @@ -14,5 +14,6 @@ "command.refresh": "Refresh", "command.run": "Run", "command.debug": "Debug", - "command.openScript": "Open" + "command.openScript": "Open", + "command.runInstall": "Run Install" } diff --git a/extensions/npm/src/npmView.ts b/extensions/npm/src/npmView.ts index db2e086d50a..7fbbd73e7a0 100644 --- a/extensions/npm/src/npmView.ts +++ b/extensions/npm/src/npmView.ts @@ -11,7 +11,10 @@ import { WorkspaceFolder, commands, debug, window, workspace, Selection, TaskGroup } from 'vscode'; import { visit, JSONVisitor } from 'jsonc-parser'; -import { NpmTaskDefinition, getPackageJsonUriFromTask, getScripts, isWorkspaceFolder, getPackageManager, getTaskName } from './tasks'; +import { + NpmTaskDefinition, getPackageJsonUriFromTask, getScripts, + isWorkspaceFolder, getPackageManager, getTaskName, createTask +} from './tasks'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); @@ -133,6 +136,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { subscriptions.push(commands.registerCommand('npm.debugScript', this.debugScript, this)); subscriptions.push(commands.registerCommand('npm.openScript', this.openScript, this)); subscriptions.push(commands.registerCommand('npm.refresh', this.refresh, this)); + subscriptions.push(commands.registerCommand('npm.runInstall', this.runInstall, this)); } private scriptIsValid(scripts: any, task: Task): boolean { @@ -258,6 +262,19 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { return scriptOffset; } + + private async runInstall(selection: PackageJSON) { + let uri: Uri | undefined = undefined; + if (selection instanceof PackageJSON) { + uri = selection.resourceUri; + } + if (!uri) { + return; + } + let task = createTask('install', 'install', selection.folder.workspaceFolder, uri, []); + workspace.executeTask(task); + } + private async openScript(selection: PackageJSON | NpmScript) { let uri: Uri | undefined = undefined; if (selection instanceof PackageJSON) { diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index 0b99a008fea..63cd80a1409 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -205,7 +205,7 @@ export function getTaskName(script: string, relativePath: string | undefined) { return script; } -function createTask(script: string, cmd: string, folder: WorkspaceFolder, packageJsonUri: Uri, matcher?: any): Task { +export function createTask(script: string, cmd: string, folder: WorkspaceFolder, packageJsonUri: Uri, matcher?: any): Task { function getCommandLine(folder: WorkspaceFolder, cmd: string): string { let packageManager = getPackageManager(folder); From e48e7e4e069f56933805e09eb9c32b246670eac5 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 28 May 2018 15:32:18 +0200 Subject: [PATCH 21/37] remove todo --- .../parts/debug/test/browser/debugANSIHandling.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/debug/test/browser/debugANSIHandling.test.ts b/src/vs/workbench/parts/debug/test/browser/debugANSIHandling.test.ts index 33ff66b0077..2b2cf4f3297 100644 --- a/src/vs/workbench/parts/debug/test/browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/parts/debug/test/browser/debugANSIHandling.test.ts @@ -23,8 +23,6 @@ suite('Debug - ANSI Handling', () => { linkDetector = instantiationService.createInstance(LinkDetector); }); - // todo: function here - test('appendStylizedStringToContainer', () => { const root: HTMLSpanElement = document.createElement('span'); let child: Node; @@ -263,4 +261,4 @@ suite('Debug - ANSI Handling', () => { }); -}); \ No newline at end of file +}); From eef87dac6b9c7f436a8155ac7c8d1854cc6b5c4d Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 28 May 2018 15:32:41 +0200 Subject: [PATCH 22/37] do not append logLevel #47774 --- .../workbench/parts/debug/electron-browser/debugService.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 4aae9cda64a..e4fb277eb94 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -54,7 +54,6 @@ import { IAction, Action } from 'vs/base/common/actions'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { RunOnceScheduler } from 'vs/base/common/async'; import product from 'vs/platform/node/product'; -import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { deepClone, equals } from 'vs/base/common/objects'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; @@ -107,7 +106,6 @@ export class DebugService implements debug.IDebugService { @ITaskService private taskService: ITaskService, @IFileService private fileService: IFileService, @IConfigurationService private configurationService: IConfigurationService, - @ILogService private logService: ILogService ) { this.toDispose = []; this.toDisposeOnSessionEnd = new Map(); @@ -778,9 +776,6 @@ export class DebugService implements debug.IDebugService { if (noDebug) { config.noDebug = true; } - if (!config.logLevel) { - config.logLevel = LogLevel[this.logService.getLevel()].toLowerCase(); - } return (type ? TPromise.as(null) : this.configurationManager.guessDebugger().then(a => type = a && a.type)).then(() => this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config).then(config => { From 037b11d6db8c351dc9d73ab99e0d09e34f82d035 Mon Sep 17 00:00:00 2001 From: Krzysztof Cieslak Date: Tue, 22 May 2018 16:28:30 +0200 Subject: [PATCH 23/37] Implement Go To Next/Previous Breakpoint editor actions --- .../parts/debug/browser/debugEditorActions.ts | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts b/src/vs/workbench/parts/debug/browser/debugEditorActions.ts index 64d2b7d6852..3b00efec8ef 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorActions.ts @@ -14,6 +14,8 @@ import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_NOT_IN_DEBUG_REPL, CONTEX import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IEditorService } from 'vs/platform/editor/common/editor'; +import { Selection } from 'vs/editor/common/core/selection'; class ToggleBreakpointAction extends EditorAction { constructor() { @@ -209,6 +211,91 @@ class ShowDebugHoverAction extends EditorAction { } } +class GoToBreakpointAction extends EditorAction { + constructor(private isNext, opts) { + super(opts); + } + + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | TPromise { + const debugService = accessor.get(IDebugService); + const editorService = accessor.get(IEditorService); + const currentUri = editor.getModel().uri; + const currentLine = editor.getPosition().lineNumber; + const allEnabledBreakpoints = + debugService.getModel().getBreakpoints() + .filter(bp => bp.enabled) + .sort((a, b) => { + if (this.isNext) { + if (a.uri === b.uri) { + return a.lineNumber - b.lineNumber; + } + return a.uri.path.localeCompare(b.uri.path); + } + else { + if (a.uri === b.uri) { + return b.lineNumber - a.lineNumber; + } + return b.uri.path.localeCompare(a.uri.path); + } + }); + + //Try to find breakpoint in current file + let moveBreakpoint = + this.isNext + ? allEnabledBreakpoints.filter(bp => bp.uri.toString() === currentUri.toString() && bp.lineNumber > currentLine)[0] + : allEnabledBreakpoints.filter(bp => bp.uri.toString() === currentUri.toString() && bp.lineNumber < currentLine)[0]; + + //Try to find breakpoints in following files + if (!moveBreakpoint) { + moveBreakpoint = + this.isNext + ? allEnabledBreakpoints.filter(bp => bp.uri.toString() > currentUri.toString())[0] + : allEnabledBreakpoints.filter(bp => bp.uri.toString() < currentUri.toString())[0]; + } + + //Move to first possible breakpoint + if (!moveBreakpoint) { + moveBreakpoint = allEnabledBreakpoints[0]; + } + + if (moveBreakpoint) { + const selection = new Selection(moveBreakpoint.lineNumber, moveBreakpoint.column || 0, moveBreakpoint.lineNumber, moveBreakpoint.column || 0); + editorService.openEditor({ + resource: moveBreakpoint.uri, + options: { + pinned: false, + revealIfOpened: true, + revealInCenterIfOutsideViewport: true, + selection: selection + } + }); + } + return TPromise.as(null); + } +} + +class GoToNextBreakpointAction extends GoToBreakpointAction { + constructor() { + super(true, { + id: 'editor.debug.action.goToNextBreakpoint', + label: nls.localize('goToNextBreakpoint', "Debug: Go To Next Breakpoint"), + alias: 'Debug: Go To Next Breakpoint', + precondition: null + }); + } +} + +class GoToPreviousBreakpointAction extends GoToBreakpointAction { + constructor() { + super(false, { + id: 'editor.debug.action.goToPreviousBreakpoint', + label: nls.localize('goToPreviousBreakpoint', "Debug: Go To Previous Breakpoint"), + alias: 'Debug: Go To Previous Breakpoint', + precondition: null + }); + } +} + registerEditorAction(ToggleBreakpointAction); registerEditorAction(ConditionalBreakpointAction); registerEditorAction(LogPointAction); @@ -216,3 +303,5 @@ registerEditorAction(RunToCursorAction); registerEditorAction(SelectionToReplAction); registerEditorAction(SelectionToWatchExpressionsAction); registerEditorAction(ShowDebugHoverAction); +registerEditorAction(GoToNextBreakpointAction); +registerEditorAction(GoToPreviousBreakpointAction); From d810a0308e4e22df71f6958e8ccf2723ad0755cb Mon Sep 17 00:00:00 2001 From: Krzysztof Cieslak Date: Mon, 28 May 2018 14:36:18 +0100 Subject: [PATCH 24/37] Include enabledOnly in debug model interface --- src/vs/workbench/parts/debug/common/debug.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 5b1d47607f0..e9424de9aa2 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -311,7 +311,7 @@ export interface IViewModel extends ITreeElement { export interface IModel extends ITreeElement { getSessions(): ReadonlyArray; - getBreakpoints(filter?: { uri?: uri, lineNumber?: number, column?: number }): ReadonlyArray; + getBreakpoints(filter?: { uri?: uri, lineNumber?: number, column?: number, enabledOnly?: boolean }): ReadonlyArray; areBreakpointsActivated(): boolean; getFunctionBreakpoints(): ReadonlyArray; getExceptionBreakpoints(): ReadonlyArray; From 6d76ef8c48ab97e731dc847e9bf41fa5a7e533bc Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 28 May 2018 15:38:11 +0200 Subject: [PATCH 25/37] remove from schema #47774 --- src/vs/workbench/parts/debug/common/debug.ts | 1 - .../parts/debug/electron-browser/debug.contribution.ts | 5 ----- 2 files changed, 6 deletions(-) diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 5b1d47607f0..decf0b30def 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -369,7 +369,6 @@ export interface IEnvConfig { postDebugTask?: string; debugServer?: number; noDebug?: boolean; - logLevel?: string; } export interface IConfig extends IEnvConfig { diff --git a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts index ef5ced91b66..8df4ab9acde 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts @@ -215,11 +215,6 @@ configurationRegistry.registerConfiguration({ description: nls.localize({ comment: ['This is the description for a setting'], key: 'enableAllHovers' }, "Controls if the non debug hovers should be enabled while debugging. If true the hover providers will be called to provide a hover. Regular hovers will not be shown even if this setting is true."), default: false }, - 'debug.logLevel': { - enum: ['off', 'trace', 'debug', 'info', 'warning', 'error', 'critical'], - description: nls.localize({ comment: ['This is the description for a setting'], key: 'logLevel' }, "Controls what diagnostic output should the debug session produce."), - default: 'info' - }, 'launch': { type: 'object', description: nls.localize({ comment: ['This is the description for a setting'], key: 'launch' }, "Global debug launch configuration. Should be used as an alternative to 'launch.json' that is shared across workspaces"), From 736a5a178503259da80af29fab291e428d43baed Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 28 May 2018 15:38:15 +0200 Subject: [PATCH 26/37] #45663 fix compilations --- .../test/electron-browser/extensionsTipsService.test.ts | 5 ++++- .../test/electron-browser/extensionsWorkbenchService.test.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts index e99f9a52370..3f7af9a154c 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -70,6 +70,7 @@ const mockExtensionGallery: IGalleryExtension[] = [ icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' }, license: { uri: 'uri:license', fallbackUri: 'fallback:license' }, repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' }, + coreTranslations: {} }), aGalleryExtension('MockExtension2', { displayName: 'Mock Extension 2', @@ -91,6 +92,7 @@ const mockExtensionGallery: IGalleryExtension[] = [ icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' }, license: { uri: 'uri:license', fallbackUri: 'fallback:license' }, repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' }, + coreTranslations: {} }) ]; @@ -148,7 +150,8 @@ const noAssets: IGalleryExtensionAssets = { license: null, manifest: null, readme: null, - repository: null + repository: null, + coreTranslations: null }; function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: IGalleryExtensionAssets = noAssets): IGalleryExtension { diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 2789c6b4322..40c885f86e0 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -116,6 +116,7 @@ suite('ExtensionsWorkbenchService Test', () => { icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' }, license: { uri: 'uri:license', fallbackUri: 'fallback:license' }, repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' }, + coreTranslations: {} }); testObject = instantiationService.createInstance(ExtensionsWorkbenchService); @@ -265,6 +266,7 @@ suite('ExtensionsWorkbenchService Test', () => { icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' }, license: { uri: 'uri:license', fallbackUri: 'fallback:license' }, repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' }, + coreTranslations: {} }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local1, local2]); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery1)); @@ -1207,7 +1209,8 @@ suite('ExtensionsWorkbenchService Test', () => { license: null, manifest: null, readme: null, - repository: null + repository: null, + coreTranslations: null }; function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: IGalleryExtensionAssets = noAssets): IGalleryExtension { From f815ab3b46e969365d8c2a18abcad44ee8135c42 Mon Sep 17 00:00:00 2001 From: Krzysztof Cieslak Date: Mon, 28 May 2018 14:49:44 +0100 Subject: [PATCH 27/37] change openBreakpointSource to use IBreakpoint --- src/vs/workbench/parts/debug/browser/breakpointsView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/debug/browser/breakpointsView.ts b/src/vs/workbench/parts/debug/browser/breakpointsView.ts index ef9c7bd2194..2c4e4fd3aa0 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/parts/debug/browser/breakpointsView.ts @@ -519,7 +519,7 @@ class FunctionBreakpointInputRenderer implements IRenderer { +export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolean, preserveFocus: boolean, debugService: IDebugService, editorService: IEditorService): TPromise { if (breakpoint.uri.scheme === DEBUG_SCHEME && debugService.state === State.Inactive) { return TPromise.as(null); } From cabd09a56b6a6d783f01861ad062b65964bfab53 Mon Sep 17 00:00:00 2001 From: Krzysztof Cieslak Date: Mon, 28 May 2018 14:58:53 +0100 Subject: [PATCH 28/37] Simplify implementation --- .../parts/debug/browser/debugEditorActions.ts | 32 +++---------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts b/src/vs/workbench/parts/debug/browser/debugEditorActions.ts index 3b00efec8ef..8766052eb3e 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorActions.ts @@ -15,7 +15,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorService } from 'vs/platform/editor/common/editor'; -import { Selection } from 'vs/editor/common/core/selection'; +import { openBreakpointSource } from 'vs/workbench/parts/debug/browser/breakpointsView'; class ToggleBreakpointAction extends EditorAction { constructor() { @@ -221,23 +221,8 @@ class GoToBreakpointAction extends EditorAction { const editorService = accessor.get(IEditorService); const currentUri = editor.getModel().uri; const currentLine = editor.getPosition().lineNumber; - const allEnabledBreakpoints = - debugService.getModel().getBreakpoints() - .filter(bp => bp.enabled) - .sort((a, b) => { - if (this.isNext) { - if (a.uri === b.uri) { - return a.lineNumber - b.lineNumber; - } - return a.uri.path.localeCompare(b.uri.path); - } - else { - if (a.uri === b.uri) { - return b.lineNumber - a.lineNumber; - } - return b.uri.path.localeCompare(a.uri.path); - } - }); + //Breakpoints returned from `getBreakpoints` are already sorted. + const allEnabledBreakpoints = debugService.getModel().getBreakpoints({ enabledOnly: true }); //Try to find breakpoint in current file let moveBreakpoint = @@ -259,16 +244,7 @@ class GoToBreakpointAction extends EditorAction { } if (moveBreakpoint) { - const selection = new Selection(moveBreakpoint.lineNumber, moveBreakpoint.column || 0, moveBreakpoint.lineNumber, moveBreakpoint.column || 0); - editorService.openEditor({ - resource: moveBreakpoint.uri, - options: { - pinned: false, - revealIfOpened: true, - revealInCenterIfOutsideViewport: true, - selection: selection - } - }); + openBreakpointSource(moveBreakpoint, false, true, debugService, editorService); } return TPromise.as(null); } From 13cc56a713c25106b0202f33a8fdb667437f6bc2 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 28 May 2018 16:14:35 +0200 Subject: [PATCH 29/37] Fixes Microsoft/monaco-editor#891: Focus editor when returning from context menu --- src/vs/editor/contrib/codeAction/codeActionWidget.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/codeAction/codeActionWidget.ts b/src/vs/editor/contrib/codeAction/codeActionWidget.ts index d65b389d1ca..0020eca69a4 100644 --- a/src/vs/editor/contrib/codeAction/codeActionWidget.ts +++ b/src/vs/editor/contrib/codeAction/codeActionWidget.ts @@ -54,7 +54,10 @@ export class CodeActionContextMenu { return at; }, getActions: () => actions, - onHide: () => { this._visible = false; }, + onHide: () => { + this._visible = false; + this._editor.focus(); + }, autoSelectFirstItem: true }); } From b8a16129400262c22ed7008b2c7c27e69402fa08 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 28 May 2018 16:22:47 +0200 Subject: [PATCH 30/37] go to next / previosu breakpoint minor polish --- .../workbench/parts/debug/browser/debugEditorActions.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts b/src/vs/workbench/parts/debug/browser/debugEditorActions.ts index 8766052eb3e..95ba85099ba 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorActions.ts @@ -8,7 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { Range } from 'vs/editor/common/core/range'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ServicesAccessor, registerEditorAction, EditorAction } from 'vs/editor/browser/editorExtensions'; +import { ServicesAccessor, registerEditorAction, EditorAction, IActionOptions } from 'vs/editor/browser/editorExtensions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_NOT_IN_DEBUG_REPL, CONTEXT_DEBUG_STATE, State, REPL_ID, VIEWLET_ID, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint } from 'vs/workbench/parts/debug/common/debug'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -212,11 +212,11 @@ class ShowDebugHoverAction extends EditorAction { } class GoToBreakpointAction extends EditorAction { - constructor(private isNext, opts) { + constructor(private isNext: boolean, opts: IActionOptions) { super(opts); } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | TPromise { + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): TPromise { const debugService = accessor.get(IDebugService); const editorService = accessor.get(IEditorService); const currentUri = editor.getModel().uri; @@ -244,8 +244,9 @@ class GoToBreakpointAction extends EditorAction { } if (moveBreakpoint) { - openBreakpointSource(moveBreakpoint, false, true, debugService, editorService); + return openBreakpointSource(moveBreakpoint, false, true, debugService, editorService); } + return TPromise.as(null); } } From 7eff96ce02bf4c69e4f1aa8e264708046987ff4c Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 28 May 2018 16:40:44 +0200 Subject: [PATCH 31/37] Add Endgame for 1.24 --- .github/calendar.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/calendar.yml b/.github/calendar.yml index 3a5dad30159..d8adda455ea 100644 --- a/.github/calendar.yml +++ b/.github/calendar.yml @@ -19,4 +19,5 @@ '2018-05-08 12:00, US/Pacific': 'development', '2018-05-10 12:00, US/Pacific': 'release', # 1.23.1 '2018-05-15 12:00, US/Pacific': 'development', + '2018-05-28 18:00, US/Pacific': 'endgame', } From f60d780a2d442c2eb73b457bab64a858d4969d0c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 28 May 2018 17:08:31 +0200 Subject: [PATCH 32/37] Fix #49145 - Enable/Disable viewlets by views when context - Move viewlet enablement/disablement to views service - Fix - adopt if a viewlet extension is disabled/removed --- .../browser/viewsContainersExtensionPoint.ts | 9 ++-- .../parts/activitybar/activitybarPart.ts | 49 ++++++------------- .../parts/compositebar/compositeBar.ts | 6 +-- .../browser/parts/panel/panelPart.ts | 6 +-- .../browser/parts/views/contributableViews.ts | 2 +- .../parts/views/{customView.ts => views.ts} | 28 ++++++++++- src/vs/workbench/common/views.ts | 17 +++++-- .../workbench/electron-browser/workbench.ts | 15 +++--- .../browser/searchViewLocationUpdater.ts | 12 +++-- .../viewlet/browser/viewletService.ts | 13 +++-- 10 files changed, 92 insertions(+), 65 deletions(-) rename src/vs/workbench/browser/parts/views/{customView.ts => views.ts} (91%) diff --git a/src/vs/workbench/api/browser/viewsContainersExtensionPoint.ts b/src/vs/workbench/api/browser/viewsContainersExtensionPoint.ts index 4e9c8a347cb..bc5a30e6f42 100644 --- a/src/vs/workbench/api/browser/viewsContainersExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsContainersExtensionPoint.ts @@ -80,12 +80,11 @@ class ViewsContainersExtensionHandler implements IWorkbenchContribution { } private registerTestViewContainer(): void { - const id = 'test'; const title = localize('test', "Test"); - const cssClass = `extensionViewlet-${id}`; + const cssClass = `extensionViewlet-test`; const icon = require.toUrl('./media/test.svg'); - this.registerCustomViewlet({ id, title, icon }, TEST_VIEW_CONTAINER_ORDER, cssClass); + this.registerCustomViewlet({ id: ViewLocation.TEST.id, title, icon }, TEST_VIEW_CONTAINER_ORDER, cssClass); } private handleAndRegisterCustomViewContainers() { @@ -139,13 +138,13 @@ class ViewsContainersExtensionHandler implements IWorkbenchContribution { const cssClass = `extensionViewlet-${descriptor.id}`; // TODO@extensionLocation const icon = join(extension.extensionLocation.fsPath, descriptor.icon); - this.registerCustomViewlet({ id: descriptor.id, title: descriptor.title, icon }, TEST_VIEW_CONTAINER_ORDER + index + 1, cssClass); + this.registerCustomViewlet({ id: `workbench.view.extension.${descriptor.id}`, title: descriptor.title, icon }, TEST_VIEW_CONTAINER_ORDER + index + 1, cssClass); }); } private registerCustomViewlet(descriptor: IUserFriendlyViewsContainerDescriptor, order: number, cssClass: string): void { const viewletRegistry = Registry.as(ViewletExtensions.Viewlets); - const id = `workbench.view.extension.${descriptor.id}`; + const id = descriptor.id; if (!viewletRegistry.getViewlet(id)) { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 338c2a65dee..3bd54b0fd57 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -25,7 +25,6 @@ import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar'; import { ToggleCompositePinnedAction, ICompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBarActions'; -import { ViewLocation, ViewsRegistry } from 'vs/workbench/common/views'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Dimension, createCSSRule } from 'vs/base/browser/dom'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -57,7 +56,6 @@ export class ActivitybarPart extends Part { private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; }; private placeholderComposites: IPlaceholderComposite[] = []; - private extensionsRegistrationCompleted: boolean = false; private compositeBar: CompositeBar; private compositeActions: { [compositeId: string]: { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } }; @@ -101,7 +99,6 @@ export class ActivitybarPart extends Part { } private onDidRegisterExtensions(): void { - this.extensionsRegistrationCompleted = true; this.removeNotExistingPlaceholderComposites(); this.updateCompositebar(); } @@ -109,8 +106,6 @@ export class ActivitybarPart extends Part { private registerListeners(): void { this.toUnbind.push(this.viewletService.onDidViewletRegister(() => this.updateCompositebar())); - this.toUnbind.push(ViewsRegistry.onViewsRegistered(() => this.updateCompositebar())); - this.toUnbind.push(ViewsRegistry.onViewsDeregistered(() => this.updateCompositebar())); // Activate viewlet action on opening of a viewlet this.toUnbind.push(this.viewletService.onDidViewletOpen(viewlet => this.compositeBar.activateComposite(viewlet.getId()))); @@ -119,7 +114,7 @@ export class ActivitybarPart extends Part { this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.compositeBar.deactivateComposite(viewlet.getId()))); this.toUnbind.push(this.viewletService.onDidViewletEnablementChange(({ id, enabled }) => { if (enabled) { - this.compositeBar.addComposite(this.viewletService.getViewlet(id), true); + this.compositeBar.addComposite(this.viewletService.getViewlet(id)); } else { this.removeComposite(id); } @@ -226,26 +221,18 @@ export class ActivitybarPart extends Part { private updateCompositebar(): void { const viewlets = this.viewletService.getViewlets(); for (const viewlet of viewlets) { - const hasPlaceholder = this.placeholderComposites.some(c => c.id === viewlet.id); + this.compositeBar.addComposite(viewlet); - // Add the composite if it has views registered or - // If it was existing before and the extensions registration is not yet completed. - if (this.hasRegisteredViews(viewlet) - || (hasPlaceholder && !this.extensionsRegistrationCompleted) - ) { - this.compositeBar.addComposite(viewlet, false); - // Pin it by default if it is new => it does not has a placeholder - if (!hasPlaceholder) { - this.compositeBar.pin(viewlet.id); - } - this.enableCompositeActions(viewlet); - const activeViewlet = this.viewletService.getActiveViewlet(); - if (activeViewlet && activeViewlet.getId() === viewlet.id) { - this.compositeBar.pin(viewlet.id); - this.compositeBar.activateComposite(viewlet.id); - } - } else { - this.removeComposite(viewlet.id); + // Pin it by default if it is new => it does not has a placeholder + if (this.placeholderComposites.every(c => c.id !== viewlet.id)) { + this.compositeBar.pin(viewlet.id); + } + + this.enableCompositeActions(viewlet); + const activeViewlet = this.viewletService.getActiveViewlet(); + if (activeViewlet && activeViewlet.getId() === viewlet.id) { + this.compositeBar.pin(viewlet.id); + this.compositeBar.activateComposite(viewlet.id); } } } @@ -254,7 +241,7 @@ export class ActivitybarPart extends Part { const viewlets = this.viewletService.getViewlets(); for (const { id } of this.placeholderComposites) { if (viewlets.every(viewlet => viewlet.id !== id)) { - this.compositeBar.addComposite({ id, name: id, order: void 0 }, false); + this.compositeBar.addComposite({ id, name: id, order: void 0 }); } } } @@ -288,14 +275,6 @@ export class ActivitybarPart extends Part { } } - private hasRegisteredViews(viewlet: ViewletDescriptor): boolean { - const viewLocation = ViewLocation.get(viewlet.id); - if (viewLocation) { - return ViewsRegistry.getViews(viewLocation).length > 0; - } - return true; - } - public getPinned(): string[] { return this.viewletService.getViewlets().map(v => v.id).filter(id => this.compositeBar.isPinned(id)); } @@ -324,7 +303,7 @@ export class ActivitybarPart extends Part { } public shutdown(): void { - const state = this.viewletService.getViewlets().filter(viewlet => this.hasRegisteredViews(viewlet)).map(viewlet => ({ id: viewlet.id, iconUrl: viewlet.iconUrl })); + const state = this.viewletService.getViewlets().filter(viewlet => this.compositeBar.isPinned(viewlet.id)).map(viewlet => ({ id: viewlet.id, iconUrl: viewlet.iconUrl })); this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEWLETS, JSON.stringify(state), StorageScope.GLOBAL); super.shutdown(); } diff --git a/src/vs/workbench/browser/parts/compositebar/compositeBar.ts b/src/vs/workbench/browser/parts/compositebar/compositeBar.ts index e9e71d81bf0..54b7a6c74e0 100644 --- a/src/vs/workbench/browser/parts/compositebar/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositebar/compositeBar.ts @@ -118,7 +118,7 @@ export class CompositeBar extends Widget implements ICompositeBar { this.updateCompositeSwitcher(); } - public addComposite({ id, name, order }: { id: string; name: string, order: number }, open: boolean): void { + public addComposite({ id, name, order }: { id: string; name: string, order: number }): void { const state = this.storedState.filter(s => s.id === id)[0]; const pinned = state ? state.pinned : true; let index = order >= 0 ? order : this.model.items.length; @@ -139,8 +139,8 @@ export class CompositeBar extends Widget implements ICompositeBar { // Add to the model if (this.model.add(id, name, order, index)) { this.computeSizes([this.model.findItem(id)]); - if (pinned || open) { - this.pin(id, open); + if (pinned) { + this.pin(id); } else { this.updateCompositeSwitcher(); } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 06578335499..68f224cc687 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -105,7 +105,7 @@ export class PanelPart extends CompositePart implements IPanelService { }); this.toUnbind.push(this.compositeBar); for (const panel of this.getPanels()) { - this.compositeBar.addComposite(panel, false); + this.compositeBar.addComposite(panel); } this.activePanelContextKey = ActivePanelContext.bindTo(contextKeyService); this.onDidPanelOpen(this._onDidPanelOpen, this, this.disposables); @@ -116,7 +116,7 @@ export class PanelPart extends CompositePart implements IPanelService { private registerListeners(): void { - this.toUnbind.push(this.registry.onDidRegister(panelDescriptor => this.compositeBar.addComposite(panelDescriptor, false))); + this.toUnbind.push(this.registry.onDidRegister(panelDescriptor => this.compositeBar.addComposite(panelDescriptor))); // Activate panel action on opening of a panel this.toUnbind.push(this.onDidPanelOpen(panel => { @@ -198,7 +198,7 @@ export class PanelPart extends CompositePart implements IPanelService { if (descriptor && descriptor.enabled !== enabled) { descriptor.enabled = enabled; if (enabled) { - this.compositeBar.addComposite(descriptor, true); + this.compositeBar.addComposite(descriptor); } else { this.removeComposite(id); } diff --git a/src/vs/workbench/browser/parts/views/contributableViews.ts b/src/vs/workbench/browser/parts/views/contributableViews.ts index 42c9ea86328..d1b0d8a3830 100644 --- a/src/vs/workbench/browser/parts/views/contributableViews.ts +++ b/src/vs/workbench/browser/parts/views/contributableViews.ts @@ -55,7 +55,7 @@ export interface IViewItem { active: boolean; } -class ViewDescriptorCollection { +export class ViewDescriptorCollection { private contextKeys = new CounterSet(); private items: IViewItem[] = []; diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/views.ts similarity index 91% rename from src/vs/workbench/browser/parts/views/customView.ts rename to src/vs/workbench/browser/parts/views/views.ts index 40431d62f5e..2e127abb4b0 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -33,8 +33,13 @@ import { FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { Emitter, Event } from 'vs/base/common/event'; +import { ViewDescriptorCollection } from './contributableViews'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { ViewletRegistry, Extensions as ViewletExtensions } from 'vs/workbench/browser/viewlet'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -export class CustomViewsService extends Disposable implements IViewsService { +export class ViewsService extends Disposable implements IViewsService { _serviceBrand: any; @@ -42,9 +47,16 @@ export class CustomViewsService extends Disposable implements IViewsService { constructor( @IInstantiationService private instantiationService: IInstantiationService, - @IViewletService private viewletService: IViewletService + @ILifecycleService private lifecycleService: ILifecycleService, + @IViewletService private viewletService: IViewletService, + @IStorageService private storageService: IStorageService ) { super(); + + ViewLocation.all.forEach(viewLocation => this.onDidRegisterViewLocation(viewLocation)); + this._register(ViewLocation.onDidRegister(viewLocation => this.onDidRegisterViewLocation(viewLocation))); + this._register(Registry.as(ViewletExtensions.Viewlets).onDidRegister(viewlet => this.viewletService.setViewletEnablement(viewlet.id, this.storageService.getBoolean(`viewservice.${viewlet.id}.enablement`, StorageScope.GLOBAL, viewlet.id !== ViewLocation.TEST.id)))); + this.createViewers(ViewsRegistry.getAllViews()); this._register(ViewsRegistry.onViewsRegistered(viewDescriptors => this.createViewers(viewDescriptors))); this._register(ViewsRegistry.onViewsDeregistered(viewDescriptors => this.removeViewers(viewDescriptors))); @@ -72,6 +84,18 @@ export class CustomViewsService extends Disposable implements IViewsService { return TPromise.as(null); } + private onDidRegisterViewLocation(viewLocation: ViewLocation): void { + const viewDescriptorCollection = this._register(this.instantiationService.createInstance(ViewDescriptorCollection, viewLocation)); + this._register(viewDescriptorCollection.onDidChange(() => this.updateViewletEnablement(viewLocation, viewDescriptorCollection))); + this.lifecycleService.when(LifecyclePhase.Eventually).then(() => this.updateViewletEnablement(viewLocation, viewDescriptorCollection)); + } + + private updateViewletEnablement(viewLocation: ViewLocation, viewDescriptorCollection: ViewDescriptorCollection): void { + const enabled = viewDescriptorCollection.viewDescriptors.length > 0; + this.viewletService.setViewletEnablement(viewLocation.id, enabled); + this.storageService.store(`viewservice.${viewLocation.id}.enablement`, enabled, StorageScope.GLOBAL); + } + private createViewers(viewDescriptors: IViewDescriptor[]): void { for (const viewDescriptor of viewDescriptors) { if ((viewDescriptor).treeView) { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 52355a0e60c..ff8cf746353 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -14,23 +14,34 @@ import { IViewlet } from 'vs/workbench/common/viewlet'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { values } from 'vs/base/common/map'; export class ViewLocation { + private static readonly _onDidRegister: Emitter = new Emitter(); + static readonly onDidRegister: Event = ViewLocation._onDidRegister.event; + private static locations: Map = new Map(); static register(id: string): ViewLocation { - const viewLocation = new ViewLocation(id); - ViewLocation.locations.set(id, viewLocation); - return viewLocation; + if (!ViewLocation.locations.has(id)) { + const viewLocation = new ViewLocation(id); + ViewLocation.locations.set(id, viewLocation); + ViewLocation._onDidRegister.fire(viewLocation); + } + return ViewLocation.get(id); } static get(value: string): ViewLocation { return ViewLocation.locations.get(value); } + static get all(): ViewLocation[] { + return values(ViewLocation.locations); + } static readonly Explorer: ViewLocation = ViewLocation.register('workbench.view.explorer'); static readonly Debug: ViewLocation = ViewLocation.register('workbench.view.debug'); static readonly Extensions: ViewLocation = ViewLocation.register('workbench.view.extensions'); static readonly SCM: ViewLocation = ViewLocation.register('workbench.view.scm.views.contributed'); + static readonly TEST: ViewLocation = ViewLocation.register('workbench.view.extension.test'); private constructor(private _id: string) { } get id(): string { return this._id; } diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index da94b87236e..be7e26968ac 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -98,7 +98,7 @@ import { IListService, ListService } from 'vs/platform/list/browser/listService' import { domEvent } from 'vs/base/browser/event'; import { InputFocusedContext } from 'vs/platform/workbench/common/contextkeys'; import { IViewsService } from 'vs/workbench/common/views'; -import { CustomViewsService } from 'vs/workbench/browser/parts/views/customView'; +import { ViewsService } from 'vs/workbench/browser/parts/views/views'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { NotificationService } from 'vs/workbench/services/notification/common/notificationService'; import { NotificationsCenter } from 'vs/workbench/browser/parts/notifications/notificationsCenter'; @@ -387,9 +387,11 @@ export class Workbench implements IPartService { } perf.mark('willRestoreViewlet'); - restorePromises.push(this.viewletService.openViewlet(viewletIdToRestore).then(() => { - perf.mark('didRestoreViewlet'); - })); + restorePromises.push(this.viewletService.openViewlet(viewletIdToRestore) + .then(viewlet => viewlet || this.viewletService.openViewlet(this.viewletService.getDefaultViewletId())) + .then(() => { + perf.mark('didRestoreViewlet'); + })); } // Restore Panel @@ -575,7 +577,7 @@ export class Workbench implements IPartService { serviceCollection.set(IPanelService, this.panelPart); // Custom views service - const customViewsService = this.instantiationService.createInstance(CustomViewsService); + const customViewsService = this.instantiationService.createInstance(ViewsService); serviceCollection.set(IViewsService, customViewsService); // Activity service (activitybar part) @@ -872,7 +874,8 @@ export class Workbench implements IPartService { else if (!hidden && !this.sidebarPart.getActiveViewlet()) { const viewletToOpen = this.sidebarPart.getLastActiveViewletId(); if (viewletToOpen) { - promise = this.sidebarPart.openViewlet(viewletToOpen, true); + promise = this.viewletService.openViewlet(viewletToOpen, true) + .then(viewlet => viewlet || this.viewletService.openViewlet(this.viewletService.getDefaultViewletId(), true)); } } diff --git a/src/vs/workbench/parts/search/browser/searchViewLocationUpdater.ts b/src/vs/workbench/parts/search/browser/searchViewLocationUpdater.ts index 851e30dc4e5..d5b6bf21e19 100644 --- a/src/vs/workbench/parts/search/browser/searchViewLocationUpdater.ts +++ b/src/vs/workbench/parts/search/browser/searchViewLocationUpdater.ts @@ -16,23 +16,29 @@ export class SearchViewLocationUpdater implements IWorkbenchContribution { @IPanelService panelService: IPanelService, @IConfigurationService configurationService: IConfigurationService ) { - const updateSearchViewLocation = () => { + const updateSearchViewLocation = (open: boolean) => { const config = configurationService.getValue(); if (config.search.location === 'panel') { viewletService.setViewletEnablement(VIEW_ID, false); panelService.setPanelEnablement(VIEW_ID, true); + if (open) { + panelService.openPanel(VIEW_ID); + } } else { panelService.setPanelEnablement(VIEW_ID, false); viewletService.setViewletEnablement(VIEW_ID, true); + if (open) { + viewletService.openViewlet(VIEW_ID); + } } }; configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('search.location')) { - updateSearchViewLocation(); + updateSearchViewLocation(true); } }); - updateSearchViewLocation(); + updateSearchViewLocation(false); } } diff --git a/src/vs/workbench/services/viewlet/browser/viewletService.ts b/src/vs/workbench/services/viewlet/browser/viewletService.ts index 0a6b990f23f..354410b5820 100644 --- a/src/vs/workbench/services/viewlet/browser/viewletService.ts +++ b/src/vs/workbench/services/viewlet/browser/viewletService.ts @@ -62,7 +62,7 @@ export class ViewletService implements IViewletService { } public setViewletEnablement(id: string, enabled: boolean): void { - const descriptor = this.getBuiltInViewlets().filter(desc => desc.id === id).pop(); + const descriptor = this.getAllViewlets().filter(desc => desc.id === id).pop(); if (descriptor && descriptor.enabled !== enabled) { descriptor.enabled = enabled; this._onDidViewletEnable.fire({ id, enabled }); @@ -74,7 +74,12 @@ export class ViewletService implements IViewletService { return this.sidebarPart.openViewlet(id, focus); } return this.extensionService.whenInstalledExtensionsRegistered() - .then(() => this.sidebarPart.openViewlet(id, focus)); + .then(() => { + if (this.getViewlet(id)) { + return this.sidebarPart.openViewlet(id, focus); + } + return null; + }); } public getActiveViewlet(): IViewlet { @@ -82,11 +87,11 @@ export class ViewletService implements IViewletService { } public getViewlets(): ViewletDescriptor[] { - return this.getBuiltInViewlets() + return this.getAllViewlets() .filter(v => v.enabled); } - private getBuiltInViewlets(): ViewletDescriptor[] { + private getAllViewlets(): ViewletDescriptor[] { return this.viewletRegistry.getViewlets() .sort((v1, v2) => v1.order - v2.order); } From 80ae899fdca7492298524f9cd19912c9e181bdda Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 28 May 2018 17:11:09 +0200 Subject: [PATCH 33/37] Avoid command palette entry (#49340) --- .../quickinput/quickInput.contribution.ts | 11 ++----- .../browser/parts/quickinput/quickInput.ts | 29 +++++++------------ 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.contribution.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.contribution.ts index 05802208d23..5aea4a4cfce 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.contribution.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.contribution.ts @@ -4,12 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { QuickPickManyToggleAction } from 'vs/workbench/browser/parts/quickinput/quickInput'; -import { inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen'; +import { QuickPickManyToggle } from 'vs/workbench/browser/parts/quickinput/quickInput'; +import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; -const registry = Registry.as(ActionExtensions.WorkbenchActions); - -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickPickManyToggleAction, QuickPickManyToggleAction.ID, QuickPickManyToggleAction.LABEL, null, inQuickOpenContext), 'Toggle Selection in Quick Pick'); +KeybindingsRegistry.registerCommandAndKeybindingRule(QuickPickManyToggle); diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 75d7ab833d3..36943437c86 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -35,7 +35,8 @@ import { onUnexpectedError, canceled } from 'vs/base/common/errors'; import Severity from 'vs/base/common/severity'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { Action } from 'vs/base/common/actions'; +import { ICommandAndKeybindingRule, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen'; const $ = dom.$; @@ -743,21 +744,13 @@ export class QuickInputService extends Component implements IQuickInputService { } } -export class QuickPickManyToggleAction extends Action { - - public static readonly ID = 'workbench.action.quickPickManyToggle'; - public static readonly LABEL = localize('quickPickManyToggle', "Toggle Selection in Quick Pick"); - - constructor( - id: string, - label: string, - @IQuickInputService private quickInputService: IQuickInputService - ) { - super(id, label); +export const QuickPickManyToggle: ICommandAndKeybindingRule = { + id: 'workbench.action.quickPickManyToggle', + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: inQuickOpenContext, + primary: undefined, + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + quickInputService.toggle(); } - - public run(event?: any): TPromise { - this.quickInputService.toggle(); - return TPromise.as(true); - } -} +}; From e3635ac300060c0a0543f8ec79e96fd73aa955a3 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 28 May 2018 17:13:45 +0200 Subject: [PATCH 34/37] [handlebars] code-folding algorithm is less useful than Indentation-based one. Fixes #48457 --- extensions/html-language-features/client/src/htmlMain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/html-language-features/client/src/htmlMain.ts b/extensions/html-language-features/client/src/htmlMain.ts index 31354268112..f463ff2ba8a 100644 --- a/extensions/html-language-features/client/src/htmlMain.ts +++ b/extensions/html-language-features/client/src/htmlMain.ts @@ -187,7 +187,7 @@ export function activate(context: ExtensionContext) { } return void 0; } - return languages.registerFoldingRangeProvider(documentSelector, { + return languages.registerFoldingRangeProvider('html', { provideFoldingRanges(document: TextDocument, context: FoldingContext, token: CancellationToken) { const param: FoldingRangeRequestParam = { textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document) From 0ab37850795f00b957b1dc56da4bea8232631e9d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 28 May 2018 17:14:30 +0200 Subject: [PATCH 35/37] use upper lang id --- .../extensionManagement/node/extensionGalleryService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index 767d01ad51d..377c1e25bb9 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -527,7 +527,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } getCoreTranslations(extension: IGalleryExtension, languageId: string): TPromise<{}> { - const asset = extension.assets.coreTranslations[languageId]; + const asset = extension.assets.coreTranslations[languageId.toUpperCase()]; if (asset) { return this.getAsset(asset) .then(asText) From 88748864701d6fa3f443022b429ea6819dc51775 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 28 May 2018 17:29:56 +0200 Subject: [PATCH 36/37] Fixes #49378 --- src/vs/editor/browser/viewParts/minimap/minimap.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 35ad7ff531b..5f8337eb5a9 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -913,6 +913,11 @@ export class Minimap extends ViewPart { minimapCharRenderer.x1BlockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont); } dx += charWidth; + + if (dx > maxDx) { + // hit edge of minimap + return; + } } } } From 8edb2a79b4ca38333040b13bbf0f562253372055 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 28 May 2018 17:32:43 +0200 Subject: [PATCH 37/37] #49145 Store placeholder states for all viewlets --- src/vs/workbench/browser/parts/activitybar/activitybarPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 3bd54b0fd57..6f53eadda76 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -303,7 +303,7 @@ export class ActivitybarPart extends Part { } public shutdown(): void { - const state = this.viewletService.getViewlets().filter(viewlet => this.compositeBar.isPinned(viewlet.id)).map(viewlet => ({ id: viewlet.id, iconUrl: viewlet.iconUrl })); + const state = this.viewletService.getViewlets().map(viewlet => ({ id: viewlet.id, iconUrl: viewlet.iconUrl })); this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEWLETS, JSON.stringify(state), StorageScope.GLOBAL); super.shutdown(); }