Skip to content

Commit 2607425

Browse files
committed
fix: fix and optimize _formatCodeInTextEditor
1 parent bad8ebd commit 2607425

File tree

2 files changed

+60
-33
lines changed

2 files changed

+60
-33
lines changed

src/CodeFormatManager.ts

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import type {
77
RangeCodeFormatProvider,
88
} from "./types"
99

10-
import { registerOnWillSave } from "@atom-ide-community/nuclide-commons-atom/FileEventHandlers"
1110
import { getFormatOnSave, getFormatOnType } from "./config"
1211
import { getLogger } from "log4js"
1312
import { ProviderRegistry } from "atom-ide-base/commons-atom/ProviderRegistry"
1413
import { applyTextEditsToBuffer } from "@atom-ide-community/nuclide-commons-atom/text-edit"
1514
import nuclideUri from "@atom-ide-community/nuclide-commons/nuclideUri"
15+
import { debounce } from "./utils"
1616

1717
// Save events are critical, so don't allow providers to block them.
18-
export const SAVE_TIMEOUT = 2500
18+
export const SAVE_TIMEOUT = 500
1919

2020
export default class CodeFormatManager {
2121
_subscriptions: CompositeDisposable
@@ -49,28 +49,33 @@ export default class CodeFormatManager {
4949
}
5050
}),
5151

52-
// Events from typing in the editor
5352
atom.workspace.observeTextEditors((editor) => {
54-
const onChangeSubs = editor.getBuffer().onDidStopChanging(async (event) => {
55-
try {
56-
await this._formatCodeOnTypeInTextEditor(editor, event)
57-
} catch (err) {
58-
getLogger("code-format").warn("Failed to format code on type:", err)
59-
}
60-
})
53+
const editorSubs = new CompositeDisposable(
54+
// Format on typing in the editor
55+
editor.getBuffer().onDidStopChanging(async (event) => {
56+
try {
57+
await this._formatCodeOnTypeInTextEditor(editor, event)
58+
} catch (err) {
59+
getLogger("code-format").warn("Failed to format code on type:", err)
60+
}
61+
}),
62+
// Format on save
63+
editor.onDidSave(
64+
debounce(async () => {
65+
const edits = await this._formatCodeOnSaveInTextEditor(editor)
66+
const success = applyTextEditsToBuffer(editor.getBuffer(), edits) as boolean
67+
if (!success) {
68+
throw new Error("No code formatting providers found!")
69+
}
70+
}, SAVE_TIMEOUT)
71+
)
72+
)
6173
// Make sure we halt everything when the editor gets destroyed.
6274
// We need to capture when editors are about to be destroyed in order to
6375
// interrupt any pending formatting operations. (Otherwise, we may end up
6476
// attempting to save a destroyed editor!)
65-
editor.onDidDestroy(() => onChangeSubs.dispose())
66-
}),
67-
68-
// Format on save
69-
registerOnWillSave(() => ({
70-
priority: 0,
71-
timeout: SAVE_TIMEOUT,
72-
callback: this._formatCodeOnSaveInTextEditor.bind(this),
73-
}))
77+
editor.onDidDestroy(() => editorSubs.dispose())
78+
})
7479
)
7580

7681
this._rangeProviders = new ProviderRegistry()
@@ -80,15 +85,19 @@ export default class CodeFormatManager {
8085
}
8186

8287
// Return the text edits used to format code in the editor specified.
83-
async _formatCodeInTextEditor(editor: TextEditor, range?: Range): Promise<Array<TextEdit>> {
88+
async _formatCodeInTextEditor(
89+
editor: TextEditor,
90+
selectionRange: Range = editor.getSelectedBufferRange()
91+
): Promise<Array<TextEdit>> {
8492
const buffer = editor.getBuffer()
85-
const selectionRange = range ?? editor.getSelectedBufferRange()
86-
const { start: selectionStart, end: selectionEnd } = selectionRange
93+
const bufferRange = buffer.getRange()
94+
8795
let formatRange: Range
8896
if (selectionRange.isEmpty()) {
8997
// If no selection is done, then, the whole file is wanted to be formatted.
90-
formatRange = buffer.getRange()
98+
formatRange = bufferRange
9199
} else {
100+
const { start: selectionStart, end: selectionEnd } = selectionRange
92101
// Format selections should start at the beginning of the line,
93102
// and include the last selected line end.
94103
// (If the user has already selected complete rows, then depending on how they
@@ -101,34 +110,38 @@ export default class CodeFormatManager {
101110
selectionEnd.column === 0 ? selectionEnd : [selectionEnd.row + 1, 0]
102111
)
103112
}
104-
const rangeProviders = [...this._rangeProviders.getAllProvidersForEditor(editor)]
105-
const fileProviders = [...this._fileProviders.getAllProvidersForEditor(editor)]
106-
const contents = editor.getText()
107113

108-
const allEdits = await this._reportBusy(
114+
// range providers
115+
const rangeProviders = [...this._rangeProviders.getAllProvidersForEditor(editor)]
116+
const allRangeEdits = await this._reportBusy(
109117
editor,
110118
Promise.all(rangeProviders.map((p) => p.formatCode(editor, formatRange)))
111119
)
112-
const rangeEdits = allEdits.filter((edits) => edits.length > 0)
120+
const rangeEdits = allRangeEdits.filter((edits) => edits.length > 0)
113121

114-
const allResults = await this._reportBusy(
122+
// file providers
123+
const fileProviders = [...this._fileProviders.getAllProvidersForEditor(editor)]
124+
const allFileEdits = await this._reportBusy(
115125
editor,
116126
Promise.all(fileProviders.map((p) => p.formatEntireFile(editor, formatRange)))
117127
)
118-
const nonNullResults = allResults.filter((result) => result !== null && result !== undefined) as {
128+
const nonNullFileEdits = allFileEdits.filter((result) => result !== null && result !== undefined) as {
119129
newCursor?: number
120130
formatted: string
121131
}[]
122-
const fileEdits = nonNullResults.map(({ formatted }) => [
132+
const contents = editor.getText()
133+
const editorRange = editor.getBuffer().getRange()
134+
const fileEdits = nonNullFileEdits.map(({ formatted }) => [
123135
{
124-
oldRange: editor.getBuffer().getRange(),
136+
oldRange: editorRange,
125137
newText: formatted,
126138
oldText: contents,
127139
} as TextEdit,
128140
])
129141

142+
// merge edits
130143
// When formatting the entire file, prefer file-based providers.
131-
const preferFileEdits = formatRange.isEqual(buffer.getRange())
144+
const preferFileEdits = formatRange.isEqual(bufferRange)
132145
const edits = preferFileEdits ? fileEdits.concat(rangeEdits) : rangeEdits.concat(fileEdits)
133146
return edits.flat() // TODO or [0]?
134147
}

src/utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/** A faster vresion of lodash.debounce */
2+
/* eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any */
3+
export function debounce<T extends (...args: any[]) => void>(func: T, wait: number): T {
4+
let timeoutId: NodeJS.Timeout | undefined
5+
// @ts-ignore
6+
return (...args: Parameters<T>) => {
7+
if (timeoutId !== undefined) {
8+
clearTimeout(timeoutId)
9+
}
10+
timeoutId = setTimeout(() => {
11+
func(...args)
12+
}, wait)
13+
}
14+
}

0 commit comments

Comments
 (0)