Did you know ... Search Documentation:
Title for pldoc(default)
Branch: development (switch to stable),
version to version

SWI-Prolog Changelog from version 10.1.12 to 10.1.13

[Aug 7 2026]

  • FIXED: MacOS: shell/1 left Prolog without usable standard streams The child of shell/1 calls adopt_ctty() when the streams it is handed are not the controlling terminal of our session -- the pty of an epilog window. It then is the session leader of a session that owns a terminal, and on BSD systems, MacOS among them, the kernel revokes such a terminal when its session leader exits. Revoking detaches every descriptor on it from the device, in this process as well: the descriptors stay open, but reading and writing them fails with EIO. Prolog died on the first message it printed after the child was gone, reporting only "Cannot write to user_error".

[Aug 6 2026]

  • ENHANCED: apropos/2: use the Unicode ellipsis for truncated summaries Only if the encoding of the message stream can represent it.
  • ADDED: apropos/2: limit and offset options and clickable matches If the terminal supports hyperlinks (Epilog), the matches of apropos/1 and apropos/2 are now links that run help/1 on the match and the line telling there are more matches links to the next page. XPCE class members are displayed using the arrow notation, e.g. dialog_item->open.
  • ADDED: help/1: make the manual hyperlinks in the output work Clicking a manual reference in an Epilog terminal now quits the pager and runs help/1 on the linked object. The links are resolved to a `man:` IRI holding the documentation object while the page is rendered, using the PlDoc link rewriting that already serves the documentation browser.
  • DOC: Update documentation for XPCE's threading model
  • FIXED: ansi_hyperlink/3 to take registered iri schemes as non-url. is_absolute_file_name/1 also considers registered iri schemes as an absolute path. We only want to exclude Windows drive specifications.

[Aug 5 2026]

  • ENHANCED: ansi_hyperlink/3: more general detection of non-file links. Now considers [a-z]+: a link, unless it is an absolute file name (e.g., C:... on Windows).
  • ADDED: html_text/2: pass hyperlinks through.
  • ADDED: ansi_format/3,4: support href(URL) to create an OSC 8 escape.
  • FIXED: Windows: quitting the pager of help/1 left the screen cleared A pager on Windows takes a screen buffer of its own from the console API, and the console swaps back to the previous one when it exits: that is what puts the terminal back as it was. A pseudo console does not carry those calls -- its alternate screen is the DEC private mode and nothing else -- so an Epilog window was left with whatever the pager had drawn on it. with_pager/1 now asks for the alternate screen itself.
  • DOC: Windows: document the pseudo console stream functions Swinpseudoconsole() and friends are how a foreign library puts a child process on the terminal behind a stream. Documented in the foreign streams section rather than in the comments of SWI-Stream.h.
  • ADDED: Windows: export the pseudo console attributes to foreign libraries Swinpseudoconsole_attributes() and Swinfree_pseudoconsole_attributes() build and free the process attribute list that puts a child on the pseudo console of an Epilog window. System() had this private; process_create/3 lives in packages/clib and needs the same, and reaching a window from a foreign library is what the Swin*pseudoconsole() calls are for.
  • FIXED: file_autoload_directives/3: Handle XPCE text_buffer correctly. Used @(Ref) rather than object/1.
  • FIXED: help/1: find pager on OSes that use extensions for executables.

[Aug 4 2026]

  • ADDED: Windows: run the child of shell/1 on the terminal's own console An Epilog window has no console: it talks to its Prolog thread over two pipes, and a child handed those gets no terminal -- nothing echoes what it reads, it writes through the console API rather than as VT, it sees no window size and ^C does not reach it.
  • FIXED: Windows: shell/0 blocked, shell/1 wrote nowhere and neither waited The child was created with CREATE_NO_WINDOW and no inherited handles, so it got a hidden console of its own: shell/0 ran a shell reading from a keyboard buffer nobody fills, and shell/1 wrote where nobody looks. A console process now shares its console with the child, and hands it the calling thread's streams, as System() in pl-os.c does on POSIX. The flag is what a GUI process needs -- there a console child pops up a window of its own -- so it is kept when we have no console to share.
  • FIXED: shell/0 on Windows: %comspec% before the posix_shell flag $SHELL and the posix_shell flag name POSIX paths, which a Windows box may well resolve without being able to execute them, so an interactive shell went looking for /bin/sh. %comspec% now comes first there. Set the shell flag to pick another one.

[Aug 3 2026]

  • FIXED: a process started from a Prolog thread had SIGINT blocked Only the main thread processes SIGINT, so every other thread blocks it. A thread that forks handed the child that mask, and exec() keeps the mask where it resets the handlers, so ^C did not reach a process started by shell/1 or process_create/3 from a thread. PL_cleanup_fork() now clears the mask, which is where the other traces of Prolog that must not leak into the new process are removed.
  • FIXED: let a Windows console act on the escape sequences itself The standard streams of a Windows console are wrapped by an emulation of our own, which parses escape sequences and acts on them through the console API. It acts on SGR. Everything else -- cursor motion, erase display, erase line, save and restore -- sits behind #if 0, calling into the console that swipl-win used to have. The parser consumed those sequences all the same, so a program that cleared the screen with ESC [ 2 J found that nothing had happened and nothing had been written either:

[Aug 2 2026]

  • FIXED: no warning when restoring the state of a terminal that is gone A terminal can disappear while we hold it: closing the master end of a pty makes every operation on the slave fail with EIO. SetTtyState() warned about that, but there is nothing left to restore and nothing the user can do about it.
  • ENHANCED: shell/1 adopts the terminal it was handed System() already gives the child the calling thread's streams, and a thread need not run on the terminal the process was started from: an Epilog window gives its Prolog thread a pty of its own. Inheriting the fd is not enough there. With no session owning that pty it has no foreground process group, so resizing the window raised no SIGWINCH in the child and ^C sent it no SIGINT.

[Aug 3 2026]

  • FIXED: is/2: 2^2147483648 and larger on Windows. Completes 8733c1a4f8efa2733e341641e64dfda8c0656205
  • ADDED: cls/0: CLear Screen.
  • FIXED: 2^2147483648 and larger on Windows.

[Aug 2 2026]

  • CLEANUP: html_text: measure table cells without rendering them twice Column widths were established by formatting every cell a second time with tty(false) and counting characters, because ANSI attributes would otherwise be counted as text. write_size/4 reports display columns and ignores escape sequences, so the cells are measured as they are printed.
  • CLEANUP: ansi_term: drop keep_line_pos/2 The stream layer no longer counts escape sequences as columns, so saving and restoring line_position/2 around every sequence written is redundant. set_stream/2 keeps the line_position property; it now has no callers in the system but remains useful for text whose width the stream cannot know.
  • DOC: display columns and ANSI escape sequences Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LmQHEtHoVQ2NH2Zzusvepa
  • ENHANCED: format/2 column stops count display columns ~t, ~|, ~+ and ~N now use the stream layer's Supdatepos() rather than a private copy of the same rule. Escape sequences are skipped, a tab advances to the next multiple of 8 and non-printable characters take no columns, so format/2 and line_position/2 always agree.
  • ENHANCED: line_position/2 skips ANSI escape sequences Escape sequences are not visible, so they no longer advance the column. This makes format/2 column stops, ~N and any code using line_position/2 work on coloured output without help. character_count/2 and byte_count/2 keep counting every character.
  • FIXED: line_position/2 for tabs and non-printable characters A tab moved the column to the next multiple of 8 minus 2 and every other non-printable character decremented it. Both stem from `linepos += PL_wcwidth(c)`, where PL_wcwidth() returns -1 for control characters.
  • TEST: Make AGC margin tests work in single threaded version.

