-
Notifications
You must be signed in to change notification settings - Fork 29.4k
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
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
|
@@ -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'; | ||
|
@@ -903,6 +909,7 @@ export class EditorContribOptions { | |
parameterHints: boolean; | ||
iconsInSuggestions: boolean; | ||
formatOnType: boolean; | ||
formatOnPaste: boolean; | ||
suggestOnTriggerCharacters: boolean; | ||
acceptSuggestionOnEnter: boolean; | ||
snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none'; | ||
|
@@ -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; | ||
|
@@ -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 | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we say we give out a |
||
|
||
/** | ||
* Returns true if this editor or one of its widgets has keyboard focus. | ||
*/ | ||
|
@@ -4098,6 +4114,8 @@ export var EventType = { | |
WillType: 'willType', | ||
DidType: 'didType', | ||
|
||
DidPaste: 'didPaste', | ||
|
||
EditorLayout: 'editorLayout', | ||
|
||
DiffUpdated: 'diffUpdated' | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> { | ||
|
There was a problem hiding this comment.
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 notIRange
). It does so for all non-serializable events.