Skip to content

Commit 3e2f213

Browse files
authored
Merge pull request #6 from atom-community/fixes
fix: fix and optimize onSave formatting and onType formatting
2 parents bad8ebd + 8bb9a29 commit 3e2f213

File tree

4 files changed

+83
-49
lines changed

4 files changed

+83
-49
lines changed

README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
# Code Format
22

3-
Format a selection of code using the `code-format:format-code` command.
4-
(Also accessible via context menu, or "Edit > Text > Format Code").
3+
## Usage
54

6-
When no selection is provided, the entire file is formatted.
5+
- By default the currently open file is formatted on save.
6+
7+
- Format a selection of code using the `code-format:format-code` (`CTRL+SHIFT+C`) command.
8+
(Also accessible via context menu, or "Edit > Text > Format Code"). When no selection is provided, the entire file is formatted.
9+
10+
- For the languages that support on type formatting, the package is able to format as you type in the editor. You should enable this from the settings of this package (disabled by default).
711

812
![Code Format](https://raw.githubusercontent.com/facebookarchive/atom-ide-ui/master/docs/images/code-format.gif)
913

14+
## Developer Service API
15+
1016
Code Format also provides APIs to:
1117

12-
- format code as you type
1318
- format code on save (after you press save but before writing to disk).
19+
- format code as you type
1420

1521
You can enable format-on-save using plain range/file providers from the atom-ide-code-format's settings
1622

17-
## Service API
18-
1923
Provide code format [Atom services](http://flight-manual.atom.io/behind-atom/sections/interacting-with-other-packages-via-services/) by adding one or more of these to your `package.json`:
2024
(Only the ones that you want to use; you don't need all of them!)
2125

src/CodeFormatManager.ts

Lines changed: 58 additions & 42 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,36 @@ 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+
if (!getFormatOnType()) {
57+
return
58+
}
59+
try {
60+
await this._formatCodeOnTypeInTextEditor(editor, event)
61+
} catch (err) {
62+
getLogger("code-format").warn("Failed to format code on type:", err)
63+
}
64+
}),
65+
// Format on save
66+
editor.onDidSave(
67+
debounce(async () => {
68+
const edits = await this._formatCodeOnSaveInTextEditor(editor)
69+
const success = applyTextEditsToBuffer(editor.getBuffer(), edits) as boolean
70+
if (!success) {
71+
throw new Error("No code formatting providers found!")
72+
}
73+
}, SAVE_TIMEOUT)
74+
)
75+
)
6176
// Make sure we halt everything when the editor gets destroyed.
6277
// We need to capture when editors are about to be destroyed in order to
6378
// interrupt any pending formatting operations. (Otherwise, we may end up
6479
// 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-
}))
80+
editor.onDidDestroy(() => editorSubs.dispose())
81+
})
7482
)
7583

7684
this._rangeProviders = new ProviderRegistry()
@@ -80,15 +88,19 @@ export default class CodeFormatManager {
8088
}
8189