[Jul 28 2026]

  • DOC: say where PL_register_blob_type() belongs It was described as something you never need to call, which is true of the registration itself but leaves the type invisible until its first blob exists. Call it from install() so that loading the library is enough for blob_type_property/2 to see the type and set_blob_type/2 to give it a collection budget, and so that its place in the standard order of terms does not depend on which type happens to create a blob first.
  • FIXED: register the remaining built-in blob types at startup A blob type is registered when its first blob is created, so zipper, undo, closure, thread and locale did not exist until something used them. Two consequences: blob_type_property/2 cannot report a type before it is used, and set_blob_type/2 cannot give it a collection budget, which is backwards for a setting whose purpose is to bound accumulation.
  • ADDED: blob_type_property/2 and set_blob_type/2 Replaces the provisional set_blob_gc_margin/2 with the pair every other property in the system uses, stream_property/2 and set_stream/2. It has somewhere to grow: agc_close_streams is a global flag that only ever affects the stream blob, and is really a per type policy.
  • DOC: describe the per blob type atom-GC budget Documents gc_margin and set_blob_gc_margin/2, and what `len' means for a PL_BLOB_NOCOPY blob now that it also decides how much the blob counts towards that budget.
  • FIXED: do not dump the target of a PL_BLOB_NOCOPY blob writeBlob(), the writer used for a binary blob whose type provides no write(), hex dumps `length' bytes starting at `name'. For a PL_BLOB_NOCOPY blob `name' is the foreign object and `length' describes what it retains rather than how much of it we may read, so this walked off the object. It was harmless while every such type passed sizeof(void*), but a type that reports a real size would print megabytes of somebody else's memory. Print the handle instead.
  • ENHANCED: let a blob type declare its own atom-GC budget Atom-GC is requested after agc_margin atoms have become candidates. An atom is about fifty bytes, but a blob atom is the same fifty bytes whether it wraps a point or a ten megabyte memory file, a compiled regex, an answer trie or an SSL context, so the count says nothing about what is being kept alive. Two hundred atoms of a hundred kilobytes each hold twenty megabytes and never come close to the margin.
  • FIXED: consider atom-GC when references are dropped, not only when created considerAGC() had one call site: lookupBlob(), every 128th new atom. The counter it tests, GD->atoms.unregistered, grows in unregister_atom(), which never tested it. Dropping the last reference to a large number of atoms therefore reclaimed nothing until something else happened to allocate atoms. Retracting 30,000 clauses that hold the only reference to an atom each and then running clause-GC reclaimed 0 atoms; it now reclaims all of them.

