Features
-
Auto scroll on text selection mode to keep the selection in view when selecting text
with the mouse or keyboard, which provides a smoother and more intuitive text selection experience,
especially for longer documents where the selected text may go out of view. -
Add optional lua scripting support (experimental, work in progress)
-
API overview
lektra.opt- for getting and setting config optionslektra.cmd- for command related stufflektra.ui- for UI related stuff (e.g. showing notifications, input dialogs, etc.)lektra.tabs- for managing tabslektra.event- for subscribing to events (e.g. page change, file open, etc.)lektra.keymap- for managing keybindingslektra.mousemap- for managing mousebindingslektra.utils- for utility functionslektra.version- for version functionslektra.capabilities- for querying about compiled optionslektra.bookmarks- for managing bookmarks
-
Check LUA-WIKI.md for more details and examples of the lua scripting support in LEKTRA.
-
-
Vim/Emacs like search hit indexing navigation if
absolute_jump = falsein[search] -
Add bookmarks support with a bookmark picker to view and manage bookmarks. Bookmarks allow users to save specific locations
in the document for quick access later. -
New bookmark related commands:
bookmark_addto add a bookmark at the current locationbookmark_removeto remove a bookmark at the current locationbookmarksto open the bookmark picker with the list of bookmarks in the document
-
Add image rotation support
-
Add missing implementation for
single_instanceoption. Now ifsingle_instanceis enabled, new files will use the
existing instance of LEKTRA instead of opening a new instance, which allows for better management of
multiple documents and prevents cluttering the taskbar with multiple instances of LEKTRA.
Bug Fixes
- Update the layout menu item names
- Fix crash (SEGV) on click selection in
SINGLElayout mode —pageAtScenePoswas
guarding theoutPageItemassignment withif (outPageItem), but the pointer is always
nullptrat that point, sopageItemwas never set andmapFromScenedereferenced null. - Fix wrong text selection after zoom in
SINGLElayout mode —setZoomAnchoredcalled
repositionPages()(which scales the old item as a visual intermediate) but never
triggered a re-render, leaving the page item permanently at a non-unity scale. Because
mapFromScenedivides by the item scale, selection coordinates were passed to
computeTextSelectionQuadin the wrong zoom space. Fixed by callingrenderPage()for
SINGLEmode afterrepositionPages()insetZoomAnchored. - Fix fit mode not working if image files are opened
- Fix memory leak in
extractTextfunction inModelclass - Add UTF-8 text conversion for file paths on Windows to fix issues with opening files with non-ASCII characters in their paths on Windows.
- Reset
m_success = falseat the start of every open inModel.cpp - [DocumentView.cpp] Made the future watcher connection single-shot and cleared stale connections before reconnecting and
added a guard inhandleOpenFileFinished()so it exits early if the model did not actually open the file. - Add missing
pageconfig section loading from the config - Fix visual line mode navigation to be more naturally
- Fix context menu on tabs not working
- Fix opening files in containers with already open file not loading.
- Fix
openSessionFromArrayfunction loading files incorrectly and breaking - Fix segfault in
buildPageCachebecause of double free of mupdf context - Add implementation for
file_reloadcommand - Fix synctex initialisation
- Fix synctex optional macro in the source code
HAS_SYNCTEX->WITH_SYNCTEX - Fix image zoom anchoring
lua/Lektra.cpp: Fix stack leak inexecuteLuaCode— the message-handler function was
pushed beforeluaL_loadstringbut never popped, growing the Lua stack by one slot on
every call. Replaced withlua_settopsave/restore and removed the broken handler
(which returned 0 instead of the required 1 value). Also fixedtoStdString().c_str()
to a stableQByteArraylocal.lua/view.cpp: Fixopenmethod readinglua_upvalueindex(1)with zero upvalues —
caused undefined behaviour/crash. Now resolves theLektrainstance via
qobject_cast<Lektra*>((*view)->window()).lua/view.cpp: Fixfit,mode, andlayoutgetter methods returning0(no values)
after pushing a value onto the stack — callers received garbage. Changed toreturn 1.lua/view.cpp: Fixgoto_locationapplying a page-index offset twice:pagenowas
already converted to 0-based, then subtracted again before passing toGotoLocation,
sending page 1 to index −1.lua/view.cpp: Fixzoomcontaining unreachablereturn 0afterreturn 1in both
branches. Collapsed to a single push +return 1.lua/view.cpp: Fixset_invertreturning 1 in the success branch without pushing
anything — now returns 0 (setter, no return value).lua/view.cpp: Replace empty stub bodies inset_modeandsave_aswith
luaL_errorso callers get a clear error instead of silent no-ops.lua/event.cpp: FixunregisterAPI mismatch — it expected(string, int)but
registerreturns only aninthandle. Unified to(EventType, handle)matching the
registersignature.lua/event.cpp: Fixoncetaking a string event name whileregistertakes an integer
EventType. Both now use integerEventTypefor consistency.lua/event.cpp: FixCOUNTsentinel exposed in thelektra.event.EventTypeLua table
due to an off-by-one<=in the population loop. Changed to<.lua/event.cpp: Fixoncecallbacks never being removed —is_onceflag was set but
dispatchLuaEventnever checked it, so the callback would fire on every subsequent
dispatch (pushing nil after the first unref). Moved cleanup intodispatchLuaEvent
using an erase-remove pass after invocation.lua/event.cpp: Fix iterator invalidation indispatchLuaEvent— callbacks could call
unregisterduring iteration. Now iterates over a local copy of the callback list.lua/cmd.cpp: Fix Lua registry reference leak on command unregister —func_refwas
captured in the action lambda butluaL_unrefwas never called when the command was
removed. Wrapped in ashared_ptrguard whose destructor callsluaL_unref.include/DispatchType.hpp: Fix duplicateOnPageChangedkey in the dispatch map —
the second entry silently overwrote the first inQHash.include/DispatchType.hpp: Rename reserved identifier__dispatchEventMap(double
leading underscore is reserved by the C++ standard) tos_dispatchEventMap.include/DispatchType.hpp: Replace O(n) linear scan indispatchTypeToStringwith an
O(1) static array indexed by enum value.
Performance
-
lua/view.cpp: Implementview:outline()— returns the document table of contents as a
recursive Lua table tree. Each entry hastitle,pageno(1-based,nilfor external
links),x,y, and achildrenarray for nested headings. -
Model.cpp/DocumentView.cpp: Overhaul animated image (GIF) playback performance:- Two-pass open:
pingImagesreads frame count and per-frame delays from headers only
(no pixel I/O). A single[0]scene read then decodes frame 0 and emits
openFileFinishedimmediately, so the image appears without waiting for the full decode.
Remaining frames are decoded in a background thread viareadImages+coalesceImages. - Remove dead
getAnimatedFrame: the function re-read and re-coalesced the entire file
from disk on every call just to produce one frame. It was never called; removed entirely. - Eliminate
m_image_cacheindirection for animated frames:setCurrentAnimFramenow
only updatesm_current_frame;requestImageRenderreads directly from
m_animated_frames[m_current_frame], removing a per-advance QImage copy. - Remove unconditional
qDebuginsetCurrentAnimFrame: the debug log fired on every
frame advance (30+ times per second in all build configurations). - Fix
cleanup_imagenot resetting animated state:m_animated_frames,
m_frame_delays_ms,m_frame_count, andm_current_framewere left populated when
switching away from an animated image. - Frame-skip during startup: the playback timer skips frames that have not been decoded
yet (background decode still in progress) instead of displaying a blank frame. - Elapsed-time frame scheduling: the animation timer now subtracts actual render time
from the next frame's nominal delay viaQElapsedTimer, keeping the playback cadence
on schedule even when individual frames render slowly. - Free raw Magick frames early: in the background decode path, the raw
readImages
vector is cleared immediately aftercoalesceImagesso decoded-but-uncompressed Magick
memory is released before the QImage conversion loop begins.
- Two-pass open:
-
DocumentView.cpp: ReplaceQGraphicsItem::data(0).toString()tag checks withQSet<int>
membership tests (m_placeholder_pages,m_preload_pages) eliminating repeated
QVariant→QStringconversions in hot loops insideremoveUnusedPageItems,
repositionPages, andrenderPages. -
DocumentView.cpp: Split the single render queue into a visible-page queue and a preload
queue sostartNextRenderJobdequeues in O(1) instead of doing an O(n) linear scan with
an O(n)removeAtto find the next visible page to render. -
DocumentView.cpp: EliminateQHash::keys()heap copies inremoveUnusedPageItems,
clearVisibleLinks, andclearVisibleAnnotations; replaced with direct iteration or a
two-pass collect-then-delete approach. -
DocumentView.cpp: Cache the last rendered search-hit state (index+ page-item pointer)
inupdateCurrentHitHighlightso the path is only recomputed when the hit or page item
actually changes, avoiding redundantQPainterPathrebuilds on every scroll event. -
Model.cpp: Skip redundantline_length()traversal infind_closest_in_pagefor lines
with no characters — the call always returned 0 so theidxincrement was a no-op. -
Model.cpp: Hoistm_inv_dprscale constant out of the per-link and per-annotation render
loops inrenderPageWithExtrasAsync; addreserveonresult.linksand
result.annotationsbefore those loops to avoid reallocation churn on pages with many
links or annotations. -
Lektra.cpp: HoistfindChildren<QShortcut *>()out of the per-key loop in
setupKeybinding— was performing a full child-object tree traversal once per key;
now called once before the loop with deleted entries removed in-place to avoid dangling
pointers. -
Lektra.cpp: Replace the temporaryQStringList() << "*.json"filter construction in
getSessionFileswith astatic const QStringList, eliminating a heap allocation on
every call.
Breaking Changes
- Remove
placeholder_textoption from[command_palette]section of the config. - Add
promptoption in[picker]common to all pickers, which allows for a
customizable prompt text shown next to the input field. - Rename
always_open_in_new_windowtosingle_instance. - C++ version requirement has been downgraded to C++20 (previously it was C++23) to allow
for wider compatibility with different platforms and compilers, as C++20 is now widely
supported by most modern compilers and platforms, while C++23 is still very new and not yet
supported on many platforms.