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 3 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
20 changes: 20 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,25 @@ 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, {
Copy link
Member

Choose a reason for hiding this comment

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

I would prefer for the editor to always give out rich objects (i.e. Range and not IRange). It does so for all non-serializable events.

startLineNumber: startPosition.lineNumber,
startColumn: startPosition.column,
endLineNumber: endPosition.lineNumber,
endColumn: 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
84 changes: 83 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,87 @@ 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;
}

var model = this.editor.getModel();
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: prefer let over var (same two lines below)


// no support
var [support] = OnTypeFormattingEditProviderRegistry.ordered(model);
if (!support || !support.autoFormatTriggerCharacters) {
Copy link
Member

Choose a reason for hiding this comment

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

No need for the support to have autoFormatTriggerCharacters. Perhaps a copy-paste error?

Copy link
Member

Choose a reason for hiding this comment

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

Now that I look at it, we should not read the OnTypeFormattingEditProvierRegistry, but the DocumentRangeFormatting bla bla Registry.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup it's a stupid copy-paste error, I made changes to one place but forgot it here.

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);
this.editor.focus();
Copy link
Member

Choose a reason for hiding this comment

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

No need to focus here, since we are not moving the focus anywhere.

});
}

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