Ann: SWI-Prolog 10.1.13

Dear SWI-Prolog user,

SWI-Prolog 10.1.13 is ready for download. This comes with quite a
few enhancements. Highlights:

  • 2^2147483648 and higher can now be evaluated on Windows.

  • read/1 and write/1 and friends have been converted from recursive C
    functions to iterative state engines. This allows for reading and
    writing much deeper nested terms and provides a consistent
    resource_error(memory) if there is insufficient memory to save the
    data requires to deal with nested terms. Before, some systems would
    generate resource_error(c_stack) (POSIX) while others (Windows, WASM)
    would crash uncontrolled.

  • Blobs, representing atom and foreign data have new support in
    read_term/2. By default, they (still) raise a syntax error. Log
    data including blobs can use the option blob(dead) to read blobs
    as a blob, but disconnected from the original foreign data. The
    toplevel uses blob(resolve), which scans the atom table for an
    existing blob that writes equivalent as the one we are trying to
    read. Note that this is slightly unsafe as the blob may have
    been subject to the ABA problem: the original blob was deleted
    but a new blob of the same type was allocated at the same address.

  • AGC (Atom Garbage Collection) got a notion of the “weight” of an
    atom for triggering a collection. Weights can be defined by type
    and garbage collection can be triggered by type (though all types
    are collected regardless of the type that triggered AGC). The
    weight of normal atoms it their memory usage in bytes.

  • Column tracking now deals with terminal escape sequences. This
    simplifies library(ansi_term) and allows for control sequences
    in format/3 while properly dealing with alignment. Note that this
    is consistent as handling double-width Unicode characters and
    Unicode combining marks was already implemented and the line
    positions is thus measured in columns rather than characters.
    ansi_format/3 now also supports href(URL) to embed hyperlinks.

  • Related, the prompt may now contain terminal escape sequences to
    achieve coloured, bold, etc. prompts.

  • XPCE object references used @Integer, where Integer was a slightly
    garbled address. Now XPCE uses blobs of the form
    <pce>(Address,Class). This makes XPCE’s memory management safe
    and relieves the user for explicitly protecting objects that would
    otherwise be garbage collected unintended.

  • The swipl-win (Epilog) terminal and Libedit commandline editor have
    received many updates and a lot of new tests:

    • Complete support for xterm-256color profile. This includes
      dealing with mouse events.
    • Mouse-click repositioning of the cursor in the commandline being
      edited (Epilog only).
    • Allow running external commands in a Prolog Epilog session with
      terminal I/O connected to the terminal. On Windows this uses
      a ConPTY pseudo terminal, which is introduced somewhere in Windows
      10. On Unix this simply inherits the PTY that is also used for
      the Prolog thread. The new process gets the terminal as controlling
      terminal though.
    • Under Epilog, apropos/1 and help/1 now emit hyperlinks that can be
      (Control) clicked.
  • Flipped (Apple natural) scrolling now works correctly.

  • Windows console version (swipl.exe) now uses Windows decoding of
    terminal sequences if supported. Otherwise it falls back to the
    limited built-in decoding (basically only bold and the standard
    16 entries color palette).

These are a lot of changes and quite likely there will be some regression :frowning:
Please report!

Enjoy --- Jan

SWI-Prolog Changelog since version 10.1.12

  • 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”.

  • 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.

  • 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.

  • 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.

  • 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:

  • 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.

  • FIXED: is/2: 2^2147483648 and larger on Windows. Completes
    8733c1a4f8efa2733e341641e64dfda8c0656205

  • ADDED: cls/0: CLear Screen.

  • FIXED: 2^2147483648 and larger on Windows.

  • 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.

  • 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.

  • FIXED: WASM: read a JavaScript object reference back using blob(resolve)
    JavaScript objects are now written as (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.

  • 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__*() 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_()
    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 (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 (freed) and is ordered by
    handle, and blob_released/1 tests for the state.

  • ENHANCED: Toplevel accepts blobs written as (…) 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 (…) blob syntax
    Non-text blobs write as (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
    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.

  • 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.

  • 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

  • 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

  • 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.

  • 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.

  • 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

  • TEST: a released blob writes as (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

  • 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”.

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

  • 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

  • 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.

  • 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.

  • 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. (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

  • 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

  • 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

  • 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

  • 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.

  • FIXED: object references now read back using read_term/3 blob(resolve)
    Python object references are written as (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

  • 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

  • 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.

  • 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 epilog: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.

  • ADDED: library(epilog): 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.

  • 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.

  • 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
    ? h and CSI ? l, the DEC private modes, but silently dropped
    the plain CSI h and CSI l. Mode 4 is IRM, so a client that
    opened room for a character with \e[4h instead of \e[@ 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.

  • 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).

  • 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.

  • 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.

  • 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.

  • 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

  • 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 @. 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 (Addr,Class) blobs
    An anonymous reference is now the blob (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.

  • 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.

  • 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.

  • 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.

  • 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.

  • 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

  • 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.

Build failure in packages/xpce/src/txt/terminal.c on macOS (missing prerequisite headers / macro collision with send)

(Note: Diagnostic and fix drafted with AI assistance.)

Environment:

  • macOS (Apple Silicon, ARM64)

  • macOS SDK (Xcode toolchain)

  • SWI-Prolog git master (packages/xpce)

Problem: Compiling packages/xpce/src/txt/terminal.c fails with:

  1. Unknown type errors (u_int, u_short, u_long, rusage_info_t) because <libproc.h> is included without prerequisite system includes (<sys/types.h>, <sys/resource.h>).

  2. Conflicting types error for send() / sendPCE: <libproc.h> pulls in <sys/socket.h> after h/kernel.h defines #define send sendPCE, which rewrites the standard BSD socket send() declaration into sendPCE(...).

Proposed Fix: Include the prerequisite system headers at the top of packages/xpce/src/txt/terminal.c before #include <h/kernel.h>:

#include <sys/types.h>
#include <sys/resource.h>
#include <sys/socket.h>
#ifdef HAVE_LIBPROC_H
#include <libproc.h>
#endif

Thanks. Claude created a bit simpler patch, but the idea is the same. Works again.