qtmonaco 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qtmonaco-0.1.1/.gitignore +183 -0
- qtmonaco-0.1.1/CHANGELOG.md +25 -0
- qtmonaco-0.1.1/PKG-INFO +22 -0
- qtmonaco-0.1.1/generate_rcc.py +158 -0
- qtmonaco-0.1.1/pyproject.toml +102 -0
- qtmonaco-0.1.1/qtmonaco/__init__.py +0 -0
- qtmonaco-0.1.1/qtmonaco/bridge_base.py +65 -0
- qtmonaco-0.1.1/qtmonaco/editor_bridge.py +200 -0
- qtmonaco-0.1.1/qtmonaco/monaco_page.py +10 -0
- qtmonaco-0.1.1/qtmonaco/monaco_web_view.py +134 -0
- qtmonaco-0.1.1/qtmonaco/pylsp_provider.py +61 -0
- qtmonaco-0.1.1/qtmonaco/resource_loader.py +28 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
**/*_venv
|
|
2
|
+
**/.idea
|
|
3
|
+
*.log
|
|
4
|
+
**/__pycache__
|
|
5
|
+
**/.DS_Store
|
|
6
|
+
**/out
|
|
7
|
+
**/.vscode
|
|
8
|
+
**/.pytest_cache
|
|
9
|
+
**/*.egg*
|
|
10
|
+
.python-version
|
|
11
|
+
|
|
12
|
+
# file writer data
|
|
13
|
+
**.h5
|
|
14
|
+
|
|
15
|
+
# Byte-compiled / optimized / DLL files
|
|
16
|
+
__pycache__/
|
|
17
|
+
*.py[cod]
|
|
18
|
+
*$py.class
|
|
19
|
+
|
|
20
|
+
# C extensions
|
|
21
|
+
*.so
|
|
22
|
+
|
|
23
|
+
# Distribution / packaging
|
|
24
|
+
.Python
|
|
25
|
+
build/
|
|
26
|
+
develop-eggs/
|
|
27
|
+
dist/
|
|
28
|
+
downloads/
|
|
29
|
+
eggs/
|
|
30
|
+
.eggs/
|
|
31
|
+
lib/
|
|
32
|
+
lib64/
|
|
33
|
+
parts/
|
|
34
|
+
sdist/
|
|
35
|
+
var/
|
|
36
|
+
wheels/
|
|
37
|
+
share/python-wheels/
|
|
38
|
+
*.egg-info/
|
|
39
|
+
.installed.cfg
|
|
40
|
+
*.egg
|
|
41
|
+
MANIFEST
|
|
42
|
+
*.rcc
|
|
43
|
+
node_modules
|
|
44
|
+
|
|
45
|
+
# PyInstaller
|
|
46
|
+
# Usually these files are written by a python script from a template
|
|
47
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
48
|
+
*.manifest
|
|
49
|
+
*.spec
|
|
50
|
+
|
|
51
|
+
# Installer logs
|
|
52
|
+
pip-log.txt
|
|
53
|
+
pip-delete-this-directory.txt
|
|
54
|
+
|
|
55
|
+
# Unit test / coverage reports
|
|
56
|
+
htmlcov/
|
|
57
|
+
.tox/
|
|
58
|
+
.nox/
|
|
59
|
+
.coverage
|
|
60
|
+
.coverage.*
|
|
61
|
+
.cache
|
|
62
|
+
nosetests.xml
|
|
63
|
+
coverage.xml
|
|
64
|
+
*.cover
|
|
65
|
+
*.py,cover
|
|
66
|
+
.hypothesis/
|
|
67
|
+
.pytest_cache/
|
|
68
|
+
cover/
|
|
69
|
+
|
|
70
|
+
# Output from end2end testing
|
|
71
|
+
tests/reference_failures/
|
|
72
|
+
|
|
73
|
+
# Translations
|
|
74
|
+
*.mo
|
|
75
|
+
*.pot
|
|
76
|
+
|
|
77
|
+
# Django stuff:
|
|
78
|
+
*.log
|
|
79
|
+
local_settings.py
|
|
80
|
+
db.sqlite3
|
|
81
|
+
db.sqlite3-journal
|
|
82
|
+
|
|
83
|
+
# Flask stuff:
|
|
84
|
+
instance/
|
|
85
|
+
.webassets-cache
|
|
86
|
+
|
|
87
|
+
# Scrapy stuff:
|
|
88
|
+
.scrapy
|
|
89
|
+
|
|
90
|
+
# Sphinx documentation
|
|
91
|
+
docs/**/_build/
|
|
92
|
+
docs/**/autodoc/
|
|
93
|
+
docs/**/_autosummary/
|
|
94
|
+
|
|
95
|
+
# PyBuilder
|
|
96
|
+
.pybuilder/
|
|
97
|
+
target/
|
|
98
|
+
|
|
99
|
+
# Jupyter Notebook
|
|
100
|
+
.ipynb_checkpoints
|
|
101
|
+
|
|
102
|
+
# IPython
|
|
103
|
+
profile_default/
|
|
104
|
+
ipython_config.py
|
|
105
|
+
|
|
106
|
+
**.prof
|
|
107
|
+
|
|
108
|
+
# pyenv
|
|
109
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
110
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
111
|
+
# .python-version
|
|
112
|
+
|
|
113
|
+
# pipenv
|
|
114
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
115
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
116
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
117
|
+
# install all needed dependencies.
|
|
118
|
+
#Pipfile.lock
|
|
119
|
+
|
|
120
|
+
# poetry
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
122
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
123
|
+
# commonly ignored for libraries.
|
|
124
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
125
|
+
#poetry.lock
|
|
126
|
+
|
|
127
|
+
# pdm
|
|
128
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
129
|
+
#pdm.lock
|
|
130
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
131
|
+
# in version control.
|
|
132
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
133
|
+
.pdm.toml
|
|
134
|
+
|
|
135
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
136
|
+
__pypackages__/
|
|
137
|
+
|
|
138
|
+
# Celery stuff
|
|
139
|
+
celerybeat-schedule
|
|
140
|
+
celerybeat.pid
|
|
141
|
+
|
|
142
|
+
# SageMath parsed files
|
|
143
|
+
*.sage.py
|
|
144
|
+
|
|
145
|
+
# Environments
|
|
146
|
+
.env
|
|
147
|
+
.venv
|
|
148
|
+
env/
|
|
149
|
+
venv/
|
|
150
|
+
ENV/
|
|
151
|
+
env.bak/
|
|
152
|
+
venv.bak/
|
|
153
|
+
|
|
154
|
+
# Spyder project settings
|
|
155
|
+
.spyderproject
|
|
156
|
+
.spyproject
|
|
157
|
+
|
|
158
|
+
# Rope project settings
|
|
159
|
+
.ropeproject
|
|
160
|
+
|
|
161
|
+
# mkdocs documentation
|
|
162
|
+
/site
|
|
163
|
+
|
|
164
|
+
# mypy
|
|
165
|
+
.mypy_cache/
|
|
166
|
+
.dmypy.json
|
|
167
|
+
dmypy.json
|
|
168
|
+
|
|
169
|
+
# Pyre type checker
|
|
170
|
+
.pyre/
|
|
171
|
+
|
|
172
|
+
# pytype static type analyzer
|
|
173
|
+
.pytype/
|
|
174
|
+
|
|
175
|
+
# Cython debug symbols
|
|
176
|
+
cython_debug/
|
|
177
|
+
|
|
178
|
+
# PyCharm
|
|
179
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
180
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
181
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
182
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
183
|
+
#.idea/
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## v0.1.1 (2025-07-11)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- Fix release step
|
|
9
|
+
([`182c7de`](https://github.com/bec-project/qtmonaco/commit/182c7dec74c46437e1a56d03c53b9992b135ad90))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## v0.1.0 (2025-07-11)
|
|
13
|
+
|
|
14
|
+
### Continuous Integration
|
|
15
|
+
|
|
16
|
+
- Add pypi build and upload
|
|
17
|
+
([`25ea54a`](https://github.com/bec-project/qtmonaco/commit/25ea54a80e6fd54a215cb3679ca80e8e49c8e97b))
|
|
18
|
+
|
|
19
|
+
- Init
|
|
20
|
+
([`0b5ef18`](https://github.com/bec-project/qtmonaco/commit/0b5ef1858bc95ec93e6c8abb993b1e01f8772cac))
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
- Add qt bridge
|
|
25
|
+
([`7361271`](https://github.com/bec-project/qtmonaco/commit/73612710b57424234900863606fe3aa74ad6754f))
|
qtmonaco-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qtmonaco
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Qt Monaco is a Python library for working with the Monaco Editor in Qt applications.
|
|
5
|
+
Project-URL: Bug Tracker, https://github.com/bec-project/qtmonaco/issues
|
|
6
|
+
Project-URL: Homepage, https://github.com/bec-project/qtmonaco
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Topic :: Scientific/Engineering
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Requires-Dist: pyside6~=6.8.2
|
|
12
|
+
Requires-Dist: python-lsp-server[all,websockets]~=1.12
|
|
13
|
+
Requires-Dist: qtpy~=2.4
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: coverage~=7.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: isort>=5.13.2,~=5.13; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest-cov~=6.1.1; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest-qt~=4.4; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest-random-order~=1.1; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-timeout~=2.2; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest-xvfb~=3.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest~=8.0; extra == 'dev'
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Generate QRC and RCC files from dist directory for PySide6.
|
|
4
|
+
|
|
5
|
+
This script creates a Qt Resource Collection (.qrc) file from the dist directory
|
|
6
|
+
and automatically compiles it to a binary RCC file for use in PySide6 applications.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python generate_qrc.py
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import subprocess
|
|
14
|
+
import xml.etree.ElementTree as ET
|
|
15
|
+
from typing import List
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_file_list(build_dir: str) -> List[str]:
|
|
19
|
+
"""
|
|
20
|
+
Get list of all files in build directory, excluding certain extensions.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
build_dir: Path to build directory
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
List of file paths relative to build_dir
|
|
27
|
+
"""
|
|
28
|
+
exclude_extensions = {".map", ".txt", ".md"}
|
|
29
|
+
|
|
30
|
+
files = []
|
|
31
|
+
for root, dirs, filenames in os.walk(build_dir):
|
|
32
|
+
# Skip hidden directories and common build artifacts
|
|
33
|
+
dirs[:] = [
|
|
34
|
+
d for d in dirs if not d.startswith(".") and d not in {"node_modules", "__pycache__"}
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
for filename in filenames:
|
|
38
|
+
file_path = os.path.join(root, filename)
|
|
39
|
+
relative_path = os.path.relpath(file_path, build_dir)
|
|
40
|
+
|
|
41
|
+
# Skip files with excluded extensions
|
|
42
|
+
if not any(relative_path.endswith(ext) for ext in exclude_extensions):
|
|
43
|
+
files.append(relative_path)
|
|
44
|
+
|
|
45
|
+
return sorted(files)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def create_qrc_content(files: List[str], build_dir: str) -> str:
|
|
49
|
+
"""
|
|
50
|
+
Create QRC XML content from file list.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
files: List of file paths relative to build directory
|
|
54
|
+
build_dir: Build directory path to include in file paths
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
QRC XML content as string
|
|
58
|
+
"""
|
|
59
|
+
# Create root element
|
|
60
|
+
root = ET.Element("RCC")
|
|
61
|
+
root.set("version", "1.0")
|
|
62
|
+
|
|
63
|
+
# Create qresource element
|
|
64
|
+
qresource = ET.SubElement(root, "qresource")
|
|
65
|
+
qresource.set("prefix", "/monaco")
|
|
66
|
+
|
|
67
|
+
# Add each file with build directory prefix
|
|
68
|
+
for file_path in files:
|
|
69
|
+
file_element = ET.SubElement(qresource, "file")
|
|
70
|
+
# Include build directory in the path
|
|
71
|
+
full_path = os.path.join("dist", file_path)
|
|
72
|
+
file_element.text = str(full_path).replace("\\", "/") # Use forward slashes for Qt
|
|
73
|
+
|
|
74
|
+
# Pretty print the XML
|
|
75
|
+
ET.indent(root, space=" ")
|
|
76
|
+
|
|
77
|
+
# Create XML declaration and return
|
|
78
|
+
xml_str = ET.tostring(root, encoding="unicode", xml_declaration=False)
|
|
79
|
+
return f'<?xml version="1.0" encoding="UTF-8"?>\n{xml_str}\n'
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def compile_to_rcc(qrc_file: str, rcc_file: str) -> bool:
|
|
83
|
+
"""
|
|
84
|
+
Compile QRC file to binary RCC file using pyside6-rcc.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
qrc_file: Path to QRC file
|
|
88
|
+
rcc_file: Path to output RCC file
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
True if compilation was successful, False otherwise
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
cmd = ["pyside6-rcc", qrc_file, "-o", rcc_file, "--binary"]
|
|
95
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
96
|
+
return True
|
|
97
|
+
except subprocess.CalledProcessError as e:
|
|
98
|
+
print(f"Error compiling QRC to RCC: {e}")
|
|
99
|
+
print(f"stderr: {e.stderr}")
|
|
100
|
+
return False
|
|
101
|
+
except FileNotFoundError:
|
|
102
|
+
print("Error: pyside6-rcc not found. Make sure PySide6 is installed.")
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def main():
|
|
107
|
+
|
|
108
|
+
build_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "dist")
|
|
109
|
+
rcc_build_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "build")
|
|
110
|
+
qrc_file = "monaco_resources.qrc"
|
|
111
|
+
rcc_file = os.path.join(rcc_build_dir, "monaco.rcc")
|
|
112
|
+
|
|
113
|
+
# Validate build directory
|
|
114
|
+
if not os.path.exists(build_dir):
|
|
115
|
+
print(f"Error: Build directory '{build_dir}' does not exist")
|
|
116
|
+
print("Make sure to run 'npm run build' first")
|
|
117
|
+
return 1
|
|
118
|
+
|
|
119
|
+
# Get file list
|
|
120
|
+
files = get_file_list(build_dir)
|
|
121
|
+
|
|
122
|
+
if not files:
|
|
123
|
+
print(f"Warning: No files found in '{build_dir}'")
|
|
124
|
+
return 1
|
|
125
|
+
|
|
126
|
+
print(f"Found {len(files)} files in '{build_dir}':")
|
|
127
|
+
for file_path in files[:5]: # Show first 5 files
|
|
128
|
+
print(f" {file_path}")
|
|
129
|
+
if len(files) > 5:
|
|
130
|
+
print(f" ... and {len(files) - 5} more files")
|
|
131
|
+
|
|
132
|
+
# Generate QRC content
|
|
133
|
+
qrc_content = create_qrc_content(files, build_dir)
|
|
134
|
+
|
|
135
|
+
# Write QRC file
|
|
136
|
+
with open(qrc_file, "w", encoding="utf-8") as f:
|
|
137
|
+
f.write(qrc_content)
|
|
138
|
+
|
|
139
|
+
print(f"\n✓ Generated QRC file: {qrc_file}")
|
|
140
|
+
|
|
141
|
+
# Compile to RCC
|
|
142
|
+
print("Compiling to RCC file...")
|
|
143
|
+
os.makedirs(rcc_build_dir, exist_ok=True) # Ensure build directory exists
|
|
144
|
+
if compile_to_rcc(qrc_file, rcc_file):
|
|
145
|
+
print(f"✓ Generated RCC file: {rcc_file}")
|
|
146
|
+
|
|
147
|
+
# Clean up QRC file since we only need the RCC
|
|
148
|
+
os.remove(qrc_file)
|
|
149
|
+
print("✓ Cleaned up temporary QRC file")
|
|
150
|
+
|
|
151
|
+
print("\n🎉 Success!")
|
|
152
|
+
return 0
|
|
153
|
+
print("✗ Failed to compile RCC file")
|
|
154
|
+
return 1
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
if __name__ == "__main__":
|
|
158
|
+
exit(main())
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "qtmonaco"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Qt Monaco is a Python library for working with the Monaco Editor in Qt applications."
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Topic :: Scientific/Engineering",
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"PySide6~=6.8.2",
|
|
17
|
+
"qtpy~=2.4",
|
|
18
|
+
"python-lsp-server[all,websockets] ~= 1.12",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
dev = [
|
|
23
|
+
"coverage~=7.0",
|
|
24
|
+
"isort~=5.13, >=5.13.2",
|
|
25
|
+
"pytest-qt~=4.4",
|
|
26
|
+
"pytest-random-order~=1.1",
|
|
27
|
+
"pytest-timeout~=2.2",
|
|
28
|
+
"pytest-xvfb~=3.0",
|
|
29
|
+
"pytest~=8.0",
|
|
30
|
+
"pytest-cov~=6.1.1",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
"Bug Tracker" = "https://github.com/bec-project/qtmonaco/issues"
|
|
35
|
+
Homepage = "https://github.com/bec-project/qtmonaco"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
include = ["*"]
|
|
39
|
+
exclude = ["docs/**", "tests/**"]
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.sdist]
|
|
42
|
+
include = ["*"]
|
|
43
|
+
exclude = ["docs/**", "tests/**"]
|
|
44
|
+
|
|
45
|
+
[tool.black]
|
|
46
|
+
line-length = 100
|
|
47
|
+
skip-magic-trailing-comma = true
|
|
48
|
+
|
|
49
|
+
[tool.isort]
|
|
50
|
+
profile = "black"
|
|
51
|
+
line_length = 100
|
|
52
|
+
multi_line_output = 3
|
|
53
|
+
include_trailing_comma = true
|
|
54
|
+
known_first_party = ["bec_widgets"]
|
|
55
|
+
|
|
56
|
+
[tool.semantic_release]
|
|
57
|
+
build_command = "pip install build wheel && python -m build"
|
|
58
|
+
version_toml = ["pyproject.toml:project.version"]
|
|
59
|
+
|
|
60
|
+
[tool.semantic_release.commit_author]
|
|
61
|
+
env = "GIT_COMMIT_AUTHOR"
|
|
62
|
+
default = "semantic-release <semantic-release>"
|
|
63
|
+
|
|
64
|
+
[tool.semantic_release.commit_parser_options]
|
|
65
|
+
allowed_tags = [
|
|
66
|
+
"build",
|
|
67
|
+
"chore",
|
|
68
|
+
"ci",
|
|
69
|
+
"docs",
|
|
70
|
+
"feat",
|
|
71
|
+
"fix",
|
|
72
|
+
"perf",
|
|
73
|
+
"style",
|
|
74
|
+
"refactor",
|
|
75
|
+
"test",
|
|
76
|
+
]
|
|
77
|
+
minor_tags = ["feat"]
|
|
78
|
+
patch_tags = ["fix", "perf"]
|
|
79
|
+
default_bump_level = 0
|
|
80
|
+
|
|
81
|
+
[tool.semantic_release.remote]
|
|
82
|
+
name = "origin"
|
|
83
|
+
type = "github"
|
|
84
|
+
ignore_token_for_push = true
|
|
85
|
+
|
|
86
|
+
[tool.semantic_release.remote.token]
|
|
87
|
+
env = "GH_TOKEN"
|
|
88
|
+
|
|
89
|
+
[tool.semantic_release.publish]
|
|
90
|
+
dist_glob_patterns = ["dist/*"]
|
|
91
|
+
upload_to_vcs_release = true
|
|
92
|
+
|
|
93
|
+
[tool.coverage.report]
|
|
94
|
+
skip_empty = true # exclude empty *files*, e.g. __init__.py, from the report
|
|
95
|
+
exclude_lines = [
|
|
96
|
+
"pragma: no cover",
|
|
97
|
+
"if TYPE_CHECKING:",
|
|
98
|
+
"return NotImplemented",
|
|
99
|
+
"raise NotImplementedError",
|
|
100
|
+
"\\.\\.\\.",
|
|
101
|
+
'if __name__ == "__main__":',
|
|
102
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from qtpy.QtCore import QObject, Signal, Slot
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseBridge(QObject):
|
|
7
|
+
initialized = Signal()
|
|
8
|
+
sendDataChanged = Signal(str, str)
|
|
9
|
+
completion = Signal(str)
|
|
10
|
+
|
|
11
|
+
def __init__(self, parent=None):
|
|
12
|
+
super().__init__(parent=parent)
|
|
13
|
+
self._initialized = False
|
|
14
|
+
self._buffer = []
|
|
15
|
+
self.initialized.connect(self._process_startup_buffer)
|
|
16
|
+
|
|
17
|
+
def _process_startup_buffer(self):
|
|
18
|
+
"""
|
|
19
|
+
Process the buffer of data that was sent before the bridge was initialized.
|
|
20
|
+
This is useful for sending initial data to the JavaScript side.
|
|
21
|
+
"""
|
|
22
|
+
for name, value in self._buffer:
|
|
23
|
+
self._send_to_js(name, value)
|
|
24
|
+
self._buffer.clear()
|
|
25
|
+
|
|
26
|
+
# Update the local buffer by reading the current state
|
|
27
|
+
# This is mostly to ensure that we are in sync with the JS side
|
|
28
|
+
self._send_to_js("read", "")
|
|
29
|
+
|
|
30
|
+
def _send_to_js(self, name, value):
|
|
31
|
+
if not self._initialized:
|
|
32
|
+
self._buffer.append((name, value))
|
|
33
|
+
return
|
|
34
|
+
data = json.dumps(value)
|
|
35
|
+
self.sendDataChanged.emit(name, data)
|
|
36
|
+
|
|
37
|
+
@Slot(str, str)
|
|
38
|
+
def receive_from_js(self, name, value):
|
|
39
|
+
data = json.loads(value)
|
|
40
|
+
|
|
41
|
+
if name == "bridge_initialized":
|
|
42
|
+
self._initialized = data
|
|
43
|
+
self.initialized.emit()
|
|
44
|
+
return
|
|
45
|
+
if name == "setValue":
|
|
46
|
+
self.on_value_changed(data)
|
|
47
|
+
return
|
|
48
|
+
print(f"Received from JS: {name} = {data}")
|
|
49
|
+
self.setProperty(name, data)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def bridge_initialized(self):
|
|
53
|
+
return self._initialized
|
|
54
|
+
|
|
55
|
+
@bridge_initialized.setter
|
|
56
|
+
def bridge_initialized(self, value):
|
|
57
|
+
if self._initialized != value:
|
|
58
|
+
self._initialized = value
|
|
59
|
+
self.initialized.emit()
|
|
60
|
+
|
|
61
|
+
def on_value_changed(self, value):
|
|
62
|
+
"""
|
|
63
|
+
Placeholder method to handle value changes.
|
|
64
|
+
This can be overridden in subclasses to implement specific behavior.
|
|
65
|
+
"""
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
from qtpy.QtCore import QObject, Signal, Slot
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Connector(QObject):
|
|
8
|
+
javascript_data_sent = Signal(str, str)
|
|
9
|
+
javascript_data_received = Signal(str, str)
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
A base class for connecting Python and JavaScript.
|
|
13
|
+
This class provides a mechanism to send and receive data between Python and JavaScript.
|
|
14
|
+
It uses Qt's signal-slot mechanism to handle communication.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, parent=None):
|
|
18
|
+
super().__init__(parent=parent)
|
|
19
|
+
self._initialized = False
|
|
20
|
+
self._buffer = []
|
|
21
|
+
|
|
22
|
+
def _process_startup_buffer(self):
|
|
23
|
+
"""
|
|
24
|
+
Process the buffer of data that was sent before the bridge was initialized.
|
|
25
|
+
This is useful for sending initial data to the JavaScript side.
|
|
26
|
+
"""
|
|
27
|
+
for name, value in self._buffer:
|
|
28
|
+
self.send(name, value)
|
|
29
|
+
self._buffer.clear()
|
|
30
|
+
|
|
31
|
+
# Update the local buffer by reading the current state
|
|
32
|
+
# This is mostly to ensure that we are in sync with the JS side
|
|
33
|
+
self.send("read", "")
|
|
34
|
+
|
|
35
|
+
def send(self, name, value):
|
|
36
|
+
"""
|
|
37
|
+
Send data to the JavaScript side.
|
|
38
|
+
Args:
|
|
39
|
+
name (str): The name of the data to send.
|
|
40
|
+
value (any): The value to send, which will be serialized to JSON.
|
|
41
|
+
"""
|
|
42
|
+
if not self._initialized:
|
|
43
|
+
self._buffer.append((name, value))
|
|
44
|
+
return
|
|
45
|
+
data = json.dumps(value)
|
|
46
|
+
self.javascript_data_sent.emit(name, data)
|
|
47
|
+
|
|
48
|
+
@Slot(str, str)
|
|
49
|
+
def _receive(self, name: str, value: str):
|
|
50
|
+
"""
|
|
51
|
+
Receive data from the JavaScript side.
|
|
52
|
+
This method is called when the JavaScript side sends data to the Python side.
|
|
53
|
+
Args:
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
self.javascript_data_received.emit(name, value)
|
|
58
|
+
|
|
59
|
+
def set_initialized(self):
|
|
60
|
+
"""
|
|
61
|
+
Set the initialized state of the connector.
|
|
62
|
+
This method is used to indicate that the connector is ready to send and receive data.
|
|
63
|
+
Args:
|
|
64
|
+
value (bool): The new initialized state.
|
|
65
|
+
"""
|
|
66
|
+
self._initialized = True
|
|
67
|
+
self._process_startup_buffer()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class EditorBridge(QObject):
|
|
71
|
+
text_changed = Signal()
|
|
72
|
+
language_changed = Signal()
|
|
73
|
+
theme_changed = Signal()
|
|
74
|
+
initialized = Signal()
|
|
75
|
+
completion = Signal(str)
|
|
76
|
+
|
|
77
|
+
def __init__(self, parent=None):
|
|
78
|
+
super().__init__(parent=parent)
|
|
79
|
+
self._connector = Connector(parent=self)
|
|
80
|
+
self._connector.javascript_data_received.connect(self.on_new_data_received)
|
|
81
|
+
self._value = ""
|
|
82
|
+
self._language = ""
|
|
83
|
+
self._theme = ""
|
|
84
|
+
self._readonly = False
|
|
85
|
+
self._initialized = False
|
|
86
|
+
self._buffer = []
|
|
87
|
+
self.initialized.connect(self._connector.set_initialized)
|
|
88
|
+
|
|
89
|
+
def on_new_data_received(self, name: str, value: str):
|
|
90
|
+
"""
|
|
91
|
+
Handle new data received from JavaScript.
|
|
92
|
+
This method is called when the JavaScript side sends data to the Python side.
|
|
93
|
+
"""
|
|
94
|
+
data = json.loads(value)
|
|
95
|
+
if hasattr(self, name):
|
|
96
|
+
method = getattr(self, name)
|
|
97
|
+
if callable(method):
|
|
98
|
+
method(data)
|
|
99
|
+
return
|
|
100
|
+
if hasattr(self, name):
|
|
101
|
+
setattr(self, name, data)
|
|
102
|
+
return
|
|
103
|
+
else:
|
|
104
|
+
print(f"Warning: No method or property named '{name}' in EditorBridge.")
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def bridge_initialized(self):
|
|
108
|
+
return self._initialized
|
|
109
|
+
|
|
110
|
+
@bridge_initialized.setter
|
|
111
|
+
def bridge_initialized(self, value):
|
|
112
|
+
if self._initialized != value:
|
|
113
|
+
self._initialized = value
|
|
114
|
+
self.initialized.emit()
|
|
115
|
+
|
|
116
|
+
def on_value_changed(self, value):
|
|
117
|
+
"""Handle value changes from the JavaScript side."""
|
|
118
|
+
self._current_text(value)
|
|
119
|
+
|
|
120
|
+
def _current_text(self, value: str):
|
|
121
|
+
if self._value == value:
|
|
122
|
+
return
|
|
123
|
+
self._value = value
|
|
124
|
+
self.text_changed.emit()
|
|
125
|
+
|
|
126
|
+
def set_text(self, value: str):
|
|
127
|
+
"""
|
|
128
|
+
Set the value in the editor.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
value (str): The new value to set in the editor.
|
|
132
|
+
"""
|
|
133
|
+
if self._value == value:
|
|
134
|
+
return
|
|
135
|
+
if self._readonly:
|
|
136
|
+
raise ValueError("Editor is in read-only mode, cannot set value.")
|
|
137
|
+
if not isinstance(value, str):
|
|
138
|
+
raise TypeError("Value must be a string.")
|
|
139
|
+
self._value = value
|
|
140
|
+
self.text_changed.emit()
|
|
141
|
+
|
|
142
|
+
def get_language(self):
|
|
143
|
+
return self._language
|
|
144
|
+
|
|
145
|
+
def set_language(self, language):
|
|
146
|
+
self._language = language
|
|
147
|
+
self._connector.send("language", language)
|
|
148
|
+
self.language_changed.emit()
|
|
149
|
+
|
|
150
|
+
def get_theme(self):
|
|
151
|
+
return self._theme
|
|
152
|
+
|
|
153
|
+
def set_theme(self, theme):
|
|
154
|
+
self._theme = theme
|
|
155
|
+
self._connector.send("theme", theme)
|
|
156
|
+
self.theme_changed.emit()
|
|
157
|
+
|
|
158
|
+
def set_readonly(self, read_only: bool):
|
|
159
|
+
"""Set the editor to read-only mode."""
|
|
160
|
+
self._connector.send("readonly", read_only)
|
|
161
|
+
self._readonly = read_only
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def value(self):
|
|
165
|
+
return self._value
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def language(self):
|
|
169
|
+
return self._language
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def theme(self):
|
|
173
|
+
return self._theme
|
|
174
|
+
|
|
175
|
+
def set_host(self, host: str):
|
|
176
|
+
"""
|
|
177
|
+
Set the host for the editor.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
host (str): The host URL for the editor.
|
|
181
|
+
"""
|
|
182
|
+
self._connector.send("lsp_url", host)
|
|
183
|
+
|
|
184
|
+
def set_cursor(
|
|
185
|
+
self,
|
|
186
|
+
line: int,
|
|
187
|
+
column: int = 1,
|
|
188
|
+
move_to_position: Literal[None, "center", "top", "position"] = None,
|
|
189
|
+
):
|
|
190
|
+
"""
|
|
191
|
+
Set the cursor position in the editor.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
line (int): Line number (1-based).
|
|
195
|
+
column (int): Column number (1-based), defaults to 1.
|
|
196
|
+
move_to_position (Literal[None, "center", "top", "position"], optional): Position to move the cursor to.
|
|
197
|
+
"""
|
|
198
|
+
self._connector.send(
|
|
199
|
+
"set_cursor", {"line": line, "column": column, "moveToPosition": move_to_position}
|
|
200
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from qtpy.QtWebEngineCore import QWebEnginePage
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MonacoPage(QWebEnginePage):
|
|
9
|
+
def javaScriptConsoleMessage(self, level, message, line, source):
|
|
10
|
+
print(f"[JS Console] {level.name} at line {line} in {source}: {message}")
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from qtpy.QtCore import Signal
|
|
4
|
+
from qtpy.QtWebChannel import QWebChannel
|
|
5
|
+
from qtpy.QtWebEngineWidgets import QWebEngineView
|
|
6
|
+
|
|
7
|
+
from qtmonaco.editor_bridge import EditorBridge
|
|
8
|
+
from qtmonaco.monaco_page import MonacoPage
|
|
9
|
+
from qtmonaco.resource_loader import get_monaco_base_url, get_monaco_html
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_pylsp_host() -> str:
|
|
13
|
+
"""
|
|
14
|
+
Get the host address for the PyLSP server.
|
|
15
|
+
This function initializes the PyLSP server if it is not already running
|
|
16
|
+
and returns the host address in the format 'localhost:port'.
|
|
17
|
+
Returns:
|
|
18
|
+
str: The host address of the PyLSP server.
|
|
19
|
+
"""
|
|
20
|
+
# lazy import to only load when needed
|
|
21
|
+
# pylint: disable=import-outside-toplevel
|
|
22
|
+
from qtmonaco.pylsp_provider import pylsp_server
|
|
23
|
+
|
|
24
|
+
if not pylsp_server.is_running():
|
|
25
|
+
pylsp_server.start()
|
|
26
|
+
|
|
27
|
+
return f"localhost:{pylsp_server.port}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Monaco(QWebEngineView):
|
|
31
|
+
initialized = Signal()
|
|
32
|
+
textChanged = Signal(str)
|
|
33
|
+
|
|
34
|
+
def __init__(self, parent=None):
|
|
35
|
+
super().__init__(parent=parent)
|
|
36
|
+
|
|
37
|
+
self.pylsp_host = get_pylsp_host()
|
|
38
|
+
|
|
39
|
+
self._setup_page()
|
|
40
|
+
self._setup_bridge()
|
|
41
|
+
self._load_editor()
|
|
42
|
+
|
|
43
|
+
def _setup_page(self):
|
|
44
|
+
"""Initialize the web engine page."""
|
|
45
|
+
page = MonacoPage(parent=self)
|
|
46
|
+
self.setPage(page)
|
|
47
|
+
|
|
48
|
+
def _setup_bridge(self):
|
|
49
|
+
"""Initialize the bridge for communication with JavaScript."""
|
|
50
|
+
self._channel = QWebChannel(self)
|
|
51
|
+
self._bridge = EditorBridge()
|
|
52
|
+
|
|
53
|
+
self.page().setWebChannel(self._channel)
|
|
54
|
+
self._channel.registerObject("connector", self._bridge._connector)
|
|
55
|
+
|
|
56
|
+
self._bridge.initialized.connect(self._set_host)
|
|
57
|
+
self._bridge.initialized.connect(self.initialized)
|
|
58
|
+
self._bridge.text_changed.connect(lambda: self.textChanged.emit(self._bridge.value))
|
|
59
|
+
|
|
60
|
+
def _load_editor(self):
|
|
61
|
+
"""Load the Monaco Editor HTML content."""
|
|
62
|
+
raw_html = get_monaco_html()
|
|
63
|
+
base_url = get_monaco_base_url()
|
|
64
|
+
self.setHtml(raw_html, base_url)
|
|
65
|
+
|
|
66
|
+
def _set_host(self):
|
|
67
|
+
"""Set the LSP host once the bridge is initialized."""
|
|
68
|
+
self._bridge.set_host(self.pylsp_host)
|
|
69
|
+
|
|
70
|
+
# Public API methods
|
|
71
|
+
def text(self):
|
|
72
|
+
return self._bridge.value
|
|
73
|
+
|
|
74
|
+
def set_text(self, text: str):
|
|
75
|
+
self._bridge.set_text(text)
|
|
76
|
+
|
|
77
|
+
def set_cursor(
|
|
78
|
+
self,
|
|
79
|
+
line: int,
|
|
80
|
+
column: int = 1,
|
|
81
|
+
move_to_position: Literal[None, "center", "top", "position"] = None,
|
|
82
|
+
):
|
|
83
|
+
"""Set the cursor position in the editor.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
line (int): Line number (1-based)
|
|
87
|
+
column (int): Column number (1-based), defaults to 1
|
|
88
|
+
"""
|
|
89
|
+
self._bridge.set_cursor(line, column, move_to_position)
|
|
90
|
+
|
|
91
|
+
def get_language(self) -> str:
|
|
92
|
+
"""Get the current programming language for syntax highlighting in the editor."""
|
|
93
|
+
return self._bridge.get_language()
|
|
94
|
+
|
|
95
|
+
def set_language(self, language: str):
|
|
96
|
+
"""Set the programming language for syntax highlighting in the editor.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
language (str): The programming language to set (e.g., "python", "javascript").
|
|
100
|
+
"""
|
|
101
|
+
self._bridge.set_language(language)
|
|
102
|
+
|
|
103
|
+
def get_theme(self):
|
|
104
|
+
return self._bridge.get_theme()
|
|
105
|
+
|
|
106
|
+
def set_theme(self, theme: str):
|
|
107
|
+
"""Set the theme for the Monaco editor.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
theme (str): The theme to apply (e.g., "vs", "vs-dark").
|
|
111
|
+
"""
|
|
112
|
+
self._bridge.set_theme(theme)
|
|
113
|
+
|
|
114
|
+
def set_read_only(self, read_only: bool):
|
|
115
|
+
"""Set the editor to read-only mode."""
|
|
116
|
+
self._bridge.set_readonly(read_only)
|
|
117
|
+
|
|
118
|
+
def shutdown(self):
|
|
119
|
+
if hasattr(self._bridge, "shutdown"):
|
|
120
|
+
self._bridge.shutdown()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
import logging
|
|
125
|
+
import sys
|
|
126
|
+
|
|
127
|
+
from qtpy.QtWidgets import QApplication
|
|
128
|
+
|
|
129
|
+
logging.basicConfig(level=logging.INFO)
|
|
130
|
+
|
|
131
|
+
app = QApplication(sys.argv)
|
|
132
|
+
editor = Monaco()
|
|
133
|
+
editor.show()
|
|
134
|
+
sys.exit(app.exec_())
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import logging
|
|
3
|
+
import signal
|
|
4
|
+
import socket
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PyLSPProvider:
|
|
11
|
+
"""A provider for the PyLSP server."""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.port = None
|
|
15
|
+
self.server_process = None
|
|
16
|
+
atexit.register(self.stop)
|
|
17
|
+
signal.signal(signal.SIGINT, self._handle_signal)
|
|
18
|
+
signal.signal(signal.SIGTERM, self._handle_signal)
|
|
19
|
+
|
|
20
|
+
def _find_free_port(self):
|
|
21
|
+
"""Find a free port for the PyLSP server."""
|
|
22
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
23
|
+
s.bind(("localhost", 0))
|
|
24
|
+
self.port = s.getsockname()[1]
|
|
25
|
+
return self.port
|
|
26
|
+
|
|
27
|
+
def start(self):
|
|
28
|
+
"""Start the PyLSP server."""
|
|
29
|
+
if self.port is None:
|
|
30
|
+
self._find_free_port()
|
|
31
|
+
# Here you would start the PyLSP server using the found port
|
|
32
|
+
logger.info(f"Starting PyLSP server on port {self.port}")
|
|
33
|
+
self.server_process = subprocess.Popen(
|
|
34
|
+
["pylsp", "--ws", "--port", str(self.port)],
|
|
35
|
+
stdout=subprocess.PIPE,
|
|
36
|
+
stderr=subprocess.PIPE,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def stop(self):
|
|
40
|
+
"""Stop the PyLSP server."""
|
|
41
|
+
if not self.server_process:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
self.server_process.terminate()
|
|
45
|
+
try:
|
|
46
|
+
self.server_process.wait(timeout=5)
|
|
47
|
+
except subprocess.TimeoutExpired:
|
|
48
|
+
self.server_process.kill()
|
|
49
|
+
self.server_process = None
|
|
50
|
+
self.port = None
|
|
51
|
+
|
|
52
|
+
def _handle_signal(self, signum, frame):
|
|
53
|
+
"""Handle termination signals."""
|
|
54
|
+
self.stop()
|
|
55
|
+
|
|
56
|
+
def is_running(self):
|
|
57
|
+
"""Check if the PyLSP server is running."""
|
|
58
|
+
return self.server_process is not None and self.server_process.poll() is None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
pylsp_server = PyLSPProvider()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from qtpy.QtCore import QFile, QIODevice, QResource, QUrl
|
|
4
|
+
|
|
5
|
+
QResource.registerResource(
|
|
6
|
+
os.path.join(os.path.dirname(os.path.dirname(__file__)), "build/monaco.rcc")
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_resource_html(resource_path: str) -> str:
|
|
11
|
+
"""Load HTML content from Qt resources."""
|
|
12
|
+
file = QFile(resource_path)
|
|
13
|
+
if file.open(QIODevice.OpenModeFlag.ReadOnly):
|
|
14
|
+
content = file.readAll()
|
|
15
|
+
file.close()
|
|
16
|
+
return content.toStdString()
|
|
17
|
+
else:
|
|
18
|
+
raise FileNotFoundError(f"Resource not found: {resource_path}")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_monaco_html():
|
|
22
|
+
"""Get Monaco Editor HTML content from Qt resources."""
|
|
23
|
+
return load_resource_html(":/monaco/dist/index.html")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_monaco_base_url():
|
|
27
|
+
"""Get the base URL for Monaco Editor resources."""
|
|
28
|
+
return QUrl("qrc:/monaco/dist/")
|