8290
// Return the text edits used to format code in the editor specified.
83-
async _formatCodeInTextEditor(editor: TextEditor, range?: Range): Promise<Array<TextEdit>> {
91+
async _formatCodeInTextEditor(
92+
editor: TextEditor,
93+
selectionRange: Range = editor.getSelectedBufferRange()
94+
): Promise<Array<TextEdit>> {
8495
const buffer = editor.getBuffer()
85-
const selectionRange = range ?? editor.getSelectedBufferRange()
86-
const { start: selectionStart, end: selectionEnd } = selectionRange
96+
const bufferRange = buffer.getRange()
97+
8798
let formatRange: Range
8899
if (selectionRange.isEmpty()) {
89100
// If no selection is done, then, the whole file is wanted to be formatted.
90-
formatRange = buffer.getRange()
101+
formatRange = bufferRange
91102
} else {
103+
const { start: selectionStart, end: selectionEnd } = selectionRange
92104
// Format selections should start at the beginning of the line,
93105
// and include the last selected line end.
94106
// (If the user has already selected complete rows, then depending on how they
@@ -101,60 +113,64 @@ export default class CodeFormatManager {
101113
selectionEnd.column === 0 ? selectionEnd : [selectionEnd.row + 1, 0]
102114
)
103115
}
104-
const rangeProviders = [...this._rangeProviders.getAllProvidersForEditor(editor)]
105-
const fileProviders = [...this._fileProviders.getAllProvidersForEditor(editor)]
106-
const contents = editor.getText()
107116

108-
const allEdits = await this._reportBusy(
117+
// range providers
118+
const rangeProviders = [...this._rangeProviders.getAllProvidersForEditor(editor)]
119+
const allRangeEdits = await this._reportBusy(
109120
editor,
110121
Promise.all(rangeProviders.map((p) => p.formatCode(editor, formatRange)))
111122
)
112-
const rangeEdits = allEdits.filter((edits) => edits.length > 0)
123+
const rangeEdits = allRangeEdits.filter((edits) => edits.length > 0)
113124

114-
const allResults = await this._reportBusy(
125+
// file providers
126+
const fileProviders = [...this._fileProviders.getAllProvidersForEditor(editor)]
127+
const allFileEdits = await this._reportBusy(
115128
editor,
116129
Promise.all(fileProviders.map((p) => p.formatEntireFile(editor, formatRange)))
117130
)
118-
const nonNullResults = allResults.filter((result) => result !== null && result !== undefined) as {
131+
const nonNullFileEdits = allFileEdits.filter((result) => result !== null && result !== undefined) as {
119132
newCursor?: number
120133
formatted: string
121134
}[]
122-
const fileEdits = nonNullResults.map(({ formatted }) => [
135+
const contents = editor.getText()
136+
const editorRange = editor.getBuffer().getRange()
137+
const fileEdits = nonNullFileEdits.map(({ formatted }) => [
123138
{
124-
oldRange: editor.getBuffer().getRange(),
139+
oldRange: editorRange,
125140
newText: formatted,
126141
oldText: contents,
127142
} as TextEdit,
128143
])
129144

145+
// merge edits
130146
// When formatting the entire file, prefer file-based providers.
131-
const preferFileEdits = formatRange.isEqual(buffer.getRange())
147+
const preferFileEdits = formatRange.isEqual(bufferRange)
132148
const edits = preferFileEdits ? fileEdits.concat(rangeEdits) : rangeEdits.concat(fileEdits)
133149
return edits.flat() // TODO or [0]?
134150
}
135151

136152
async _formatCodeOnTypeInTextEditor(
137153
editor: TextEditor,
138-
aggregatedEvent: BufferStoppedChangingEvent
154+
{ changes }: BufferStoppedChangingEvent
139155
): Promise<Array<TextEdit>> {
140156
// Don't try to format changes with multiple cursors.
141-
if (aggregatedEvent.changes.length !== 1) {
157+
if (changes.length !== 1) {
158+
return []
159+
}
160+
// if no provider return immediately
161+
const providers = [...this._onTypeProviders.getAllProvidersForEditor(editor)]
162+
if (providers.length === 0) {
142163
return []
143164
}
144-
const event = aggregatedEvent.changes[0]
165+
const event = changes[0]
145166
// This also ensures the non-emptiness of event.newText for below.
146-
if (!shouldFormatOnType(event) || !getFormatOnType()) {
167+
if (!shouldFormatOnType(event)) {
147168
return []
148169
}
149170
// In the case of bracket-matching, we use the last character because that's
150171
// the character that will usually cause a reformat (i.e. `}` instead of `{`).
151172
const character = event.newText[event.newText.length - 1]
152173

153-
const providers = [...this._onTypeProviders.getAllProvidersForEditor(editor)]
154-
if (providers.length === 0) {
155-
return []
156-
}
157-
158174
const contents = editor.getText()
159175
const cursorPosition = editor.getCursorBufferPosition().copy()
160176

src/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"formatOnType": {
99
"title": "Format on Type",
1010
"type": "boolean",
11-
"default": true,
11+
"default": false,
1212
"description": "Automatically format code as you type it for supported languages."
1313
}
1414
}

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)