From 99f3f79ea4b7f6c926990fe17662eab55d658800 Mon Sep 17 00:00:00 2001 From: Shuang Wu Date: Sun, 12 Feb 2023 12:46:41 -0500 Subject: [PATCH] feat: add configuration to enable/disable RCC live execution --- package.json | 6 + src/rcc/rcc-live-execution.ts | 38 +++++- src/test/suite/qss/e2e.test.ts | 11 +- src/test/suite/rcc/rcc-live-execution.test.ts | 126 ++++++++++++++++++ src/test/suite/test-utils.ts | 1 + src/test/suite/uic/uic-live-execution.test.ts | 2 +- 6 files changed, 177 insertions(+), 7 deletions(-) create mode 100644 src/test/suite/rcc/rcc-live-execution.test.ts diff --git a/package.json b/package.json index 8516ede..f0a3d9a 100644 --- a/package.json +++ b/package.json @@ -238,6 +238,12 @@ "markdownDescription": "The options passed to Qt `qmlls` executable. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.", "scope": "window" }, + "qtForPython.rcc.liveExecution": { + "type": "boolean", + "default": true, + "markdownDescription": "Enable live execution of Qt `rcc` executable. This will automatically compile the resource file when it is saved.", + "scope": "resource" + }, "qtForPython.rcc.path": { "type": "string", "default": "", diff --git a/src/rcc/rcc-live-execution.ts b/src/rcc/rcc-live-execution.ts index db2cc99..64fc1e5 100644 --- a/src/rcc/rcc-live-execution.ts +++ b/src/rcc/rcc-live-execution.ts @@ -2,10 +2,21 @@ import type { X2jOptionsOptional } from 'fast-xml-parser' import { XMLParser } from 'fast-xml-parser' import * as fs from 'node:fs/promises' import * as path from 'node:path' -import { concatMap, defer, from, merge, mergeMap, of, startWith } from 'rxjs' +import { + concatMap, + defer, + firstValueFrom, + from, + merge, + mergeMap, + of, + startWith, +} from 'rxjs' import { RelativePattern, workspace } from 'vscode' import { URI } from 'vscode-uri' import { z } from 'zod' +import { getConfiguration$ } from '../configurations' +import { EXTENSION_NAMESPACE } from '../constants' import type { ErrorResult, SuccessResult } from '../types' import { getWatcher$ } from '../watcher' import { compileResource } from './compile-resource' @@ -13,9 +24,9 @@ import { compileResource } from './compile-resource' export function registerRccLiveExecution$({ extensionUri, }: RegisterRccLiveExecutionArgs) { - const qrcFiles$ = defer(async () => workspace.findFiles('**/*.qrc')).pipe( - concatMap(uris => from(uris)), - ) + const qrcFiles$ = defer(async () => + workspace.findFiles('**/*.qrc', '**/site-packages/**'), + ).pipe(concatMap(uris => from(uris))) return merge(qrcFiles$, getWatcher$('**/*.qrc')).pipe( mergeMap(qrcUri => @@ -40,7 +51,24 @@ function registerResourcesLiveExecution$({ ...result.value.map(uri => getWatcher$(new RelativePattern(uri, '*'))), ).pipe( startWith(undefined), // Trigger compilation on resource file changes - concatMap(async () => compileResource({ extensionUri }, qrcUri)), + concatMap(() => + firstValueFrom( + getConfiguration$({ + section: `${EXTENSION_NAMESPACE}.rcc`, + key: 'liveExecution', + defaultValue: true, + resource: qrcUri, + }), + ), + ), + concatMap(async enabled => { + if (!enabled) + return { + kind: 'Success', + value: 'Live execution disabled', + } as const + return compileResource({ extensionUri }, qrcUri) + }), ) }), ) diff --git a/src/test/suite/qss/e2e.test.ts b/src/test/suite/qss/e2e.test.ts index 008932b..ab5ca62 100644 --- a/src/test/suite/qss/e2e.test.ts +++ b/src/test/suite/qss/e2e.test.ts @@ -3,9 +3,18 @@ import * as path from 'node:path' import type { ColorInformation, TextDocument } from 'vscode' import { commands, window, workspace } from 'vscode' import { URI } from 'vscode-uri' -import { E2E_TIMEOUT, TEST_ASSETS_PATH } from '../test-utils' +import { + E2E_TIMEOUT, + setupE2EEnvironment, + TEST_ASSETS_PATH, +} from '../test-utils' suite('qss/e2e', () => { + suiteSetup(async function () { + this.timeout(E2E_TIMEOUT) + await setupE2EEnvironment() + }) + suite('color picker', () => { suite('when open QLabel.qss', () => { let document: TextDocument diff --git a/src/test/suite/rcc/rcc-live-execution.test.ts b/src/test/suite/rcc/rcc-live-execution.test.ts new file mode 100644 index 0000000..1c72191 --- /dev/null +++ b/src/test/suite/rcc/rcc-live-execution.test.ts @@ -0,0 +1,126 @@ +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, + waitFor, +} from '../test-utils' + +suite('rcc-live-execution/e2e', () => { + const sampleQrcFilenameNoExt = 'sample' + + let originalFullText: string + + suiteSetup(async function () { + this.timeout(E2E_TIMEOUT) + await setupE2EEnvironment() + }) + + setup(async function () { + this.timeout(E2E_TIMEOUT) + await removeGeneratedFile(sampleQrcFilenameNoExt) + }) + + teardown(async function () { + this.timeout(E2E_TIMEOUT) + await removeGeneratedFile(sampleQrcFilenameNoExt) + }) + + suite('when qrc file changed', () => { + const qrcFilePath = path.resolve( + TEST_ASSETS_PATH, + 'qrc', + `${sampleQrcFilenameNoExt}.qrc`, + ) + + setup(async function () { + this.timeout(E2E_TIMEOUT) + originalFullText = fs.readFileSync(qrcFilePath, { encoding: 'utf-8' }) + }) + + teardown(function () { + this.timeout(E2E_TIMEOUT) + fs.writeFileSync(qrcFilePath, originalFullText, { encoding: 'utf-8' }) + }) + + test('should recompile', async () => { + fs.writeFileSync( + qrcFilePath, + originalFullText.replace(/rc0.txt<\/file>/gi, ''), + ) + + await waitFor(() => + assert.ok( + fs.existsSync( + path.resolve( + TEST_ASSETS_PATH, + 'qrc', + `rc_${sampleQrcFilenameNoExt}.py`, + ), + ), + ), + ) + }).timeout(E2E_TIMEOUT) + }) + + suite('when a resource file changed', () => { + const sampleResourceFilename = 'rc1.txt' + + const resourceFilePath = path.resolve( + TEST_ASSETS_PATH, + 'qrc', + sampleResourceFilename, + ) + + setup(async function () { + this.timeout(E2E_TIMEOUT) + originalFullText = fs.readFileSync(resourceFilePath, { + encoding: 'utf-8', + }) + }) + + teardown(function () { + this.timeout(E2E_TIMEOUT) + fs.writeFileSync(resourceFilePath, originalFullText, { + encoding: 'utf-8', + }) + }) + + test('should recompile', async () => { + fs.writeFileSync( + resourceFilePath, + originalFullText.replace(/hello/gi, 'world'), + ) + + await waitFor(() => + assert.ok( + fs.existsSync( + path.resolve( + TEST_ASSETS_PATH, + 'qrc', + `rc_${sampleQrcFilenameNoExt}.py`, + ), + ), + ), + ) + }) + }) +}).timeout(E2E_TIMEOUT) + +async function removeGeneratedFile(sampleFilenameNoExt: string) { + return waitFor(async () => { + await sleep() // Wait for the file to be created asynchronously by the extension + fs.rmSync( + path.resolve(TEST_ASSETS_PATH, 'qrc', `rc_${sampleFilenameNoExt}.py`), + { force: true, recursive: true }, + ) + assert.ok( + !fs.existsSync( + path.resolve(TEST_ASSETS_PATH, 'qrc', `rc_${sampleFilenameNoExt}.py`), + ), + ) + }) +} diff --git a/src/test/suite/test-utils.ts b/src/test/suite/test-utils.ts index 7f0ceed..9a6b4f1 100644 --- a/src/test/suite/test-utils.ts +++ b/src/test/suite/test-utils.ts @@ -26,6 +26,7 @@ export async function sleep(ms = DEFAULT_TIMEOUT) { return new Promise(resolve => setTimeout(resolve, ms)) } +// TODO: This might not be necessary export async function setupE2EEnvironment() { await sleep() // wait for extension to load Extension Gallery diff --git a/src/test/suite/uic/uic-live-execution.test.ts b/src/test/suite/uic/uic-live-execution.test.ts index edd6afa..40661a8 100644 --- a/src/test/suite/uic/uic-live-execution.test.ts +++ b/src/test/suite/uic/uic-live-execution.test.ts @@ -40,7 +40,7 @@ suite('uic-live-execution/e2e', () => { removeGeneratedFile(sampleFilenameNoExt) }) - test('should recompile UI file changed', async () => { + test('should recompile when UI file changed', async () => { fs.writeFileSync( uiFilePath, originalFullText.replace(/My Window Title/gi, 'My New Window Title'),