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

Commit 50e46fe

Browse files
committed
feat(designer): support Qt designer features
1 parent 2df9db2 commit 50e46fe

File tree

18 files changed

+252
-25
lines changed

18 files changed

+252
-25
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@
2727
- [x] `qmldir`
2828
- [ ] `qss`
2929
- [x] `qrc`
30-
- [ ] `ui`
30+
- [x] `ui`
3131
- [ ] ~~`qt.ts`~~
3232
- [ ] ~~`pro.user`~~
3333
- [ ] ~~qmake `pro`~~
3434
- [ ] Simple tools integration
3535
- [x] pyside6-rcc
3636
- [x] pyside6-uic
37-
- [ ] pyside6-designer
37+
- [x] pyside6-designer
3838
- [ ] pyside6-project
3939
- [ ] ~~i18n tools~~
4040
- [ ] Continuous compilation

package.json

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@
5353
"activationEvents": [
5454
"onLanguage:qml",
5555
"onCommand:qtForPython.compileResource",
56-
"onCommand:qtForPython.compileUi"
56+
"onCommand:qtForPython.compileUi",
57+
"onCommand:qtForPython.createUi",
58+
"onCommand:qtForPython.editUi"
5759
],
5860
"contributes": {
5961
"languages": [
@@ -98,6 +100,16 @@
98100
"command": "qtForPython.compileUi",
99101
"title": "Compile Qt UI File",
100102
"category": "Qt for Python"
103+
},
104+
{
105+
"command": "qtForPython.createUi",
106+
"title": "Create Qt UI File",
107+
"category": "Qt for Python"
108+
},
109+
{
110+
"command": "qtForPython.editUi",
111+
"title": "Edit Qt UI File",
112+
"category": "Qt for Python"
101113
}
102114
],
103115
"menus": {
@@ -111,6 +123,16 @@
111123
"command": "qtForPython.compileUi",
112124
"when": "resourceExtname == .ui && resourceLangId == xml",
113125
"group": "qtForPython"
126+
},
127+
{
128+
"command": "qtForPython.createUi",
129+
"when": "explorerResourceIsFolder == true",
130+
"group": "qtForPython"
131+
},
132+
{
133+
"command": "qtForPython.editUi",
134+
"when": "resourceExtname == .ui && resourceLangId == xml",
135+
"group": "qtForPython"
114136
}
115137
],
116138
"explorer/context": [
@@ -123,6 +145,16 @@
123145
"command": "qtForPython.compileUi",
124146
"when": "resourceExtname == .ui && resourceLangId == xml",
125147
"group": "qtForPython"
148+
},
149+
{
150+
"command": "qtForPython.createUi",
151+
"when": "explorerResourceIsFolder == true",
152+
"group": "qtForPython"
153+
},
154+
{
155+
"command": "qtForPython.editUi",
156+
"when": "resourceExtname == .ui && resourceLangId == xml",
157+
"group": "qtForPython"
126158
}
127159
],
128160
"editor/title": [
@@ -135,6 +167,16 @@
135167
"command": "qtForPython.compileUi",
136168
"when": "resourceExtname == .ui && resourceLangId == xml",
137169
"group": "qtForPython"
170+
},
171+
{
172+
"command": "qtForPython.createUi",
173+
"when": "explorerResourceIsFolder == true",
174+
"group": "qtForPython"
175+
},
176+
{
177+
"command": "qtForPython.editUi",
178+
"when": "resourceExtname == .ui && resourceLangId == xml",
179+
"group": "qtForPython"
138180
}
139181
],
140182
"editor/context": [
@@ -147,6 +189,16 @@
147189
"command": "qtForPython.compileUi",
148190
"when": "resourceExtname == .ui && resourceLangId == xml",
149191
"group": "qtForPython"
192+
},
193+
{
194+
"command": "qtForPython.createUi",
195+
"when": "explorerResourceIsFolder == true",
196+
"group": "qtForPython"
197+
},
198+
{
199+
"command": "qtForPython.editUi",
200+
"when": "resourceExtname == .ui && resourceLangId == xml",
201+
"group": "qtForPython"
150202
}
151203
]
152204
},
@@ -156,13 +208,13 @@
156208
"qtForPython.qmllint.enabled": {
157209
"type": "boolean",
158210
"default": true,
159-
"markdownDescription": "Enable the qmllint integration.",
211+
"markdownDescription": "Enable the Qt `qmllint` integration.",
160212
"scope": "resource"
161213
},
162214
"qtForPython.qmllint.path": {
163215
"type": "string",
164216
"default": "",
165-
"markdownDescription": "The path to `qmllint` executable. Set to empty string to automatically resolve from the installed Python package. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
217+
"markdownDescription": "The path to Qt `qmllint` executable. Set to empty string to automatically resolve from the installed Python package. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
166218
"scope": "resource"
167219
},
168220
"qtForPython.qmllint.options": {
@@ -171,13 +223,13 @@
171223
"type": "string"
172224
},
173225
"default": [],
174-
"markdownDescription": "The options passed to `qmllint` executable. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
226+
"markdownDescription": "The options passed to Qt `qmllint` executable. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
175227
"scope": "resource"
176228
},
177229
"qtForPython.rcc.path": {
178230
"type": "string",
179231
"default": "",
180-
"markdownDescription": "The path to `rcc` executable. Set to empty string to automatically resolve from the installed Python package. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
232+
"markdownDescription": "The path to Qt `rcc` executable. Set to empty string to automatically resolve from the installed Python package. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
181233
"scope": "resource"
182234
},
183235
"qtForPython.rcc.options": {
@@ -189,13 +241,13 @@
189241
"-o",
190242
"${resourceDirname}${pathSeparator}rc_${resourceBasenameNoExtension}.py"
191243
],
192-
"markdownDescription": "The options passed to `rcc` executable. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
244+
"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.",
193245
"scope": "resource"
194246
},
195247
"qtForPython.uic.path": {
196248
"type": "string",
197249
"default": "",
198-
"markdownDescription": "The path to `uic` executable. Set to empty string to automatically resolve from the installed Python package. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
250+
"markdownDescription": "The path to Qt `uic` executable. Set to empty string to automatically resolve from the installed Python package. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
199251
"scope": "resource"
200252
},
201253
"qtForPython.uic.options": {
@@ -207,7 +259,22 @@
207259
"-o",
208260
"${resourceDirname}${pathSeparator}ui_${resourceBasenameNoExtension}.py"
209261
],
210-
"markdownDescription": "The options passed to `uic` executable. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
262+
"markdownDescription": "The options passed to Qt `uic` executable. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
263+
"scope": "resource"
264+
},
265+
"qtForPython.designer.path": {
266+
"type": "string",
267+
"default": "",
268+
"markdownDescription": "The path to Qt `designer` executable. Set to empty string to automatically resolve from the installed Python package. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
269+
"scope": "resource"
270+
},
271+
"qtForPython.designer.options": {
272+
"type": "array",
273+
"items": {
274+
"type": "string"
275+
},
276+
"default": [],
277+
"markdownDescription": "The options passed to Qt `designer` executable. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
211278
"scope": "resource"
212279
}
213280
}

