Skip to content

Commit d90a98c

Browse files
feat: support charset (#442)
1 parent d8387a9 commit d90a98c

10 files changed

Lines changed: 141 additions & 20 deletions

File tree

package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"version": "0.17.4",
77
"icon": "EditorConfig_icon.png",
88
"engines": {
9-
"vscode": "^1.98.0"
9+
"vscode": "^1.100.0"
1010
},
1111
"author": "EditorConfig Team",
1212
"license": "MIT",
@@ -114,7 +114,7 @@
114114
"devDependencies": {
115115
"@types/mocha": "^10.0.10",
116116
"@types/node": "^20.19.0",
117-
"@types/vscode": "~1.98.0",
117+
"@types/vscode": "~1.100.0",
118118
"@typescript-eslint/eslint-plugin": "^8.34.0",
119119
"@typescript-eslint/parser": "^8.34.0",
120120
"@vscode/test-electron": "^2.5.2",

src/DocumentWatcher.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,31 @@ import {
88
window,
99
workspace,
1010
} from 'vscode'
11+
import { KnownProps } from 'editorconfig'
12+
1113
import {
1214
InsertFinalNewline,
1315
PreSaveTransformation,
1416
SetEndOfLine,
1517
TrimTrailingWhitespace,
1618
} from './transformations'
17-
1819
import {
1920
applyTextEditorOptions,
2021
resolveCoreConfig,
2122
resolveFile,
2223
resolveTextEditorOptions,
2324
} from './api'
2425

26+
type Charset = Exclude<KnownProps['charset'], undefined | 'unset'>
27+
type EncodingMap = Record<Charset, TextDocument['encoding']>
28+
const encodingMap = {
29+
'utf-8': 'utf8',
30+
'utf-8-bom': 'utf8bom',
31+
'utf-16le': 'utf16le',
32+
'utf-16be': 'utf16be',
33+
latin1: 'iso88591',
34+
} as const satisfies EncodingMap
35+
2536
export default class DocumentWatcher {
2637
private disposable: Disposable
2738
private preSaveTransformations: PreSaveTransformation[] = [
@@ -65,6 +76,8 @@ export default class DocumentWatcher {
6576
if (path.basename(doc.fileName) === '.editorconfig') {
6677
this.log('.editorconfig file saved.')
6778
}
79+
// in case document was dirty on open/text editor change
80+
this.handleDocumentEncoding(doc)
6881
}),
6982
)
7083

@@ -78,6 +91,10 @@ export default class DocumentWatcher {
7891
}),
7992
)
8093

94+
subscriptions.push(
95+
workspace.onDidOpenTextDocument(this.handleDocumentEncoding),
96+
)
97+
8198
this.disposable = Disposable.from.apply(this, subscriptions)
8299
this.log('Document watcher initialized')
83100
}
@@ -156,6 +173,39 @@ export default class DocumentWatcher {
156173
onNoActiveTextEditor: this.onNoActiveTextEditor,
157174
onSuccess: this.onSuccess,
158175
})
176+
this.handleDocumentEncoding(editor.document)
159177
}
160178
}
179+
180+
private async handleDocumentEncoding(document: TextDocument) {
181+
const relativePath = workspace.asRelativePath(document.fileName)
182+
const editorconfigSettings = await resolveCoreConfig(document, {
183+
onBeforeResolve: this.onBeforeResolve,
184+
})
185+
186+
const { charset } = editorconfigSettings
187+
this.log(`${relativePath}: Target charset is`, charset ?? 'not set')
188+
if (!charset) {
189+
return
190+
}
191+
if (!(charset in encodingMap)) {
192+
this.log(`${relativePath}: Unsupported charset`)
193+
return
194+
}
195+
196+
const targetEncoding = encodingMap[charset as keyof typeof encodingMap]
197+
if (targetEncoding === document.encoding) {
198+
return
199+
}
200+
201+
if (document.isDirty) {
202+
this.log(`${relativePath}: Cannot change encoding, document is dirty`)
203+
return
204+
}
205+
206+
this.log(`${relativePath}: Re-opening document with ${targetEncoding} encoding...`)
207+
await workspace.openTextDocument(document.uri, {
208+
encoding: targetEncoding,
209+
})
210+
}
161211
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
root = true
2+
3+
[*]
4+
charset = latin1
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
root = true
2+
3+
[*]
4+
charset = unset
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-16be
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-16le
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8-bom
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8

