Skip to content
This repository has been archived by the owner on Oct 15, 2024. It is now read-only.

Commit

Permalink
feat(uic): add live execution support
Browse files Browse the repository at this point in the history
  • Loading branch information
seanwu1105 committed Aug 28, 2022
1 parent 8c9d06e commit a757644
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 8 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
- [x] pyside6-designer
- [ ] ~~pyside6-project~~ (see https://bugreports.qt.io/browse/PYSIDE-2045)
- [ ] ~~i18n tools~~
- [ ] Continuous compilation
- [ ] uic
- [x] Continuous compilation
- [x] uic
- [ ] ~~rcc~~
- [ ] ~~i18n tools~~
- [ ] Support PyQt6
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
],
"activationEvents": [
"onLanguage:qml",
"workspaceContains:**/*.ui",
"onCommand:qtForPython.compileResource",
"onCommand:qtForPython.compileUi",
"onCommand:qtForPython.createUi",
Expand Down Expand Up @@ -244,6 +245,11 @@
"markdownDescription": "The options passed to Qt `rcc` executable. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
"scope": "resource"
},
"qtForPython.uic.liveExecution": {
"type": "boolean",
"default": true,
"markdownDescription": "Automatically recompile Qt UI files when any `*.ui` file has changed or created."
},
"qtForPython.uic.path": {
"type": "string",
"default": "",
Expand Down
4 changes: 2 additions & 2 deletions python/tests/assets/ui/sample.ui
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
<string>My Window Title</string>
</property>
<widget class="QWidget" name="centralWidget">
<widget class="QPushButton" name="pushButton">
Expand Down Expand Up @@ -51,4 +51,4 @@
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>
</ui>
13 changes: 10 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,32 @@ import {
} from './qmllint/client'
import type { ExecError, StdErrError } from './run'
import type { ErrorResult, SuccessResult } from './types'
import { registerUicLiveExecution } from './uic/uic-live-execution'
import { notNil } from './utils'

let outputChannel: ReturnType<typeof window.createOutputChannel>
let qmlLintClient: LanguageClient | undefined = undefined

export async function activate(context: ExtensionContext) {
outputChannel = window.createOutputChannel('Qt for Python')

registerCommands(context)

registerUicLiveExecution({ context, onResultReceived })

await activateQmlLintFeatures(context)
}

function registerCommands(context: ExtensionContext) {
function registerCommands({ extensionPath, subscriptions }: ExtensionContext) {
return COMMANDS.map(command =>
context.subscriptions.push(
subscriptions.push(
commands.registerCommand(
`${EXTENSION_NAMESPACE}.${command.name}`,
async (...args) => {
try {
return onResultReceived(await command.callback(context, ...args))
return onResultReceived(
await command.callback({ extensionPath }, ...args),
)
} catch (e) {
return onResultReceived({
kind: 'UnexpectedError',
Expand Down
6 changes: 5 additions & 1 deletion src/qmllint/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ export async function startClient({
documentSelector: [{ scheme: 'file', language: 'qml' }],
}

const client = new LanguageClient('qmllint', serverOptions, clientOptions)
const client = new LanguageClient(
'qmllint Language Server',
serverOptions,
clientOptions,
)

const disposables = [
client,
Expand Down
60 changes: 60 additions & 0 deletions src/test/suite/uic/uic-live-execution.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as assert from 'node:assert'
import * as fs from 'node:fs'
import * as path from 'node:path'
import {
E2E_TIMEOUT,
setupE2EEnvironment,
sleep,
TEST_ASSETS_PATH,
} from '../utils'

suite('uic-live-execution/e2e', () => {
const sampleFilenameNoExt = 'sample'

const uiFilePath = path.resolve(
TEST_ASSETS_PATH,
'ui',
`${sampleFilenameNoExt}.ui`,
)

let originalFullText: string

suiteSetup(async function () {
this.timeout(E2E_TIMEOUT)
await setupE2EEnvironment()
})

setup(async () => {
originalFullText = fs.readFileSync(uiFilePath, { encoding: 'utf-8' })

removeGeneratedRcFile(sampleFilenameNoExt)
})

teardown(() => {
fs.writeFileSync(uiFilePath, originalFullText, { encoding: 'utf-8' })

removeGeneratedRcFile(sampleFilenameNoExt)
})

test('should recompile UI file changed', async () => {
fs.writeFileSync(
uiFilePath,
originalFullText.replace(/My Window Title/gi, 'My New Window Title'),
)

await sleep() // wait for file watcher to trigger

assert.ok(
fs.existsSync(
path.resolve(TEST_ASSETS_PATH, 'ui', `ui_${sampleFilenameNoExt}.py`),
),
)
}).timeout(E2E_TIMEOUT)
}).timeout(E2E_TIMEOUT)

function removeGeneratedRcFile(sampleFilenameNoExt: string) {
return fs.rmSync(
path.resolve(TEST_ASSETS_PATH, 'ui', `ui_${sampleFilenameNoExt}.py`),
{ force: true, recursive: true },
)
}
55 changes: 55 additions & 0 deletions src/uic/uic-live-execution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { ExtensionContext, Uri } from 'vscode'
import { workspace } from 'vscode'
import { EXTENSION_NAMESPACE } from '../constants'
import type { ExecError, StdErrError } from '../run'
import type { ErrorResult, SuccessResult } from '../types'
import { compileUi } from './compile-ui'

export function registerUicLiveExecution({
context: { extensionPath, subscriptions },
onResultReceived,
}: RegisterUicLiveExecutionArgs) {
const watcher = workspace.createFileSystemWatcher('**/*.ui')
watcher.onDidChange(async uri =>
onResultReceived(
await onUiFileUpdated({ uri, extensionPath: extensionPath }),
),
)
watcher.onDidCreate(async uri =>
onResultReceived(
await onUiFileUpdated({ uri, extensionPath: extensionPath }),
),
)
subscriptions.push(watcher)
}

type RegisterUicLiveExecutionArgs = {
readonly context: ExtensionContext
readonly onResultReceived: (result: OnUiFileUpdatedResult) => void
}

async function onUiFileUpdated({
uri,
extensionPath,
}: OnUiFileUpdatedArgs): Promise<OnUiFileUpdatedResult> {
const enabled =
workspace
.getConfiguration(`${EXTENSION_NAMESPACE}.uic`, uri)
.get<boolean>('liveExecution') ?? true

if (!enabled) return { kind: 'Success', value: 'Live execution disabled' }

return compileUi({ extensionPath }, uri)
}

type OnUiFileUpdatedArgs = {
readonly extensionPath: string
readonly uri: Uri
}

type OnUiFileUpdatedResult =
| SuccessResult<string>
| ExecError
| StdErrError
| ErrorResult<'NotFound'>
| ErrorResult<'Type'>

0 comments on commit a757644

Please sign in to comment.