[Jul 27 2026]

  • FIXED: WASM: read a JavaScript object reference back using blob(resolve) JavaScript objects are now written as <js>(Id,Class) rather than <js_Class>(Id). The name between the brackets must be the name of the blob type for the reader to find the object back, so the blob type is renamed from `js_object' to `js'.
  • TEST: read_term/3 blob(resolve) matching on the blob address A zipper writes the address of the blob data and is thus found on the address; a stream writes the address of its handle and takes the fallback. Either way the text must match exactly.
  • DOC: describe how read_term/3 blob(resolve) finds the blob Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dv8qG8JrSfiHguZaFbLAW
  • ENHANCED: read_term/3 blob(resolve) finds the blob on its address Most blob write() functions print the blob data using `%p'. If the first argument of the text is that address we no longer need to write every blob of this type to find the one that produced the text. For 20,000 live regex blobs this makes resolving one 150 times faster. It also avoids taking a lock the write() may need, e.g., the Python GIL for library(janus).
  • ENHANCED: Sfprintf() %As accepts the precision as string length Allows writing text that is not 0-terminated, e.g., the pattern of a library(pcre) blob.
  • ADDED: SfprintfX() %As to emit a string as a quoted atom A blob write() function must emit its arguments as valid Prolog syntax for read_term/2,3 to be able to read them back. %As does that for a name, file name, pattern, etc. without creating a Prolog term.
  • DOC: Merge all read predicates into one description. Follows the write predicates: read/1,2 and read_term/2,3 now share the description of read_term/3, the most general form. read_clause/3 moved below the option list as it refers to it.

[Jul 26 2026]

  • DOC: read/1 and write/1 no longer use the C stack Section "Other Limits" claimed they may crash the system on deeply nested terms, which no longer holds now both use an explicit stack.
  • CLEANUP: rename O_C_STACK_GUARDED to O_ALTSIGSTACK Nothing is guarded since the setjmp() context was removed; what the flag enables is handling SIGSEGV on an alternative signal stack. Also renames initGuardCStack() to initAltSignalStack().
  • CLEANUP: remove the unused C-stack overflow guard Nothing uses C_STACK_OVERFLOW_GUARDED since the reader and the writer no longer recurse on the C stack, so the setjmp() context and the sig_critical flag in LD->signal are dead as well. What remains of O_C_STACK_GUARDED is running SIGSEGV on an alternative signal stack: an overflow leaves no C stack to run the handler on, and this way sigCrashHandler() can still print the crash report. alt_segv_handler() did nothing else and is replaced by sigCrashHandler() itself.
  • ENHANCED: shrink the writer's resume frame Keep the sorted order of the dict pairs on the heap rather than in the frame: the inline array made every frame 64 bytes larger, while dicts are a small minority of the frames pushed. Writing deeply structured terms is now within 1-3% of the recursive writer and writing dicts is about 10% faster, as pl_for_dict()'s callback and its 2Kb stack buffer are gone.
  • TEST: deep term nesting for write/1 Write 20,000 levels of compounds, lists, list tails, braces, operators (prefix, infix and block), dicts and a mix of these, in the operator, the canonical and the dotted list notation, and read the result back. The old recursive writer ran out of C stack at about 18,000 levels natively and 2,000 on WASM.
  • CLEANUP: drop C_STACK_OVERFLOW_GUARDED around writeTerm() writeTopTerm() was the last user of the SIGSEGV catching guard, which is redundant now the writer no longer recurses on the C stack. The sigaltstack machinery itself stays: unify, compare and friends do still recurse.
  • ENHANCED: writeTerm() iterative via segstack Fold the recursion between writeTerm(), writeTerm2(), writeList() and writeDictPair() into a state machine over a segstack of wframe resume frames. Writing a term used to cost about 450 bytes of C stack per level of nesting, limiting the depth to about 18,000 levels natively and 2,000 on WASM; it now nests on the heap.
  • DOC: refer to the split read_<construct>_*() functions Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dv8qG8JrSfiHguZaFbLAW
  • TEST: deep term nesting for read/1 Read 20,000 levels of compounds, lists, list tails, braces, parentheses, operators, dicts and a mix of these, as well as a syntax error at that depth. The old recursive parser ran out of C stack at about 8,000 levels natively and 1,000 on WASM. Restores the unconditional termtest(1000), which had to be reduced to 500 on WASM for the latter reason.
  • CLEANUP: drop C_STACK_OVERFLOW_GUARDED around complex_term() The parser no longer recurses on the C stack, so the SIGSEGV catching guard in read_term() is redundant. Note that it never worked on WASM, which has neither sigaltstack() nor C-stack overflow detection.
  • ENHANCED: complex_term() iterative via segstack Fold the recursion between complex_term(), simple_term() and the read_<construct>() functions into a state machine over a segstack of pframe resume frames. Reading a term used to cost about 1Kb of C stack per level of nesting, limiting the depth to a few 10,000 levels natively and about 1,000 on WASM; it now nests on the heap.
  • TEST: Reduced term_to_atom-2 on WASM, where the C-stack is too small.
  • FIXED: use GCD dispatch semaphores on macOS instead of named POSIX semaphores macOS does not implement unnamed POSIX semaphores (sem_init() is a deprecated ENOSYS stub), so this code used a named semaphore opened with a fixed name "pl" via sem_open()/sem_unlink(). That name lives in a single machine-global namespace, which has two failure modes when many short-lived Prolog processes run concurrently:
  • BUILD: ship prebuilt desktop/swipl.icns instead of generating it at build time iconutil (used by desktop/make_icns.sh) delegates to the IconServices system daemon, which refuses callers running under sandbox-exec and is unavailable in many CI environments, breaking the macOS icon build there. Commit the generated icon as a source asset and copy it into the build tree with copy_if_different. Regeneration from swipl-256.png is now the opt-in regenerate_icns target (macOS, outside a sandbox), documented in desktop/README.md.
  • DOC: blob(Mode) read option, blobs in the standard order of terms Documents the read_term/2,3 blob(Mode) option, why `error' is the default, the cost of blob(resolve) and the constraint that the notation is unavailable where `<' is a prefix operator. Notes that blob/2 reports the type a dead blob stands for while current_blob/2 reports `unavailable', documents blob_released/1, and states the requirement that a blob write() callback emit valid Prolog syntax between the brackets.
  • TEST: read_term/2,3 option blob(Mode) and blob_released/1 39 tests over four suites: blob(dead) (round-trip, type profile, blob/2 reporting the recorded type, accessors rejecting it, compare/3 agreeing with ==/2, Prolog-syntax arguments), blob_released/1, blob(resolve) (finding a live stream and a clause reference, falling back for a stale address) and the syntax itself (the default remains a syntax error, forms like <(a,b), <>(a) and f(a<b, c>d) keep their meaning, and a prefix `<' operator keeps <a>(f) reading as >(<(a),f)).
  • ADDED: blob_released/1 and a safe representation for released blobs PL_free_blob() sets the blob's data to NULL but left its type in place, so write/1 still dispatched to the type's write() callback and compare/3 to its compare(), both of which dereference the object that is gone. This crashed for any blob type that does not check PL_blob_data() for NULL. Such a blob now writes as <Type>(freed) and is ordered by handle, and blob_released/1 tests for the state.
  • ENHANCED: Toplevel accepts blobs written as <type>(...) The interactive toplevel reads with blob(resolve), so a blob copied from earlier output can be pasted back into a goal. A form whose object no longer exists reads as a dead blob rather than a syntax error, which keeps the rest of the goal usable.
  • ADDED: read_term/2,3 option blob(Mode) to read <type>(...) blob syntax Non-text blobs write as <type>(Arg, ...), which the reader rejected, so a single blob made the whole enclosing term unparsable: stack traces, ~q log files and toplevel copy/paste all suffer. The reader now claims this notation, but only when asked: blob(error) is the default and unchanged, blob(dead) yields a blob without a foreign object, and blob(resolve) additionally looks for a live blob that writes as this text.
  • MODIFIED: Sfprintf() %p now prints a canonical 0x<lowercase-hex> pointer The platform %p is neither portable nor readable as a Prolog number: glibc prints NULL as "(nil)" and the MSVC runtime prints uppercase hex without the 0x prefix. Blob write() callbacks print their handle with %p, and that output must be acceptable to the reader. Rendering the pointer ourselves fixes all ~250 %p uses in one place.
  • TEST: Pin the current reader behaviour for the `<type>(...)` blob syntax Nine cases covering the forms the reader must keep rejecting (`<stream>(0x…)` and friends) and the neighbouring forms that do parse today and must keep their meaning (<(a,b), <>(a), f(a<b, c>d)). This guards the upcoming reserved blob token, which is only active when read_term/2,3 is given a non-default blob(Mode).
  • CLEANUP: Turn reader token types into an enum The reader's token types were #defines named T_FUNCTOR, T_STRING, etc. These collide with the VM instruction names of the same spelling, which pl-read.c sees through pl-incl.h; the macros silently redefined them for this file. They are now an enum token_type with a TK_ prefix, which removes the collision and gives the switch on token types compiler exhaustiveness checking.

[Jul 24 2026]

  • DOC: Settle modes for foldl/4-7 and scanl/4-7 This invalidates PR #820.
  • DOC: PR #770 Improve docs on determinism, \+/1 and type tests. This merges PR #770 after several edits.

[Jul 19 2026]

  • CLEANUP: library(apply): removed the indexing helper predicates Predicates such as maplist/3 called a helper with the list as first argument to get clause indexing. This is no longer needed as clause indexing considers the first argument for which some clause has a non-variable.

Package archive

[Jul 28 2026]

  • ENHANCED: register the blob type when the library is loaded A blob type is otherwise registered only when its first blob is created, so blob_type_property/2 cannot report it and set_blob_type/2 cannot give it a collection budget until the library has already been used, which is backwards for a setting whose purpose is to bound accumulation.

Package clib

[Aug 5 2026]

  • FIXED: Windows: process_create/3 gave its child no terminal An Epilog window has no console: it talks to its Prolog thread over two pipes, and a child handed those has nothing that echoes, no window size and no ^C. with_pager/1 in library(help) therefore hung on Windows -- the pager sat waiting for a key from a console nobody types on.

[Aug 3 2026]

  • FIXED: process_create/3 adopts the terminal it was handed A thread need not run on the terminal the process was started from: an Epilog window gives its Prolog thread a pty of its own, and process_create/3 hands that pty to the child. Inheriting the fd is not enough there. With no session owning that pty it has no foreground process group, so resizing the window raised no SIGWINCH in the child and ^C sent it no SIGINT.

[Jul 28 2026]

  • ENHANCED: register the blob type when the library is loaded A blob type is otherwise registered only when its first blob is created, so blob_type_property/2 cannot report it and set_blob_type/2 cannot give it a collection budget until the library has already been used, which is backwards for a setting whose purpose is to bound accumulation.

