Skip to content

Commit

Permalink
feat: add qmlformat support
Browse files Browse the repository at this point in the history
Fix #263
  • Loading branch information
seanwu1105 committed Sep 10, 2023
1 parent e86cb12 commit 00a8955
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 32 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ All features support multi-root workspace project.
- Support `.qmllint.ini` configuration file
- Code completion (requires PySide6 >= 6.4)
- Preview QML file in a separate window (requires PySide6)
- Format QML file (requires PySide6 >= 6.5.2)

### Qt UI Files

Expand Down
29 changes: 1 addition & 28 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,6 @@
"command": "qtForPython.compileTranslations",
"title": "Compile Qt Translation File (lrelease)",
"category": "Qt for Python"
},
{
"command": "qtForPython.formatQml",
"title": "Format QML File (qmlformat)",
"category": "Qt for Python"
}
],
"menus": {
Expand Down Expand Up @@ -191,11 +186,6 @@
"command": "qtForPython.compileTranslations",
"when": "resourceExtname == .ts && resourceLangId == xml",
"group": "qtForPython"
},
{
"command": "qtForPython.formatQml",
"when": "resourceLangId == qml",
"group": "qtForPython"
}
],
"explorer/context": [
Expand Down Expand Up @@ -238,11 +228,6 @@
"command": "qtForPython.compileTranslations",
"when": "resourceExtname == .ts && resourceLangId == xml",
"group": "qtForPython"
},
{
"command": "qtForPython.formatQml",
"when": "resourceLangId == qml",
"group": "qtForPython"
}
],
"editor/title": [
Expand Down Expand Up @@ -285,11 +270,6 @@
"command": "qtForPython.compileTranslations",
"when": "resourceExtname == .ts && resourceLangId == xml",
"group": "qtForPython"
},
{
"command": "qtForPython.formatQml",
"when": "resourceLangId == qml",
"group": "qtForPython"
}
],
"editor/context": [
Expand Down Expand Up @@ -332,11 +312,6 @@
"command": "qtForPython.compileTranslations",
"when": "resourceExtname == .ts && resourceLangId == xml",
"group": "qtForPython"
},
{
"command": "qtForPython.formatQml",
"when": "resourceLangId == qml",
"group": "qtForPython"
}
]
},
Expand Down Expand Up @@ -512,9 +487,7 @@
"items": {
"type": "string"
},
"default": [
"--inplace"
],
"default": [],
"markdownDescription": "The options passed to `qmlformat` executable for QML formatting. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
"scope": "resource"
}
Expand Down
2 changes: 2 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { catchError, of } from 'rxjs'
import type { ExtensionContext, OutputChannel } from 'vscode'
import { window } from 'vscode'
import { registerCommands$ } from './commands'
import { registerQmlFormatter$ } from './qmlformat/format-qml'
import { registerQmlLanguageServer$ } from './qmlls/client'
import { registerQssColorProvider } from './qss/color-provider'
import { registerRccLiveExecution$ } from './rcc/rcc-live-execution'
Expand All @@ -27,6 +28,7 @@ export async function activate({
registerUicLiveExecution$({ extensionUri }),
registerRccLiveExecution$({ extensionUri }),
registerQmlLanguageServer$({ extensionUri, outputChannel }),
registerQmlFormatter$({ extensionUri }),
]

const observer: Partial<Observer<Result>> = {
Expand Down
65 changes: 65 additions & 0 deletions src/qmlformat/format-qml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ReplaySubject, firstValueFrom, using } from 'rxjs'
import { Range, TextEdit, languages } from 'vscode'
import type { URI } from 'vscode-uri'
import type { ExecError, StdErrError } from '../run'
import { run } from '../run'
import { getToolCommand$ } from '../tool-utils'
import type { SuccessResult } from '../types'
import { type ErrorResult } from '../types'

export function registerQmlFormatter$({
extensionUri,
}: {
readonly extensionUri: URI
}) {
const formatResult$ = new ReplaySubject<
SuccessResult<string> | ErrorResult<'NotFound'> | ExecError | StdErrError
>(1)

return using(
() => {
const disposable = languages.registerDocumentFormattingEditProvider(
'qml',
{
async provideDocumentFormattingEdits(document) {
const getToolCommandResult = await firstValueFrom(
getToolCommand$({
tool: 'qmlformat',
extensionUri,
resource: document.uri,
}),
)
if (getToolCommandResult.kind !== 'Success') {
formatResult$.next(getToolCommandResult)
return []
}

const { command, options } = getToolCommandResult.value
const runResult = await run({
command: [...command, ...options, document.uri.fsPath],
})
if (runResult.kind !== 'Success') {
formatResult$.next(runResult)
return []
}

const formatted = runResult.value.stdout
const fullRange = document.validateRange(
new Range(
document.lineAt(0).range.start,
document.lineAt(document.lineCount - 1).range.end,
),
)
formatResult$.next({
kind: 'Success',
value: `Formatted ${document.uri.fsPath}`,
})
return [TextEdit.replace(fullRange, formatted)]
},
},
)
return { unsubscribe: () => disposable.dispose() }
},
() => formatResult$.asObservable(),
)
}
52 changes: 52 additions & 0 deletions src/test/suite/qmlformat/format-qml.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as assert from 'node:assert'
import * as path from 'node:path'
import type { TextDocument, TextEdit } from 'vscode'
import { WorkspaceEdit, commands, window, workspace } from 'vscode'
import { URI } from 'vscode-uri'
import {
E2E_TIMEOUT,
TEST_ASSETS_PATH,
setupE2EEnvironment,
} from '../test-utils'

suite('format-qml/e2e', () => {
suiteSetup(async function () {
this.timeout(E2E_TIMEOUT)
await setupE2EEnvironment()
})

suite('when a qml file is open', () => {
const sampleFilenameNoExt = 'unformatted'
let document: TextDocument

setup(async function () {
this.timeout(E2E_TIMEOUT)

document = await workspace.openTextDocument(
URI.file(
path.resolve(TEST_ASSETS_PATH, 'qml', `${sampleFilenameNoExt}.qml`),
),
)
await window.showTextDocument(document)
})

teardown(async function () {
this.timeout(E2E_TIMEOUT)
await commands.executeCommand('workbench.action.closeActiveEditor')
})

test('should be able to run formatQml command', async () => {
const originalContent = document.getText()
const edits: TextEdit[] = await commands.executeCommand(
'vscode.executeFormatDocumentProvider',
document.uri,
)

const workspaceEdit = new WorkspaceEdit()
workspaceEdit.set(document.uri, edits)
await workspace.applyEdit(workspaceEdit)

return assert.notDeepStrictEqual(originalContent, document.getText())
}).timeout(E2E_TIMEOUT)
})
}).timeout(E2E_TIMEOUT)
12 changes: 8 additions & 4 deletions src/test/suite/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as assert from 'node:assert'
import * as path from 'node:path'
import { extensions, workspace } from 'vscode'
import { URI } from 'vscode-uri'
import { notNil } from '../../utils'
import { isNil, notNil } from '../../utils'

// eslint-disable-next-line @typescript-eslint/no-var-requires
const { name, publisher } = require('../../../package.json')
Expand Down Expand Up @@ -55,16 +55,20 @@ export async function waitFor<T>(
}

const start = Date.now()
let error: unknown | undefined
while (Date.now() - start < (options?.timeout ?? defaultOptions.timeout)) {
try {
return await callback()
} catch (e) {
error = e
await sleep(options?.interval ?? defaultOptions.interval)
}
}
throw new Error(
`Timeout during waitFor: ${options?.timeout ?? defaultOptions.timeout}ms`,
)
if (isNil(error))
throw new Error(
`Timeout during waitFor: ${options?.timeout ?? defaultOptions.timeout}ms`,
)
throw error
}

export async function forceDeleteFile(filename: string) {
Expand Down

0 comments on commit 00a8955

Please sign in to comment.