Skip to main content

js-cpp-protocol

Reference for the communication protocol between the JavaScript editor (CodeMirror or Monaco) and the C++/Qt UI layer via QWebChannel. Use when implementing new editor features, adding messages to the bridge, debugging JS/C++ communication, or understanding how editor commands flow between layers.

소스 정보

저장소
notepadqq/notepadqq
최근 소스 활동
2026년 7월 4일 16:42
감지된 SKILL.md 언어
영어
스타
2,334
포크
318

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
js-cpp-protocol
description
Reference for the communication protocol between the JavaScript editor (CodeMirror or Monaco) and the C++/Qt UI layer via QWebChannel. Use when implementing new editor features, adding messages to the bridge, debugging JS/C++ communication, or understanding how editor commands flow between layers.
# JS Editor / C++ UI Communication Protocol ## Overview Notepadqq's editor uses **Qt WebEngine** (`QWebEngineView`) with **Qt WebChannel** (`QWebChannel`) for bi-directional communication between the native C++ UI and the JavaScript editor engine (either **CodeMirror** or **Monaco**). The C++ side is unaware of which engine is running — both engines implement the identical message protocol. ## Architecture ``` ┌───────────────────────────┐ QWebChannel ┌──────────────────────────┐ │ C++ / Qt │ ◄─────────────────────────► │ JavaScript Editor │ │ │ │ │ │ Editor (editor.cpp) │ signal: messageReceivedByJs │ UiDriver.js │ │ ├─ JsToCppProxy (QObject)│ ──────────────────────────► │ ├─ QWebChannel bridge │ │ └─ CustomQWebView │ ◄────────────────────────── │ └─ messageReceived() │ │ │ slot: receiveMessage() │ │ │ EditorTabWidget │ │ app.js / app_monaco.js │ │ MainWindow │ │ └─ event handlers │ └───────────────────────────┘ └──────────────────────────┘ ``` ## Key Files | Side | File | Role | |------|------|------| | JS | `src/editor/classes/UiDriver.js` | Core bridge: QWebChannel setup, `sendMessage()`, `registerEventHandler()`, `messageReceived()` dispatcher | | JS | `src/editor/app.js` | CodeMirror implementation: registers handlers for all `C_CMD_*` / `C_FUN_*` messages | | JS | `src/editor/app_monaco.js` | Monaco implementation: registers handlers for all `C_CMD_*` / `C_FUN_*` messages | | JS | `src/editor/index.html` | CodeMirror HTML entry point | | JS | `src/editor/index_monaco.html` | Monaco HTML entry point | | C++ | `src/ui/EditorNS/editor.cpp` | Creates QWebChannel, sends messages, receives replies, manages async callbacks | | C++ | `src/ui/include/EditorNS/editor.h` | Declares `JsToCppProxy`, `Editor`, `AsyncReply` | | C++ | `src/ui/EditorNS/customqwebview.cpp` | QWebEngineView subclass: event handling, drag-drop, context menu | ## Communication Mechanism ### Setup 1. **C++** (`editor.cpp:47-70`): Creates a `JsToCppProxy` QObject (registered as `"cpp_ui_driver"`), creates a `QWebChannel`, sets it on the WebEngine page, and registers the proxy: `channel->registerObject("cpp_ui_driver", m_jsToCppProxy)`. 2. **JS** (`index.html:7` / `index_monaco.html:7`): Includes `<script src="qrc:///qtwebchannel/qwebchannel.js"></script>` (Qt's built-in WebChannel JS library). 3. **JS** (`UiDriver.js:8-26`): On `DOMContentLoaded`, creates `new QWebChannel(qt.webChannelTransport, callback)`, obtains `cpp_ui_driver = channel.objects.cpp_ui_driver`, and connects to the `messageReceivedByJs` signal. ### C++ → JS (Commands and Function Calls) Two modes exist: **Synchronous (legacy/deprecated):** `sendMessage(msg, data)` - `editor.cpp:374-382`: Emits `messageReceivedByJs` signal, which Qt WebChannel delivers to JS. - JS receives it via `cpp_ui_driver.messageReceivedByJs.connect(...)` and processes it immediately. **Asynchronous (preferred):** `asyncSendMessageWithResultP(msg, data)` - `editor.cpp:389-430`: Generates a unique message ID, wraps the message in `[ASYNC_REQUEST]` prefix + `[ID=N]` suffix. - JS receives it, dispatches to registered handlers, then sends back `[ASYNC_REPLY]` with the same ID. - C++ resolves the `QtPromise::QPromise<QVariant>` on receipt. **Message format for async:** ``` C++ sends: [ASYNC_REQUEST]C_CMD_GET_VALUE[ID=42] JS replies: [ASYNC_REPLY]C_CMD_GET_VALUE[ID=42] ``` ### JS → C++ (Events) JS calls `UiDriver.sendMessage("J_EVT_*", data)` which invokes `cpp_ui_driver.receiveMessage(msg, data)` (UiDriver.js:36). C++ receives it in `JsToCppProxy::receiveMessage()` (editor.h:44), which emits `messageReceived` signal → `Editor::on_proxyMessageReceived()` (editor.cpp:143). ## Message Naming Convention | Prefix | Direction | Semantics | |--------|-----------|-----------| | `C_CMD_*` | C++ → JS | Fire-and-forget command (no return value expected) | | `C_FUN_*` | C++ → JS | Function call (return value expected via async reply) | | `J_EVT_*` | JS → C++ | Event notification (no return value) | ## Complete Message Reference ### C_CMD_* — Commands (C++ → JS, no return value) | Message | Data | Description | |---------|------|-------------| | `C_CMD_SET_VALUE` | `QString` | Set full editor text content | | `C_CMD_MARK_CLEAN` | none | Mark document as clean (no unsaved changes) | | `C_CMD_MARK_DIRTY` | none | Mark document as dirty (unsaved changes) | | `C_CMD_SET_LANGUAGE` | language MIME string | Set syntax highlighting language | | `C_CMD_SET_INDENTATION_MODE` | `{useTabs: bool, size: int}` | Set tab/space indentation | | `C_CMD_SET_SELECTIONS_TEXT` | `{text: string[], select: "after"\|"before"\|"selected"}` | Replace selected text | | `C_CMD_SET_SELECTION` | `[fromLine, fromCol, toLine, toCol]` | Set selection range | | `C_CMD_SET_CURSOR` | `[line, col]` | Set cursor position | | `C_CMD_SET_RTL` | none | Set text direction to RTL | | `C_CMD_SET_LTR` | none | Set text direction to LTR | | `C_CMD_SET_SCROLL_POS` | `[left, top]` | Set scroll position | | `C_CMD_SELECT_ALL` | none | Select entire document | | `C_CMD_UNDO` | none | Undo last change | | `C_CMD_REDO` | none | Redo last undone change | | `C_CMD_CLEAR_HISTORY` | none | Clear undo history | | `C_CMD_SET_LINE_WRAP` | `bool` | Toggle line wrapping | | `C_CMD_SHOW_END_OF_LINE` | `bool` | Show/hide end-of-line characters | | `C_CMD_SHOW_WHITESPACE` | `bool` | Show/hide whitespace characters | | `C_CMD_SET_TABS_VISIBLE` | `bool` | Show/hide tab characters | | `C_CMD_SET_THEME` | `{name: string, path: string}` | Apply editor theme | | `C_CMD_SET_FONT` | `{family: string, size: int, lineHeight: double}` | Set editor font | | `C_CMD_SET_LINE_NUMBERS_VISIBLE` | `bool` | Show/hide line numbers | | `C_CMD_SET_OVERWRITE` | `bool` | Toggle overwrite mode | | `C_CMD_SET_SMART_INDENT` | `bool` | Toggle smart indent | | `C_CMD_SET_FOCUS` | none | Focus the editor | | `C_CMD_BLUR` | none | Blur the editor | | `C_CMD_DISPLAY_PRINT_STYLE` | none | Switch to print-friendly CSS | | `C_CMD_DISPLAY_NORMAL_STYLE` | none | Switch back to normal CSS | | `C_CMD_DUPLICATE_LINE` | none | Duplicate current line | | `C_CMD_MOVE_LINE_UP` | none | Move current line up | | `C_CMD_MOVE_LINE_DOWN` | none | Move current line down | | `C_CMD_TRANSPOSE_LINE` | none | Transpose with previous line | | `C_CMD_DELETE_LINE` | none | Delete current line | | `C_CMD_TRIM_LEADING_TRAILING_SPACE` | none | Trim leading and trailing whitespace | | `C_CMD_TRIM_TRAILING_SPACE` | none | Trim only trailing whitespace | | `C_CMD_TRIM_LEADING_SPACE` | none | Trim only leading whitespace | | `C_CMD_ENABLE_MATH` | `bool` | Enable/disable LaTeX math rendering | | `C_CMD_TAB_TO_SPACE` | none | Convert tabs to spaces | | `C_CMD_SPACE_TO_TAB_ALL` | none | Convert all spaces to tabs | | `C_CMD_SPACE_TO_TAB_LEADING` | none | Convert leading spaces to tabs | | `C_CMD_EOL_TO_SPACE` | none | Replace line endings with spaces | | `C_CMD_GET_DOCUMENT_INFO` | none | Request document info (replied via J_EVT_DOCUMENT_INFO) | ### C_FUN_* — Function Calls (C++ → JS, return value via async reply) | Message | Data | Return Type | |---------|------|-------------| | `C_FUN_IS_CLEAN` | none | `bool` | | `C_FUN_GET_HISTORY_GENERATION` | none | `int` | | `C_FUN_GET_VALUE` | none | `QString` | | `C_FUN_GET_INDENTATION_MODE` | none | `{useTabs: bool, size: int}` | | `C_FUN_GET_SELECTIONS_TEXT` | none | `QStringList` | | `C_FUN_GET_SELECTIONS` | none | `[{anchor: {line, col}, head: {line, col}}]` | | `C_FUN_GET_TEXT_LENGTH` | none | `int` | | `C_FUN_GET_LINE_COUNT` | none | `int` | | `C_FUN_GET_CURSOR` | none | `[line, col]` | | `C_FUN_GET_SCROLL_POS` | none | `[left, top]` | | `C_FUN_SEARCH` | `[regex: string, modifiers: string, forward: bool]` | `bool` (found or not) | | `C_FUN_REPLACE` | `[regex, modifiers, forward, replacement, searchMode]` | `bool` | | `C_FUN_REPLACE_ALL` | `[regex, modifiers, replacement, searchMode]` | `int` (count) | | `C_FUN_SEARCH_SELECT_ALL` | `[regex, modifiers]` | `int` (count) | | `C_FUN_GET_LANGUAGES` | none | `array` of `{name, mime, mode, ext}` | | `C_FUN_DETECT_INDENTATION_MODE` | none | `{found: bool, useTabs: bool, size: int}` | | `C_FUN_GET_CURRENT_WORD` | none | `QString` | | `C_FUN_IS_MATH_ENABLED` | none | `bool` | ### J_EVT_* — Events (JS → C++, no return value) | Message | Data | When Sent | |---------|------|-----------| | `J_EVT_READY` | none | Editor initialization complete | | `J_EVT_CONTENT_CHANGED` | none | Document content changes (throttled ~50ms) | | `J_EVT_CLEAN_CHANGED` | `bool` | Clean/dirty state changes | | `J_EVT_CURSOR_ACTIVITY` | `{cursor: {...}, selections: [...], content: {...}}` | Cursor/selection changes (throttled ~50ms) | | `J_EVT_DOCUMENT_INFO` | `{cursor, selections, content}` | Reply to `C_CMD_GET_DOCUMENT_INFO` | | `J_EVT_GOT_FOCUS` | none | Editor receives focus | ## Handler Registration Pattern (JS) Handlers are registered in `app.js` or `app_monaco.js`: ```javascript UiDriver.registerEventHandler("C_CMD_SET_VALUE", function(msg, data, prevReturn) { editor.setValue(data); }); ``` Multiple handlers can be registered for the same message. They are called in registration order; each handler receives the previous handler's return value as `prevReturn`. ## Async Flow in Detail 1. **C++ generates a unique ID** (`messageIdentifier` counter, `editor.cpp:387`). 2. **C++ creates a promise** and stores an `AsyncReply{id, message, value, callback}` in the `asyncReplies` list (`editor.cpp:407-412`). 3. **C++ sends** `[ASYNC_REQUEST]C_FUN_GET_CURSOR[ID=42]` via `messageReceivedByJs` signal (`editor.cpp:414-418`). 4. **JS receives** in `UiDriver.messageReceived()` (`UiDriver.js:52-75`), parses the real message and ID via regex `/^\[ASYNC_REQUEST\](.*)\[ID=(\d+)\]$/`, dispatches to handler(s), then **sends back** `[ASYNC_REPLY]C_FUN_GET_CURSOR[ID=42]` with the return value.
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기