Package cpp

[Jul 26 2026]

  • TEST: a released blob writes as <Type>(freed) Its own write() is no longer called, as that would dereference the object that is gone, so the freed blob prints using the registered type name rather than the mangled C++ one. Also exercises blob_released/1.

Package libedit

[Aug 7 2026]

  • FIXED: MacOS: reconnect the terminal after a child took it down el_wrap/4 hands libedit duplicates of the streams' descriptors, as libedit owns and closes what it is given. On BSD systems, MacOS among them, the kernel revokes a controlling terminal when its session leader exits -- a child of shell/1 on the pty of an epilog window -- which detaches every descriptor on it from the device, in this process as well. Prolog reconnects the descriptors of its own streams, but our duplicates it cannot see, so reading the next line failed with EIO for good and the window filled with "I/O error in read on stream user_input".

[Aug 4 2026]

  • BUILD: build library(win_console) only with LIBEDIT_DEBUG It drives a Windows console and reads its screen buffer back, which is of no use outside a test of what swipl.exe draws there. It now shares the option that gates libedit's refresh trace, LIBEDIT_TRACE renamed to LIBEDIT_DEBUG for covering both.
  • FIXED: Windows: an abandoned pipe read wrote into a dead stack frame pipe_read_or_msg() returned with the overlapped ReadFile() still queued on three paths: WM_QUIT, a Prolog exception from PL_handle_signals(), and any other wait result. The kernel owns the OVERLAPPED and the buffer being read into, and both are locals -- the OVERLAPPED of pipe_read_or_msg(), the buffer of read_char(). When the next character arrived it was written into a stack frame that had long gone, and the event it signalled had been closed and possibly reused.

[Aug 3 2026]

  • FIXED: build against a stock libedit again Swrite_libedit() takes the input line off the screen with EL_ERASELINE, which is an addition of our copy of libedit. The binding still builds against the one the system provides -- SYSTEM_LIBEDIT, never on Windows -- and there the constant does not exist, so it did not compile at all.
  • FIXED: put the input line back after output on a console Having taken the input line off the screen and let the output through, Swrite_libedit() puts the line back once the output has ended its line. It decided that from the last byte written, which a write handler is in no position to read: it is handed the encoded text, and the encoding of a Windows console stream is wchar. writeln/1 arrives there as "hello\r\n" in UTF-16, whose last byte is the zero half of the newline, so the line looked unfinished and stayed off the screen -- the output overwrote the prompt and no new prompt appeared until a key was pressed.
  • DEBUG: trace where foreign output is put on the screen Swrite_libedit() decides whether output from elsewhere may take the input line down, and whether the line goes back up afterwards. Nothing said which way it went, and the two are hard to tell apart from the screen alone: a line that was never taken down and one that was never put back both leave the output where the input should be.
  • FIXED: keep a thread's output out of the line being edited on a console Swrite_libedit() takes the input line off the screen, writes, and puts the line back only when the write comes from a thread other than the one waiting for a key. It reads ctx->reader to tell those apart, and only do_read() ever set it -- which is compiled on Unix alone. On a Windows console nothing looked like it was reading, so a background thread's output went straight to the caret, into the middle of the line the user was editing, and stayed there until the next keystroke.
  • ADDED: library(win_console): drive a Windows console and read it back Runs a program on a Windows console and reports what is on that console, cell by cell, out of the screen buffer with ReadConsoleOutputW(). It exists so that what swipl.exe draws on a console can be checked against the console itself: every other way we have of testing the line editor puts a terminal of our own on the other end, which can only show that libedit does what we think it does.

[Aug 2 2026]

  • ENHANCED: allow ANSI escape sequences in the prompt set_prolog_flag(toplevel_prompt, '\e[1m?- \e[0m') now gives a bold prompt that is measured correctly; before, the escape bytes counted as columns and every redraw, wrap and cursor motion was off by that many.

[Jul 31 2026]

  • FIXED: keep output from other threads out of the input line A write that arrives while the line editor owns the screen went to wherever the caret was -- into the middle of the line being edited -- and stayed there until the next keystroke redrew the line below it. Take the input line off the screen first, let the output through, and paint the line back below it, at once rather than on the next key.

[Aug 1 2026]

  • FIXED: Epilog on Windows: keep libedit's terminal size in step An Epilog window raises no SIGWINCH: resizing it only updates the size on the stream, and el_init_handles() runs terminal_init() before EL_GETSZFN and EL_CLIENTDATA are set, so libedit never learned the window size at all -- it took whatever the console fallback produced, which for a window that has no console is uninitialised. Editing an input line that wraps then paints on the wrong rows.

[Jul 21 2026]

  • ENHANCED: use the Epilog terminal font for libedit column widths When libedit drives an Epilog (xpce) terminal (el_flags&EPILOG) the visible width of a glyph is decided by the terminal font, not by the static Unicode tables PL_wcwidth() reads. Symbol and emoji code points that PL_wcwidth() reports as width 1 are routinely drawn two cells wide by an emoji-presenting font, which made libedit's cursor arithmetic drift against what Epilog paints.

Package pcre

[Jul 28 2026]

  • ENHANCED: register the blob type when the library is loaded A blob type is otherwise registered only when its first blob is created, so blob_type_property/2 cannot report it and set_blob_type/2 cannot give it a collection budget until the library has already been used, which is backwards for a setting whose purpose is to bound accumulation.

[Jul 27 2026]

  • CLEANUP: write the pattern using the %As of SfprintfX() Avoids creating a term on a foreign frame to write the pattern as a quoted atom. A blob write() function may be called while the atom table is scanned, e.g., by read_term/3 using the blob(resolve) option.

[Jul 26 2026]

  • FIXED: write a regex blob as valid Prolog syntax The pattern was written unquoted between slashes, so a pattern holding a bracket or a quote produced output that could not be read back at all, e.g. <regex>(0x55c1e0, /a(b)c/). It is now written as a quoted atom, which makes the blob acceptable to read_term/2,3 using blob(dead).

Package pldoc

[Aug 6 2026]

  • ADDED: xpce_object_label/2: xpce arrow notation for a class member Turns e.g. xpce(point,get,x) into point<-x. Extracted from the PlDoc hooks that already used this notation so apropos/1 can use it too.
  • ADDED: man_page//2: link_scheme(Scheme) option Writes the manual references as Scheme:Object IRIs instead of links into the PlDoc server, removing the references that do not resolve to a documentation object. This allows help/1 to create clickable terminal output without depending on the PlDoc HTTP handlers.
  • ADDED: pldoc_href_object/2, man_object_uri/2 and man_uri_object/2 These map a PlDoc HREF onto the documentation object it refers to and convert such an object to and from a `man:` IRI. The latter is used to embed manual hyperlinks into terminal output using OSC8 escape sequences.

Package semweb

