From 5d37ebcc02f6ca2ce1ced93f803dd1daf634d866 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Tue, 27 Oct 2026 14:00:49 +0000 Subject: feat(editor): support PP directives in VS Code and Bundle Share preprocessor highlighting through the existing grammar, keep conditional blocks at the ordinary indentation depth, and exercise switches in Bundle's native runner test. Co-Authored-By: Claude Opus 5.5 --- bundle/README.md | 2 ++ bundle/src-tauri/src/runner.rs | 6 ++++ plugin/README.md | 2 +- plugin/ThirdPartyNotices.md | 6 ++-- plugin/examples/highlighting.cps | 10 ++++++ plugin/syntaxes/cie-pseudocode.tmLanguage.json | 42 +++++++++++++++++++------- plugin/test/highlighting.test.js | 26 ++++++++++++---- plugin/test/indentation.test.js | 10 ++++++ 8 files changed, 83 insertions(+), 21 deletions(-) diff --git a/bundle/README.md b/bundle/README.md index 6147fc0..44fd4a0 100644 --- a/bundle/README.md +++ b/bundle/README.md @@ -8,6 +8,8 @@ Shortcuts use Cmd on macOS and Ctrl on Windows: **N** new, **Shift+N** new windo Nightly provides a standalone Windows x64 `.exe` and a macOS Apple Silicon `.tar.xz` containing the `.app`. Windows extracts the included `cpsc.exe` into the current working directory on startup; macOS keeps it inside the `.app`. Windows uses the system WebView2 runtime. +The editor shares the VS Code plugin's highlighting and indentation rules, including PP_DEFINE, PP_UNDEF, PP_IFDEF, PP_IFNDEF and PP_ENDIF. Preprocessor conditions do not add indentation. The included compiler supports these file-local switches and uses PP_DEFINE __LIB for library code. + For development and build instructions, see [AGENTS/DEVELOPMENT.md](AGENTS/DEVELOPMENT.md). At startup the Bundle checks whether a newer nightly exists and tells you where to download it; it never updates itself. diff --git a/bundle/src-tauri/src/runner.rs b/bundle/src-tauri/src/runner.rs index f8fa353..e0831e8 100644 --- a/bundle/src-tauri/src/runner.rs +++ b/bundle/src-tauri/src/runner.rs @@ -183,6 +183,12 @@ mod tests { async fn prefilled_input_empty_eof_and_diagnostics() { let runner = Runner::default(); for (program, input, expected, code) in [ + ( + "PP_DEFINE Enabled\nPP_IFDEF Enabled\nOUTPUT 1\nPP_ENDIF\nPP_UNDEF Enabled\nPP_IFNDEF Enabled\nOUTPUT 2\nPP_ENDIF\n", + "", + "1\n2\n", + 0, + ), ( "DECLARE N : INTEGER\nINPUT N\nOUTPUT N\nOUTPUT \"\\U0001F600\"\n", "42\n", diff --git a/plugin/README.md b/plugin/README.md index 48738dc..b0d6c01 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -20,7 +20,7 @@ Open a `.cps` file. The language mode should show **CIE Pseudocode**. ## Features -- C++ style syntax colors that follow your dark theme. +- C++ style syntax colors that follow your dark theme, including PP_DEFINE, PP_UNDEF, PP_IFDEF, PP_IFNDEF and PP_ENDIF. Preprocessor conditions do not add indentation. - Automatic Tab indentation and outdent for block keywords. - **Format Selection**: select code, then press **Cmd+K, Cmd+F** on macOS or **Ctrl+K, Ctrl+F** on Windows/Linux. Only indentation changes. - Colored `()` and `[]` pairs; unmatched brackets turn red. Quotes and comments are ignored. diff --git a/plugin/ThirdPartyNotices.md b/plugin/ThirdPartyNotices.md index e24f2c8..b195ffa 100644 --- a/plugin/ThirdPartyNotices.md +++ b/plugin/ThirdPartyNotices.md @@ -5,9 +5,9 @@ Microsoft VS Code’s built-in C++ extension. Exception keywords reuse its `keyword.control.try.cpp`, `keyword.control.catch.cpp` and `keyword.control.throw.cpp` scopes; ENDTRY uses the TRY scope. Selection, class, access, constant, template, operator and external-declaration highlighting also -reuse C++ scopes. OVERLOAD borrows a declaration modifier, #LIB borrows #pragma -and INCLUDE_OBJ borrows #include; ellipses use the C++ variadic scope. Literal -escapes reuse `constant.character.escape`; the adjacent C string suffix borrows +reuse C++ scopes. OVERLOAD borrows a declaration modifier, PP_* directives borrow +C++ preprocessor keyword and macro-name scopes, and INCLUDE_OBJ borrows #include; +ellipses use the C++ variadic scope. Literal escapes reuse `constant.character.escape`; the adjacent C string suffix borrows `keyword.other.suffix.literal.user-defined.string.cpp`. Custom types, template parameters and angle delimiters use the corresponding C++ type and template scopes; class-method receivers reuse `variable.language.this.cpp`. diff --git a/plugin/examples/highlighting.cps b/plugin/examples/highlighting.cps index 10d93d8..a7ec8c6 100644 --- a/plugin/examples/highlighting.cps +++ b/plugin/examples/highlighting.cps @@ -75,3 +75,13 @@ ENDCLASS DECLARE Count : Counter CALL Count.Add(Limit) OUTPUT Count.Get() + +// Preprocessor conditions do not add indentation. +PP_DEFINE Demo +PP_IFDEF Demo +OUTPUT "Enabled" +PP_IFNDEF OtherDemo +OUTPUT "Nested condition" +PP_ENDIF +PP_ENDIF +PP_UNDEF Demo diff --git a/plugin/syntaxes/cie-pseudocode.tmLanguage.json b/plugin/syntaxes/cie-pseudocode.tmLanguage.json index 16a4ac4..14ef33c 100644 --- a/plugin/syntaxes/cie-pseudocode.tmLanguage.json +++ b/plugin/syntaxes/cie-pseudocode.tmLanguage.json @@ -417,6 +417,9 @@ { "include": "#strings" }, + { + "include": "#preprocessor" + }, { "include": "#class" }, @@ -441,17 +444,6 @@ { "include": "#return_type" }, - { - "match": "^\\s*(#)(LIB)\\s*$", - "captures": { - "1": { - "name": "punctuation.definition.directive.cpp" - }, - "2": { - "name": "keyword.control.directive.pragma.cpp" - } - } - }, { "match": "(? { - assert.ok(scopesAt('\t#LIB ', '#').includes('punctuation.definition.directive.cpp')); - assert.ok(scopesAt('\t#LIB ', 'LIB').includes('keyword.control.directive.pragma.cpp')); - for (const text of ['#lib', '#LIBRARY', '#LIB // comment', 'X #LIB', '// #LIB', 'OUTPUT "#LIB"']) { - assert.ok(!scopesAt(text, '#') - .includes('punctuation.definition.directive.cpp'), text); +test('preprocessor directives reuse C++ scopes and shield switch names from ordinary keywords', () => { + for (const [word, scope] of [ + ['PP_DEFINE', 'keyword.control.directive.define.cpp'], + ['PP_UNDEF', 'keyword.control.directive.undef.cpp'], + ['PP_IFDEF', 'keyword.control.directive.conditional.cpp'], + ['PP_IFNDEF', 'keyword.control.directive.conditional.cpp'], + ['PP_ENDIF', 'keyword.control.directive.conditional.cpp'] + ]) { + assert.ok(scopesAt(`\t${word} __LIB // comment`, word).includes(scope)); + for (const text of [word.toLowerCase(), `${word}Value`, `_${word}`, `X ${word}`, `// ${word}`, `OUTPUT "${word}"`]) { + assert.ok(!scopesAt(text, word.toLowerCase() === text ? text : word).includes(scope), text); + } } + for (const word of ['__LIB', 'FUNCTION', 'INTEGER']) { + assert.ok(scopesAt(`PP_DEFINE ${word}`, word).includes('entity.name.function.preprocessor.cpp')); + } + assert.ok(scopesAt('PP_IFDEF Flag // PP_ENDIF', 'PP_ENDIF').some(s => s.startsWith('comment.'))); + assert.ok(!scopesAt('#LIB', 'LIB').some(s => s.startsWith('keyword.control.directive.'))); +}); + +test('ellipses use the C++ variadic scope', () => { for (const text of ['TEMPLATE ', 'EXTERN "C" EXTERN_PROCEDURE print(C_INT, ...)', 'CALL print(Values...)']) { assert.ok(scopesAt(text, '...').includes('punctuation.vararg-ellipses.cpp'), text); } diff --git a/plugin/test/indentation.test.js b/plugin/test/indentation.test.js index 9140ed3..e71540f 100644 --- a/plugin/test/indentation.test.js +++ b/plugin/test/indentation.test.js @@ -82,3 +82,13 @@ test('escaped quotes keep block keywords inside literals masked', () => { 'THEN', String.raw`OUTPUT "escaped \" ENDIF //"`, 'ENDIF' ]), [0, 0, 1, 0]); }); + +test('preprocessor conditions preserve ordinary block depth without adding indentation', () => { + const lines = [ + 'PP_DEFINE Flag', 'PP_IFDEF Flag', 'PROCEDURE Demo()', 'PP_IFNDEF Other', + 'IF Ready THEN', 'OUTPUT 1', 'ENDIF', 'PP_ENDIF', 'PP_UNDEF Flag', + 'ENDPROCEDURE', 'PP_ENDIF' + ]; + assert.deepEqual(lineDepths(lines), [0, 0, 0, 1, 1, 2, 1, 1, 1, 0, 0]); + assert.deepEqual(indentationEdits(['PP_IFDEF Flag', 'OUTPUT 1', 'PP_ENDIF'], 0, 2), []); +}); -- cgit v1.3.1-4-g156e