Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix #13945. support format on paste #18476

Merged
merged 4 commits into from
Jan 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/vs/editor/common/commonCodeEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom
public readonly onDidDispose: Event<void> = fromEventEmitter<void>(this, editorCommon.EventType.Disposed);
public readonly onWillType: Event<string> = fromEventEmitter<string>(this, editorCommon.EventType.WillType);
public readonly onDidType: Event<string> = fromEventEmitter<string>(this, editorCommon.EventType.DidType);
public readonly onDidPaste: Event<Range> = fromEventEmitter<Range>(this, editorCommon.EventType.DidPaste);

protected domElement: IContextKeyServiceTarget;

Expand Down Expand Up @@ -588,6 +589,20 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom
return;
}

if (handlerId === editorCommon.Handler.Paste) {
if (!this.cursor || typeof payload.text !== 'string' || payload.text.length === 0) {
// nothing to do
return;
}
const startPosition = this.cursor.getSelection().getStartPosition();
this.cursor.trigger(source, handlerId, payload);
const endPosition = this.cursor.getSelection().getStartPosition();
if (source === 'keyboard') {
this.emit(editorCommon.EventType.DidPaste, new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column));
}
return;
}

let candidate = this.getAction(handlerId);
if (candidate !== null) {
TPromise.as(candidate.run()).done(null, onUnexpectedError);
Expand Down
6 changes: 6 additions & 0 deletions src/vs/editor/common/config/commonEditorConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ class InternalEditorOptionsHelper {
parameterHints: toBoolean(opts.parameterHints),
iconsInSuggestions: toBoolean(opts.iconsInSuggestions),
formatOnType: toBoolean(opts.formatOnType),
formatOnPaste: toBoolean(opts.formatOnPaste),
suggestOnTriggerCharacters: toBoolean(opts.suggestOnTriggerCharacters),
acceptSuggestionOnEnter: toBoolean(opts.acceptSuggestionOnEnter),
snippetSuggestions: opts.snippetSuggestions,
Expand Down Expand Up @@ -666,6 +667,11 @@ const editorConfiguration: IConfigurationNode = {
'default': DefaultConfig.editor.formatOnType,
'description': nls.localize('formatOnType', "Controls if the editor should automatically format the line after typing")
},
'editor.formatOnPaste': {
'type': 'boolean',
'default': DefaultConfig.editor.formatOnPaste,
'description': nls.localize('formatOnPaste', "Controls if the editor should automatically format the pasted content")
},
'editor.suggestOnTriggerCharacters': {
'type': 'boolean',
'default': DefaultConfig.editor.suggestOnTriggerCharacters,
Expand Down
1 change: 1 addition & 0 deletions src/vs/editor/common/config/defaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class ConfigClass implements IConfiguration {
iconsInSuggestions: true,
autoClosingBrackets: true,
formatOnType: false,
formatOnPaste: false,
suggestOnTriggerCharacters: true,
acceptSuggestionOnEnter: true,
snippetSuggestions: 'bottom',
Expand Down
18 changes: 18 additions & 0 deletions src/vs/editor/common/editorCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,11 @@ export interface IEditorOptions {
* Defaults to false.
*/
formatOnType?: boolean;
/**
* Enable format on paste.
* Defaults to false.
*/
formatOnPaste?: boolean;
/**
* Enable the suggestion box to pop-up on trigger characters.
* Defaults to true.
Expand Down Expand Up @@ -879,6 +884,7 @@ export class EditorContribOptions {
readonly parameterHints: boolean;
readonly iconsInSuggestions: boolean;
readonly formatOnType: boolean;
readonly formatOnPaste: boolean;
readonly suggestOnTriggerCharacters: boolean;
readonly acceptSuggestionOnEnter: boolean;
readonly snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none';
Expand All @@ -903,6 +909,7 @@ export class EditorContribOptions {
parameterHints: boolean;
iconsInSuggestions: boolean;
formatOnType: boolean;
formatOnPaste: boolean;
suggestOnTriggerCharacters: boolean;
acceptSuggestionOnEnter: boolean;
snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none';
Expand All @@ -923,6 +930,7 @@ export class EditorContribOptions {
this.parameterHints = Boolean(source.parameterHints);
this.iconsInSuggestions = Boolean(source.iconsInSuggestions);
this.formatOnType = Boolean(source.formatOnType);
this.formatOnPaste = Boolean(source.formatOnPaste);
this.suggestOnTriggerCharacters = Boolean(source.suggestOnTriggerCharacters);
this.acceptSuggestionOnEnter = Boolean(source.acceptSuggestionOnEnter);
this.snippetSuggestions = source.snippetSuggestions;
Expand All @@ -949,6 +957,7 @@ export class EditorContribOptions {
&& this.parameterHints === other.parameterHints
&& this.iconsInSuggestions === other.iconsInSuggestions
&& this.formatOnType === other.formatOnType
&& this.formatOnPaste === other.formatOnPaste
&& this.suggestOnTriggerCharacters === other.suggestOnTriggerCharacters
&& this.acceptSuggestionOnEnter === other.acceptSuggestionOnEnter
&& this.snippetSuggestions === other.snippetSuggestions
Expand Down Expand Up @@ -3800,6 +3809,13 @@ export interface ICommonCodeEditor extends IEditor {
*/
onDidType(listener: (text: string) => void): IDisposable;

/**
* An event emitted when users paste text in the editor.
* @event
* @internal
*/
onDidPaste(listener: (range: Range) => void): IDisposable;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we say we give out a Range, but the implementation gives out an IRange. Please adjust the implementation.


/**
* Returns true if this editor or one of its widgets has keyboard focus.
*/
Expand Down Expand Up @@ -4098,6 +4114,8 @@ export var EventType = {
WillType: 'willType',
DidType: 'didType',

DidPaste: 'didPaste',

EditorLayout: 'editorLayout',

DiffUpdated: 'diffUpdated'
Expand Down
83 changes: 82 additions & 1 deletion src/vs/editor/contrib/format/common/formatActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import { TPromise } from 'vs/base/common/winjs.base';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { editorAction, ServicesAccessor, EditorAction, commonEditorContribution } from 'vs/editor/common/editorCommonExtensions';
import { OnTypeFormattingEditProviderRegistry } from 'vs/editor/common/modes';
import { OnTypeFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry } from 'vs/editor/common/modes';
import { getOnTypeFormattingEdits, getDocumentFormattingEdits, getDocumentRangeFormattingEdits } from '../common/format';
import { EditOperationsCommand } from './formatCommand';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
import { Range } from 'vs/editor/common/core/range';

import ModeContextKeys = editorCommon.ModeContextKeys;
import EditorContextKeys = editorCommon.EditorContextKeys;
Expand Down Expand Up @@ -150,6 +151,86 @@ class FormatOnType implements editorCommon.IEditorContribution {
}
}

@commonEditorContribution
class FormatOnPaste implements editorCommon.IEditorContribution {

private static ID = 'editor.contrib.formatOnPaste';

private editor: editorCommon.ICommonCodeEditor;
private workerService: IEditorWorkerService;
private callOnDispose: IDisposable[];
private callOnModel: IDisposable[];

constructor(editor: editorCommon.ICommonCodeEditor, @IEditorWorkerService workerService: IEditorWorkerService) {
this.editor = editor;
this.workerService = workerService;
this.callOnDispose = [];
this.callOnModel = [];

this.callOnDispose.push(editor.onDidChangeConfiguration(() => this.update()));
this.callOnDispose.push(editor.onDidChangeModel(() => this.update()));
this.callOnDispose.push(editor.onDidChangeModelLanguage(() => this.update()));
this.callOnDispose.push(DocumentRangeFormattingEditProviderRegistry.onDidChange(this.update, this));
}

private update(): void {

// clean up
this.callOnModel = dispose(this.callOnModel);

// we are disabled
if (!this.editor.getConfiguration().contribInfo.formatOnPaste) {
return;
}

// no model
if (!this.editor.getModel()) {
return;
}

let model = this.editor.getModel();

// no support
let [support] = DocumentRangeFormattingEditProviderRegistry.ordered(model);
if (!support || !support.provideDocumentRangeFormattingEdits) {
return;
}

this.callOnModel.push(this.editor.onDidPaste((range: Range) => {
this.trigger(range);
}));
}

private trigger(range: Range): void {
if (this.editor.getSelections().length > 1) {
return;
}

const model = this.editor.getModel();
const { tabSize, insertSpaces } = model.getOptions();
const state = this.editor.captureState(editorCommon.CodeEditorStateFlag.Value, editorCommon.CodeEditorStateFlag.Position);

getDocumentRangeFormattingEdits(model, range, { tabSize, insertSpaces }).then(edits => {
return this.workerService.computeMoreMinimalEdits(model.uri, edits, []);
}).then(edits => {
if (!state.validate(this.editor) || isFalsyOrEmpty(edits)) {
return;
}
const command = new EditOperationsCommand(edits, this.editor.getSelection());
this.editor.executeCommand(this.getId(), command);
});
}

public getId(): string {
return FormatOnPaste.ID;
}

public dispose(): void {
this.callOnDispose = dispose(this.callOnDispose);
this.callOnModel = dispose(this.callOnModel);
}
}

export abstract class AbstractFormatAction extends EditorAction {

public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): TPromise<void> {
Expand Down
6 changes: 6 additions & 0 deletions src/vs/monaco.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,11 @@ declare module monaco.editor {
* Defaults to false.
*/
formatOnType?: boolean;
/**
* Enable format on paste.
* Defaults to false.
*/
formatOnPaste?: boolean;
/**
* Enable the suggestion box to pop-up on trigger characters.
* Defaults to true.
Expand Down Expand Up @@ -1521,6 +1526,7 @@ declare module monaco.editor {
readonly parameterHints: boolean;
readonly iconsInSuggestions: boolean;
readonly formatOnType: boolean;
readonly formatOnPaste: boolean;
readonly suggestOnTriggerCharacters: boolean;
readonly acceptSuggestionOnEnter: boolean;
readonly snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none';
Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/telemetry/common/telemetryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ const configurationValueWhitelist = [
'editor.detectIndentation',
'editor.formatOnType',
'editor.formatOnSave',
'editor.formatOnPaste',
'window.openFilesInNewWindow',
'javascript.validate.enable',
'editor.mouseWheelZoom',
Expand Down