[Jul 28 2026]

  • ENHANCED: register the blob type when the library is loaded A blob type is otherwise registered only when its first blob is created, so blob_type_property/2 cannot report it and set_blob_type/2 cannot give it a collection budget until the library has already been used, which is backwards for a setting whose purpose is to bound accumulation.

Package ssl

[Jul 28 2026]

  • ENHANCED: register the blob type when the library is loaded A blob type is otherwise registered only when its first blob is created, so blob_type_property/2 cannot report it and set_blob_type/2 cannot give it a collection budget until the library has already been used, which is backwards for a setting whose purpose is to bound accumulation.

Package swipy

[Jul 28 2026]

  • ENHANCED: register the blob type when the library is loaded A blob type is otherwise registered only when its first blob is created, so blob_type_property/2 cannot report it and set_blob_type/2 cannot give it a collection budget until the library has already been used, which is backwards for a setting whose purpose is to bound accumulation.

[Jul 27 2026]

  • FIXED: object references now read back using read_term/3 blob(resolve) Python object references are written as <py>(Ref,Class) rather than <py_Class>(Ref). The name between the brackets must be the name of the blob type for the reader to find the object back, which is what makes pasting an object reference into a toplevel goal work. This also makes blob/2 report the same type for a live and a dead reference.

Package tipc

[Jul 28 2026]

  • ENHANCED: register the blob type when the library is loaded A blob type is otherwise registered only when its first blob is created, so blob_type_property/2 cannot report it and set_blob_type/2 cannot give it a collection budget until the library has already been used, which is backwards for a setting whose purpose is to bound accumulation.

Package xpce

[Aug 7 2026]

  • TEST: terminal: read the echo client's bytes in the C locale BSD `cat -v', which is what MacOS has, escapes per character rather than per byte: in a UTF-8 locale it passes the 0xC2 of a two-byte sequence through as itself and escapes only the second byte, so a mouse report encoded as UTF-8 (DEC private mode 1005) came back as `ÂM-^E' where the test expected `M-BM-^E'. What these tests are about is the bytes the terminal sent, so ask for the byte-wise reading.
  • FIXED: MacOS: terminal died when a child of shell/1 left it A process that makes the pty its controlling terminal -- a child of shell/1 or process_create/3 -- is the session leader of a session that owns a terminal, and on BSD systems, MacOS among them, the kernel revokes such a terminal when its session leader exits. Every descriptor on the client side is detached from the device, ours as well as those the Prolog thread runs on, and the master sees end of file. We took that for the client closing the connection and took the pty down for good; Prolog, left without usable streams, died on the first message it printed.
  • FIXED: release a window from the window system when deleted from its frame Closing one terminal of a split Epilog window and moving the mouse printed "ws_window_frame_position(...) failed": the event dispatching still had the deleted window as the window holding the pointer. ->uncreate is a no-op if the window is not on a created frame, and frame ->delete cleared <-frame before sending it. Uncreate the window while it is still a member. This also releases its Cairo surface and texture.
  • FIXED: epilog: focus the new terminal after splitting a window With click-to-focus the new terminal only got the keys after clicking it.
  • FIXED: make the keyboard focus follow the click rather than the pointer Moving the pointer over a window made it the frame's input window, so the caret and thus the keyboard focus followed the mouse. Class frame now has a class variable focus_follows_mouse (default @off): only a click in a window makes it the frame's ->keyboard_focus. Setting it to @on restores the old behaviour.
  • FIXED: send keyboard events to the window holding the input focus After splitting an Epilog window the caret moved to the selected terminal, but typing went to the original one. A frame without an explicit ->keyboard_focus routed keys to the first member window that has a <-keyboard_focus, while the caret follows ->input_window, which is set when the pointer enters a window. Make <-keyboard_focus consult ->input_window before scanning the members, so both agree.

[Aug 6 2026]

  • ADDED: prolog_terminal <-thread Provides the Prolog thread that is connected to an Epilog terminal. Used by library(help) to find out whether the page a clicked hyperlink appears in is still being shown by a pager.
  • ADDED: library(epilog): epilog:tty_link_hook/2 to hook clicking links, This replaces tty_link_hook/1, which was present only one day. Our aim is to make manual page links clickable and show them in the same terminal. So, we need the terminal.

[Aug 5 2026]

  • ADDED: library(epilog): tty_link_hook/1 to hook clicking links.
  • FIXED: terminal: hand the function keys to a client that owns it F5 and F6 are epilog's debugger keys, which meant `mc', `vim' and every other full screen program could never be given them. They now go the way ^C does: while a process group of another session owns the pty, the function keys are that program's.
  • FIXED: terminal: pass the function keys on to the client ->typed knew the cursor keys and a handful of named ones and dropped everything else, so F1..F12 never reached the program running on the terminal. They are now sent as xterm spells them, as are Shift, Alt and Control held with a function or cursor key.
  • TEST: terminal: wait for the echo client to turn echo off The tests that read back what the terminal sent its client start `stty -echo; cat -v' and begin typing as soon as the shell owns the pty, which is before its `stty' has run. A key pressed in that window came back twice: once echoed by the line discipline and once from the client. The client now announces itself after `stty', and start_echo_client/1 waits for that.
  • FIXED: "Natural" scrolling direction We negated the amount when flipped was indicated, but SDL3 already did that.
  • FIXED: terminal: cls brought the oldest lines of the session back Erasing the display with no saved lines starts the ring over at slot 0, where the oldest lines still sit, and the redraw paints a whole window from `window_start' without stopping at `last'. cls/0 gets there every time: its ED 3 drops the scroll back, so the ED 2 that follows takes this branch and the banner reappeared with the next prompt written over its first characters. Emptying the buffer now empties its lines.
  • FIXED: Windows: the caret went to the top of the screen after a client The reset that ends a console session sent the normal screen back first and reset the scrolling region after it. DECSTBM homes the caret, so whatever the screen restored was undone a sequence later and the next prompt appeared in the top left rather than where the client found it. Come back to the normal screen last of all.
  • FIXED: Windows: a console session left the terminal as its client had it A real console owns the screen buffer the application draws on: when the application goes away the buffer dies with it and the one underneath comes back, which is why `less' leaves a Windows console as it found it without sending anything. A pseudo console does not do that for us, and once it is closed nobody is left to send the sequences either, so the terminal now sends them to itself: normal screen, caret, attributes, scrolling region, cursor keys, mouse reports and bracketed paste.
  • FIXED: terminal: a stray rmcup blanked the window for good Leaving an alternate screen that was never entered erased the display and then found nothing saved to restore, so the window stayed empty with nothing that could bring it back. Entering it twice was as bad: the second smcup saved the alternate screen over the normal one. Both come out of a Windows pseudo console around a full screen application such as `less'. Ignore either, as xterm does.
  • TEST: terminal: the Windows console description gained capabilities Mirror win_ncurses.c, which now answers the keys (arrows, Home, End), the parameterised motion and insert, home and the bell. Under this profile the suite therefore drives libedit with the escape sequences the console reports rather than with the editor bindings it fell back on.
  • BUILD: keep the xpce copy of SWI-Stream.h in step It shadows the core header through the same include guard, so the pseudo console declarations added there must appear here as well.
  • TEST: terminal: cover the 1005 (UTF-8) mouse encoding It only differs from the default past the 96th column, where a coordinate stops fitting in a byte, so the test widens the terminal to 120 columns and clicks there.
  • ADDED: terminal: mouse reporting (DEC private modes 9/1000/1002/1003) An application that asks for the mouse now gets it, encoded as it asked for with 1006 (SGR), 1015 (urxvt), 1005 (UTF-8) or the default three bytes. This is what `emacs -nw', `less --mouse', vim, mc and tmux read, and the wheel is a button (64 and 65) like any other to them. As in xterm, shift is the user's way out: a shifted event is never reported, so selecting, pasting and scrolling back keep working, and mouse reports take the wheel before alternate scroll does.
  • ADDED: terminal: alternate scroll (DEC private mode 1007) While the alternate screen is up and no application asked for mouse reports, the wheel is sent to the application as cursor keys, three lines to the notch. That is what makes the wheel scroll `less', `man' and other full screen applications in xterm and VTE, which have this on by default; so do we. Shift keeps the wheel on this side.
  • FIXED: terminal: wheel scrolled the scroll back on the alternate screen The lines the alternate screen replaced belong to the normal screen: a full screen application owns the window until it gives them back, so scrolling them into view showed the text under the application. The scroll bar offered a bubble for them as well; it now fills the bar.

