From 6f4cea3b889c751afaa716325a63462181babcb6 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Thu, 12 Jan 2017 09:47:36 -0800 Subject: [PATCH] Walk-Through Editor --- .../electron-browser/workbench.main.ts | 2 + .../walkThrough/common/walkThroughInput.ts | 27 +++ .../editor/editorWalkThrough.md | 31 +++ .../editor/editorWalkThrough.ts | 36 +++ .../walkThrough.contribution.ts | 25 ++ .../electron-browser/walkThroughPart.css | 126 ++++++++++ .../electron-browser/walkThroughPart.ts | 223 ++++++++++++++++++ 7 files changed, 470 insertions(+) create mode 100644 src/vs/workbench/parts/walkThrough/common/walkThroughInput.ts create mode 100644 src/vs/workbench/parts/walkThrough/electron-browser/editor/editorWalkThrough.md create mode 100644 src/vs/workbench/parts/walkThrough/electron-browser/editor/editorWalkThrough.ts create mode 100644 src/vs/workbench/parts/walkThrough/electron-browser/walkThrough.contribution.ts create mode 100644 src/vs/workbench/parts/walkThrough/electron-browser/walkThroughPart.css create mode 100644 src/vs/workbench/parts/walkThrough/electron-browser/walkThroughPart.ts diff --git a/src/vs/workbench/electron-browser/workbench.main.ts b/src/vs/workbench/electron-browser/workbench.main.ts index 9b76773a50d..18ce5c24b5a 100644 --- a/src/vs/workbench/electron-browser/workbench.main.ts +++ b/src/vs/workbench/electron-browser/workbench.main.ts @@ -60,6 +60,8 @@ import 'vs/workbench/parts/markers/browser/markersPanel'; // can be packaged sep import 'vs/workbench/parts/html/browser/html.contribution'; +import 'vs/workbench/parts/walkThrough/electron-browser/walkThrough.contribution'; + import 'vs/workbench/parts/extensions/electron-browser/extensions.contribution'; import 'vs/workbench/parts/extensions/browser/extensionsQuickOpen'; import 'vs/workbench/parts/extensions/electron-browser/extensionsViewlet'; // can be packaged separately diff --git a/src/vs/workbench/parts/walkThrough/common/walkThroughInput.ts b/src/vs/workbench/parts/walkThrough/common/walkThroughInput.ts new file mode 100644 index 00000000000..8fe7794ec6e --- /dev/null +++ b/src/vs/workbench/parts/walkThrough/common/walkThroughInput.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import URI from 'vs/base/common/uri'; +import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; +import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; + +export class WalkThroughInput extends ResourceEditorInput { + + // just a marker class + constructor( + name: string, + description: string, + resource: URI, + public readonly onReady: (container: HTMLElement) => void, + @ITextModelResolverService textModelResolverService: ITextModelResolverService + ) { + super(name, description, resource, textModelResolverService); + } + + getResource(): URI { + return this.resource; + } +} diff --git a/src/vs/workbench/parts/walkThrough/electron-browser/editor/editorWalkThrough.md b/src/vs/workbench/parts/walkThrough/electron-browser/editor/editorWalkThrough.md new file mode 100644 index 00000000000..a9b7355c9d4 --- /dev/null +++ b/src/vs/workbench/parts/walkThrough/electron-browser/editor/editorWalkThrough.md @@ -0,0 +1,31 @@ +### Multi-Cursor Editing + +Use ⇧⌥Shift+Alt while selecting text with the mouse to select a rectangular area and change multiple lines at once. + +```css +.global-message-list.transition { + -webkit-transition: top 200ms linear; + -ms-transition: top 200ms linear; + -moz-transition: top 200ms linear; + -khtml-transition: top 200ms linear; + -o-transition: top 200ms linear; + transition: top 200ms linear; +} +``` + +### IntelliSense + +Visual Studio Code comes with powerful IntelliSense for JavaScript and TypeScript preinstalled. Other languages can be upgraded with better IntelliSense through one of the many [extensions](command:workbench.extensions.action.showPopularExtensions). + +In the below example, position the text cursor in front of the error underline, right after the dot and press to invoke IntelliSense. + +```js +var express = require('express'); +var app = express(); + +app.get('/', function (req, res) { + res.send(`Hello ${req.}`); +}); + +app.listen(3000); +``` \ No newline at end of file diff --git a/src/vs/workbench/parts/walkThrough/electron-browser/editor/editorWalkThrough.ts b/src/vs/workbench/parts/walkThrough/electron-browser/editor/editorWalkThrough.ts new file mode 100644 index 00000000000..0471ffd9f63 --- /dev/null +++ b/src/vs/workbench/parts/walkThrough/electron-browser/editor/editorWalkThrough.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { localize } from 'vs/nls'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { Position } from 'vs/platform/editor/common/editor'; +import { Action } from 'vs/base/common/actions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TPromise } from 'vs/base/common/winjs.base'; +import URI from 'vs/base/common/uri'; +import { WalkThroughInput } from 'vs/workbench/parts/walkThrough/common/walkThroughInput'; + +export class EditorWalkThroughAction extends Action { + + public static ID = 'workbench.action.editorWalkThrough'; + public static LABEL = localize('editorWalkThrough', "Editor Walk-Through"); + + constructor( + id: string, + label: string, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IInstantiationService private instantiationService: IInstantiationService + ) { + super(id, label); + } + + public run(): TPromise { + const uri = URI.parse(require.toUrl('./editorWalkThrough.md')); + const input = this.instantiationService.createInstance(WalkThroughInput, localize('editorWalkThrough.title', "Editor Walk-Through"), '', uri, null); + return this.editorService.openEditor(input, { pinned: true }, Position.ONE) + .then(() => void (0)); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/walkThrough/electron-browser/walkThrough.contribution.ts b/src/vs/workbench/parts/walkThrough/electron-browser/walkThrough.contribution.ts new file mode 100644 index 00000000000..98072f898da --- /dev/null +++ b/src/vs/workbench/parts/walkThrough/electron-browser/walkThrough.contribution.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { localize } from 'vs/nls'; +import { WalkThroughInput } from 'vs/workbench/parts/walkThrough/common/walkThroughInput'; +import { WalkThroughPart } from 'vs/workbench/parts/walkThrough/electron-browser/walkThroughPart'; +import { EditorWalkThroughAction } from 'vs/workbench/parts/walkThrough/electron-browser/editor/editorWalkThrough'; +import { Registry } from 'vs/platform/platform'; +import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; + +(Registry.as(EditorExtensions.Editors)).registerEditor(new EditorDescriptor(WalkThroughPart.ID, + localize('walkThrough.editor.label', "Walk-Through"), + 'vs/workbench/parts/walkThrough/electron-browser/walkThroughPart', + 'WalkThroughPart'), + [new SyncDescriptor(WalkThroughInput)]); + +Registry.as(Extensions.WorkbenchActions) + .registerWorkbenchAction(new SyncActionDescriptor(EditorWalkThroughAction, EditorWalkThroughAction.ID, EditorWalkThroughAction.LABEL), 'Help: Editor Walk-Through', localize('help', "Help")); diff --git a/src/vs/workbench/parts/walkThrough/electron-browser/walkThroughPart.css b/src/vs/workbench/parts/walkThrough/electron-browser/walkThroughPart.css new file mode 100644 index 00000000000..232c9e189d2 --- /dev/null +++ b/src/vs/workbench/parts/walkThrough/electron-browser/walkThroughPart.css @@ -0,0 +1,126 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent { + box-sizing: border-box; + padding: 10px 20px; + line-height: 22px; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent img { + max-width: 100%; + max-height: 100%; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent a { + color: #4080D0; + text-decoration: none; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent a:focus, +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent input:focus, +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent select:focus, +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent textarea:focus { + outline: 1px solid -webkit-focus-ring-color; + outline-offset: -1px; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent hr { + border: 0; + height: 2px; + border-bottom: 2px solid; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent h1 { + padding-bottom: 0.3em; + line-height: 1.2; + border-bottom-width: 1px; + border-bottom-style: solid; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent h1, h2, h3 { + font-weight: normal; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent a:hover { + color: #4080D0; + text-decoration: underline; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent table { + border-collapse: collapse; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent table > thead > tr > th { + text-align: left; + border-bottom: 1px solid; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent table > thead > tr > th, +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent table > thead > tr > td, +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent table > tbody > tr > th, +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent table > tbody > tr > td { + padding: 5px 10px; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent table > tbody > tr + tr > td { + border-top: 1px solid; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent blockquote { + margin: 0 7px 0 5px; + padding: 0 16px 0 10px; + border-left: 5px solid; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent code { + font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-size: 14px; + line-height: 19px; +} + +.monaco-workbench.mac > .part.editor > .content .walkThroughContainer .walkThroughContent code { + font-size: 12px; + line-height: 18px; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent code > div { + padding: 16px; + border-radius: 3px; + overflow: auto; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent .monaco-tokenized-source { + white-space: pre; +} + +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent .mac-only, +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent .windows-only, +.monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent .linux-only { + display: none; +} +.monaco-workbench.mac > .part.editor > .content .walkThroughContainer .walkThroughContent .mac-only { + display: initial; +} +.monaco-workbench.windows > .part.editor > .content .walkThroughContainer .walkThroughContent .windows-only { + display: initial; +} +.monaco-workbench.linux > .part.editor > .content .walkThroughContainer .walkThroughContent .linux-only { + display: initial; +} + +.vs .monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent .monaco-editor-background, +.vs .monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent .glyph-margin { + background-color: #eee; +} + +.vs-dark .monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent .monaco-editor-background, +.vs-dark .monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent .glyph-margin { + background-color: #111; +} + +.hc-black .monaco-workbench > .part.editor > .content .walkThroughContainer .walkThroughContent .monaco-editor { + border: 1px white solid +} diff --git a/src/vs/workbench/parts/walkThrough/electron-browser/walkThroughPart.ts b/src/vs/workbench/parts/walkThrough/electron-browser/walkThroughPart.ts new file mode 100644 index 00000000000..eb1537f95ed --- /dev/null +++ b/src/vs/workbench/parts/walkThrough/electron-browser/walkThroughPart.ts @@ -0,0 +1,223 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import 'vs/css!./walkThroughPart'; +import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { ScrollbarVisibility } from 'vs/base/common/scrollable'; +import * as strings from 'vs/base/common/strings'; +import URI from 'vs/base/common/uri'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { DefaultConfig } from 'vs/editor/common/config/defaultConfig'; +import { IEditorOptions, IModel } from 'vs/editor/common/editorCommon'; +import { $, Dimension, Builder } from 'vs/base/browser/builder'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { EditorOptions } from 'vs/workbench/common/editor'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { WalkThroughInput } from 'vs/workbench/parts/walkThrough/common/walkThroughInput'; +import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { marked } from 'vs/base/common/marked/marked'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import * as uuid from 'vs/base/common/uuid'; +import { CodeEditor } from 'vs/editor/browser/codeEditor'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import * as path from 'path'; +import { tmpdir } from 'os'; +import { mkdirp } from 'vs/base/node/extfs'; +import { IMode } from 'vs/editor/common/modes'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { localize } from 'vs/nls'; + +const UNBOUND_COMMAND = localize('walkThrough.unboundCommand', "unbound"); + +export class WalkThroughPart extends BaseEditor { + + static ID: string = 'workbench.editor.walkThroughPart'; + + private disposables: IDisposable[] = []; + private contentDisposables: IDisposable[] = []; + private content: HTMLDivElement; + private scrollbar: DomScrollableElement; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IInstantiationService private instantiationService: IInstantiationService, + @IThemeService private themeService: IThemeService, + @IOpenerService private openerService: IOpenerService, + @IFileService private fileService: IFileService, + @IModelService protected modelService: IModelService, + @IKeybindingService private keybindingService: IKeybindingService, + @IModeService private modeService: IModeService + ) { + super(WalkThroughPart.ID, telemetryService); + } + + createEditor(parent: Builder): void { + const container = parent.getHTMLElement(); + container.classList.add('walkThroughContainer'); + + this.content = document.createElement('div'); + this.content.classList.add('walkThroughContent'); + + this.scrollbar = new DomScrollableElement(this.content, { + canUseTranslate3d: false, + horizontal: ScrollbarVisibility.Auto, + vertical: ScrollbarVisibility.Auto + }); + this.disposables.push(this.scrollbar); + container.appendChild(this.scrollbar.getDomNode()); + + this.registerClickHandler(); + } + + private registerClickHandler() { + this.content.addEventListener('click', event => { + let node = event.target; + if (node instanceof HTMLAnchorElement && node.href) { + let baseElement = window.document.getElementsByTagName('base')[0]; + if (baseElement && node.href.indexOf(baseElement.href) >= 0 && node.hash) { + let scrollTarget = window.document.getElementById(node.hash.substr(1, node.hash.length - 1)); + if (scrollTarget) { + scrollTarget.scrollIntoView(); + } + } else { + this.openerService.open(URI.parse(node.href)); + } + event.preventDefault(); + } else if (node instanceof HTMLButtonElement) { + const href = node.getAttribute('data-href'); + if (href) { + this.openerService.open(URI.parse(href)); + } + } + }); + } + + layout({ width, height }: Dimension): void { + $(this.content).style({ height: `${height}px`, width: `${width}px` }); + this.contentDisposables.forEach(disposable => { + if (disposable instanceof CodeEditor) { + disposable.layout(); + } + }); + this.scrollbar.scanDomNode(); + } + + focus(): void { + this.content.focus(); + } + + setInput(input: WalkThroughInput, options: EditorOptions): TPromise { + this.contentDisposables = dispose(this.contentDisposables); + this.content.innerHTML = ''; + + const folderName = path.join(tmpdir(), 'vscode-walk-through', uuid.generateUuid()); + const folder = new TPromise((c, e) => mkdirp(folderName, null, err => err ? e(err) : c(folderName))); + + return super.setInput(input, options) + .then(() => this.fileService.resolveContent(input.getResource(), { acceptTextOnly: true })) + .then(content => { + if (strings.endsWith(input.getResource().path, '.html')) { + this.content.innerHTML = content.value; + this.decorateContent(); + if (input.onReady) { + input.onReady(this.content); + } + this.scrollbar.scanDomNode(); + return; + } + + const files: TPromise[] = []; + const codes: { id: string; model: IModel }[] = []; + const renderer = new marked.Renderer(); + renderer.code = (code, lang) => { + const id = `code-${uuid.generateUuid()}`; + const mode = this.getModeForLanguage(lang); + const resource = URI.file(path.join(folderName, `${id}.${lang}`)); + const model = this.modelService.createModel(code, mode, resource); + codes.push({ id, model }); + + // E.g., the TypeScript service needs files on disk. + files.push(folder.then(() => this.fileService.createFile(resource, code))); + + return `
`; + }; + this.content.innerHTML = marked(content.value, { renderer }); + this.decorateContent(); + + // TODO: also create jsconfig.json and tsconfig.json + return TPromise.join(files).then(() => { + codes.forEach(({ id, model }) => { + const div = this.content.querySelector(`#${id}`) as HTMLElement; + + var options: IEditorOptions = { + scrollBeyondLastLine: false, + scrollbar: DefaultConfig.editor.scrollbar, + overviewRulerLanes: 3, + fixedOverflowWidgets: true, + lineNumbersMinChars: 1, + theme: this.themeService.getColorTheme(), + }; + + const editor = this.instantiationService.createInstance(CodeEditor, div, options); + editor.setModel(model); + this.contentDisposables.push(editor); + + const lineHeight = editor.getConfiguration().lineHeight; + const height = model.getLineCount() * lineHeight; + div.style.height = height + 'px'; + + this.contentDisposables.push(this.themeService.onDidColorThemeChange(theme => editor.updateOptions({ theme }))); + + editor.layout(); + }); + if (input.onReady) { + input.onReady(this.content); + } + this.scrollbar.scanDomNode(); + }); + }); + } + + private getModeForLanguage(lang: string): TPromise { + return new TPromise(c => { + const that = this; + function tryGetMode() { + const modeId = that.modeService.getModeIdForLanguageName(lang); + const mode = modeId && that.modeService.getOrCreateMode(modeId); + if (mode) { + c(mode); + } else { + const subscription = that.modeService.onDidAddModes(() => { + subscription.dispose(); + tryGetMode(); + }); + } + } + tryGetMode(); + }); + } + + private decorateContent() { + const keys = this.content.querySelectorAll('.shortcut[data-command]'); + Array.prototype.forEach.call(keys, (key: Element) => { + const command = key.getAttribute('data-command'); + const keybinding = command && this.keybindingService.lookupKeybindings(command)[0]; + const label = keybinding ? this.keybindingService.getLabelFor(keybinding) : UNBOUND_COMMAND; + key.appendChild(document.createTextNode(label)); + }); + } + + dispose(): void { + this.contentDisposables = dispose(this.contentDisposables); + this.disposables = dispose(this.disposables); + super.dispose(); + } +} -- GitLab