src/test/suite/index.test.ts

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,51 @@ suite('EditorConfig extension', function () {
315315
'editor selection end line changed',
316316
)
317317
})
318+
319+
test('charset (utf-8)', async () => {
320+
const document = await withSetting('charset', 'utf-8').createDoc()
321+
assert.strictEqual(
322+
document.encoding,
323+
'utf8',
324+
`document encoding is ${document.encoding} instead of utf8`,
325+
)
326+
})
327+
328+
test('charset (utf-8-bom)', async () => {
329+
const document = await withSetting('charset', 'utf-8-bom').createDoc()
330+
assert.strictEqual(
331+
document.encoding,
332+
'utf8bom',
333+
`document encoding is ${document.encoding} instead of utf8bom`,
334+
)
335+
})
336+
337+
test('charset (utf-16le)', async () => {
338+
const document = await withSetting('charset', 'utf-16le').createDoc()
339+
assert.strictEqual(
340+
document.encoding,
341+
'utf16le',
342+
`document encoding is ${document.encoding} instead of utf16le`,
343+
)
344+
})
345+
346+
test('charset (utf-16be)', async () => {
347+
const document = await withSetting('charset', 'utf-16be').createDoc()
348+
assert.strictEqual(
349+
document.encoding,
350+
'utf16be',
351+
`document encoding is ${document.encoding} instead of utf16be`,
352+
)
353+
})
354+
355+
test('charset (latin1)', async () => {
356+
const document = await withSetting('charset', 'latin1').createDoc()
357+
assert.strictEqual(
358+
document.encoding,
359+
'iso88591',
360+
`document encoding is ${document.encoding} instead of iso88591`,
361+
)
362+
})
318363
})
319364

320365
function withSetting(
@@ -327,11 +372,13 @@ function withSetting(
327372
) {
328373
return {
329374
async getText() {
330-
return (await createDoc(options.contents, options.fileName)).getText()
375+
return (
376+
await this.createDoc(options.contents, options.fileName)
377+
).getText()
331378
},
332379
saveText(text: string) {
333380
return new Promise<string>(async resolve => {
334-
const doc = await createDoc(options.contents, options.fileName)
381+
const doc = await this.createDoc(options.contents, options.fileName)
335382
workspace.onDidChangeTextDocument(doc.save)
336383
workspace.onDidSaveTextDocument(savedDoc => {
337384
assert.strictEqual(savedDoc.isDirty, false, 'dirty saved doc')
@@ -346,15 +393,15 @@ function withSetting(
346393
)
347394
})
348395
},
349-
}
350-
async function createDoc(contents = '', name = 'test') {
351-
const uri = await utils.createFile(
352-
contents,
353-
getFixturePath([rule, value, name]),
354-
)
355-
const doc = await workspace.openTextDocument(uri)
356-
await window.showTextDocument(doc)
357-
await wait(50) // wait for EditorConfig to apply new settings
358-
return doc
396+
async createDoc(contents = '', name = 'test') {
397+
const uri = await utils.createFile(
398+
contents,
399+
getFixturePath([rule, value, name]),
400+
)
401+
const doc = await workspace.openTextDocument(uri)
402+
await window.showTextDocument(doc)
403+
await wait(50) // wait for EditorConfig to apply new settings
404+
return doc
405+
},
359406
}
360407
}

0 commit comments

Comments
 (0)