[Aug 4 2026]

  • FIXED: terminal tests: wait for the terminal to settle before using it A terminal was handed to a unit as soon as its prompt appeared, but the prompt is drawn while the rest of the banner is still on its way. With the units in one process that went unnoticed; with several running at once the rest could arrive after a test had painted the screen, and terminal_screen:delete_lines found its first row overwritten by the prompt.
  • TEST: terminal: a process per unit, run in parallel and only when asked The suite drives a terminal and reads the screen back, so it spends its time waiting rather than computing: a minute against the four backends, where the rest of the xpce tests together take five seconds. It is off now unless XPCE_TERMINAL_TESTS is set, and worth setting when touching the terminal, libedit or shell/1 -- several of the bugs in those were caught by nothing else.
  • TEST: terminal: a process started by the Prolog thread runs on the window shell/1 hands the child the terminal its caller runs on. The new terminal_child_on_terminal unit checks that from the outside: the child writes to the screen, reads what is typed at the window, echoes it, reports its exit status, and leaves the terminal usable afterwards. It says nothing about how any of that is arranged, so it runs on both platforms, and every Windows attempt at this failed in a way it would have caught.
  • ADDED: terminal: let a process run on this terminal's pseudo console ->launch puts a process on the terminal through a pseudo console over the window's own pipes. The Prolog thread running on the terminal needs the same thing for shell/1, but cannot use ->launch: it creates the process itself and has to wait for it. The client streams therefore answer SIO_[GET|REL]WINPSEUDOCONSOLE, and Swinpseudoconsole() in the core asks them. See System() in src/pl-nt.c.