python/.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ omit =
44
scripts/qmllint.py
55
scripts/rcc.py
66
scripts/uic.py
7+
scripts/designer.py
78

89
[report]
910
fail_under = 100

python/scripts/designer.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import re
2+
import sys
3+
4+
from utils import is_installed
5+
6+
if __name__ == "__main__":
7+
if is_installed("PySide6"):
8+
from PySide6.scripts.pyside_tool import designer
9+
else:
10+
sys.exit("No rcc can be found in current Python environment.")
11+
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
12+
sys.exit(designer())

python/tests/test_designer.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import os
2+
import subprocess
3+
4+
import pytest
5+
6+
from tests import ASSETS_DIR, SCRIPTS_DIR
7+
8+
9+
@pytest.mark.skip(reason="a GUI app cannot be closed gracefully")
10+
def test_designer_sample_ui():
11+
filename = "sample.ui"
12+
result = invoke_designer_py([get_assets_path(filename)])
13+
assert result.returncode == 0
14+
assert len(result.stdout.decode("utf-8")) > 0
15+
16+
17+
def invoke_designer_py(args: list[str]):
18+
return subprocess.run(
19+
["poetry", "run", "python", "designer.py", *args],
20+
cwd=SCRIPTS_DIR,
21+
capture_output=True,
22+
)
23+
24+
25+
def get_assets_path(filename: str) -> str:
26+
return os.path.join(ASSETS_DIR, "ui", filename)

src/commands.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import type { ExtensionContext, Uri } from 'vscode'
22
import { window } from 'vscode'
33
import { URI } from 'vscode-uri'
4-
import { compileResource } from './rcc/compile'
4+
import { createUi } from './designer/create-ui'
5+
import { editUi } from './designer/edit-ui'
6+
import { compileResource } from './rcc/compile-resource'
57
import type { ErrorResult, SuccessResult } from './types'
6-
import { compileUi } from './uic/compile'
8+
import { compileUi } from './uic/compile-ui'
79
import { isNil } from './utils'
810

