diff --git a/.gitignore b/.gitignore index b2a1064..5dd1301 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ -# Build artifacts +# Build artifacts (Windows: *.dll, *.pyd; the commands are under bin/) *.o *.d *.so +*.dll +*.pyd __pycache__/ # Editor / OS cruft diff --git a/README.md b/README.md index 4880a59..223709a 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Installation guides are in [`doc/install/`](doc/install/): - Linux, from source — `doc/install/linux_source_install.md` - macOS, from source — `doc/install/macos_source_install.md` +- Windows, from source (native) — `doc/install/windows_source_install.md` +- Windows, Linux inside WSL — `doc/install/windows_wsl_install.md` - Linux, container — `doc/install/linux_container_install.md` - macOS, container — `doc/install/macos_container_install.md` @@ -51,7 +53,7 @@ are regenerated on each release — patches cannot be merged directly. Report problems (or send patches) to the author; accepted changes are applied to the development tree and appear in a following snapshot. -This snapshot was assembled from development commit `f344a37d772c`. +This snapshot was assembled from development commit `c3c40f6c8fca`. ## License diff --git a/bin/README.md b/bin/README.md index 83f0674..3454ac0 100644 --- a/bin/README.md +++ b/bin/README.md @@ -1,5 +1,6 @@ # bin This directory holds the built Klammertext command executables (`ktext`, -`kdesc`, `kdiag`) after you build Klammertext with `make -C com`. -It is empty in the repository. +`kdesc`, `kdiag`) after you build Klammertext with `make -C com`. On +Windows the library and the compiler's runtime DLLs are copied here too, +so `bin\` alone needs to be on the `Path`. It is empty in the repository. diff --git a/com/Makefile b/com/Makefile index 7c6cf42..8e76200 100644 --- a/com/Makefile +++ b/com/Makefile @@ -13,8 +13,10 @@ COMMANDS := kdiag kdesc ktext # Build output directory: commands are linked directly into ../bin, so there is # exactly one copy of each (no redundant executables in com/) and make still # tracks them by path for incremental builds. +# The targets carry the system's executable suffix (makefile.env's EXE) so +# that on Windows the file gcc writes, ktext.exe, is the target itself. BINDIR := ../bin -BINCOMMANDS := $(addprefix $(BINDIR)/,$(COMMANDS)) +BINCOMMANDS := $(addprefix $(BINDIR)/,$(addsuffix $(EXE),$(COMMANDS))) # System install location (out-of-tree). Override on the command line, e.g. # make install PREFIX=/opt DESTDIR=/tmp/stage @@ -23,7 +25,7 @@ DESTDIR ?= # Shared library location LIBDIR := ../lib -LIBRARY := $(LIBDIR)/libklammertext.so +LIBRARY := $(LIBDIR)/$(KLAMMERTEXT_LIBFILE) # Linker flags for commands COM_LDFLAGS := $(EXPORT_DYNAMIC) -Wl,-rpath,'$(ORIGIN)/../lib' -L$(LIBDIR) @@ -36,7 +38,7 @@ ifneq (,$(filter MINGW% MSYS%,$(UNAME_S))) MANIFEST_OBJ := manifest.o # The DLLs beside the commands (makefile.env explains); with them, bin/ on # PATH is all a Windows user needs besides Python. -RUNTIME_DLLS := $(addprefix $(BINDIR)/,libklammertext.so $(GCC_RUNTIME_DLLS)) +RUNTIME_DLLS := $(addprefix $(BINDIR)/,$(KLAMMERTEXT_LIBFILE) $(GCC_RUNTIME_DLLS)) else MANIFEST_OBJ := RUNTIME_DLLS := @@ -46,8 +48,9 @@ endif DEPFILES := $(addsuffix .d,$(COMMANDS)) # Compiler flags for dependency generation ($(@F) keeps the .d files in com/, -# not in the ../bin output directory). -DEPFLAGS = -MMD -MP -MF $(@F).d +# not in the ../bin output directory; $(basename) drops the .exe so the file +# is ktext.d on every system, the name DEPFILES includes and clean removes). +DEPFLAGS = -MMD -MP -MF $(basename $(@F)).d .PHONY: all clean redo clang mac sks install @@ -65,7 +68,7 @@ commands : $(BINCOMMANDS) $(RUNTIME_DLLS) ifneq (,$(filter MINGW% MSYS%,$(UNAME_S))) manifest.o : klammertext.rc klammertext.manifest windres $< $@ -$(BINDIR)/libklammertext.so : $(LIBRARY) | $(BINDIR) +$(BINDIR)/$(KLAMMERTEXT_LIBFILE) : $(LIBRARY) | $(BINDIR) cp $< $@ $(BINDIR)/%.dll : $(GCC_BIN)%.dll | $(BINDIR) cp $< $@ @@ -86,18 +89,18 @@ sks : | mac $(MAKE) -C $(KS)/document # Explicit rules for each command - link against shared library, output to bin/ -$(BINDIR)/kdiag : kdiag.cpp $(LIBRARY) $(MANIFEST_OBJ) | $(BINDIR) +$(BINDIR)/kdiag$(EXE) : kdiag.cpp $(LIBRARY) $(MANIFEST_OBJ) | $(BINDIR) $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< $(MANIFEST_OBJ) -o $@ $(KLAMMERTEXT_LIB) $(LDLIBS) -$(BINDIR)/kdesc : kdesc.cpp $(LIBRARY) $(MANIFEST_OBJ) | $(BINDIR) +$(BINDIR)/kdesc$(EXE) : kdesc.cpp $(LIBRARY) $(MANIFEST_OBJ) | $(BINDIR) $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< $(MANIFEST_OBJ) -o $@ $(KLAMMERTEXT_LIB) $(LDLIBS) -$(BINDIR)/ktext : ktext.cpp $(LIBRARY) $(MANIFEST_OBJ) | $(BINDIR) +$(BINDIR)/ktext$(EXE) : ktext.cpp $(LIBRARY) $(MANIFEST_OBJ) | $(BINDIR) $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< $(MANIFEST_OBJ) -o $@ $(KLAMMERTEXT_LIB) $(LDLIBS) # Out-of-tree system install (copies; the build tree stays intact). The # installed commands still need KLAMMERTEXT_HOME pointing at a Klammertext tree -# for sks/ and the Python modules; their rpath finds libklammertext.so in +# for sks/ and the Python modules; their rpath finds the library in # $(PREFIX)/lib. install : all install -d $(DESTDIR)$(PREFIX)/bin $(DESTDIR)$(PREFIX)/lib @@ -115,9 +118,9 @@ else $(MAKE) -C $(KS)/kutil clean $(MAKE) -C $(KS)/target clean $(MAKE) -C $(KS)/document clean - # ref/reflect.so links against libklammertext.so and is dlopened, so + # ref/reflect$(DSO) links against the library and is dlopened, so # it shares the class layouts: a stale one after a header change is - # the same memory-corruption hazard as a stale sks/document.so. + # the same memory-corruption hazard as a stale sks/document$(DSO). $(MAKE) -C $(K)/ref clean $(MAKE) -C $(K)/kpy clean $(MAKE) clean diff --git a/doc/edit/README.md b/doc/edit/README.md index f9f04a2..c80be86 100644 --- a/doc/edit/README.md +++ b/doc/edit/README.md @@ -1,7 +1,7 @@ # Klammertext editor support Editing support for Klammertext files (`.kt` documents and `.k` klammer -definitions) in four editors, built around one shared implementation. +definitions) in five editors, built around one shared implementation. | Directory | Contents | |---|---| @@ -10,6 +10,7 @@ definitions) in four editors, built around one shared implementation. | `sublime/` | Sublime Text package. Syntax file plus thin plugin wrappers that import the shared core directly (Sublime's plugin host is Python). | | `vim/` | Vim plugin. Syntax/ftplugin files plus commands that run the shared core's CLI; with `+python3`, the core also runs in-process (`=` operator, live match highlighting). | | `vscode/` | Visual Studio Code extension. TextMate grammar plus a dependency-free extension that spawns the language server and speaks LSP to it. | +| `notepadpp/` | Notepad++ (Windows). A User Defined Language for the highlighting, and two Run-menu commands that pass the current file through the shared core's CLI (delimiter check, reindent). No plugin host, so nothing structural runs in-process. | ## The architecture @@ -41,7 +42,8 @@ document/range formatting (reindentation), documentHighlight and definition (the matcher), and a `klammertext.alignTable` command. The VS Code extension is its first client; any LSP client works — Neovim's built-in LSP, Emacs eglot, Sublime's LSP package — with a one-line -configuration pointing at `python3 klammertext_ls.py`. +configuration pointing at `python3 klammertext_ls.py` (on Windows +`python klammertext_ls.py`). Syntax highlighting and cursor-latency features stay native in each editor (a tokenizer or a per-keystroke matcher cannot round-trip to Python), so @@ -68,10 +70,12 @@ Two kinds of artifact cannot read it and restate parts of it by hand: defcustoms, checked byte-for-byte against the shared core by `tst/editor_test.sh`. 2. **The static syntax files** — the literal-klammer set (`@code`) appears - in the Sublime `.sublime-syntax`, the Vim `syntax/klammertext.vim`, and - the VS Code `tmLanguage.json` grammar (and the Emacs defcustom). When - you add a literal klammer, change them together; each file's header - carries the same SYNC note. + in the Sublime `.sublime-syntax`, the Vim `syntax/klammertext.vim`, the + VS Code `tmLanguage.json` grammar, and the Notepad++ `Klammertext.udl.xml` + (and the Emacs defcustom). When you add a literal klammer, change them + together; each file's header carries the same SYNC note. The UDL can + hold only names that are not prefixes of other klammer names (it has + no word boundary), so it carries `code` and not `c`. ## Testing diff --git a/doc/edit/notepadpp/Klammertext.udl.xml b/doc/edit/notepadpp/Klammertext.udl.xml new file mode 100644 index 0000000..b90c5d4 --- /dev/null +++ b/doc/edit/notepadpp/Klammertext.udl.xml @@ -0,0 +1,114 @@ + + + + + + + + + + 00# 01 02 03#[ 04]# + + + + + + + + | : + ## #- #+ #/ + + + + + + + #[ + + ]# + @@@ + @@ + @ + ^ + + + + + 00@code 01 02code@ 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/edit/notepadpp/README.md b/doc/edit/notepadpp/README.md new file mode 100644 index 0000000..d7cdbb3 --- /dev/null +++ b/doc/edit/notepadpp/README.md @@ -0,0 +1,114 @@ +# Klammertext support for Notepad++ + +Notepad++ support for editing Klammertext files (`.kt` documents and `.k` +klammer definitions): syntax highlighting through a **User Defined +Language**, and the shared editor core's delimiter check and +reindentation through two **Run-menu commands**. Notepad++ is Windows +only, and so is this package. + +Notepad++ has no Python plugin host and no built-in language-server +client, so it cannot import the shared core the way Sublime Text does or +talk to the language server the way VS Code does. What it has is a +declarative highlighter and a Run menu that can invoke any program on the +current file, and this package uses exactly those two things. + +## Files + +| File | Purpose | +|---|---| +| `Klammertext.udl.xml` | The User Defined Language: highlighting for the three `@`-tiers, escapes, text removal, and the `@code` verbatim interior. Its header lists what it colours and the limits of a UDL. | +| `klammertext_edit.py` | The shared editor core (in the editing zip it sits here; in the Klammertext repository it is `doc\edit\shared\klammertext_edit.py`). The Run commands call it. | +| `README.md` | This file. | + +## Requirements + +- Notepad++ 7.9 or later (the `userDefineLangs` folder; any recent + version qualifies). +- For the Run commands: `python` on the `Path`, which the Klammertext + installation already needs. + +## Install the highlighting + +Copy `Klammertext.udl.xml` into Notepad++'s user-defined-language folder +and restart Notepad++: + +```powershell +$d = "$env:AppData\Notepad++\userDefineLangs" +New-Item -ItemType Directory -Force $d | Out-Null +Copy-Item Klammertext.udl.xml $d +``` + +Notepad++ loads every file in that folder at start. `.kt` and `.k` files +then open as "Klammertext" (the language shows in the status bar and at +the bottom of the **Language** menu). The alternative is the dialog: +**Language → User Defined Language → Define your language… → Import…**, +which copies the file to the same place. + +What you see: klammer applications (`@name … @`) in blue, definitions +(`@@name … @@`) in green, system commands (`@@@name … @@@`) in orange; +a `#` at the start of a line greys the rest of the line, `#[ … ]#` greys +and folds a region; `##`, `#-`, `#+`, `#/`, `|` and `:` in the marker +red; the interior of `@code … code@` muted and uninterpreted. The +colours are the palette's light-ground values with transparent +backgrounds, so a dark theme keeps its own ground. + +Two limits, both properties of a UDL rather than of Klammertext, are +documented in the file's header: a `#` that is not the first thing on +its line is not treated as a comment (so that the whitespace operators +`#-`, `#+`, `#/` never grey the rest of their line), and `@c … c@` is not +made verbatim (a UDL delimiter has no word boundary, so `@c` would also +open at `@caption` and `@cond`). + +## The Run commands + +The shared core's command-line interface reads a file on standard input +and writes the result on standard output; Notepad++'s **Run → Run…** +dialog (`F5`) runs a command line with the current file's path filled in +as `$(FULL_CURRENT_PATH)`. Two commands cover the structural tools. +Replace `C:\Users\\klammertext\doc\edit\shared` with the folder that +holds `klammertext_edit.py` (with the editing zip, the folder this +README is in). + +**Check the delimiters** — lists every unclosed opening, extra close, +name or tier mismatch, and unclosed literal span or `#[` block, as +`line:column: message`; an empty window means the file is balanced. +The `/k` keeps the window open to read: + +``` +cmd /k python "C:\Users\\klammertext\doc\edit\shared\klammertext_edit.py" check < "$(FULL_CURRENT_PATH)" +``` + +**Reindent the file** — rewrites the file with the canonical +indentation (2 spaces per nesting level, bars and closes at their +opener's column, `@document` transparent, verbatim and removed text +untouched): + +``` +cmd /c python "C:\Users\\klammertext\doc\edit\shared\klammertext_edit.py" indent < "$(FULL_CURRENT_PATH)" > "$(FULL_CURRENT_PATH).tmp" && move /y "$(FULL_CURRENT_PATH).tmp" "$(FULL_CURRENT_PATH)" +``` + +Paste each line into the Run dialog once, press **Save…**, give it a +name ("Klammertext check", "Klammertext reindent") and a shortcut +(`Ctrl+Alt+K`, `Ctrl+Alt+I` are free); they then appear at the bottom of +the **Run** menu with those keys. + +Two things to know about the reindent command. It works on the file +**on disk**, so save first (`Ctrl+S`); Notepad++ does not save before +running. And after it runs, Notepad++ notices the changed file: with the +default setting it asks whether to reload, which you answer Yes; under +**Settings → Preferences → MISC. → File Status Auto-Detection** the +choice "Update silently" makes the reload automatic. Undo history is +lost on a reload, as for any external edit. + +Table alignment and jump-to-match need a caret position the Run dialog +cannot pass, so they are not offered here. They are available in every +other supported editor, and for Notepad++ through the PythonScript +plugin, which embeds a Python that can import the shared core and reach +the caret; a script for it is a possible addition, not part of this +package. + +## Keeping things in sync + +The UDL restates the literal-klammer set (`@code`) that the shared core +owns in `LITERAL_KLAMMERS`; its header says how to add one. The editor +support's overview is `doc/edit/README.md` in the Klammertext repository. diff --git a/doc/edit/shared/klammertext_edit.py b/doc/edit/shared/klammertext_edit.py index 4ffccfb..ea8a096 100644 --- a/doc/edit/shared/klammertext_edit.py +++ b/doc/edit/shared/klammertext_edit.py @@ -56,7 +56,10 @@ import sys # --- policy ---------------------------------------------------------------- # Klammer names whose content is a literal argument (verbatim interior, -# closed by a named NAME@ delimiter). +# closed by a named NAME@ delimiter). The static syntax artifacts restate +# this set by hand (Emacs defcustom, .sublime-syntax, vim syntax, tmLanguage, +# the Notepad++ UDL -- which can hold only "code"); doc/edit/README.md, +# "Keeping things in sync". LITERAL_KLAMMERS = set(["code", "c"]) # Klammers that contribute no indentation level (a @document's paragraphs @@ -1037,6 +1040,13 @@ def _cli(argv): if len(argv) < 1: return usage() mode = argv[0] + # Klammertext writes LF on every system. Python's text-mode stdout turns + # every "\n" into "\r\n" on Windows, and an editor -- or the test suite -- + # comparing the result with an LF buffer then sees every line changed. + # stdin keeps universal newlines: a CRLF buffer arrives as "\n" lines, + # which is what the algorithms expect. + if hasattr(sys.stdout, 'reconfigure'): # 3.7+; the floor is 3.8 + sys.stdout.reconfigure(newline='\n') s = sys.stdin.read() if mode == 'indent': diff --git a/doc/edit/sublime/README.md b/doc/edit/sublime/README.md index 72f8217..213d99e 100644 --- a/doc/edit/sublime/README.md +++ b/doc/edit/sublime/README.md @@ -203,8 +203,8 @@ elsewhere) are not part of this package, but they exist: the **Klammertext language server** (`doc/edit/shared/klammertext_ls.py`, the same program the VS Code extension uses) serves them to Sublime through the community LSP package. Install "LSP" from Package Control and add a client with -`command: ["python3", "/path/to/klammertext_ls.py"]` for the -`text.klammertext` selector. Everything in this package works the same +`command: ["python3", "/path/to/klammertext_ls.py"]` (on Windows +`"python"`) for the `text.klammertext` selector. Everything in this package works the same with or without it. ## Troubleshooting diff --git a/doc/edit/vim/README.md b/doc/edit/vim/README.md index 060cdef..b78d154 100644 --- a/doc/edit/vim/README.md +++ b/doc/edit/vim/README.md @@ -14,7 +14,8 @@ sync. ## Requirements - Vim 8+ (or Neovim) with `+eval` — any normal Vim; only `vim-tiny` lacks it. -- `python3` on `PATH` (Klammertext itself already requires Python). +- `python3` on `PATH` — on Windows `python`, the name python.org installs + (Klammertext itself already requires Python). - The shared core, found automatically in this order: 1. `g:klammertext_edit_py`, if you set it (a full path to `klammertext_edit.py`); @@ -81,7 +82,7 @@ in-process: the `=` operator reindents through `'indentexpr'` (`==`, `gg=G`), and the matching delimiter is highlighted live as the cursor sits on one — the show-paren equivalent, with a mismatched or unbalanced delimiter shown in red plus a message. Without `+python3` the commands above still work; -they shell out to `python3`. +they shell out to `python3` (on Windows `python`). **Comment toggling** — `'commentstring'` is set to `# %s`, so Vim 9.1's built-in commenting and Neovim's `gcc`/`gc` (or the commentary plugin) @@ -92,7 +93,7 @@ toggle `#` line removal. | Variable | Meaning (default) | |---|---| | `g:klammertext_edit_py` | full path to `klammertext_edit.py` (auto-detected) | -| `g:klammertext_python` | Python interpreter (`python3`) | +| `g:klammertext_python` | Python interpreter (`python3`; on Windows `python`) | | `g:klammertext_no_mappings` | set to define no default mappings | ## Neovim and the language server diff --git a/doc/edit/vim/autoload/klammertext.vim b/doc/edit/vim/autoload/klammertext.vim index 29dd317..ff92226 100644 --- a/doc/edit/vim/autoload/klammertext.vim +++ b/doc/edit/vim/autoload/klammertext.vim @@ -11,7 +11,8 @@ " Configuration: " g:klammertext_edit_py full path to klammertext_edit.py (overrides the " search described below) -" g:klammertext_python the Python interpreter (default "python3") +" g:klammertext_python the Python interpreter (default "python3"; +" on Windows "python", the name python.org installs) " --- locating the shared core --------------------------------------------- " Search order: an explicit g:klammertext_edit_py; a vendored copy at this @@ -37,7 +38,8 @@ function! s:EditScript() abort endfunction function! s:Python() abort - return exists('g:klammertext_python') ? g:klammertext_python : 'python3' + return exists('g:klammertext_python') ? g:klammertext_python + \ : (has('win32') ? 'python' : 'python3') endfunction " Run the core CLI over the whole buffer. ARGS is the argument string diff --git a/doc/edit/vscode/README.md b/doc/edit/vscode/README.md index 79faad7..0dc75ad 100644 --- a/doc/edit/vscode/README.md +++ b/doc/edit/vscode/README.md @@ -18,8 +18,9 @@ language's structure, everywhere. - VS Code 1.75 or later — a minimum, not a target: VS Code's monthly releases count 1.75, 1.76, … (1.75 is from January 2023), so any version from the last few years qualifies. -- `python3` on `PATH` (or set `klammertext.pythonPath`); Klammertext itself - already requires Python. +- `python3` on `PATH` — on Windows `python`, the name python.org installs + (or set `klammertext.pythonPath`); Klammertext itself already requires + Python. - The language server, found automatically in this order: 1. the `klammertext.serverPath` setting, if set; 2. `klammertext_ls.py` vendored next to `extension.js` (the layout the @@ -32,8 +33,9 @@ language's structure, everywhere. ## Install Package the extension as a `.vsix` and install it (the builder needs only -bash and python3; the editing zip from the Klammertext website ships a -prebuilt `klammertext.vsix`, so there the first step is already done): +bash and Python — on Windows, run it in the MSYS2 shell the Klammertext +build uses; the editing zip from the Klammertext website ships a prebuilt +`klammertext.vsix`, so there the first step is already done): ./make_vsix.sh code --install-extension klammertext.vsix @@ -129,7 +131,7 @@ Shortcuts** (`Ctrl+K Ctrl+S`), search "klammertext", and rebind. ## Settings A normal installation needs neither setting: the extension runs `python3` -from `PATH` and finds the language server automatically (the copy vendored +(on Windows `python`) from `PATH` and finds the language server automatically (the copy vendored next to `extension.js`, then `../shared/`, then `$KLAMMERTEXT_HOME/doc/edit/shared/`). They exist for unusual setups. Set them in the Settings UI (`Ctrl+,`, search "klammertext") or in @@ -138,10 +140,10 @@ reload the window after changing either. | Setting | Meaning (default) | |---|---| -| `klammertext.pythonPath` | Python interpreter for the server (`python3`) | +| `klammertext.pythonPath` | Python interpreter for the server (`python3`; on Windows `python`) | | `klammertext.serverPath` | full path to `klammertext_ls.py` (auto-located) | -`pythonPath` matters when `python3` is not on the `PATH` VS Code sees — a +`pythonPath` matters when the interpreter is not on the `PATH` VS Code sees — a VS Code launched from the desktop inherits a different environment than your shell — or when a specific interpreter is wanted. `serverPath` matters only when the server file lives outside the search chain above. diff --git a/doc/edit/vscode/extension.js b/doc/edit/vscode/extension.js index 310ce3f..42451b8 100644 --- a/doc/edit/vscode/extension.js +++ b/doc/edit/vscode/extension.js @@ -165,7 +165,11 @@ function activate(context) { 'diagnostics, formatting, matching and alignment need the server.'); return; } - const python = vscode.workspace.getConfiguration('klammertext').get('pythonPath') || 'python3'; + // The setting's declared default is empty so that this fallback is what + // decides: python3 everywhere but Windows, where python.org installs + // "python" only (the Klammertext commands there use the same name). + const python = vscode.workspace.getConfiguration('klammertext').get('pythonPath') || + (process.platform === 'win32' ? 'python' : 'python3'); client = new LspClient(python, [serverPath], log); client.proc.on('error', (err) => { vscode.window.showWarningMessage( diff --git a/doc/edit/vscode/make_vsix.sh b/doc/edit/vscode/make_vsix.sh index f5f2a5c..54f04af 100755 --- a/doc/edit/vscode/make_vsix.sh +++ b/doc/edit/vscode/make_vsix.sh @@ -9,7 +9,7 @@ # code --install-extension klammertext.vsix # # A .vsix is a zip holding a two-file manifest plus the extension payload -# under extension/. This script builds it with bash + python3 only — no +# under extension/. This script builds it with bash + Python only — no # npm, no vsce — matching the extension's no-dependency design. The shared # core and language server are vendored into the payload: from this folder # if present (the editing-zip layout), else from ../shared (the repository @@ -18,6 +18,12 @@ set -e cd "$(dirname "$0")" +# The interpreter: $PYTHON if the environment names one (env/runtime.env +# does), else python3, else python -- the only name python.org installs on +# Windows, where this runs in the MSYS2 shell. +PYTHON="${PYTHON:-$(command -v python3 || command -v python)}" +[ -n "$PYTHON" ] || { echo "make_vsix.sh: no python3 or python on PATH" >&2; exit 1; } + OUT="$PWD/klammertext.vsix" STAGE="$(mktemp -d)" trap 'rm -rf "$STAGE"' EXIT @@ -36,7 +42,7 @@ for f in klammertext_edit.py klammertext_ls.py; do fi done -VERSION=$(python3 -c "import json; print(json.load(open('package.json'))['version'])") +VERSION=$("$PYTHON" -c "import json; print(json.load(open('package.json'))['version'])") cat > "$STAGE/[Content_Types].xml" <<'EOF' @@ -75,7 +81,7 @@ cat > "$STAGE/extension.vsixmanifest" < EOF -python3 - "$STAGE" "$OUT" <<'EOF' +"$PYTHON" - "$STAGE" "$OUT" <<'EOF' import os, sys, zipfile stage, out = sys.argv[1], sys.argv[2] with zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED) as z: diff --git a/doc/edit/vscode/package.json b/doc/edit/vscode/package.json index 3d47278..b1fb345 100644 --- a/doc/edit/vscode/package.json +++ b/doc/edit/vscode/package.json @@ -83,8 +83,8 @@ "properties": { "klammertext.pythonPath": { "type": "string", - "default": "python3", - "description": "Python interpreter used to run the Klammertext language server." + "default": "", + "description": "Python interpreter used to run the Klammertext language server. Leave blank for python3 (on Windows: python)." }, "klammertext.serverPath": { "type": "string", diff --git a/doc/install/linux_source_install.md b/doc/install/linux_source_install.md index 2aaaad4..ce4014c 100644 --- a/doc/install/linux_source_install.md +++ b/doc/install/linux_source_install.md @@ -7,6 +7,8 @@ including `:haskell` and `:shell` commands that depend on locally installed software. For the container installation on Linux, see `linux_container_install.md`. +On Windows, this page also applies unchanged inside the Windows Subsystem +for Linux; `windows_wsl_install.md` says what is specific to WSL. ## Prerequisites @@ -21,15 +23,19 @@ sudo apt-get install g++ make python3-dev The C++ compiler must support C++20. GCC 11 or later is required (Ubuntu 22.04 and later include GCC 12+). -The SKS `@image` klammer requires OpenImageIO Python bindings. These must match -the Python version that ktext is built against (check with -`python3.XX -c "import OpenImageIO"`). For example, if ktext links against -Python 3.12: +The SKS `@image` klammer requires the OpenImageIO Python bindings, for the +same Python that ktext is built against (the system `python3`). Ubuntu +packages them: ```bash -pip3.12 install OpenImageIO +sudo apt-get install python3-openimageio +python3 -c "import OpenImageIO" ``` +(A `pip` install works too, but a fresh Ubuntu has no `pip` until +`python3-pip` is installed, and since 24.04 it refuses to install into the +system Python without `--break-system-packages`; the package avoids both.) + Verify: ```bash @@ -284,8 +290,8 @@ ktext -s '@eval 1 + 1 @' -d # Shell evaluation ktext -s '@eval :shell date @' -d -# Show machine state (SKS is loaded by default) -ktext -s '' -m +# Show the machine's initial state: targets, argument types, the primitive klammers +kdiag --machine # HTML and PDF output (uses the hello.kt from the Build section) ktext hello.kt -t html diff --git a/doc/install/windows_source_install.md b/doc/install/windows_source_install.md index 4756236..7d19029 100644 --- a/doc/install/windows_source_install.md +++ b/doc/install/windows_source_install.md @@ -13,6 +13,14 @@ The installation uses three things a Windows user installs once: building; **Python** from python.org, which Klammertext embeds; and **TeX Live**, for PDF output. Everyday use needs only PowerShell. +This is one of three ways to have Klammertext on Windows. If you are at +home in Linux, the Windows Subsystem for Linux gives you the Linux +installation unchanged, with the Windows editors and browser alongside: +`windows_wsl_install.md`. If you want no build at all, the prebuilt +container images run on Windows through Docker Desktop, which that page +also describes. This page is the native route: Windows programs, no +Linux layer. + ## Prerequisites @@ -119,10 +127,11 @@ optimizing compiler may run out of memory and report `out of memory allocating`. If so, run `make -C com` without `-j`: slower, and it finishes. -This builds the library, the Standard Klammer Set's components and the -three commands into `bin\`, and copies the library and the compiler's -runtime DLLs there too, so `bin\` is self-contained. The Python binding is -built into `kpy\`. Verify: +This builds the library (`klammertext.dll`), the Standard Klammer Set's +components and the three commands (`ktext.exe`, `kdesc.exe`, `kdiag.exe`) +into `bin\`, and copies the library and the compiler's runtime DLLs there +too, so `bin\` is self-contained. The Python binding is built into `kpy\`. +Verify: ```bash ktext -s '@eval 1 + 1 @' -d # prints 2 @@ -221,21 +230,37 @@ ktext hello.kt -t pdf # writes hello.pdf ## Using the commands from PowerShell Building needs the UCRT64 shell; using Klammertext does not. For everyday -use from PowerShell, put three directories on the `Path` and set +use from PowerShell, put two directories on the `Path` and set `KLAMMERTEXT_HOME`, once, as user environment variables (Settings ▸ -System ▸ About ▸ Advanced system settings ▸ Environment Variables, or with -`setx`): +System ▸ About ▸ Advanced system settings ▸ Environment Variables, or +from PowerShell): ```powershell -setx KLAMMERTEXT_HOME "C:\Users\\klammertext" -setx Path "$env:Path;C:\Users\\klammertext\bin;C:\Users\\external\texlive\2026\bin\windows" +[Environment]::SetEnvironmentVariable("KLAMMERTEXT_HOME", "C:\Users\\klammertext", "User") +$p = [Environment]::GetEnvironmentVariable("Path", "User") +[Environment]::SetEnvironmentVariable("Path", "$p;C:\Users\\klammertext\bin;C:\Users\\external\texlive\2026\bin\windows", "User") ``` -Python is already on the `Path` from its installer. Open a new PowerShell +(Not `setx Path "$env:Path;…"`: `$env:Path` is the machine and user +values merged, so that copies every machine entry into your user `Path`, +and `setx` silently cuts a value over 1024 characters.) Python is already +on the `Path` from its installer. Open a new PowerShell window and `ktext`, `kdesc` and `kdiag` work there directly. Paths Klammertext prints use forward slashes on every system; paths you type may use either. +One PowerShell rule to know when you pass Klammertext text with `-s`: an +apostrophe inside a single-quoted string ends the string, so `It's` must +be written `It''s` (two apostrophes), or the whole text put in double +quotes. The error you get otherwise mentions a "here-string header", +because PowerShell reads the next `@'` as one; it is PowerShell's message, +not Klammertext's. + +```powershell +ktext -s '@i It''s a Pyrrhic victory! @' -d +ktext -s "@i It's a Pyrrhic victory! @" -d +``` + ## Verifying the installation @@ -250,12 +275,12 @@ ktext hello.kt -t html # html ktext hello.kt -t pdf # pdf, requires TeX Live make -C $KLAMMERTEXT_HOME/tst test # the engine's test suites -make -C $KLAMMERTEXT_HOME/sks/tst test # the Standard Klammer Set's suites (needs TeX Live) ``` A few suite cases report `SKIP` on Windows for things the MSYS2 shell cannot provide, such as a real terminal for the colour tests or a -symbolic link; that is expected. +symbolic link; that is expected. (The Standard Klammer Set's own suites +are part of the development repository, not of this distribution.) ## Updating @@ -269,6 +294,56 @@ make -C com -j ``` +## Editor support + +Klammertext's editor support (`doc\edit\`, one shared implementation +behind VS Code, Sublime Text, Vim and Emacs integrations) works on Windows +with the Python already installed. Each editor's README in `doc\edit\` +has the details; the Windows-specific steps are these. + +**Visual Studio Code.** Build the extension package in the UCRT64 shell, +then install it from PowerShell (or from any window where `code` is on the +`Path`): + +```bash +cd ~/klammertext/doc/edit/vscode && ./make_vsix.sh +``` + +```powershell +code --install-extension C:\Users\\klammertext\doc\edit\vscode\klammertext.vsix +``` + +Reload VS Code. The extension runs `python` from the `Path` and finds the +language server inside the package; nothing needs configuring. + +**Sublime Text 4.** Copy the package and the shared core into Sublime's +`Packages` directory (**Preferences → Browse Packages…** opens it): + +```powershell +$p = "$env:AppData\Sublime Text\Packages\Klammertext" +New-Item -ItemType Directory -Force $p | Out-Null +Copy-Item C:\Users\\klammertext\doc\edit\sublime\* $p +Copy-Item C:\Users\\klammertext\doc\edit\shared\klammertext_edit.py $p +``` + +Sublime loads the package live. + +**Notepad++.** Highlighting comes from a User Defined Language file, the +delimiter check and reindentation from two Run-menu commands +(`doc\edit\notepadpp\README.md` has both): + +```powershell +$d = "$env:AppData\Notepad++\userDefineLangs" +New-Item -ItemType Directory -Force $d | Out-Null +Copy-Item C:\Users\\klammertext\doc\edit\notepadpp\Klammertext.udl.xml $d +``` + +Restart Notepad++; `.kt` and `.k` files open as Klammertext. + +**Vim.** As in `doc\edit\vim\README.md`; the plugin runs the shared core +through `python` on Windows automatically. + + ## Troubleshooting **The command window closes at once, or "The code execution cannot proceed @@ -282,7 +357,7 @@ runtime, which nearly every Windows machine already has but a freshly installed one may not. Install it from https://aka.ms/vs/17/release/vc_redist.x64.exe and try the import again. -**"libklammertext.so was not found" or "libstdc++-6.dll was not found".** +**"klammertext.dll was not found" or "libstdc++-6.dll was not found".** `bin\` is not on the `Path`, or the build did not finish: `make -C com -j` places the library and the runtime DLLs in `bin\` beside the commands. diff --git a/doc/install/windows_wsl_install.md b/doc/install/windows_wsl_install.md new file mode 100644 index 0000000..56fc7f9 --- /dev/null +++ b/doc/install/windows_wsl_install.md @@ -0,0 +1,153 @@ +# Klammertext on Windows through the Windows Subsystem for Linux + +This page is for someone who works in Linux by habit and has a Windows +machine to work on. The Windows Subsystem for Linux (WSL 2) runs a real +Linux kernel and an ordinary Ubuntu inside Windows, and Klammertext's +Linux installation applies there unchanged: the same compiler, the same +Python, the same TeX Live, the same `runtime.env`. So this page is short. +It gets you to an Ubuntu prompt, sends you to the Linux page, and then +says what is specific to living in WSL: where files belong, how to look +at the output from Windows, and how the Windows editors reach the Linux +side. The steps were followed on a Windows 11 machine with WSL 2.7 and +the Ubuntu it installs today, 26.04. + +There are two other ways to have Klammertext on Windows: as native +Windows programs built from source (`windows_source_install.md`), and +from the prebuilt container images, which this page covers at the end, +since Docker runs inside WSL as it runs on Linux. + + +## Getting Linux + +In a PowerShell window: + +```powershell +wsl --install +``` + +This turns on the subsystem, installs the WSL layer and the current +Ubuntu, and may ask you to restart Windows; do that before going on. +Then open a terminal and type `wsl` (or start "Ubuntu" from the Start +menu). The first start asks for a Linux username and a password; these +are separate from your Windows account. Ubuntu may also ask whether to +send platform metrics; you can say no. You are then at a bash prompt in +your Linux home directory, and `wsl -l -v` in PowerShell shows Ubuntu as +version 2. + +Two things worth knowing about the Ubuntu you now have: `sudo` works with +the password you just chose, and the machine's Windows `Path` is appended +to the Linux `PATH`, so Windows programs can be run from the Linux prompt +by name (`explorer.exe`, `code`). That is convenient below and harmless +otherwise; a Windows Python or TeX Live on the machine is not picked up +by Linux, since Linux does not resolve `.exe` names. + + +## Installing Klammertext + +Follow `linux_source_install.md` at the Ubuntu prompt, from its first +line. Everything on it applies: the packages by `apt-get`, the clone, the +line in `~/.bashrc` that sources `runtime.env`, the build, TeX Live by the +bundled script, the verification. Nothing on that page is different +inside WSL. On the machine this page was written on, the build took under +a minute and TeX Live nine, and the engine's test suite passed as it does +on Linux. + +Keep the clone and your documents in the Linux file system, under your +Linux home, not under `/mnt/c`. The Windows drives are mounted there and +work, but every file operation crosses between the two systems, and a +build or a PDF run on `/mnt/c` is several times slower. + + +## Seeing the output from Windows + +WSL has no browser or PDF viewer of its own, and it needs none. Windows +sees the Linux file system as a network path, `\\wsl.localhost\Ubuntu\`, +and Windows programs can open files there. From the Linux prompt, after +`ktext hello.kt -t html` and `ktext hello.kt -t pdf`: + +```bash +# at the Ubuntu prompt (type wsl in PowerShell to get there), not in PowerShell +explorer.exe "$(wslpath -w hello/index.html)" +explorer.exe "$(wslpath -w hello.pdf)" +``` + +`wslpath -w` turns a Linux path into the Windows form of the same file, +and `explorer.exe` opens it with whatever Windows opens that kind of file +with: your browser, your PDF viewer. The first time, Windows may ask +which application to use. `explorer.exe .` opens the current Linux +directory in Explorer, and from the Windows side the same files are in +Explorer under "Linux" in the sidebar. (`explorer.exe` reports exit +status 1 even when it succeeds; a script that needs a true status can use +`powershell.exe -NoProfile -Command "Start-Process \"$(wslpath -w hello.pdf)\""` +instead.) + + +## Editors + +The Windows editors reach the Linux side, and the Linux editors are +simply there. + +**Visual Studio Code**, the case this page was written for. Install VS +Code on Windows and its "WSL" extension (Microsoft's +`ms-vscode-remote.remote-wsl`). Then, at the Linux prompt, in the +directory you want to work in: + +```bash +code . +``` + +The first time, this installs VS Code's Linux-side server into your +Linux home and opens a VS Code window on Windows connected to it. The +Klammertext extension is installed on that Linux side, once, from the +package built in the clone: + +```bash +cd ~/klammertext/doc/edit/vscode && ./make_vsix.sh && code --install-extension klammertext.vsix +``` + +Run from the Linux prompt, `code --install-extension` installs to the +WSL side, where the extension then starts the Klammertext language +server with the Linux `python3`; everything in `doc/edit/vscode/README.md` +applies as on Linux. (Extensions installed on the Windows side of VS Code +are a separate list and do not run in WSL windows.) + +**Emacs and Vim** are Linux programs: `sudo apt-get install emacs` or +`vim`, then the Emacs and Vim packages under `doc/edit/` as on Linux. + +**Sublime Text and Notepad++** on Windows can open the Linux files +through `\\wsl.localhost\Ubuntu\...` and highlight them with their +Klammertext packages (`doc/edit/sublime/`, `doc/edit/notepadpp/`); their +commands that run the shared core would run the Windows `python`, so for +those two editors the native Windows installation is the better fit. + + +## The container route + +If you would rather not build anything, the prebuilt Klammertext images +run inside WSL exactly as `linux_container_install.md` describes, since +the Ubuntu WSL installs today runs systemd and therefore Docker's +service: follow that page at the Ubuntu prompt, from `apt-get install +docker.io` on. That was done on the machine this page was written on: +the image, 2.4 GB with its TeX Live, pulled in about a minute, and its +`ktext` made the page's web page and PDF, owned by the Linux user. Docker +Desktop for Windows is the other way to run the same images; its engine +is WSL 2 as well, and it has its own licence terms for larger companies. + + +## Troubleshooting + +**`wsl --install` says virtualization is not enabled.** WSL 2 needs the +CPU's virtualization support turned on in the machine's firmware +settings; the name varies (Intel VT-x, AMD-V, SVM). Turn it on and run +the command again. + +**`code .` says the command is not found.** VS Code's `bin` directory is +put on the Windows `Path` by its installer; open a new WSL terminal after +installing VS Code, so the Linux `PATH` picks it up. + +**A build or a PDF run is slow.** The clone or the document is under +`/mnt/c`. Move it under your Linux home. + +**The Ubuntu window asks for a username again.** The distribution was +reset or reinstalled; the setup creates a fresh Linux home, and +Klammertext must be installed again inside it. diff --git a/env/makefile.env b/env/makefile.env index cea6efd..8c847b3 100644 --- a/env/makefile.env +++ b/env/makefile.env @@ -63,14 +63,16 @@ SHARED = -dynamiclib SONAME = -install_name @rpath/$(notdir $@) ORIGIN := @loader_path EXPORT_DYNAMIC = -Wl,-export_dynamic +EXE := +DSO := .so +KLAMMERTEXT_LIBFILE := libklammertext.so else ifneq (,$(filter MINGW% MSYS%,$(UNAME_S))) # ---------- Windows (MSYS2, UCRT64 environment; native PE binaries) ---------- # uname says MINGW64_NT-... under UCRT64 too. The compilers come from the # environment's PATH (/ucrt64/bin). libdl.a is the mingw-w64-dlfcn shim over # LoadLibrary, linked statically so no libdl.dll travels with the products. # PE has no soname and no rpath; a DLL is found through PATH or -# the executable's own directory, and GNU ld's -l search list on PE does not -# include ".so", hence the exact-name form below. +# the executable's own directory. GCC := g++ CLANG := clang++ LDFLAGS = @@ -79,15 +81,20 @@ SHARED = -shared SONAME = ORIGIN := $$ORIGIN EXPORT_DYNAMIC = -Wl,--export-all-symbols -KLAMMERTEXT_LIB = -l:libklammertext.so +EXE := .exe +DSO := .dll +KLAMMERTEXT_LIBFILE := klammertext.dll # A Windows program finds a DLL through PATH or its own directory, never an # rpath. So every directory that holds a program or a Python extension also -# holds the DLLs it needs: libklammertext.so and the three gcc runtime DLLs +# holds the DLLs it needs: klammertext.dll and the three gcc runtime DLLs # the toolchain links (from the compiler's own bin/). com/ and kpy/ copy them. GCC_BIN := $(dir $(shell cygpath -m "$$(command -v g++)")) GCC_RUNTIME_DLLS := libstdc++-6.dll libgcc_s_seh-1.dll libwinpthread-1.dll else # ---------- Linux ---------- +EXE := +DSO := .so +KLAMMERTEXT_LIBFILE := libklammertext.so GCC := /usr/bin/g++ CLANG := $(shell command -v clang++-18 2>/dev/null || command -v clang++ 2>/dev/null) LDFLAGS = -L/usr/lib/x86_64-linux-gnu @@ -97,8 +104,21 @@ SONAME = -Wl,-soname,$(notdir $@) ORIGIN := $$ORIGIN EXPORT_DYNAMIC = -rdynamic endif -# How a link line names libklammertext (set above for Windows). -KLAMMERTEXT_LIB ?= -lklammertext +# Product names (decided 2026-09-25: Windows users get Windows names). The +# three variables above are set per platform: EXE is the executable suffix +# (".exe" on Windows, none elsewhere), DSO the suffix of a shared object +# loaded at run time (".dll" / ".so": document$(DSO), reflect$(DSO)), and +# KLAMMERTEXT_LIBFILE the library's file name (klammertext.dll / +# libklammertext.so). A make target carries the suffix so that the file the +# compiler writes IS the target: bin/ktext never existed on Windows -- gcc +# wrote ktext.exe -- and every make relinked the three commands. The engine's +# own copy of the DSO suffix is shared_library_suffix in mac/file.h. +# +# One link form everywhere: GNU ld's -l search list on PE ends with +# ".dll", so -lklammertext finds klammertext.dll as it finds +# libklammertext.so on Linux (it did NOT find a DLL called libklammertext.so, +# which needed the exact-name -l: form). +KLAMMERTEXT_LIB = -lklammertext # Resolve the compiler choice (gcc by default; clang with COMPILER=clang). CXX := $(if $(filter clang,$(COMPILER)),$(CLANG),$(GCC)) diff --git a/kpy/Makefile b/kpy/Makefile index e26353b..2a6ba59 100644 --- a/kpy/Makefile +++ b/kpy/Makefile @@ -1,8 +1,8 @@ # kpy/ -- the Python binding of the Klammermachine. # # klammertext.cpp is a CPython extension module (see its header comment). -# Like ref/reflect.so and sks/document/document.so it links against -# libklammertext.so and shares the engine's class layouts, so it is rebuilt +# Like ref/reflect$(DSO) and sks/document/document$(DSO) it links against +# the shared library and shares the engine's class layouts, so it is rebuilt # whenever they are (mac/Makefile's "all" runs "make -C kpy"; com/Makefile's # "redo" and dev/rebuild.sh clean it -- a clean that misses a directory is a # mixed-object hazard, not a tidiness one). @@ -19,7 +19,7 @@ K := $(KLAMMERTEXT_HOME) include $(K)/env/makefile.env LIBDIR := $(K)/lib -LIBRARY := $(LIBDIR)/libklammertext.so +LIBRARY := $(LIBDIR)/$(KLAMMERTEXT_LIBFILE) EXT_SUFFIX := $(shell $(PYTHON) -c 'import sysconfig; print(sysconfig.get_config_var("EXT_SUFFIX"))') MODULE := klammertext$(EXT_SUFFIX) @@ -38,7 +38,7 @@ DEPFLAGS = -MMD -MP -MF $(@:.o=.d) # extension's dependencies from the extension's own directory, never from # PATH or an rpath -- there is none on PE); see makefile.env. ifneq (,$(filter MINGW% MSYS%,$(UNAME_S))) -BESIDE_DLLS := libklammertext.so $(GCC_RUNTIME_DLLS) +BESIDE_DLLS := $(KLAMMERTEXT_LIBFILE) $(GCC_RUNTIME_DLLS) else BESIDE_DLLS := endif @@ -54,14 +54,14 @@ $(MODULE) : $(OBJECTS) $(LIBRARY) -o $@ $(OBJECTS) $(KLAMMERTEXT_LIB) $(LDLIBS) ifneq (,$(filter MINGW% MSYS%,$(UNAME_S))) -libklammertext.so : $(LIBRARY) +$(KLAMMERTEXT_LIBFILE) : $(LIBRARY) cp $< $@ %.dll : $(GCC_BIN)%.dll cp $< $@ endif clean : - rm -f $(OBJECTS) $(DEPFILES) klammertext.*.so klammertext.*.pyd libklammertext.so *.dll *~ + rm -f $(OBJECTS) $(DEPFILES) klammertext.*.so klammertext.*.pyd *.dll *~ rm -rf __pycache__ -include $(DEPFILES) diff --git a/lib/README.md b/lib/README.md index 51c4883..25ee39d 100644 --- a/lib/README.md +++ b/lib/README.md @@ -1,5 +1,5 @@ # lib This directory holds the compiled Klammermachine shared library -(`libklammertext.so`) after you build Klammertext with `make -C com`. -It is empty in the repository. +(`libklammertext.so`; on Windows `klammertext.dll`) after you build +Klammertext with `make -C com`. It is empty in the repository. diff --git a/mac/Makefile b/mac/Makefile index 321cf66..d0b0068 100644 --- a/mac/Makefile +++ b/mac/Makefile @@ -21,7 +21,7 @@ DEPFILES := $(addsuffix .d,$(BASENAMES)) # Shared library LIBDIR := ../lib -LIBRARY := $(LIBDIR)/libklammertext.so +LIBRARY := $(LIBDIR)/$(KLAMMERTEXT_LIBFILE) # Compiler flags for dependency generation DEPFLAGS = -MMD -MP -MF $(@:.o=.d) diff --git a/mac/command.cpp b/mac/command.cpp index 238b691..6dd9bb8 100644 --- a/mac/command.cpp +++ b/mac/command.cpp @@ -1,7 +1,11 @@ #ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include #include #include #endif +#include #include #include "argv.h" @@ -39,6 +43,26 @@ void set_verbose_level(int argc, char* argv[]) // byte. The C runtime's text mode would append it to every line. _setmode(_fileno(stdout), _O_BINARY); _setmode(_fileno(stderr), _O_BINARY); + // A console decodes what a program writes in the CONSOLE's output code + // page, which on a localized Windows is an OEM page (850 on a German one) + // and never UTF-8, whatever the process's own code page (the manifest + // makes that UTF-8). The commands write UTF-8 -- kdesc --katoms's + // ⟨name⟩, an error report's ⟨katom⟩ -- so a console showed three cp850 + // characters per symbol while a file or a pipe received the right bytes + // (which is why no suite saw it; Andy did, 2026-09-25). Switch the + // console to UTF-8 while a command runs, when a stream is a console, and + // put it back at exit: the page belongs to the console, not the process, + // and would outlive it. + static UINT console_output_cp = 0; + static UINT console_output_cp_now = 0; + if (_isatty(_fileno(stdout)) || _isatty(_fileno(stderr))) { + console_output_cp = GetConsoleOutputCP(); + if (console_output_cp != 0 && console_output_cp != CP_UTF8) { + SetConsoleOutputCP(CP_UTF8); + console_output_cp_now = GetConsoleOutputCP(); + std::atexit([] { SetConsoleOutputCP(console_output_cp); }); + } + } #endif //command_name = absolute_pathname(argv[0]); command_name = fs::path(argv[0]).generic_string(); @@ -49,6 +73,13 @@ void set_verbose_level(int argc, char* argv[]) args.opt("v", "'verbosity'", "level", "0", "'verbosity'"); args.parse(argc, argv, false); verbose_level = args.as_verbosity("v"); +#ifdef _WIN32 + if (console_output_cp != 0 && console_output_cp != CP_UTF8) { + K::log(1, "Console output code page set to UTF-8 for this run (was " + + std::to_string(console_output_cp) + ", now " + + std::to_string(console_output_cp_now) + ")"); + } +#endif } bool show_usage(int argc, char* argv[]) diff --git a/mac/eval.cpp b/mac/eval.cpp index 4aceb51..0a1f469 100644 --- a/mac/eval.cpp +++ b/mac/eval.cpp @@ -320,7 +320,7 @@ std::string Eval::eval_command(katom_iter begin, katom_iter end) if (!khome.empty()) { lib_text = string_replace(lib_text, "*KLAMMERTEXT_HOME*", khome); } - fs::path libpath(lib_text + ".so"); + fs::path libpath(lib_text + shared_library_suffix); // A relative library name not found from the cwd is searched in the // directories of the files the Machine has read (same rule as the // Python module path: the library lives next to the file using it). diff --git a/mac/file.cpp b/mac/file.cpp index 4d11ff8..d82fd7c 100644 --- a/mac/file.cpp +++ b/mac/file.cpp @@ -72,14 +72,17 @@ size_t count_substrings(const std::string& text, const std::string& substring) { std::string home_directory() { - if (const char* home = std::getenv("HOME"); home && *home) { - return home; - } #ifdef _WIN32 + // USERPROFILE first: an MSYS2 shell may set HOME to its own /home/ + // (C:\msys64\home\), and a Windows user's ~/.klammertext must be + // the same directory from PowerShell and from that shell. if (const char* profile = std::getenv("USERPROFILE"); profile && *profile) { return profile; } #endif + if (const char* home = std::getenv("HOME"); home && *home) { + return home; + } return {}; } diff --git a/mac/file.h b/mac/file.h index 522aba2..7554ea1 100644 --- a/mac/file.h +++ b/mac/file.h @@ -25,9 +25,17 @@ constexpr char path_list_separator = ';'; #else constexpr char path_list_separator = ':'; #endif -// The user's home directory as the system names it: HOME, and on Windows -// USERPROFILE when HOME is unset (a plain PowerShell or cmd sets no HOME; -// an MSYS2 shell does). Empty when neither is set. +// The suffix of a shared library loaded at run time (the library a +// "@eval :cpp" names): ".dll" on Windows, ".so" everywhere else -- the same +// choice makefile.env's DSO makes when it builds one. +#ifdef _WIN32 +constexpr const char* shared_library_suffix = ".dll"; +#else +constexpr const char* shared_library_suffix = ".so"; +#endif +// The user's home directory as the system names it: HOME; on Windows +// USERPROFILE first, so the directory is the same from PowerShell (no HOME) +// and from an MSYS2 shell (which may set HOME to its own /home/). std::string home_directory(); // The first PROGRAM on PATH (split on path_list_separator; on Windows the // name also tried with ".exe"), or "" when none: what "which" answers, without diff --git a/ref/Makefile b/ref/Makefile index 4ddc80b..000d6fc 100644 --- a/ref/Makefile +++ b/ref/Makefile @@ -4,7 +4,8 @@ # character tables and return Klammertext (a "@table" call and such), which # the .kt fragments in this directory then render for whichever target is # in force. They are reached through "@eval :cpp", so this builds a DSO -# loaded at run time, not part of libklammertext.so. +# loaded at run time, not part of the shared library: reflect.so, on +# Windows reflect.dll (makefile.env's DSO). # # Adapted from sks/document/Makefile. Two things differ and both matter: # ref/ has no sks dependencies (a generator returns Klammertext, so it @@ -19,10 +20,11 @@ include $(K)/env/makefile.env # reflect.cpp includes (machine, character, error, show). No local -I. LIBDIR := $(K)/lib -LIBRARY := $(LIBDIR)/libklammertext.so +LIBRARY := $(LIBDIR)/$(KLAMMERTEXT_LIBFILE) +REFLECT := reflect$(DSO) -# "../lib", not "../../lib": reflect.so sits in $(K)/ref, so one level up -# reaches $(K)/lib. (sks/document.so needs two, which is what this was +# "../lib", not "../../lib": the DSO sits in $(K)/ref, so one level up +# reaches $(K)/lib. (sks/document's needs two, which is what this was # copied from -- a wrong rpath here shows up only at dlopen time, and only # where the loading process does not already have the library.) REF_LDFLAGS := $(SHARED) $(EXPORT_DYNAMIC) -Wl,-rpath,'$(ORIGIN)/../lib' -L$(LIBDIR) @@ -34,7 +36,7 @@ DEPFLAGS = -MMD -MP -MF $(@:.o=.d) .PHONY: all clean -all : reflect.so +all : $(REFLECT) %.o : %.cpp @echo Compiling $< @@ -46,11 +48,11 @@ all : reflect.so # for the same file -- compiling reflect.cpp inline AND linking reflect.o -- # defines every symbol in it twice. Building through the pattern rule also # gets reflect.d, so a header change rebuilds it. -reflect.so : $(OBJECTS) $(LIBRARY) +$(REFLECT) : $(OBJECTS) $(LIBRARY) $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(REF_LDFLAGS) $(LDFLAGS) \ -o $@ $(OBJECTS) $(KLAMMERTEXT_LIB) $(LDLIBS) clean : - rm -f $(OBJECTS) $(DEPFILES) *.so *~ + rm -f $(OBJECTS) $(DEPFILES) *.so *.dll *~ -include $(DEPFILES) diff --git a/sks/Makefile b/sks/Makefile index 1538d63..436aeea 100644 --- a/sks/Makefile +++ b/sks/Makefile @@ -1,31 +1,34 @@ # Klammertext sks/ Makefile -# Builds all .so files in sks/ subdirectories +# Builds all the DSOs (document$(DSO): .so, on Windows .dll) in sks/ +# subdirectories K := $(KLAMMERTEXT_HOME) KS := $(K)/sks KM := $(K)/mac -# Shared library that all .so files depend on -LIBRARY := $(K)/lib/libklammertext.so +include $(K)/env/makefile.env + +# Shared library that all the DSOs depend on +LIBRARY := $(K)/lib/$(KLAMMERTEXT_LIBFILE) # Support directories (build .o files used by other sks/ components) SUPPORT_DIRS := kutil target # Excluded directories: -# kutil, target - support directories that build .o files, not .so -# book, phase - obsolete, not updated for libklammertext.so +# kutil, target - support directories that build .o files, not a DSO +# book, phase - obsolete, not updated for the shared library -# Directories that build .so files (excluding support and obsolete directories) -# Find all subdirectories with Makefiles that have .so build targets -# (look for lines like "xyz.so :" or "all : xyz.so") +# Directories that build a DSO (excluding support and obsolete directories): +# every subdirectory whose Makefile names its product with makefile.env's +# $(DSO) suffix ("document$(DSO) :", "all : document$(DSO)"). SO_DIRS := $(shell for dir in $(KS)/*/; do \ - if [ -f "$${dir}Makefile" ] && grep -qE '^[a-z]+\.so\s*:|all\s*:.*\.so' "$${dir}Makefile" 2>/dev/null; then \ + if [ -f "$${dir}Makefile" ] && grep -qE '\$$\(DSO\)' "$${dir}Makefile" 2>/dev/null; then \ basename "$$dir"; \ fi; done | grep -v -E '^(kutil|target|book|phase)$$') .PHONY: all clean support $(SUPPORT_DIRS) $(SO_DIRS) -# Default target: build support first, then all .so files +# Default target: build support first, then every DSO all : support $(SO_DIRS) # Build support directories @@ -37,7 +40,7 @@ kutil : target : kutil $(MAKE) -j -C $(KS)/target -# Pattern rule for .so directories +# Pattern rule for the DSO directories # Each depends on the library and support directories $(SO_DIRS) : support $(LIBRARY) $(MAKE) -j -C $(KS)/$@ diff --git a/sks/document/Makefile b/sks/document/Makefile index d741fde..9865905 100644 --- a/sks/document/Makefile +++ b/sks/document/Makefile @@ -12,16 +12,19 @@ LOCAL_CPPFLAGS := -I$(KM) -I$(KS)/kutil -I$(KS)/target # Shared library location LIBDIR := $(K)/lib -LIBRARY := $(LIBDIR)/libklammertext.so +LIBRARY := $(LIBDIR)/$(KLAMMERTEXT_LIBFILE) -# Linker flags for document.so +# The product: document.so, on Windows document.dll (makefile.env's DSO) +DOCUMENT := document$(DSO) + +# Linker flags for the DSO DOC_LDFLAGS := $(SHARED) $(EXPORT_DYNAMIC) -Wl,-rpath,'$(ORIGIN)/../../lib' -L$(LIBDIR) # Local object files LOCAL_OBJECTS := document_class.o document_html.o document_latex.o heading.o reference.o LOCAL_DEPFILES := document_class.d document_html.d document_latex.d heading.d reference.d document.d -# External object files from sks/ (mac/*.o now in libklammertext.so) +# External object files from sks/ (mac/*.o now in the shared library) SKS_OBJECTS := $(KS)/kutil/kutil.o $(KS)/kutil/klammer_base.o \ $(KS)/target/html_util.o $(KS)/target/latex_util.o @@ -33,7 +36,7 @@ DEPFLAGS = -MMD -MP -MF $(@:.o=.d) .PHONY: all clean deps # Default target -all : document.so +all : $(DOCUMENT) # Build external dependencies first deps : @@ -45,13 +48,13 @@ deps : @echo Compiling $< $(CXX) -c $(CPPFLAGS) $(LOCAL_CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $< -o $@ -# Shared object - link against libklammertext.so -document.so : $(LOCAL_OBJECTS) $(SKS_OBJECTS) $(LIBRARY) document.cpp | deps +# Shared object - link against the library +$(DOCUMENT) : $(LOCAL_OBJECTS) $(SKS_OBJECTS) $(LIBRARY) document.cpp | deps $(CXX) $(CPPFLAGS) $(LOCAL_CPPFLAGS) $(CXXFLAGS) $(DOC_LDFLAGS) $(LDFLAGS) \ document.cpp -o $@ $(ALL_OBJECTS) $(KLAMMERTEXT_LIB) $(LDLIBS) clean : - rm -f $(LOCAL_OBJECTS) $(LOCAL_DEPFILES) *.so *~ + rm -f $(LOCAL_OBJECTS) $(LOCAL_DEPFILES) *.so *.dll *~ # document_txt.py is imported at render time (2026-09-18), so this # directory now exists here; rm -f refused it and failed every clean. rm -rf __pycache__ diff --git a/tst/editor/editor_driver.py b/tst/editor/editor_driver.py index f97c69d..de524f5 100644 --- a/tst/editor/editor_driver.py +++ b/tst/editor/editor_driver.py @@ -62,7 +62,9 @@ def main(): out = run_cli(script, ['align', str(line), str(col)], s) else: sys.exit("editor_driver.py: unknown mode: " + mode) - with open(outfile, 'w') as f: + # newline='': the core's "\n" reaches the file untranslated on every + # system (text mode would write CRLF on Windows, against LF fixtures). + with open(outfile, 'w', newline='') as f: f.write(out) # Error and diagnostics paths. diff --git a/tst/editor/vscode_ext_test.js b/tst/editor/vscode_ext_test.js index ab92c49..eb25f7a 100644 --- a/tst/editor/vscode_ext_test.js +++ b/tst/editor/vscode_ext_test.js @@ -81,7 +81,9 @@ const vscodeStub = { Uri: { parse: (s) => ({ toString: () => s }) }, DiagnosticSeverity: { Error: 0, Warning: 1 }, workspace: { - getConfiguration: () => ({ get: (k) => (k === 'pythonPath' ? 'python3' : '') }), + // Both settings blank: the extension's own defaults are what is tested + // (python3, on Windows python; the server located by the search chain). + getConfiguration: () => ({ get: () => '' }), textDocuments: [fakeDoc], onDidOpenTextDocument: (fn) => { listeners.open.push(fn); return { dispose() {} }; }, onDidChangeTextDocument: (fn) => { listeners.change.push(fn); return { dispose() {} }; }, diff --git a/tst/editor_test.sh b/tst/editor_test.sh index 099083c..6f7d2e0 100755 --- a/tst/editor_test.sh +++ b/tst/editor_test.sh @@ -59,6 +59,17 @@ FIX="$HERE/editor" OUT="$(mktemp -d)" trap 'rm -rf "$OUT"' EXIT +# A path written INSIDE a Python string must be one the interpreter can open. +# Under MSYS2 the shell's /c/... form is converted only for standalone +# arguments to a native program, never inside a string, so those get the +# Windows form (forward slashes; a backslash in a Python literal is an escape). +native() { + if command -v cygpath >/dev/null 2>&1; then cygpath -m "$1"; else printf '%s' "$1"; fi +} +SHARED_NATIVE="$(native "$SHARED_DIR")" +SUBLIME_NATIVE="$(native "$SUBLIME_DIR")" +OUT_NATIVE="$(native "$OUT")" + INDENT_FIXTURES="indent_list indent_document indent_table indent_untouched indent_defs indent_escapes indent_named_close indent_literal_c" ALIGN_FIXTURES="align_mixed align_empty_cells align_boundary align_colspan @@ -105,10 +116,10 @@ fi # --- the Sublime adapters resolve to the shared core ----------------------- if PYTHONDONTWRITEBYTECODE=1 "$PYTHON" -c " -import sys -sys.path.insert(0, '$SUBLIME_DIR') +import os, sys +sys.path.insert(0, '$SUBLIME_NATIVE') import Klammertext, Klammertext_indent, Klammertext_align -assert Klammertext.KE.__file__.startswith('$SHARED_DIR') +assert os.path.normpath(Klammertext.KE.__file__).startswith(os.path.normpath('$SHARED_NATIVE')) " 2>"$OUT/sublime.log"; then echo "${green}PASS${reset} sublime adapters import the shared core" PASS=$((PASS + 1)) @@ -118,6 +129,25 @@ else FAIL=$((FAIL + 1)) fi +# --- the Notepad++ UDL: well-formed, and it restates the literal klammer ----- +# (a static artifact of the SYNC set; nothing else can check it) +if PYTHONDONTWRITEBYTECODE=1 "$PYTHON" -c " +import sys, xml.etree.ElementTree as ET +u = ET.parse('$(native "$EDIT_DIR")/notepadpp/Klammertext.udl.xml').getroot().find('UserLang') +assert u.get('name') == 'Klammertext' and set(u.get('ext').split()) == {'kt', 'k'}, u.attrib +lists = {k.get('name'): (k.text or '') for k in u.find('KeywordLists')} +assert '00@code' in lists['Delimiters'].split() and '02code@' in lists['Delimiters'].split(), lists['Delimiters'] +assert lists['Keywords1'].strip() == '@@@' and lists['Keywords2'].strip() == '@@' and lists['Keywords3'].strip() == '@' +assert '03#[' in lists['Comments'].split() and '04]#' in lists['Comments'].split() +" 2>"$OUT/udl.log"; then + echo "${green}PASS${reset} notepad++ UDL is well-formed and restates the literal klammer" + PASS=$((PASS + 1)) +else + echo "${red}FAIL${reset} notepad++ UDL is well-formed and restates the literal klammer" + cat "$OUT/udl.log" + FAIL=$((FAIL + 1)) +fi + # --- the language server protocol test ------------------------------------- if PYTHONDONTWRITEBYTECODE=1 "$PYTHON" "$FIX/ls_test.py" "$SHARED_DIR" "$FIX" >"$OUT/ls.log" 2>&1; then echo "${green}PASS${reset} language server protocol ($(grep -c '^PASS' "$OUT/ls.log") checks)" @@ -230,17 +260,29 @@ else fi # --- the VS Code extension (optional: needs a Node runtime) ---------------- -# node itself, or VS Code's Electron binary run as Node. +# node itself, or VS Code's Electron binary run as Node, wherever an +# installed VS Code keeps it: the Linux package, the macOS app bundle, the +# Windows per-user install (LOCALAPPDATA is a Windows variable, so the shell +# is MSYS2 there and cygpath exists). +CODE_BIN="" +if [ -x /usr/share/code/code ]; then + CODE_BIN=/usr/share/code/code +elif [ -x "/Applications/Visual Studio Code.app/Contents/MacOS/Electron" ]; then + CODE_BIN="/Applications/Visual Studio Code.app/Contents/MacOS/Electron" +elif [ -n "$LOCALAPPDATA" ] && command -v cygpath >/dev/null 2>&1; then + c="$(cygpath -u "$LOCALAPPDATA")/Programs/Microsoft VS Code/Code.exe" + [ -x "$c" ] && CODE_BIN="$c" +fi run_node() { if command -v node >/dev/null 2>&1; then node "$@" - elif [ -x /usr/share/code/code ]; then - ELECTRON_RUN_AS_NODE=1 /usr/share/code/code "$@" + elif [ -n "$CODE_BIN" ]; then + ELECTRON_RUN_AS_NODE=1 "$CODE_BIN" "$@" else return 127 fi } -if command -v node >/dev/null 2>&1 || [ -x /usr/share/code/code ]; then +if command -v node >/dev/null 2>&1 || [ -n "$CODE_BIN" ]; then if run_node "$FIX/vscode_ext_test.js" "$EDIT_DIR/vscode" "$FIX" >"$OUT/vscode.log" 2>&1; then echo "${green}PASS${reset} vscode extension ($(grep -c '^PASS' "$OUT/vscode.log") checks)" PASS=$((PASS + 1)) @@ -260,7 +302,7 @@ cp -R "$EDIT_DIR/vscode" "$EDIT_DIR/shared" "$OUT/vsix/" if ( cd "$OUT/vsix/vscode" && ./make_vsix.sh >/dev/null 2>&1 ) && \ PYTHONDONTWRITEBYTECODE=1 "$PYTHON" -c " import sys, zipfile -names = set(zipfile.ZipFile('$OUT/vsix/vscode/klammertext.vsix').namelist()) +names = set(zipfile.ZipFile('$OUT_NATIVE/vsix/vscode/klammertext.vsix').namelist()) need = {'extension.vsixmanifest', '[Content_Types].xml', 'extension/package.json', 'extension/extension.js', 'extension/language-configuration.json', 'extension/README.md', diff --git a/tst/klammerset_test.sh b/tst/klammerset_test.sh index 1f5d3fd..e7c30e5 100755 --- a/tst/klammerset_test.sh +++ b/tst/klammerset_test.sh @@ -86,7 +86,10 @@ check_contains() { FAIL=$((FAIL + 1)) return fi - if printf '%s' "$output" | grep -qF "$needle"; then + # Wrap-insensitive: error text is justified to 80 columns, and where the + # needle breaks across a line depends on the installation's path length + # (found in Windows Sandbox, whose user name is long: "was not \nfound"). + if printf '%s' "$output" | tr '\n' ' ' | tr -s ' ' | grep -qF "$needle"; then echo "${green}PASS${reset} $test_name" PASS=$((PASS + 1)) else @@ -114,7 +117,8 @@ check_fails() { FAIL=$((FAIL + 1)) return fi - if printf '%s' "$output" | grep -qF "$needle"; then + # Wrap-insensitive, like the other helper above. + if printf '%s' "$output" | tr '\n' ' ' | tr -s ' ' | grep -qF "$needle"; then echo "${green}PASS${reset} $test_name" PASS=$((PASS + 1)) else