[Aug 3 2026]

  • DOC: terminal_image: <-foreground_process and ->interrupt Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VnmviTFXmNbwtckwLhxHvP
  • FIXED: terminal: <-foreground_process on a terminal that is gone ->unlink leaves ti->data at NULL, so the getter must check before it looks at the pty.
  • TEST: terminal: control keys reach a process running in the terminal Adds a terminal_control_keys unit: ^C interrupts a child, ^X reaches it as input, and a child that turned signal generation off gets ^C as input too. Presses the keys with the new term_press/2, which goes through ->typed and thus past the key bindings; term_send/2 puts bytes on the pty and would not see them.
  • FIXED: terminal: pass control characters to a process running in it The window handled ^C, ^X, ^V and ^Y itself, so a program started with shell/1 or process_create/3 never saw them. While a process group of another session owns the pty, control characters now go to the client and the tty line discipline decides what they mean: it raises SIGINT for a program that wants signals and delivers the character to one that does not. Ctrl+Shift and Meta bindings are unaffected, and with no such process the window keeps its own bindings.
  • FIXED: terminal: a CSI private marker is not an SGR parameter `CSI > 4 ; m' (XTMODKEYS, which vim sends on every start) ran as SGR 4 and underlined everything after it. Keep the marker that opened the sequence and let `m', `c' and `S' see it; `CSI > c' now gets the secondary device attributes rather than the primary ones.
  • ENHANCED: epilog: claim xterm-256color The terminal implements the string capabilities of that description and the colours it adds: ANSI 16, the 256 colour cube and 24 bit RGB. As `xterm' it told applications it had eight colours, so nothing used the palette it can actually draw.
  • FIXED: terminal: do not report unknown sequences on the screen A terminal ignores what it does not know quietly; the stream belongs to the application, and writing our own notes into it corrupts the screen it is painting. The reports are still there under debugpce(term). Also act on DECSCNM (`CSI ?5h'), which is terminfo's `flash'.
  • FIXED: terminal: erased rows stayed on the screen Erasing the display, or from the caret down, moved the end of the buffer up but left the rows themselves in the ring. Both the painter and <-row walk the ring, so the erased text stayed visible until something wrote over it.
  • ADDED: terminal: autowrap, REP and the reset sequences DECAWM (`CSI ?7h/l') lets a program hold the caret in the last column rather than wrap, REP (`CSI Ps b') repeats the last character written, and DECSTR (`CSI !p') and RIS (`ESC c') put the terminal back to its defaults. DECSTR is part of terminfo's is2, so it runs whenever a full screen application starts.
  • ADDED: terminal: tab stops (ESC H, CSI Ps g, CSI Ps I and CSI Ps Z) A tab used to run to the next multiple of eight cells, which is not the same as the next multiple of eight columns once a line holds wide or combining characters. Keep the stops in a map that HTS and TBC edit and move over them in columns.
  • ADDED: terminal: scroll up and down (CSI Ps S and CSI Ps T) Terminfo's indn/rin. Scrolling the whole window up pushes its top lines into the scroll back as a line feed does; inside a scrolling region, or on the alternate screen, they are dropped.
  • ADDED: terminal: ESC 7, ESC 8, ESC D and ESC E DECSC/DECRC are what terminfo's sc/rc use; they save and restore the attributes and character sets along with the position, where `CSI s' and `CSI u' save the position alone. IND and NEL used to end up in the "unknown escape" branch.
  • FIXED: terminal: erase sequences (EL, ED and ECH) `CSI Ps K' ignored its parameter, so erasing to the start of the line erased to the end of it instead. Implement EL 1 and 2, ED 1 (erase above the caret) and ECH (`CSI Ps X'), which blanks columns in place.
  • FIXED: terminal: honour the scrolling region (CSI Ps ; Ps r, DECSTBM) Deleting a line in emacs pulled up everything below it, including the mode line and the echo area: emacs bounds the delete with a scrolling region, which the terminal parsed and ignored. IL, DL, the reverse index and the line feed at the bottom of the region now stay inside it. The region covers the whole window by default and a resize resets it.
  • ENHANCED: terminal: insert and delete lines (CSI Ps L and CSI Ps M) Implement the ANSI IL and DL sequences. Both move the caret to the first column and work on the region from the caret to the bottom of the window; the terminal has no scrolling region of its own (CSI r). The reverse index (ESC M) and the scroll of a saved screen are the same operation and now share its implementation.
  • TEST: terminal: a program clearing the screen clears it Covers what the Windows console emulation quietly ate: ESC [ 2 J written by the program, rather than by the line editor, which asks the console to act on its own sequences and so was unaffected.
  • FIXED: terminal: cursor motion stops at the edge of the line Moving the cursor right past the last column wrapped it to the next line, and moving left from column 0 took it to the line above. Only writing a character wraps: ECMA-48 has CUF not pass the last column, and a terminal without `bw' does not take a backspace in column 0 to the line above.
  • FIXED: terminal: ignore NUL and DEL rather than printing them Any character the terminal had no meaning for was written to the screen, NUL among them. A terminal description that asks for padding -- vt100 puts $<3> on el and $<50> on clear -- has tputs send that padding as NUL bytes, and at 38400 baud $<3> is twelve of them, so every clear-to-end- of-line walked the caret twelve columns to the right. Everything drawn after it landed in the wrong place.
  • FIXED: terminal: the suite's own hook made the console thread spin A terminal's Prolog thread reports one io_error when the terminal is taken away from under it, and then exits: read_query_line/2 rethrows and the top level answers with thread_exit(io_error). Silencing that message with a message_hook/3 that succeeds stops it doing so, and the thread reads, fails and reports for as long as the process lives -- 90,000 times and a whole core in the three seconds after a terminal was destroyed.
  • TEST: terminal: read the screen back from a real Windows console Adds the `console' backend, which puts swipl.exe on a Windows console through library(win_console) and reads conhost's own screen buffer. It is the only backend whose screen is not one of ours, so it is the only one that shows what a user would see rather than what our terminal makes of what libedit wrote.
  • TEST: terminal: run the suite as more terminal descriptions see it Two more runs against a child. TERM=linux has ich1, which sends libedit down its insert-mode path rather than its insert-character one, and so covers the terminal's IRM support.
  • FIXED: terminal: implement insert mode (IRM) The terminal acted on CSI ? <n> h and CSI ? <n> l, the DEC private modes, but silently dropped the plain CSI <n> h and CSI <n> l. Mode 4 is IRM, so a client that opened room for a character with \e[4h instead of \e[<n>@ had that character overwrite the line rather than push it right, losing one character per row.
  • TEST: terminal: run the suite on a terminal without xenl Adds a run against a child on TERM=ansi. That description has am but not xenl, the shape win_ncurses.c reports on Windows, so it is where the Windows console class of redisplay bug can be caught without a Windows console.
  • TEST: terminal: run the suite from ctest Registers the terminal suite twice: against the epilog terminal, and against a child process on that terminal's pty. Until now it was in no CMakeLists at all and had to be started by hand.
  • TEST: terminal: run the suite against a child process on the pty Adds the child(Profile) backend: a child `swipl' started with shell/1 from the epilog thread, so it runs on the terminal's pty with TERM taken from Profile. That is the only way to reach libedit's ordinary termcap paths -- the epilog backend always runs it with the EPILOG flag, so everything a non-epilog terminal does was untested.
  • TEST: terminal: drive the suite through a backend layer The suite only ever needs three primitives -- send a keystroke, read the cursor, read a row -- so it can run against more than one terminal. Route every test through term_send/2, term_cursor/3 and term_row/3 and select the terminal with test_terminal/1, so that the same assertions can be aimed at a line editor other than the in-process epilog one.
  • ADDED: terminal_image <-columns and <-rows Report the size of the terminal in character units. The pixel size only yields this after dividing by the font's cell width and subtracting the margin, i.e. after duplicating rlc_resize_pixel_units() outside the terminal.

[Aug 2 2026]

  • TEST: xpce: cover the term description of a Prolog method's object answer Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LmQHEtHoVQ2NH2Zzusvepa
  • FIXED: xpce: unify the object a Prolog method returns with a term description get(Object, Selector, size(W,H)) decomposes the returned object when the method is implemented in XPCE, but failed when it is implemented in Prolog: that path unified the answer term as-is and only fell back to unifyObject() when the answer was @Reference. An anonymous reference is a blob rather than @Integer since the object references were turned into blobs, so the fallback was no longer reached and the thread monitor silently failed to open: <-axis_length returns a size that ->thread_diagram reads as size(H,V).

[Aug 1 2026]

  • TEST: terminal: clicking in the input line Synthesises clicks and drags: the caret follows a click by the distance clicked, on a wrapped row as well, a click outside the input line leaves it alone, and a drag selects without moving it.
  • ADDED: click in the input line to move the caret there The line being edited belongs to the client, so the terminal cannot place the caret itself. It can ask, though, and the request every line editor understands is cursor-left and cursor-right: count the grapheme clusters between the caret and the click and send that many.
  • TEST: terminal: resizing, rewrapping and the caret Covers the Epilog terminal's behaviour across a resize: editing a wrapped input line, shrinking to a width the input fills exactly, a wrapped row whose last column is a space, a key pressed before the resize has reached libedit, walking the caret over a wrapped line and widening afterwards, and a selection made before the resize.

[Jul 31 2026]

  • TEST: terminal: output from another thread while typing Asserts that the text appears on its own row and the input line is painted below it, without waiting for a keystroke.

[Aug 1 2026]

  • FIXED: moving the caret down no longer breaks a wrapped line rlc_caret_down() cleared softreturn on every line it landed on, so a client walking down its input with newlines -- libedit going to the end of a wrapped line with ^E, or stepping from one display row to the next -- turned each continuation into a hard line break. Rewrapping on the next resize then reflowed the pieces on their own, and the repaint that followed left parts of the old layout on the screen.
  • FIXED: rewrapping on resize carries the caret and the selection rlc_resize() rebuilds the ring of lines, but the caret and the selection are indices into it, so afterwards they named different text: the caret no longer marked the end of the input, and libedit -- which rewinds relative to it to repaint -- started a row off and left a copy of a row on the screen. A selection made before the resize covered something else.
  • FIXED: selections that span the ring's wrap-around The lines live in a ring, but two places ordered them by their bare indices: once it has wrapped, a line further down the screen has the smaller index. rlc_read_from_window() then took a region covering both ends of the buffer for invalid and read nothing, and SelLT() -- which decides whether a drag extends the selection forwards or backwards -- took a drag downwards for a drag upwards. Selecting the whole contents of a scrolled terminal, by select-all or by dragging from the first line to the last, therefore stopped at the wrap.
  • TEST: xpce: cover object references and weak hash tables tests/test_object_reference.pl covers the representation, identity, conversion, lifetime and the released state. The lifetime tests assert what this work is for: 1000 objects created and dropped leave the answer stack exactly as they found it, an object reachable only from a Prolog variable stays alive across a garbage collection, and one that is not reachable at all does not. A table created with refer=none leaves its entries collectable while a normal one keeps them alive, and lookup works either way.
  • DOC: xpce: document handle-based object references Rewrites the places that described an anonymous reference as @Integer and updates the examples that printed one. The User's Guide chapters affected are Getting started, the Prolog interface, memory management, debugging and the glossary, plus the xpce-client manual page.
  • FIXED: xpce: an address does not name an object at the XPCE level either @pce<-object_from_reference still took an integer and turned it into a pointer, guarded only by a range check and the object magic, so it named whatever had since been created at that address. object<-convert reached the same code by parsing text of the form "@35435623". Both now accept names only.
  • FIXED: xpce: the inspector and instance browser monitor objects without owning them Both tools watch objects they must not keep alive, so both stored the reference rather than the object: the inspector in its three associations, the instance browser in the dict_item's object slot. That worked only because nothing was ever reclaimed; it also meant the reference could go stale, and <-reference was broken outright for anonymous objects, since get(_, _, @Var) has no anonymous form.
  • ENHANCED: xpce: an object reference is its own xref source identifier library(pce_prolog_xref) reduced a buffer to the integer inside its @/1 reference because "the cross-referencer runs faster if the reference is an indexable term". An anonymous reference is now an opaque handle, which indexes exactly as an atom does, so the detour buys nothing and costs the guarantee: the integer can go stale while the handle cannot.

[Jul 30 2026]

  • FIXED: demo: read the text of a text_item selection, not its reference text_item <-selection answers an XPCE string object, and item_string/2 fell through to formatting it with ~w before parsing the result as a Prolog list. That wrote the object reference rather than the text, so Generate reported

[Aug 1 2026]

  • FIXED: xpce: atomic/1 no longer distinguishes a value from an object An anonymous object reference used to be @Integer, a compound term, so code could use atomic/1 to mean "a name or number rather than an object". A blob is atomic, so those tests now accept objects.
  • FIXED: xpce: adjust Prolog code that took object references apart An anonymous reference is a blob, so Obj = @Ref no longer decomposes one. Adds object_reference/2, object_from_reference/2 and is_object_reference/1 over the XPCE <-object_reference and @pce<-object_from_reference methods, and uses them everywhere the reference was reached through the @/1 term.
  • ENHANCED: xpce: print an anonymous reference as the host writes it pp(), and with it the %O of the string formatter, still wrote an anonymous object as @<integer>. That form no longer exists on the host side and can no longer be read back, so it was misleading wherever it surfaced: the visual hierarchy's status line, the inspector's value labels and clipboard, show_slots/1 and every XPCE error message.
  • ADDED: xpce: represent object references as <pce>(Addr,Class) blobs An anonymous reference is now the blob <pce>(0xADDR,Class) rather than the term @Integer, where the integer was the object's address. Prolog holding a reference now means XPCE holds a reference, so the answer stack no longer has to pin everything that escapes to Prolog: rewindAnswerStack() is passed NIL instead of the result at both host entry points. Objects reachable only from Prolog are reclaimed by the atom garbage collector. Creating 10,000 points and dropping them used to leave 10,000 cells on the answer stack forever; it now leaves none. Named references remain @Name.
  • ADDED: xpce: hash_table ->initialise takes the reference mode A hash table already knows whether its keys and values create references (<-refer), and the kernel uses non-referring tables for the object registries behind <-attribute, the method caches and @display's placed table. Until now that could only be set from C, so Prolog could not build a table that monitors objects without keeping them alive.
  • ADDED: xpce: host API to hold object references on behalf of the host A host reference is an ordinary object reference held for the host language. It makes the object non-virgin, so it survives the rewind of the answer stack, and it makes noRefsObj() false, so ->free() only marks the object F_FREED and defers the unalloc: a host reference can never dangle.
  • BUILD: xpce: run the tests against SDL's dummy video driver XPCE needs a display, so the bom, selection and image test sets were skipped wherever the build host has no window server, which is the normal case for CI. SDL's dummy driver provides a headless display, and all eighteen test sets pass against it.

[Jul 28 2026]

  • FIXED: xpce: clear F_ANSWER for every cell dropped from the answer stack _rewindAnswerStack() cleared F_ANSWER only on the branch that also frees the object. An object that acquired a reference while it was on the answer stack survives the rewind, but its cell is dropped, leaving the flag set with no cell to match. isVirginObj() is then false forever, so freeableObj() can never reclaim the object once the last reference disappears.
  • TEST: xpce: determine display availability by asking XPCE display_present/0 tested $DISPLAY, which is an X11 leftover: since XPCE moved to SDL that variable says nothing about whether a display can actually be opened. XQuartz sets $DISPLAY on macOS sessions that have no window server access, so the bom, selection and image test sets ran anyway and crashed the suite, masking the ten test sets that follow them.

[Jul 29 2026]

  • TEST: terminal: deleting a wide character in front of NFD text Regression test for the Windows-only bug where backspacing a two-column character left of NFD content also removed the cluster behind it. libedit sent the two columns as two CSI P sequences; we delete whole grapheme clusters, so the second one took a cluster of its own.
  • FIXED: Epilog terminal <-cwidth: do not measure non-characters Surrogates and Unicode non-characters have no glyph, so measuring one returns the width of the font's "missing glyph" box -- typically two cells. libedit on Windows, where wchar_t is 16 bits, asks about both halves of a surrogate pair and about its MB_FILL_CHAR sentinel (0xFFFF), and then counted an emoji as four columns instead of two. Fail instead, which leaves the caller with PL_wcwidth().
  • TEST: terminal: editing at the right margin stays on the input row Regression test for the Windows-only Epilog console bug where replacing the last character of a line that fills the row exactly drew the new character on the row above the line being edited, after which the display walked up the screen.

[Jul 24 2026]

  • ENHANCED: Navigator: browse all files and filter the tree on file name The Navigator can now show all files of a directory in addition to the loaded files and the Prolog source files, switch the content mode of the whole tree at once and incrementally filter the tree on a file-name pattern. Browsing is scoped to the seed directories (the loaded files, the library search path and the working directory) and the expanded directories are preserved across mode switches and filtering.

[Jul 19 2026]

  • FIXED: Navigator: include non-terminals in the module export list The cross referencer only matched Name/Arity in the module header, so exported non-terminals (Name//Arity) were missing when expanding the module node. Their definitions are found as grammar rules.

[Jul 21 2026]

  • ADDED: terminal_image <-cwidth: columns occupied by a code point Expose terminal_char_cells(), the classification the terminal renderer itself uses, as terminal_image <-cwidth(Code) -> Int. Clients that must predict our layout now have a single point of truth instead of a second copy that can drift.

Package yaml

[Jul 28 2026]

  • ENHANCED: register the blob type when the library is loaded A blob type is otherwise registered only when its first blob is created, so blob_type_property/2 cannot report it and set_blob_type/2 cannot give it a collection budget until the library has already been used, which is backwards for a setting whose purpose is to bound accumulation.