911
export const COMMANDS = [
@@ -15,6 +17,14 @@ export const COMMANDS = [
1517
name: 'compileUi',
1618
callback: compileUi,
1719
},
20+
{
21+
name: 'createUi',
22+
callback: createUi,
23+
},
24+
{
25+
name: 'editUi',
26+
callback: editUi,
27+
},
1828
] as const
1929

2030
export type CommandDeps = Pick<ExtensionContext, 'extensionPath'>

src/designer/create-ui.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import * as fs from 'node:fs'
2+
import * as path from 'node:path'
3+
import type { URI } from 'vscode-uri'
4+
import type { CommandDeps } from '../commands'
5+
import { getTargetDocumentUri } from '../commands'
6+
import type { ExecError, StdErrError } from '../run'
7+
import { run } from '../run'
8+
import { getToolCommand } from '../tool-utils'
9+
import type { ErrorResult, SuccessResult } from '../types'
10+
import { notNil } from '../utils'
11+
12+
export async function createUi(
13+
{ extensionPath }: CommandDeps,
14+
...args: any[]
15+
): Promise<CreateUiResult> {
16+
const targetDocumentUriResult = getTargetDocumentUri(...args)
17+
18+
if (targetDocumentUriResult.kind !== 'Success') return targetDocumentUriResult
19+
20+
const uri = targetDocumentUriResult.value
21+
22+
const getToolCommandResult = await getToolCommand({
23+
tool: 'designer',
24+
extensionPath,
25+
resource: uri.toString(),
26+
})
27+
28+
if (getToolCommandResult.kind !== 'Success') return getToolCommandResult
29+
30+
const getDirectoryPathResult = await getDirectoryPath(uri)
31+
32+
if (getDirectoryPathResult.kind !== 'Success') return getDirectoryPathResult
33+
34+
return run({
35+
command: [
36+
...getToolCommandResult.value.command,
37+
...getToolCommandResult.value.options,
38+
],
39+
cwd: getDirectoryPathResult.value,
40+
})
41+
}
42+
43+
type CreateUiResult =
44+
| SuccessResult<string>
45+
| ExecError
46+
| StdErrError
47+
| ErrorResult<'NotFound'>
48+
| ErrorResult<'Type'>
49+
| ErrorResult<'IO'>
50+
51+
async function getDirectoryPath(uri: URI): Promise<GetDirectoryPathResult> {
52+
return new Promise<GetDirectoryPathResult>(resolve => {
53+
fs.lstat(uri.fsPath, (err, stats) => {
54+
if (notNil(err))
55+
resolve({ kind: 'IOError', message: `${JSON.stringify(err)}` })
56+
else if (stats.isDirectory())
57+
resolve({ kind: 'Success', value: uri.fsPath })
58+
else resolve({ kind: 'Success', value: path.dirname(uri.fsPath) })
59+
})
60+
})
61+
}
62+
63+
type GetDirectoryPathResult = SuccessResult<string> | ErrorResult<'IO'>

src/designer/edit-ui.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { CommandDeps } from '../commands'
2+
import { getTargetDocumentUri } from '../commands'
3+
import type { ExecError, StdErrError } from '../run'
4+
import { run } from '../run'
5+
import { getToolCommand } from '../tool-utils'
6+
import type { ErrorResult, SuccessResult } from '../types'
7+
8+
export async function editUi(
9+
{ extensionPath }: CommandDeps,
10+
...args: any[]
11+
): Promise<EditUiResult> {
12+
const targetDocumentUriResult = getTargetDocumentUri(...args)
13+
14+
if (targetDocumentUriResult.kind !== 'Success') return targetDocumentUriResult
15+
16+
const uiFile = targetDocumentUriResult.value
17+
18+
const getToolCommandResult = await getToolCommand({
19+
tool: 'designer',
20+
extensionPath,
21+
resource: uiFile.toString(),
22+
})
23+
24+
if (getToolCommandResult.kind !== 'Success') return getToolCommandResult
25+
26+
return run({
27+
command: [
28+
...getToolCommandResult.value.command,
29+
...getToolCommandResult.value.options,
30+
uiFile.fsPath,
31+
],
32+
})
33+
}
34+
35+
type EditUiResult =
36+
| SuccessResult<string>
37+
| ExecError
38+
| StdErrError
39+
| ErrorResult<'NotFound'>
40+
| ErrorResult<'Type'>

src/extension.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ function onResultReceived(
7272
| ErrorResult<'Unexpected'>
7373
| ErrorResult<'NotFound'>
7474
| ErrorResult<'Type'>
75-
| ErrorResult<'Parse'>,
75+
| ErrorResult<'Parse'>
76+
| ErrorResult<'IO'>,
7677
) {
7778
const indent = 2
7879
switch (result.kind) {
File renamed without changes.

0 commit comments

Comments
 (0)