Branch: development (switch to
stable),
SWI-Prolog Changelog from version 10.1.14 to 10.1.15
[Sep 15 2026]
- BUILD: macOS deps: use Berkeley DB 5.3.28 instead of 6.1.26 Berkeley
DB 6.x is AGPL, which would conflict with distributing the bundle,
e.g. as a Homebrew cask. 5.3 is under the Sleepycat licence and,
unlike 6.x, is still freely downloadable from Oracle. Pin it with
a comment so it is not "updated" back.
- BUILD: macOS: derive LSMinimumSystemVersion from the deployment target
The app and framework Info.plist claimed 10.14 while the binaries are
built for 10.15. Both templates now use CMAKE_OSX_DEPLOYMENT_TARGET.
[Sep 13 2026]
- FIXED: #1526 Windows:
tmp_free(NULL) corrupted the heap tmp_free() has
two implementations. The MMAP_STACK one, used wherever <sys/mman.h>
exists, accepts NULL like free() does. The fallback, which is what
Windows builds get, walked back over the size header and called free()
on NULL-sizeof(word). On Windows that is reported as heap corruption,
exit code 0xC0000374.
- FIXED: #1525 ~:f ignored the locale decimal point in a non-C
environment
localizeDecimalPoint() assumed the number in the buffer
was printed with the decimal point of the `default' PL_locale,
and skipped the rewrite when that matched the requested one. Since
PL_initialise() no longer sets the process LC_NUMERIC (issue #1093)
that assumption is wrong: the C library prints "." while `default'
carries the environment's ",". In a German locale format('~2:f',
[123456.7]) therefore produced '123.456.70' instead of '123.456,70'.
- FIXED: #1524 Windows: undefined behaviour scanning UNC path host names
[Sep 12 2026]
- FIXED: macOS release build: take system
libs from the Xcode SDK make-distribution put
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr on
CMAKE_PREFIX_PATH, while cmake uses the Xcode SDK as CMAKE_OSX_SYSROOT.
The Command Line Tools 27.0 update made that symlink point at
MacOSX27.0.sdk, whose .tbd stubs name an arm64e.x1 target the
Xcode 26.5 linker rejects with "tapi error: malformed file".
As libcurses.tbd/libform.tbd end up in CMAKE_REQUIRED_LIBRARIES,
every
check_function_exists() link failed and the configure reported
signal(), signbit(), access(), fork(), ... as missing.
- FIXED: Single threaded: record the module a file is loaded from
source_file_property(File, load_context(Module, Location, Options))
had no solutions at all in a single threaded system: recording the
context was done by the loader of the multi-threaded system only.
make/0 could therefore not reload a file the way it was loaded, and
a .qlf file did not depend on the files whose term or goal expansion
went into it, nor on the file an XPCE class template was copied from.
- BUILD: macOS deps: update versions, build both arches on one machine
Update the bundle dependencies to current releases: OpenSSL 3.6.4, zlib
1.3.2, libarchive 3.8.9, unixODBC 2.3.14, pcre2 10.48, libffi 3.8.0,
SDL3 3.4.16 (3.4.14 is broken), SDL3_image 3.4.6, pango 1.58.2.
Berkeley DB and OSSP uuid stay where they are, being no longer
downloadable. libjpeg is dropped: nothing references it any more, and
with the CoreText font map neither do FreeType, fontconfig or libpng.
- BUILD: Keep the C and the C++ compiler flags apart The flags of
a build type used the optimization flags of C for C++ as well,
and those were $CFLAGS entire: every option meant for the C
compiler reached the C++ compiler too, so a $CFLAGS carrying, e.g.,
-Werror=implicit-function-declaration warned on every C++ file it
compiled. Fixes #1518.
[Sep 11 2026]
- FIXED: atom GC scanned 8 times too far above the local stack top
markAtomsOnLocalStack() added LOCAL_MARGIN, which is in bytes, to a
Word pointer and thus scanned about 67Kb above lTop instead of the
frame header plus MAXARITY arguments a call may be building there.
Atoms, and notably blobs, left in that area by deeper calls that had
long returned were kept alive. This could also form a pointer beyond
the local stack.
- BUILD: install customize/ into the build home tree The user init
file template customize/init.pl was installed, but not linked into
the build tree home directory, so
swi('customize/init.pl') did not
resolve when running from a build directory. Move it from a plain
install() to the SWIPL_DATA_DIRS machinery, which does both.
[Sep 10 2026]
- FIXED: tmp_dir flag held an OS path rather than a Prolog one On Windows
the flag was the raw value of %TEMP%, which uses "\" as the separator.
Every other path Prolog hands out uses "/", so a file built from the
flag with directory_file_path/3 did not compare equal to the same file
named any other way: .qlf files recorded a source under one name and
looked it up under the other, and a buffer opened on such a file was
not recognised as being open.
- ADDED: term_automaton/2 and automaton_minimal/2,3 term_automaton/2
converts between a term and its term graph written out as an explicit
automaton, and automaton_minimal/2,3 minimises such an automaton under
bisimulation, collapsing bisimilar states and dropping the ones the
start state cannot reach. Composed, the two are term_minimal/2.
[Sep 9 2026]
- FIXED: Windows build of pl-bisim.c The bs_graph field grp2 clashes
with a macro from the Windows header dlgs.h, which defines the common
dialog control ids grp1..grp4 as plain numbers. Renamed the field
to grp_next.
- ADDED: variant_hash/2 and variant_sha1/2 accept cyclic terms Both
refused a cyclic term with
type_error(acyclic_term, T). The manual
said why: "Cyclic terms are not supported because this would require
establishing a canonical cycle. That is, given A=[a|A] and B=[a,a|B],
A and B should produce the same hash. This is not (yet) implemented."
- FIXED: term_factorized/3: looping and under-factoring on cyclic terms
term_factorized/3,4 are now built in, computed from the term graph
quotient. The library(terms) implementation kept an rbtree of the
subterms it had seen, keyed on the standard order, and the standard
order is not antisymmetric on rational trees -- for A =
s(A,0), B =
s(C,1), C = s(B,0), compare/3 answers (>) in both directions. The tree
could therefore lose a key that was present, and term_factorized/3
either looped forever or, less visibly, counted a subterm reached
twice as reached once and did not factor it out at all. Over 4000
random cyclic terms the old implementation diverged or under-factored
on 11 of them.
- ADDED: term_minimal/2 and term_factorized/3,4 A term in the store is a
graph, and a cyclic term is a finite graph denoting an infinite tree.
Two terms denote the same tree exactly when their graphs are bisimilar,
which is the equivalence ==/2 already decides. What was missing is a
canonical representative: the smallest graph denoting the same tree.
pl-bisim.c computes it, and the new predicates hand it to Prolog.
- FIXED: =@=/2 and \=@=/2: use after free, and errors reported as answers
isomorphic() held a node pointer across the second term_id() call.
That call can grow the node buffer, and growing it moves it, so
the pointer was read and written after the block it points into had
been freed. It takes a term with enough sharing to enter isomorphic()
and enough cells to grow the buffer; f(D,D) nested four deep against
the same term written out in full was enough to crash. Number both
cells before looking either of them up.
[Sep 8 2026]
[Sep 7 2026]
[Sep 6 2026]
- ENHANCED: decide whether a .qlf file is out of date by content, not
by time A .qlf file now records a hash of the content of every source
that went into it, and a source is recompiled only when that hash
says it really changed. Modification times cannot say: a tree that
arrives by checkout, copy, unpack or install carries times of its own,
in either direction and at the resolution of the file system it landed
on, and a file edited in the second its .qlf file was written has the
very time of that file. Touching all 707 sources of the system library
now recompiles none of them, and hashing them to find that out takes
97ms -- against minutes for one file wrongly kept or wrongly rebuilt.
[Sep 5 2026]
[Sep 2 2026]
- DOC: WindowsInstaller.md: document the SignPath signing step Describe
how signing fits into the release-windows workflow, which artifact to
download, how to verify the signature, the one-time SignPath setup and
how to dispatch the workflow from a branch when testing changes to it.
[Sep 1 2026]
- FIXED: library(persistency): CRLF handling of the database file on
Windows The database file is now read and written using
newline(posix).
On Windows the file was written using CRLF, while the reader deletes
all carriage returns. As a result the byte counts used to repair
a truncated database were off and a file ending in a lone CR was
considered to end in layout, causing a new term to be glued to the
`.` of the previous one.
[Aug 30 2026]
- ENHANCED: library(persistency): recover from an incomplete last term
If a persistent database file ends in a partially written term, e.g.,
because the disk was full or the process was killed while writing,
db_attach/2 now prints a warning and truncates the file to just after
the last complete term instead of raising a syntax error.
Package json
[Sep 1 2026]
Package libedit
[Sep 13 2026]
- FIXED: link the threading library into the binding libedit4pl.c uses
a pthread mutex to serialise painting the input line, but the plugin
never linked a threading library. On platforms where pthreads are
not part of the C library, notably MSYS2 and MinGW, that leaves
pthread_mutex_init() and friends undefined at link time.
Package nlp
[Sep 12 2026]
- FIXED: isub/4 goal expansion to not raise an error Compiling a call to
isub/4 that passes a constant as Normalize, or that passes options
that are only known at runtime, raised an error while expanding
the goal. The error dropped the clause that held the call from
the program. This affected, e.g., apropos/1, whose isub/4 call sits
in library(help).
Package pldoc
[Sep 12 2026]
- TEST: man_links: skip the test if the documentation is not installed
The test renders manual pages and resolves the links in them.
Both library(help) and the manual index it reads only exist if the
documentation is built, so without them there is nothing to test.
[Sep 1 2026]
- FIXED: #9803 help/1 on a manual section that embeds a figure Rendering
such a section raised `http_handler_id manual_file does not exist`.
The handler id used for images in the main manual has never existed;
it is pldoc_refman. That handler now serves the images, and without
a PlDoc server (help/1) images become a `file://` URI.
Package ssl
[Sep 13 2026]
- FIXED: macOS HTTPS hang in system_root_certificates/1
On macOS the system root certificates were obtained using
SecTrustCopyAnchorCertificates(). This talks to securityd over Mach
IPC and can block forever if the keychain is not accessible, e.g.,
in an ssh session while the login keychain is locked. Such a block
cannot be interrupted using e.g. call_with_time_limit/2. We now read
the PEM file provided by the OS as on other Unix systems.
[Sep 1 2026]
- FIXED: #176 SSL streams became unusable after a read timeout with
OpenSSL 4 Our BIO read and write callbacks returned -1 on a stream
timeout without setting the BIO retry flags, so OpenSSL considered
the failure fatal. Up to OpenSSL 3 this surfaced as SSL_ERROR_SYSCALL
and left the SSL object usable, but since openssl/openssl@ebb188f0d0
(OpenSSL 4.0) a fatal record layer read latches the state machine in
MSG_FLOW_ERROR and every further SSL_read() fails with SSL_ERROR_SSL
and an empty error queue, reported as "SSL(00000000) :
reason(0)".
- FIXED: SWI-Prolog/packages-ssl#176
Package xpce
[Sep 17 2026]
- FIXED: SDL: Windows live resize painted only the window backgrounds
display_manager<-test_queue defaults to @on on Windows, which makes
redrawDisplayManager() stop as soon as events are queued. Inside the
modal resize loop the queue cannot drain, so the resize watch aborted
every redraw and only the freshly cleared backing surfaces were shown.
The watch now disables the queue test while it redraws.
[Sep 16 2026]
- FIXED: SDL:
ws_draw_frame() crashed on a frame that is not created
The guard lacked its return, so a frame without an SDL window fell
through and dereferenced a NULL WsFrame instead of failing.
- FIXED: SDL: redraw windows while the user is dragging a border On
MacOS and Windows the window system runs a modal event loop for the
duration of a resize, leaving our main loop blocked in SDL_WaitEvent().
The resulting RESIZED and EXPOSED events were queued but only handled
after the drag, so MacOS stretched the last drawable and Windows
padded the window with black. We now relayout and repaint from an
SDL_AddEventWatch() callback, which SDL calls from inside that modal
loop, giving the immediate resize of X11 and Wayland on all platforms.
- FIXED: bitmap->image left a dangling <-bitmap reference in the old
image tab_frame ->drop took anything dragged over a pane for a pane,
warping the pointer to a nested window used the wrong offset, and
epilog/0 was non-deterministic. The display_manager obtains its class
variables early, getting the windows of panes and tiles is simpler,
and a C method can be edited directly where addr2line is available.
- FIXED: terminal: Home and End with a modifier were not passed to
the client Only the plain keys have a binding. With a modifier
they now send the xterm sequence, e.g. Ctrl-End sends `CSI 1;5F'.
The wheel events of the test set the rotation slot, which replaced
the attribute in 37170e65.
- FIXED: two memory errors AddressSanitizer found Each font
name lookup leaked the string it formatted the name with, and
@pce<-unresolved_types read beyond a name object: a type for a class
that does not yet exist has the class name as its context.
- FIXED: pane_frame: rebuilding the menu bar leaked the old menus
Taking a popup off the bar does not free it: its items refer to it.
Everything that fills the bar makes its popups anew on every rebuild,
so the old ones are freed now.
- FIXED: xpce: free objects when their last reference disappears Since
b1dec9a9
delRefObject() only did so for objects being inspected, so
everything else that lost its last reference leaked, e.g. the argument
vector of every message with two or more arguments. Objects with code
references, as well as locked, protected and answer objects are left
alone. Code that used an object after dropping the last reference
to it now holds a code reference while it does: cleanTile(), the
receiver of a running method, ->erase and the recompute of a device,
and the cycle between a menu_item and its pull-right.
- ADDED: tools to find what leaks: class<-no_reachable and
object->_check(silent := @on) say which instances a check reaches,
object->_find_holders which objects refer to a set of objects, and
pce_lost_objects/1, pce_lost_instances/2 and pcerefer/1 turn that into
the classes that leak, their lost instances and the chain of holders
of one of them. These report Prolog references, and take care neither
to pin what they report nor to hold on to what they collect.
[Sep 15 2026]
- ENHANCED: Epilog: only terminals and unsaved editors keep the
application alive Added `frame<->keep_alive` (class variable, default
@off) and an optional
keep_alive argument to `->has_visible_frames`.
pane_frame asks its panes: Epilog terminals and PceEmacs views with
unsaved file changes say @on. ep_main/0 now halts when no such frame
is visible, so tools, demos and user windows no longer block exit.
Closing the main console halts immediately unless keep-alive frames
remain.
- ENHANCED: Profiler GUI opens showing the most interesting predicate
For a cumulative profile this is the first predicate using less than
90% of the time; otherwise it is the first in the list.
- FIXED: PceEmacs: error from the history buttons after closing a pane
The tool bar of a pane_frame kept the client it was created with,
the pane in view at that time. After closing that pane, updating the
history buttons raised an existence error. pane_menu_dialog ->client
now also updates the client of the tool bar.
- ENHANCED: terminal_image scrolls while the selection is dragged past
its edge Dragging a selection above or below the terminal scrolls
the scroll-back and extends the selection over what comes into view,
faster the further out the pointer is. Not on the alternate screen
or while the application tracks the mouse.
- FIXED: freeing a terminal_image without a scroll_bar sent ->destroy to
@nil Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018urSFBQAQZtC1sjsbkrdwm
- FIXED: MacOS: Option held while the focus moved to a new window
was lost SDL forgets the modifiers when a window loses the focus.
Holding Option from M-x into PceEmacs' command prompt made M-p type π
rather than walk the history. Take Option from Cocoa if SDL misses it.
- FIXED: SDL keyboard debug message printed garbage for the timestamp
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018urSFBQAQZtC1sjsbkrdwm
[Sep 14 2026]
- MODIFIED: meta (Alt) is a modifier in event <-buttons, not an offset on
<-id A key typed with meta held now has the plain character as <-id and
BUTTON_meta in <-buttons, like shift, control and gui. META_OFFSET and
Meta() are gone,
char no longer accepts M-x or \ex, and ->typed
methods that must see the meta key take event|event_id. User code
that compares <-id against offset values, or declares Id:event_id on
a method that forwards keys, must be updated.
- ENHANCED: store wheel rotation in event <-rotation instead of an
attribute Wheel events are frequent and attaching an attribute to
each event object is expensive. Class event now has a
rotation
slot (int*), @nil for all other events. Code using get(Ev, attribute,
rotation, R) must use get(Ev, rotation, R).
- FIXED: editing a tab label left the pane's focus active and lost it
afterwards A double click on a tab label opened the label editor while
the window of the tab kept an active cursor, and after the edit that
window looked focused without receiving keys. The editor now takes
the frame's focus, the tabbed window no longer passes it on to its
tab while a label is edited, and the focus goes back to the window
the user was typing in.
- FIXED: PceEmacs: tabs of the editor had no popup menu The editor
keeps its source tabs in an emacs_pane, which never got the label
popup the window puts on its own tabs. It now has one with Close
tab, Close other tabs and Move to new window. Closing other tabs
goes through ->close_tab so modified buffers are still asked about.
- ENHANCED: PceEmacs Prolog mode: inject consult buffer and make/0 If
there is a current Epilog terminal, inject the command there.
- ADDED:
@prolog_ide<-current_epilog Find the current Epilog
terminal.
- CLEANUP: Removed swi_console. Outdated.
- ENHANCED: Epilog: safely inject commands. Handles preserving partial
input and signalling if the console is not waiting.
- FIXED: Wayland: native message boxes froze all xpce windows On Wayland,
SDL_ShowMessageBox() runs zenity and blocks the calling thread until
the box is closed. When called from the SDL main thread this stopped
event dispatching, so windows were not redrawn. The box is now shown
from a helper thread while the main thread keeps dispatching events.
User input is discarded during the wait, except for mouse button
releases, which are re-queued afterwards.
- ADDED: prolog_terminal->inject Check that the terminal can be used now,
i.e., that it is at the prompt or reading while we can save/restore
the already typed input.
- FIXED: swipl-win: capture messages. The terminal is created in
frame->create, which runs asynchronously. This means we must
drive the event loop until the terminal is ready before we can enable
capturing.
- ENHANCED: Answer stack: constant time delete; reclaim leftovers at
the host boundary Deleting an object from the answer stack no longer
scans the stack. Objects left on the answer stack when control
returns to Prolog are reclaimed, so code that creates objects outside
a mark/rewind pair no longer leaks them. Use
debugpce(gc) to list
such objects.
- FIXED: SDL events were never reclaimed The SDL backend created events
as answer objects before marking the answer stack, so the rewind
after dispatching did not reach them and
freeableObj() refused them
while they carried the answer flag. Every event leaked, keeping the
windows and frames it referred to allocated after they were freed.
[Sep 13 2026]
- ENHANCED: checkpce/0: show reference counts and freed state along
the path Each object in the "Path from checked object" listing,
as well as object keys of hash tables, now prints its reference
and code reference counts and the freed, freeing, creating, locked,
protected and answer flags.
- ENHANCED: A new tab opens beside the current one, not at the end
of the row Command-T and Control-Shift-T in Epilog put the terminal
they make at the far end of the tab row, which in a window with a few
tabs is nowhere near where the user is looking. A tab made on purpose
now goes straight after the tab it was made from. The new-tab button
keeps appending at the end: the button is at the end of the label row,
which is where the tab it makes belongs.
- FIXED: Epilog windows claimed a keyboard focus they did not have An
Epilog window went on drawing an active caret after the window system
gave the keyboard to another window, kept the platform text input
enabled, and never sent its client the focus-out report (CSI ? 1004).
The mirror image was true of a new window: it showed a pane, but the
first keystroke went nowhere until the pointer entered that pane.
Moving the focus between the panes of one window always worked,
which is what made both hard to place.
- FIXED: Epilog terminal: a character's column width must not depend on
the font The terminal promoted a character to two columns when the
font drew its glyph more than 1.5 cells wide. The column grid is a
contract with whatever writes to the pty though: that program lays
its output out with its own
wcwidth() and cannot see our font, so a
character we make a column wider puts us ahead of it for the rest of
the line, and the frame it redraws over that lands on the wrong rows.
On macOS the heuristic caught U+23BF LEFT PARENTHESIS LOWER HOOK,
resolved from a symbol face that draws it at ~1.6 cells -- and U+23BF
opens every tool-result line Claude Code prints, so its display came
apart into overwritten and interleaved rows.
- ADDED: Chart gallery demo covering
library(plot) One window with a cell
per feature of the two libraries that draw charts: plot_graph's poly,
smooth and points_only curves, plot_axis grid lines, formats, labels
and log scale, an axis that prints its own tick labels, plot_mark,
and bar charts in both orientations with stacks, groups, ->sort, bars
that can be dragged and bars with a bar_button_group of their own.
Listed in the demo browser as "Chart gallery", or run ?- charts_demo.
- DOC: bitmap: transparency comes from the image, not from the bitmap
Class bitmap documented a <-transparent and a bug about bitmaps not
being partly transparent. Both went away with bitmap<->transparent
(cb34992c): a bitmap is transparent where its image's alpha channel
says so, which is also what clears its solid flag.
- MODIFIED: image ->save: PNG is the only format, and only to a file The
`format' argument was typed {xbm,xpm,pnm,pbm,pgm,ppm,jpeg,gif} and
`in' as a source_sink, but the only thing that can be written is a PNG
to a file. Every value the type accepted was refused by the backend,
and png -- the default -- was not in the type. Both are now honest:
in=[file], format=[{png}].
- FIXED: barchart: vertical bar charts could not be created at all
Appending a bar to a vertical bar_chart raised "no implementation for:
->transparent" and left the chart empty. Class bar_label is now a
figure that rotates its text with ->rotate rather than drawing it
into an image and rotating that, so the label is anti-aliased and
any angle works, not just multiples of 90.
- FIXED: PceEmacs: the editor's reports never reached the bar of its
window A report climbs from the window that made it to the frame,
and every pane on the way is asked whether it is the one in view.
PceEmacs is a group -- one pane holding a tab per source -- and
`pane_frame <-current_pane' looks inside a group, so the group compared
itself against the view it holds, decided it was not the pane in view,
and dropped everything its own editors had to say: `Mark set', the
mode it switched to, and the running feedback of an incremental search.
[Sep 12 2026]
- ENHANCED: Navigator: the "Show:" menu sits on the tool bar The
menu that chooses between the loaded files, the Prolog files and
all files moved from the filter dialog to the tool bar of the Prolog
Navigator, which leaves the whole dialog below it to the file filter.
That filter now says what it is for -- "Filter files" -- rather than
carrying a label.
- FIXED: PceEmacs: do not load the user's files if xpce_defaults is
`none' The mode extensions in
app_config('xpce/emacs') and the init
file user_profile('.pceemacsrc') are the customisation of whoever
runs PceEmacs, so the flag that says a program is not to depend on
their preferences now also says to leave those alone.
- TEST: test_undefined: help/1 is only there if the documentation
is built src/CMakeLists.txt only installs library(help) if the
documentation is built, so help/1, called by the
mode's ->prolog_manual, has nothing to autoload from in such a build.
- FIXED: the alternate screen dropped the text of the line it was started
on A client that had written to the line without a newline after it
-- a prompt, a progress line -- found it empty on coming back from
a full screen application.
- FIXED: the folds of a session were lost by running a full screen
application Coming back from `less' or `man' the session had all its
folds open, because the application had cleared its own screen along
the way.
- FIXED: full screen applications scrambled the screen on a session with
folds Running `less', `man' or any other application that takes the
whole window left it with rows missing and the caret in the middle of
it, on a terminal that had closed folds on the screen. Clearing the
terminal was the way out.
- FIXED: xpce:bookmark_store and xpce:bookmarks failed without a display
Both were registered with
test_libs() but left out of XPCE_EXTRA_TESTS,
which is the list that gets SDL_VIDEODRIVER=dummy in its environment.
Without it swipl-rc's has_display/0 fails on a headless machine, xpce
is never set up, and library(emacs/bookmark_store) cannot be found.
[Sep 11 2026]
- FIXED: checkpce/0: only take pce blobs held by clauses or records
as roots A
pce blob that lives only on a Prolog stack is no longer
a root for the check. The atom GC scans the stacks conservatively,
including a margin above the top of the local stack, so a window that
was just closed leaves blobs for its tiles and chains there. They keep
those objects alive, and checkpce/0 reported them as holding freed
objects. Stack garbage can neither be collected nor reliably wiped,
so a blob now only counts if '$atom_references'/2 says a clause, a
record or foreign code registered it. Calling garbage_collect_atoms/0
first is dropped: it cannot change that set.
- FIXED: bookmark editor: bookmarks lost between instances, notes lost
when unfocussed The bookmarks were written out as a whole tree when
a session ended, so the last instance of PceEmacs to leave overwrote
whatever the others had marked, and one that never got to leave
wrote nothing at all. They are kept as an append-only log now --
the new
library(emacs/bookmark_store), over library(log_store) --
in which a bookmark made, annotated or thrown away is written down
there and then, and which is tidied to one record per bookmark once
it has grown long. A note is given to its bookmark when the caret
leaves it as well as when another bookmark is selected, and the note
field is inactive while no bookmark is selected, there being nowhere
to put what is typed into it.
- ADDED:
library(log_store): a file of Prolog terms kept as an
append-only log The window arrangements the IDE learns are kept
this way already, so that several instances running at once do not
overwrite each other's; the bookmarks of PceEmacs need the same.
Moved the file, the lock and the terms out of library(pane_layouts),
which now says only what a record of its own means and when the log
has grown long enough to summarise.
- FIXED: Settings/User init file when the init file does not exist
Answering "yes" to "Preferences file ... doesn't exist. Create
it?" did not create the file, after which PceEmacs reported "No
source for ...". The template was looked up as customize/swipl.ini or
customize/dotswiplrc, names that vanished with the autoconf build; it
is customize/init.pl. If no template is found at all we now create an
empty file rather than nothing. Also create the user config directory
if it is missing: locating the init file used absolute_file_name/3
with
access(write), which fails when <config>/swi-prolog does not
exist yet, silently failing the whole menu action.
- FIXED: emacs_prolog_mode <-forward_clause handed itself to a regex
`regex ->search' wants a char_array, text_buffer or fragment. A mode
is none of those: it only delegates to its editor, so the send raised
a type error, <-forward_clause failed and ->fill_paragraph filled the
clause as if it were a paragraph of prose rather than indenting it.
Alt-Q in Prolog code printed the error and did the wrong thing.
- FIXED: objects that outlived what owned them Three leaks that
checkpce/0 reported by the thousand after using the IDE for a while:
cloning an event copied the whole user interface, a window left the
graphicals of its fixed layer behind when it was destroyed, and the
manual kept freed tools in its tool table.
- ENHANCED: object->_check tells where the faulty object was found Each
inconsistency found by object->_check (checkpce/0) is now followed
by the path from the checked object to the object that holds the
illegal value. A step of this path is printed as the object and the
slot we descend in: `<-name' for an instance variable, `[index]' for
an element of a chain or vector and `{key}' for a key of a hash table.
Without it a report names an anonymous chain and there is no way to
tell what put the freed object in it.
- ENHANCED: The symbol picker is a pane, and shows a range from the top
It was a frame of its own. It is a `tool_pane' now, so it docks like
the navigator or the list of buffers; the arrangements the system
comes with give it a window of its own, which is where a thing you
pick from while typing elsewhere belongs.
- FIXED: A tool given a window of its own could not be learned Two
things stood in the way of "this tool lives in a window of its own"
ever becoming what the IDE does.
- ADDED: Settings/Remember how this window is arranged The IDE learns
where panes go from how long a window is worked in, which takes a
while to come round and fades again. This says it outright: the
window as it stands is where panes of these kinds go.
- ENHANCED: The event viewer counts a repeated event rather than
listing it Dragging the mouse across the box filled the history with
a screenful of `loc_move' and pushed what came before it out of sight.
A run of the same event now counts up on the line it is already on --
"loc_move (5×)" -- and that line keeps the last of them, which is
the one selecting it shows.
- FIXED: A text_item that cannot be typed in showed its clear button The
button clears the field, which a field that is not <-editable does
not allow, and clicking it there did nothing. It is not drawn now,
and the room kept for it goes to the text: a read-only field has no
use for a strip down its right. ->editable asks for a recompute,
as the width the field wants from its dialog changes with it.
- FIXED: The popup of the buffer list asked for things that are not
there Three items on it had gone stale:
- ADDED: editor ->placeholder An editor that holds no text writes its
<-placeholder where the text would be, in the text colour faded to
`editor.placeholder_opacity' (0.5), so that an empty one can say what
it is for. Class text_item has had one for a while; this is the same
thing for the classes built on an editor, and class view answers for
its editor, so a `view ->placeholder' works as well.
- ENHANCED: The PceEmacs bookmarks are a pane that can be docked The
bookmark editor was a frame of its own, holding a tool bar, the tree
of bookmarks, the note being written and a reporter, so it could only
float over the window it was asked for. It is a `tool_pane' now,
like the navigator and the list of buffers, and the arrangements the
system comes with put it right of the editor at a third of the width.
`find_references_editor/2' makes hit lists of the same class, so
those dock too, pin and all.
- ENHANCED: The PceEmacs buffer menu is a pane that can be docked It
was a frame of its own, holding a tool bar, the list of buffers and
a reporter, so it could only float over the window it was asked for.
It is a `tool_pane' now, like the navigator and the debug monitor,
and the arrangements the system comes with put it down the left of
the editor at a fifth of the width. Asking for it again brings the
one there is into view rather than making a second.
- FIXED: The menus of the bar ignored where their items belong The File
menu put "Close window" and "Halt prolog" at the top, and the Settings
menu showed a tick beside "User init file", which is a command rather
than a setting. Both come of a menu of the bar being filled from two
sides: the application puts its own items on it and the pane in view
adds what it can do afterwards.
[Sep 10 2026]
- FIXED: bookmarks: a bookmark on another drive was silently dropped
->bookmark walks the root of the bookmark tree up until it holds
the file being marked. On Windows the walk runs out of parents
before it gets there whenever the file is on another drive than the
one the tree is rooted on, and the bookmark was then thrown away.
Root the tree at "My Computer" instead: that root holds every drive,
and ->append already knows how to place a file below it.
- FIXED: terminal: Block Elements drawn as glyphs when the font lacks
them A Block Element is drawn as rectangles of the cell rather than
from the font, but its width was still classified by asking the
font how wide the glyph is. Where the font has no glyph for it --
the quadrants are missing from the fixed font on Windows -- that
measures the hex box the shaper substitutes, which is wider than a
cell, so the character was taken to be double width. A double-width
cell is a two-cell cluster, which the painter does not draw itself,
and the hex box went to the screen.
- FIXED: font copy left the `creating' flag set
getCopyFont() builds
the instance with allocObject() but never called createdClass(),
so the copy kept F_CREATING for its entire life. Such an object is
reported by `Object ->_check' as
- FIXED:
char type rejected Meta-modified key codes Raising META_OFFSET
above the Unicode range (b996819) made every Meta-modified key
code larger than UNICODE_MAX, so the `char' type validation started
rejecting them. As a result `Object ->_check' (and thus checkpce/0)
reported e.g.
- CLEANUP: epilog: leave fading an inactive terminal to the
frame A prolog_terminal faded itself on losing the keyboard
focus, from an `inactive_opacity' class variable of its own.
A pane_frame already does that for every pane it holds, driven by
`pane_frame.inactive_opacity', so the terminal was saying twice what
the frame says once -- and only for the terminal image, not for the
pane around it.
- FIXED: window <-opacity had no effect Fading a window is how a
pane_frame shows which of its panes has the focus, but nothing
rendered it: <-opacity was applied by pushing a Cairo group around
what a graphical paints into the surface it shares with the graphicals
around it, and a window paints nothing there. It has a surface of
its own that the frame composites, so the group wrapped only the
selection and inversion of the window rectangle, which the window's
own texture then covered. Setting `pane_frame.inactive_opacity'
in a Defaults file reached <-opacity of the pane and stopped there.
- DOC: how the panes of a window are managed, and how to configure
them Defaults.user gains the class variables that govern the layout
of a window of the IDE, and the User Guide a chapter on panes: how
a window is put together, what the user can do to it by hand, how
the system decides where a new pane goes, how
library(pane_layouts)
learns that from the way windows have been arranged, and the class
variables that change any of it.
- FIXED: pane_menu_bar <-menu_order taken from a Defaults file The
order of the pulldown menus is a class variable so that a user
can name it in their Defaults file, but doing so had no effect:
the XPCE tokeniser reads a quoted name as a string, and `GUI' and
`*' have to be quoted there. `chain <-index' then matched neither,
so <-rank failed for GUI and for every menu that should have taken
the place of `*', leaving all of them at the end of the bar.
- FIXED: initial tty size of an Epilog terminal was 0x0 The pty is
created lazily, when the Prolog thread is connected, and nothing
gave it a size: TIOCSWINSZ was only issued from a resize, and the
terminal is created at the size it is laid out at, so the first
resize never comes. tty_size/2 therefore reported 0,0 until
the user resized the window. Tell the pty the size the terminal
already has when creating it. On Windows the same size now reaches
CreatePseudoConsole() instead of a hardcoded 80x25.
- ENHANCED: SDL: upload only the part of a window a redraw changed
Drawing a frame uploaded every window's full Cairo backing into its
texture, so the cost of an update was proportional to the total pixel
area of the frame rather than to what changed. A window that updates
by itself, such as the thread monitor, therefore got more expensive
the more windows it shared its frame with. Updating one small text
now costs 0.16ms instead of 1.73ms in a frame of four 800x600 windows,
and 0.29ms instead of 2.07ms in one of sixteen 400x300 windows.
- FIXED: the busy cursor could stay on screen until the mouse moves
Dropping `display ->busy_cursor' only forgot the cursor, leaving it
to the
updateCursorWindow() that runs after each event to put the
normal shape back. That does not happen if the event went to a frame
rather than to one of its windows, which is the case for a keystroke
while the pointer is outside the frame: typing a command in the GUI
debugger left the watch cursor until the pointer moved.
- ADDED: PceEmacs keeps its tabs to itself: the editor is a pane with
tabs A source the user asks to see -- edit/1, M-. find-definition,
a location picked in a tool -- takes a tab of the editor rather than
the editor that is there. The tabs of the window are whole layouts,
so putting a source in one of those would take whatever sits beside
the editor off the screen; the editor now holds a tab per source of
its own, and splitting it keeps those tabs with it.
- FIXED: emacs_view ->pane_term: a buffer restored by its name
`dict <-member' answers the dict_item rather than the object in
it, so a window description naming a buffer rather than a file --
`
editor([buffer('*scratch*')])' -- restored nothing and reported a
type error.
- FIXED: tab_frame <-current: the keyboard focus in a window of a window
A window on a tab may itself hold windows -- a tabbed_window used as
a pane, as class tool_pane is -- and then the frame's keyboard focus
is on one of those rather than on a window the tab holds. The tab
answered whichever window it last made current instead, so clicking
inside such a pane left the tab, and its frame, believing the user
was working somewhere else.
- FIXED: the menu bar could be dragged shut A tile that was given
a size held on to it and stayed resizable, whether the size came
from the user's hand or from the window in it asking for the room
its content needs. The menu bar of a pane_frame asks for that room
whenever the strip changes -- a second row of menus, a tool bar coming
or going -- and so does the status bar when a prompter does not fit,
which marks the tiles above it as well. The gap under the menu bar
then became a handle, and a strip dragged shut cannot be opened again:
`tile ->rebalance' leaves a tile with no size alone, and what is left
to grab lies under the frame's border.
- FIXED: Call sites using an outdated signature for ->inform,
->confirm and ->report `display ->inform' and `display ->confirm' are
`for=[visual], title=[char_array], message=char_array, any ...' and
`->report' is `kind={status,inform,progress,done,warning,error,fatal},
format=[char_array], argument=any ...'. A number of call sites still
passed the message as the first argument, raising a type error instead
of showing the message or asking for confirmation.
[Sep 9 2026]
- ENHANCED: The debugger opens in the window the traced thread runs in
A debugger used to take a window of its own, so tracing a thread that
talks in a terminal of the IDE left the user with a second window to
watch it in. It is now a tab of the window that thread is talking in:
`prolog_ide <-thread_frame' finds that window from the terminal the
thread runs in, and the arrangements the system comes with put a
debugger in a tab beside a terminal.
- FIXED: laying out, drawing and resizing the panes of a window The
room a window has is divided in pixels, so it has to be there before
the shares are handed out. A frame that is not open has only been
fitted -- every pane at the least it will take -- and dividing that
gives nothing, so building a window from a term now resizes it first;
an open window is not laid out again from ->resize, which would put
the panes back where they were before the menu strip and the status
bar took theirs. `tile ->rebalance' turns the layout as it stands
into the wish, so a hand resize of the panes is kept when the window
is resized: the resize gesture, the frame's own separator drag and
the shares a `window_tree' asks for all end with one.
[Sep 7 2026]
- ADDED:
library(pane_layouts): the IDE learns where you put your
panes Where a tool or a source appeared was decided by one setting
for all of it, `prolog_ide.tool_placement', which gives the same
answer whatever is already on the screen. library(pane_layouts)
reads arrangements instead: a window written down with the content
left out. Given the kinds of pane a window holds and the kind of the
one to add, it answers `window', `tab', or `split(Kinds, Side, Share)'
-- beside the panes of those kinds, on that edge, at that share of
their room. It answers in kinds rather than in panes, so all of it is
plain Prolog that is read and tested without a window on the screen.
The setting gains a fourth value, `as_arranged', and it is the default.
- MODIFIED: every source the user asks to see opens where they asked A
window of the IDE offers one setting on its Settings menu for where
new things go -- in a window of its own, in a tab, or beside what is
there -- and a source is now placed by it, as a tool is. The item says
"New tools and sources open" now that it means both.
[Sep 6 2026]
- MODIFIED: the tools of the IDE are panes of the IDE Every tool that
was a window of its own is a pane now, so it sits in a tab of any
window of the IDE or beside a terminal or an editor, is dragged
around by its grip like anything else, and says what it is doing on
the status bar of the window it is in. Where a tool goes is the "New
tools open as" setting. Being panes rather than persistent_frames,
they no longer remember their own geometry: they take the size of
the pane they are given.
[Sep 5 2026]
- ADDED: class tool_pane and window ->display_fixed: a tool is a pane
too A tool that shows several windows -- the thread monitor is the
first of them -- could only be a tab. Class tool_pane makes it a pane:
a tabbed_window holding a single tab_frame, which lays its windows out
with a tile the way class frame does for its members while presenting
one window to the outside. ->append_window adds a window beside the
ones it has, <-window finds one by class, and it carries the grip,
so a tool can sit beside a terminal or an editor in one tab and be
dragged to another window like anything else.
[Sep 4 2026]
- ADDED:
library(pane_frame): one main window holding tools in tabs and
panes A pane_frame carries a menu bar on top, a set of tabs below
it that each hold one or more panes side by side, and optionally a
bar at the bottom that reports and prompts. It is the one window
Epilog, PceEmacs and the IDE tools live in, so that a terminal and an
editor can sit beside one another. PceEmacs and Epilog run in one,
and their windows belong to @prolog_ide.
- ADDED:
library(tab_frame): a tab that holds a tiled set of windows A
tab could show one window. Class tab_frame lays a set of them out
with a tile hierarchy, the way class frame does for its members,
so a tab can hold a split window: windows are split, resized and
dragged from one tab to another. ->split and ->drop put a window
beside another, a separator is drawn in every gap that can be resized,
and class split_handle is the grip in the corner of a pane that splits
or moves it.
[Sep 2 2026]
- ENHANCED: xpce: window<-image, beside frame<-image Reads back the
pixels of one window, on a surface of the window's own size and with
the window at its origin. A window decorator and any subwindows come
along, as they do for the frame.
- FIXED: epilog: draw the Block Elements as rectangles of the cell A
font lays its glyphs out in its own em box, which is not the cell:
the box is placed by the baseline and is shorter than the line height
a terminal gives a row. Drawn from the font, U+2588 FULL BLOCK left
a strip of the cell background above it and a quadrant sat below the
top of its cell, so a picture built out of these characters -- Claude
Code's logo -- came out in stripes, with a dark line where the client
had set a background colour and a gap between the body and the feet.
- FIXED: epilog: Shift+Tab is the back-tab, not a tab The key sent a
plain tab, so a client that walks a form both ways -- Claude Code
cycles its modes with it -- could not tell it from Tab. It now sends
the CBT xterm spells `CSI Z', and `CSI 1 ; <mod> Z' when another
modifier is held with it.
- FIXED: epilog: the CSI parameter prefixes `<' and `=' `ESC[<u', which
Claude Code writes when it exits to pop the kitty keyboard protocol's
flags, printed a stray `u' on the screen: `<' was not recognised as
one of the ECMA-48 parameter prefixes, so the sequence ended there
and its final byte was taken for text. For the same reason `ESC[>5u'
was read as the `CSI u' that restores the caret.
- TEST: epilog: the copy keys must not reach the client Covers b5dfc657:
Command-C and Ctrl+Shift-C with nothing selected used to fall through
to the program on the terminal, as a `c' and as ^C.
- FIXED: epilog: Command-C and Ctrl+Shift-C must not reach the client
A key the terminal has a binding for is now kept by the window even
when the bound method declines. Copying with nothing selected made
Command-C insert a `c' into the program running on the terminal,
and Ctrl+Shift-C reach it as the ^C the keymap makes of it.
[Sep 1 2026]
- FIXED:
text_item_combo_width() read <-style of a non-text_item
The completion browser is shared with class menu: a ->kind:
cycle menu opens it as its combo box and becomes @completer's
<-client. destroyCompleter() asks the client for its combo width,
so text_item_combo_width() was reached with a menu and read a slot
that class does not have.
- FIXED: crash when `menu ->initialise' fails A failed ->initialise
is still followed by ->unlink, and
unlinkMenu() walked <-members,
which is still @nil when the argument type check rejects the call
before initialiseMenu() ever runs. `new(M, menu(test, cycle, <bad
message>))' crashed rather than failing.
- ENHANCED: profile GUI: do not allow scrolling the table off screen.
- FIXED: terminal_image: drop a selection the line editor left behind
A selection on the line being edited stayed as it was made while the
line moved on under it: the cursor keys walk the caret away from it,
and what a key does to the text can leave the highlight over something
else entirely. Every key that goes to the program now drops it,
the way an editor drops the selection as soon as one types on, unless
the key edits the selection itself -- typing replaces it and Delete
takes it, which ->delete_selection already did.
- TEST: terminal_input_selection: the selection over the line being
edited, with the two ends of what is left alone.
- ENHANCED: Epilog: keep the report bar off the line being worked on
The bar with the short messages is drawn over a line of the terminal,
which is a nuisance when that is the line being edited. The new
`epilog_report.placement' class variable says which line it takes:
`smart' (the default) takes the last one unless the caret or an end
of the selection is there, in which case it takes the first, `bottom'
and `top' pin it and `none' sends the messages to the normal XPCE
reporting instead.
- ADDED: terminal_image <-selection_start, <-selection_end: the ends of
the selection as
point(col, row), in the coordinates <-cursor_position
uses. `smart' needs them to see where the hit of a running incremental
search is, as that hit is the selection.
- FIXED: colour: the alpha of a `#rgba' colour name Expanding the four
nibbles of the short form to bytes doubled the blue one into the
alpha rather than the alpha itself, so `#f008' came out at an alpha
of 8 rather than 136.
[Aug 31 2026]
- ADDED: edit the selection of the line being typed A left click already
moves the caret inside the line the program on the terminal is reading.
This lets the selection be edited as well: Delete and Backspace take
it, ^X and Command-X cut it, typing replaces it, and so does a paste
from the clipboard.
- ADDED: Epilog: Command-Z undoes on MacOS Command-Z is how MacOS
asks for undo, and terminal_image->typed already offers a Command
key to the terminal's own bindings before the program on it.
Add prolog_terminal->undo, which sends ^_ -- the key libedit binds
undo to -- and bind it in the `apple' epilog table beside the other
Command keys.
- FIXED: Control-Shift-`-' did not reach the client as ^_ SDL reports the
unshifted key, so Control-Shift-`-' arrived as `-' (0x2D). That is
below `@', so the C0 conversion skipped it and the client was sent a
bare `-'. libedit binds undo to ^_, which could therefore never be
typed in a terminal.
- ENHANCED: show keyboard accelerators in the native menu bar An item
of a natively displayed menu_bar showed no shortcut, because nothing
turned menu_item<-accelerator into an NSMenuItem key equivalent.
Doing so needs two things.
- FIXED: Apple bindings for Command-arrow never fired
characterName()
writes a named key between angle brackets, so Command-Left is
`\s-<cursor_left>', not `\s-cursor_left'. The four cursor entries
of the `apple' editor bindings used the latter and could therefore
never match an event, leaving Command-Left/Right/Up/Down without
the beginning_of_line, end_of_line, point_to_top_of_file and
point_to_bottom_of_file they were meant to have.
- ENHANCED: PceEmacs: reach the location history without the tool bar
The two history buttons in the mode dialog's tool_bar were the only
way to walk the location history: no menu item and no key binding.
With the menu bar displayed natively (MacOS) the menu_bar is not drawn,
which left those buttons alone in an otherwise empty strip.
- ADDED: `menu_bar<-native`
- FIXED: PceDraw: type error setting an attribute from the editor
colour->equal declared its argument as `colour', so comparing a colour
against anything else raised a type mismatch rather than answering
false. The attribute editor does exactly that when it checks whether
the value changed, so setting the colour of a shape raised an error.
equalColour() already guards both arguments with instanceOfObject(),
so the declaration is what was wrong.
- FIXED: drain the terminal pty on the watcher thread The console
pty was read only by
receiveTerminalImage(), which runs on the main
thread from ws_dispatch(). The main thread is also what writes to it,
so once the pty filled, a main-thread write could never complete:
the only thread that could make room was the one stuck in write().
Any sufficiently large write hung the application -- reporting an
XPCE error with a backtrace was simply the easiest way to get there.
- MODIFIED:
library(dragdrop): use the `move' cursor Also brings the
file header up to date.
- FIXED: let an empty window collapse instead of claiming a strip A
dialog holding only a menu_bar that is displayed natively (MacOS)
has nothing to show, but still occupied a visible strip at the top
of the frame, so the frame appeared to draw a double-height border
above its content.
[Aug 30 2026]
- BUILD: compile the Objective-C menu bar with Clang, not the C compiler
Cocoa headers use blocks, nullability and instancetype, which only
Clang understands, so building XPCE with e.g. MacPorts GCC failed:
enable_language(OBJC) defaults CMAKE_OBJC_COMPILER to CMAKE_C_COMPILER,
and gcc-mp-15 cannot parse Foundation.h.
[Aug 31 2026]
- ENHANCED: native MacOS menu bar for class menu_bar On MacOS the menus
of a `menu_bar' are now shown in the system menu bar at the top of
the screen rather than drawn inside the frame. The XPCE `menu_bar'
object remains the model: its `popup' members are mirrored into
an NSMenu, and selecting an item is routed back through the normal
`popup->execute' path, so applications need no changes. Controlled by
the new class variable `menu_bar.native', which defaults to @on on
MacOS and @off elsewhere.