Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[3.0.0] - 2026-09-05¶
A major release: the View trait changes for every downstream crate, so the
other API changes that needed a breaking release ride along. Start with
UPGRADING-TO-3.0.md, which is this entry rearranged as
an ordered checklist. The analysis and plan are in docs/MISSING-INHERITANCE.md;
the coordinate model is in docs/OWNER-COORDINATES.md.
Migrating to owner-relative coordinates¶
A view's bounds() are now relative to its owner, as Borland's TView::origin
is, and stay put when the owner moves. A view draws in its own space, (0, 0)
to extent(), and mouse positions arrive in that same space; the owner
translates on the way down and back up.
// 2.x: bounds were screen coordinates once added
let x = self.bounds().a.x;
write_line_to_terminal(terminal, x, self.bounds().a.y + row, &buf);
if self.bounds().contains(event.mouse.pos) { .. }
// 3.0.0: draw at (0, 0); mouse positions are view-local
write_line_to_terminal(terminal, 0, row, &buf);
if self.extent().contains(event.mouse.pos) { .. }
A view that holds children by value pushes each child's origin around its
draw and dispatches events in the child's space: views::view::draw_child
and views::view::dispatch_to_child do both. Code outside the tree that draws
a top-level view itself (app.desktop.draw(&mut app.terminal) in a custom
loop) calls app.terminal.draw_view(&mut app.desktop) instead, and dispatches
with dispatch_to_child(&mut app.desktop, &mut event). Positions given when
adding a child were always owner-relative and do not change.
Migrating a downstream view¶
// 2.x
pub struct MyView { bounds: Rect, state: StateFlags, options: u16, palette_chain: Option<PaletteChainNode>, .. }
impl View for MyView {
fn bounds(&self) -> Rect { self.bounds }
fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
/* eight more accessors */
fn draw(..) { .. } fn handle_event(..) { .. } fn get_palette(..) { .. }
}
// 3.0.0
pub struct MyView { core: ViewCore, .. }
impl View for MyView {
fn core(&self) -> &ViewCore { &self.core }
fn core_mut(&mut self) -> &mut ViewCore { &mut self.core }
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
fn draw(..) { .. } fn handle_event(..) { .. } fn get_palette(..) { .. }
}
// A type wrapping a Window, 3.0.0
impl GroupLike for MyWindow { fn group(&self) -> &Group { self.window.group() } fn group_mut(&mut self) -> &mut Group { self.window.group_mut() } }
impl WindowLike for MyWindow { fn window(&self) -> &Window { &self.window } fn window_mut(&mut self) -> &mut Window { &mut self.window } }
impl_view_for_window!(MyWindow {
fn handle_event(&mut self, event: &mut Event) { self.window_handle_event(event); /* ... */ }
});
| Change | Before | After |
|---|---|---|
| Base fields | Own bounds, state, options, palette_chain fields and ten accessors |
One core: ViewCore field, core() and core_mut() |
| Downcasting | as_any optional, panicking default |
as_any and as_any_mut required |
Hooks removed from View |
is_default_button, button_command, set_list_selection, get_list_selection, get_end_state, set_end_state |
Downcast to Button or ListBox; GroupLike::end_state / end_modal, reached through View::as_group() |
| Window-shaped types | impl View for MyWindow with forwarding |
impl WindowLike for MyWindow plus impl_view_for_window!(MyWindow { overrides }) |
| Idle | impl IdleView |
fn idle on View |
| Adding children | add(Box::new(v)) |
add(v); boxing still accepted; GroupLike must be in scope (it is in the prelude) |
| Reading a field | Rc<RefCell<String>> passed to InputLineBuilder::data |
let h = dialog.add_typed(field) then dialog.get(h).unwrap().text() |
| Flags | SF_MODAL, OF_SELECTABLE, GF_GROW_ALL, MF_OK_BUTTON as integers |
State::MODAL, Options::SELECTABLE, Grow::ALL, MsgBox::OK_BUTTON; old names deprecated for one release |
| Dialog close | Any command below 1000 | CloseOn policy; default closes on the standard commands and the dialog's own buttons |
| Commands | Library exports application commands | Library reserves 0 to 199; applications start at CM_USER, 200 |
| Message boxes | helpers::msgbox or views::msgbox |
views::msgbox only (helpers re-exports it, deprecated) |
| Menus and status | Positional constructors with scan codes | Builders with chord strings |
| Application loop | Copy run() to add a command handler |
impl AppHandler and run_with |
| Modal loops | Copy Dialog::execute to poll a job while a dialog is up |
app.execute_modal(&mut dialog, tick) |
| Shared children | Hand-written SharedX(Rc<RefCell<X>>) newtypes |
Shared<X> |
| Finding a desktop window | Iterate children and downcast_ref |
desktop.get(handle) |
| Editor access | EditWindow::editor_rc() |
editor() / editor_mut() (editor_rc deprecated) |
Added¶
- Owner-relative coordinates.
View::extent()(BorlandgetExtent);Terminal::push_origin/pop_origin/originandTerminal::draw_view;views::view::draw_childanddispatch_to_childfor owners that hold children by value.Terminal::write_cell,write_line,read_cell,show_cursorandpush_cliptake locali16coordinates and translate by the pushed origins.TestBackend::cursor_handlereads back the cursor position a test drew. AppHandlerandApplication::run_with. Application-level hooks (pre_event,handle_command,idle,window_closed) replace the hand-written copies of the event loop that programs wrote to handle their own commands (Borland:TApplication::handleEventandidleoverrides).Application::run()is nowrun_with(&mut ()).-
Application::execute_modalandModalTick. The one modal loop behindDialog::execute,FileDialog::executeandHelpWindow::execute; a per-tick closure can poll a background job and end the dialog. Events go to the outer type'shandle_event, so aFileDialogno longer needs its own copy of the loop to react to its children. -
Typed child handles.
GroupLike::add_typedreturns aHandle<T>;get/get_mutgive the concrete child back.Desktophas the same three methods for its windows.
Fixed¶
- Frame children follow the window when it is resized (#108). A view added
with
Window::add_frame_child, a scroll bar on the border being the usual case, kept its place through a resize, so it no longer hugged the edge it was put on. It now moves and stretches by its grow bits, the same rule the interior's children follow; one with no grow bits stays put, as Borland leaves any view that declares no growth.EditWindowis unaffected: its scroll bars carry no grow bits and it positions them itself. - A child too big for its group painted over the frame around it (#108).
Groupclipped its children to its own extent grown by one cell in each direction, an allowance meant for the shadow a window casts outside its bounds. Any interior view larger than the window it sits in used that cell to erase the window's right border and bottom edge: the showcase's ASCII table did it as soon as the window was tiled below the table's width. The overhang is now opt-in,Group::set_child_overhang, and onlyDesktopasks for it, sized fromshadow_size(). Every other group clips tight. - A window as large as its owner (a full-desktop editor with a shadow) was
pushed to a negative origin, hiding its top row and left column. Drag limits
now clamp the far edges first and the near edges last, as Borland's
TView::moveGrowdoes, so the top-left corner stays put.
Changed (breaking)¶
- Owner-relative coordinates.
Group::add,Window::addandWindow::add_frame_childstore the bounds they are given instead of converting them to screen coordinates;group_set_boundsno longer shifts children on a move, only applies the grow bits on a resize (Borland:TGroup::changeBounds). Every view draws at(0, 0)and sees view-local mouse positions (see the migration section above).set_parent_boundsisset_owner_extentand receives the owner's extent;make_globalandmake_localconvert one hop, between a view and its owner. Standalone popups (MenuBox,HistoryWindow, the combo box drop-down) push their own origin. Tests that inspected a child'sbounds()after adding it now see owner coordinates. - Typed flag sets.
State,OptionsandGrow(incore::state),MsgBox(inviews::msgbox) andValidatorOptions(inviews::validator) replace the bareu16/u8bit masks, so a state cannot be handed to an options parameter by mistake.View::options()andset_optionsuseOptions;ViewCore's fields are typed. The old constants (SF_MODAL,OF_SELECTABLE,GF_GROW_ALL,MF_OK_BUTTON,VO_FILL, ...) remain as deprecated aliases of the associated constants (State::MODAL,Options::SELECTABLE,Grow::ALL,MsgBox::OK_BUTTON,ValidatorOptions::FILL) for one release. Tests likestate & SF_X != 0becomestate.contains(State::X);StateFlagsandGrowFlagsare type aliases ofStateandGrow. CloseOnreplaces the "commands below 1000 close the dialog" rule. A modalDialognow ends onCM_OK,CM_CANCEL,CM_YES,CM_NOand on the commands of the buttons added to it (CloseOn::StandardAndButtons, the default);CloseOn::StandardandCloseOn::Commands(..)are the alternatives, set withDialogBuilder::close_onorDialog::set_close_on. A command from any other child (a list box, say) is left for the caller whatever its number, so commands no longer need to be numbered above 1000 to pass through.- Command numbers have owners.
0..=99are Borland's standard commands (theeditors.hfile setCM_NEW..CM_CLOSE_FILEmoves to 30..35),100..=199are this crate's internal commands and broadcasts (CM_REDRAW,CM_SHOW_HISTORY,CM_SCREENSHOTand friends are renumbered into that band), and applications start atCM_USER(200). The demo-application commands (CM_ABOUT,CM_BIRTHDATE,CM_TEXT_VIEWER,CM_CONTROLS_DEMO,CM_FIND_IN_FILES,CM_ZOOM_IN,CM_ZOOM_OUT,CM_TOGGLE_SIDEBAR,CM_TOGGLE_STATUSBAR,CM_KEYBOARD_REF,CM_LISTBOX_DEMO,CM_LISTBOX_SELECT,CM_MEMO_DEMO) leave the library; define them in your program. - One message-box module, one
StatusItem,idleonView.views::msgboxis the only message-box implementation (it gainsMF_ABOUT);helpers::msgboxre-exports it, with deprecatedinput_box/input_box_rectshims keeping the old tuple return.views::status_line::StatusItemis now a re-export ofcore::status_data::StatusItem. TheIdleViewtrait is gone:Viewhasfn idle(&mut self) {}andApplication::add_overlay_widgettakes anyView. - Key chords in the menu and status builders.
MenuBuilder::item(text, command)binds no key,item_key(text, command, "Ctrl+O")binds and shows the chord,MenuItemBuilder::key(..)andStatusItemBuilder::key(..)do the same for single items, andMenuBuilder::add(item)takes a prebuilt item. The positionalMenuItem::new,MenuItem::with_shortcut,MenuItem::new_disabledandStatusItem::neware gone;MenuItem::flag,submenuandseparatorstay, and theKB_*constants remain public forhandle_eventmatch arms. An unknown chord panics at first run. addtakes any view.GroupLike::add,Desktop::add,Application::exec_viewandadd_overlay_widgetacceptimpl View(impl IdleViewfor overlays);Box<dyn View>still works because a boxed view is itself aView, soadd(Box::new(v))keeps compiling. The boxed primitive isadd_boxed.InputLineowns its text.InputLine::new(bounds, max_length)andwith_validator(bounds, max_length, validator)drop theRc<RefCell<String>>parameter;InputLineBuilder::text(..)replacesdata(..); read the value back withtext()through aHandle<InputLine>afterexecute.History::newtakes theHandle<InputLine>it is linked to, and the owning dialog records and fills the input (Borland'slinkpointer resolved by the owner), so theCM_RECORD_HISTORYandCM_HISTORY_SELECTEDbroadcasts are no longer handled byHistory.EditWindow::editor()/editor_mut()andHelpWindow::viewer()/viewer_mut()replaceeditor_rc()/viewer_rc(), which are deprecated.Desktop::remove_closed_windowsreturnsVec<ViewId>(the windows it removed) instead ofbool.FileDialog::handle_selectionno longer takes a&mut Terminal.View::core()andView::core_mut()are required. Every view owns aViewCoreholdingbounds,state,options,grow_modeandpalette_chain; the ten field accessors are now trait defaults that read it, so a view can no longer forget to report its state.View::as_any()andView::as_any_mut()are required. The panicking defaults are gone; every view can be downcast.GroupLikeandWindowLiketraits.Group's andWindow's behaviour live in trait defaults withgroup_*/window_*names so a container type can make a base call.WindowandDialogno longer have inherentadd,child_*,execute,end_modal,get_end_stateorset_end_state; importGroupLike(it is in the prelude) to keep calling them. Window-shaped types get theirViewimpl fromimpl_view_for_window!, with overrides written inline.- Six hooks removed from
View:is_default_button,button_command,set_list_selection,get_list_selection,get_end_state,set_end_state. Downcast toButton(is_default(),command()) orListBox(set_selection,get_selection) instead; the modal end state lives onGroupLike::end_state/end_modal, reachable from a&dyn Viewthrough the newView::as_group()(Borland'sdynamic_cast<TGroup*>). Shared<T>replaces the per-typeSharedScrollBar,SharedEditor,SharedIndicator,SharedHelpViewerandSharedTerminalWidgetnewtypes.
[2.4.2] - 2026-09-05¶
Fixed¶
- Clicking the zoom icon did nothing. The frame turned the click into
CM_ZOOM, but the desktop only looked for that command before handing the event to its windows, and a click arrives as a mouse event. The command came back out of the window unhandled and leaked to the application. The desktop now handles aCM_ZOOMits windows produce, so the icon and a double-click on the title bar both zoom and restore. The menu item was unaffected since a menu command already enters the desktop as a command.
[2.4.1] - 2026-09-05¶
Fixed¶
- The frame's zoom triangle went stale. It was written only by the zoom
command, so anything else that resized a window left the wrong glyph on the
title bar until the next zoom toggle: after Tile or Cascade shrank a zoomed
window, it still read as
\u{25BC}. The state is now derived at draw time from the window's bounds against the extent a zoom would fill, so it cannot disagree with what the window looks like, whatever moved it.Desktopalso pushes the new extent to its windows when the terminal is resized, which keeps both the triangle and the drag limits right across a resize.
Added¶
Window::is_zoomedandFrame::set_max_bounds, the accessor and the hook the derived state needs.
Deprecated¶
Frame::set_zoomed. The zoom state is derived from the bounds now; the setter is still honoured for a bareFramethat was never told its maximum extent, and ignored for any window inside a desktop.
[2.4.0] - 2026-09-05¶
Added¶
ProgressBarview. A determinate or marquee progress indicator, the first item from the newdocs/MORE-CONTROLS.mdroadmap. Determinate bars fill in proportion tovalue / max; marquee bars sweep a block for work of unknown duration and animate themselves throughIdleView, so adding one as an overlay widget is enough. Three glyph styles:Smooth(default, partial block glyphs give eighth-of-a-cell resolution),Blocks, andAsciifor terminals without box-drawing characters. The centred overlay is the truncated percentage by default and can be switched off withset_show_percent(false)/ProgressBarBuilder::show_percent(false), or replaced with fixed text viaset_caption. NewCP_PROGRESS_BARpalette reuses the scrollbar gauge colours, so a bar drops into an existing dialog or window unchanged. Seeexamples/progress_bar.rs.ComboBoxview. A read-only field showing one choice, with a drop-down list. F4, Alt+Down or a click opens the list; Up and Down cycle the choice without opening it. Opening is a two-step the way the history button already works: the control emitsCM_SHOW_DROPDOWN, and theDialogmodal loop orApplicationruns the popup, since a control cannot reach the terminal fromhandle_event. The popup draws its own thin frame, flips above the field when there is no room below, scrolls past eight items and writes the choice back into the sharedComboState.Spinnerview. A numeric field with up and down steppers, holding one integer inside a range. Typed input is clamped rather than rejected, and the first digit of a focus session replaces the value instead of extending it. Arrows step, PgUp and PgDn step ten times as far, Home and End jump to the range ends, Backspace drops a digit. Optional wrap-around and unit suffix.Tableview. A scrollable grid with a header row, per-column widths and alignment. Focus is a cell rather than a row: Up and Down move rows, Left and Right move columns, Ctrl+Left and Ctrl+Right jump to the end columns, and the grid scrolls in both directions by whole columns so a column is never clipped in half. Rows are read positionally, so a ragged row draws blank cells instead of panicking. This is the controlListViewer::num_colswas never meant to be: that field lays one list out in newspaper columns.TabbedPaneview. A tab strip over a stack of pages, each page aGroupthat holds ordinary controls and runs its own focus traversal. Drawn as enclosed tab boxes sitting on the page frame, with the active tab's floor open so the two read as one shape. F6 and Shift+F6 switch pages; Ctrl+PgUp and Ctrl+PgDn do too, where the terminal sends them, and a tilde-marked letter in a title is its Alt hotkey. The pane is transparent, so a page's controls take the owner's colours as if they sat in the dialog directly.SplitPaneview. Two panes divided by a draggable splitter, vertical or horizontal, with a minimum size for each half. A bare divider cannot resize siblings it does not own, so the control owns both halves, each aGroup, the wayTabbedPaneowns its pages. Dragging the divider moves it, clicking a half focuses it, and F8 moves focus between them. Moving the divider from the keyboard is left to the host throughgrow_firstandshrink_first, rather than stealing a key from the controls inside the panes.- Multi-select in
ListBox.set_multi_selectturns on marks that are independent of the focus: Space marks the focused item, Shift+click marks a run from the anchor, andmarked_items/marked_textreport them in list order. Marked rows carry a check glyph in a two-cell column that keeps the text aligned, andis_selectedfollows the marks in that mode. Off by default, so existing single-selection lists behave exactly as before. Replacing the items drops the marks, whose indices would otherwise refer to the old list. - ScrollBar mouse auto-repeat, the last omission recorded against the
scrollbar in
TO-DO.md.Applicationtracks whether a mouse button is held and, only then, broadcasts the newCM_MOUSE_AUTO_REPEATfrom its idle pass. A held arrow or track press repeats after 400 ms, then every 80 ms, and stops on release or at the end of the range. Nothing is broadcast while no button is down, so an idle app stays idle. examples/new_controls.rs. One dialog running all five new controls: a tabbed pane over two pages, the first wiring three combo boxes and a spinner to a progress bar, the second a table.CheckBoxesandRadioButtons. The Borland shape the port was missing: each holds its items in one focusable control with a single bitmask value. Arrows move within the cluster, Space toggles a box or selects a button, Tab leaves it, and a tilde-marked letter is an item's Alt hotkey; items can be disabled individually and draw dimmed. Radio clusters keep exactly one bit set, so pressing Space on the selected button does not turn it off. The existing one-labelCheckBoxandRadioButtonare untouched and still supported.Tooltipview. Hover hints for a whole dialog: register a rect and a line of text per control, and the pointer resting on one raises the hint beside it, flipping above the control when there is no room below. Add it last, since it draws over its neighbours. Any click or keypress takes the hint down.- Frame zoom triangle. A resizable window's title bar now carries
[\u{25B2}]beside the close box, turning into[\u{25BC}]once zoomed. It tracks press and release like the close box, so a press that slides off cancels rather than zooming. Dialogs show none: Borland pairs wfZoom with wfGrow, and a dialog has neither. Closes the last "visual polish only" note inTO-DO.md. CM_IDLE_TICK. Broadcast fromApplication::idlewhenever the event poll times out, so views can run timers of their own. The tooltip's hover delay is the first user; animation is the obvious second. Views must not consume it, since a broadcast stops travelling once it is.examples/cluster_tooltip.rs. The three above in one dialog.docs/MORE-CONTROLS.md. Gap analysis of the widget set against Borland Turbo Vision and modern text-UI expectations, with a checklist roadmap. Every item on it is now done.
Changed¶
- Focus is visible on the new controls. Borland's input palette gives
"normal" and "focused" the same colour because a
TInputLineshows focus with its cursor.ComboBoxandSpinnerdraw no cursor, so they use the selected-text colour when focused.
[2.3.1] - 2026-09-05¶
Added¶
- Auto-dismissing message boxes. A new
MF_AUTO_DISMISSflag on bothviews::msgboxandhelpers::msgboxcloses the box afterMESSAGE_BOX_AUTO_DISMISS_TIMEOUT(3 seconds) if the user has not dismissed it, returning the default button's command (OK when present, otherwise the first button,CM_CANCELwhen there are none). It is built on a genericDialog::set_auto_dismiss(timeout, command)that theDialog::executemodal loop honours, so any dialog can use it.
Changed¶
- Message-box height follows the buttons. A message box with no button
flags no longer reserves the button row: the text area runs to the
bottom frame and the box is two rows shorter (three in
helpers::msgbox, whose minimum drops from 9 to 5 rows). This makes button-less, auto-dismissing boxes usable as splash screens. - The Pascal IDE example's startup about box is now such a splash: no OK button, gone after 3 seconds, Esc closes it early.
[2.3.0] - 2026-09-04¶
Fixed¶
- Windows now follow a terminal resize.
Windownever implementedgrow_mode/set_grow_mode, so it inheritedView's fixed default andset_grow_modeon it was a silent no-op. The resize cascade reached each window, was told it was fixed, and left it at its old geometry: frames stayed at the old width and contents were clipped at the new screen edge.Windownow stores grow flags and defaults toGF_GROW_HI_X | GF_GROW_HI_Y, pinning the top-left and growing the bottom-right edge, which is what a window filling the desktop wants. Note this is deliberately not Borland's literalgfGrowAll: in this crate's cascade all four bits mean "translate by the size delta, keep the same size", which would slide a window away from the corner it was filling rather than stretch it. - A terminal that resizes at startup left the whole app a row too tall.
Warp reports the pre-alternate-screen size for a moment after the switch (one
row taller than the real alternate screen), and the matching SIGWINCH can
land before crossterm's event source exists, so it never arrives as a resize
event: the app stayed laid out for the wrong size for the rest of the
session, with the editor window's bottom frame and indicator falling on the
status-line row.
Application::idlenow re-checks the backend size and re-lays out if it disagrees with the cached one, which self-heals a resize that was never delivered. - The editor did not follow a window resize.
EditorWindowinheritedView's fixed grow mode, so when a terminal resized (Warp resizes right after startup) the window and its frame moved to the new geometry while the editor kept its old size: a too-tall editor overdrew the bottom frame, so the window looked one row taller than its client area and the indicator line disappeared.EditorWindownow carries a grow mode, defaulting toGF_GROW_HI_X | GF_GROW_HI_Yso it keeps filling the window interior, and re-clamps the cursor and scrollbars to the new size. EditWindow,LogWindowandHelpWindowwrap aWindowand delegatebounds/stateto it, but had not delegated the grow-mode accessors, so they carried the same bug. They now do.
Added¶
WindowBuilder::grow_modefor callers that want something other than the default.- Global block-edit mode.
Application::block_edit_mode,set_block_edit_modeandtoggle_block_edit_moderead and write a global flag (core::state) that decides whether an editor selection is rectangular.Applicationhandles the newCM_TOGGLE_BLOCK_MODEcommand, andMenuItem::flag(alsoMenuItemBuilder::checked) creates a checkable menu item whose check mark is re-queried on every draw.MenuBar::set_right_indicatorandStatusLine::set_right_indicatordraw a right-aligned marker (also re-queried on every draw) at the far end of those bars. Thepascal_ideexample gains an Edit menu with a "Block mode" flag item and shows a "▭ Block" marker at the right of the status line while the mode is on.
Behaviour change¶
-
Block selection no longer uses the Alt/Option modifier. Alt+arrows and Alt-drag no longer start a rectangular selection, because terminals disagree on whether they deliver Alt at all (macOS Terminal.app only does so with "Use Option as Meta key" off). Selections are always extended with Shift+arrows or a mouse drag; their shape now comes from the global block-edit mode above.
-
Any application that positions a
Windowitself and adds it to aDesktopwill now see that window move and resize when the terminal is resized, where previously it silently did not. Callwindow.set_grow_mode(0)to keep the old fixed behaviour.
2.2.1 - 2026-08-31¶
Added¶
- Rectangular (block) selection in the editor. Hold Alt/Option with the arrow keys, or Alt-drag with the mouse, to select a column block instead of a continuous stream. Block copy yields each row's column slice (joined by newlines); block delete removes the column band from every row and undoes in a single step. Stream selection (Shift+arrows / plain drag) is unchanged.
CM_SELECT_ALLis now a working command.EditorWindowandMemohandle it (focus-gated) in addition to the existing Ctrl+A, so an Edit-menu item or command dispatch selects all content of the focused window.
Changed¶
- Bold no longer brightens explicitly-chosen colors. The ANSI parser still
applies the classic bold-is-bright convention to the basic 16-color palette
(
\x1b[1;31m→ bright red), but a foreground set via 256-color (38;5;n) or truecolor (38;2;r;g;b) now keeps its exact hue when bold — only theBOLDstyle flag is set. This lets syntax highlighters bold keywords without shifting their color.
2.2.0 - 2026-08-31¶
Added¶
- Text styling attributes.
Attrnow carries aStylebitset alongside its foreground/background colors, supporting bold, dim, italic, underline, reverse (inverse video), and strikethrough. Apply them with composable builder methods —Attr::new(fg, bg).bold().italic()— or set several at once via.with_style(Style::ITALIC | Style::UNDERLINE). - Styles are emitted as real SGR escape codes by the renderer: on the live
terminal and SSH output (
Terminal::flush) and in ANSI screen dumps (ansi_dump). The ANSI parser now recognizes incoming style codes, and the help viewer renders**bold**/*italic*markdown segments with real terminal styles instead of color-only emphasis. - New
text_stylingexample (cargo run --example text_styling) printing a table of every style and several combinations.
Notes¶
- Fully backward compatible:
Attr::new(fg, bg)keeps its two-argument signature (style defaults to empty), so existing call sites are unaffected. - The classic Turbo Vision color byte still encodes colors only —
to_u8/from_u8preserve colors and drop style, by design. - PNG screenshots remain color-only; blink is intentionally not supported.
2.0.0 - 2026-07-02¶
Full code review against the kloczek/tvision C++ reference, with all critical (P0), high (P1), and medium (P2) findings fixed. See TO-DO.md for the itemized checklist.
Breaking¶
- Standard command IDs renumbered to Borland's values (
cmQuit=1,cmClose=4,cmZoom=5,cmResize=6,cmNext=7,cmPrev=8,cmCut/Copy/Paste/Undo=20-23,cmClear=24,cmTile=25,cmCascade=26,cmRecordHistory=60,cmGrabDefault/cmReleaseDefault=61/62,cmFileFocused/cmFileDoubleClicked=102/103). Demo file-menu commands moved to 300-305. Code that uses theCM_*constants is unaffected. KB_CTRL_F12corrected to the BIOS scan code0x8A00.- SSH server authentication is now deny-by-default: configure
SshServerConfig::auth_password_fn/auth_publickey_fn, or opt into the old accept-everything behavior explicitly withallow_anonymous(). Scroller::set_limitfollows Borland semantics: the maximum scroll offset is content size minus one page.- Saving an editor buffer appends a POSIX trailing newline for programmatic content and preserves the loaded file's CRLF/trailing-newline style.
Eventgained aninfo: u16field (BorlandinfoPtrequivalent) used by several new broadcasts.
Fixed¶
- UTF-8 safety: InputLine, editor search, Memo selection, ParamText, TerminalWidget, and SortedListBox no longer mix byte and character indices — multibyte text (e.g. pasting "é") previously panicked.
- Editor: Enter and line-join deletions are undoable; overwrite mode is a
single undo step;
replace_allno longer loops forever when the replacement contains the pattern;find_nextno longer skips adjacent matches; search matches Borland's non-wrapping behavior. - Event routing: clicks that hit no child are no longer delivered to the focused control; broadcasts reach every child; removing the focused child re-establishes focus; the focus chain skips disabled children and never strands focus.
- Windows:
SF_ACTIVEpropagates so inactive windows draw with the inactive palette; auto-close honorsvalid(cmClose); modal views are tracked by identity (not index);valid(endState)re-entry lets failing validators veto OK; frame close button tracks press+release; frame titles are centered; per-child grow modes (GF_GROW_*) with BorlandcalcBounds. - Dialogs/controls: Enter presses the focused button (grab-default semantics); disabled commands no longer fire from the status line or menu boxes; menu dropdown hit-testing uses real widths; buttons fire on release (cancellable by dragging off); radio buttons are mutually exclusive; checkboxes/radios respond to the mouse; ColorDialog returns the actual selection; SortedListBox has incremental type-to-search; menu item shortcuts (F2, ...) dispatch while the bar is closed.
- Validators: PictureValidator is a complete TPXPictureValidator port
(
#?&@!,;escapes,{}/[]groups,*Nrepetition,,alternatives) with auto-fill wired into InputLine typing; InputLine validators can veto dialog close. - File dialog: typed paths (e.g.
src/main.rs) return the file instead of being discarded; wildcard filtering is a real glob (*/?). - Palettes: restored three zeroed bytes in the app palette, remapped
CP_BLUE_DIALOGto a proper blue region, correctedCP_CLUSTER. - SSH/terminal:
poll_eventhonors its timeout (no more 100% CPU per session); dead backends stop the app instead of spinning; client window resizes reach the layout; the input parser survives partial X10 mouse sequences and caps unterminated CSI buffering;set_esc_timeoutworks. - Desktop: tile uses Borland's exact-fill algorithm; cascade extends all
windows to the corner; quit during a modal loop returns
cmQuit. - Parallel test runs no longer segfault (OS clipboard access serialized and skipped under tests) or flake on the global history manager.
Added¶
- History system wired end-to-end: the History button opens its popup,
dialog OK records the linked InputLine text (
cmRecordHistory), and the selected entry is copied back. Application::put_event(BorlandputEventpending-event slot).- Alt+1..9 window selection (
cmSelectWindowNum), window numbers drawn in the frame,Window::set_number. cmZoomdispatch with double-click-title zoom;cmResizekeyboard move/resize mode (arrows move, Shift+arrows resize, Enter/Esc).StatusLine::with_defs+update(): Borland TStatusDef item sets that switch with the help context (Application::set_help_context).- Editor word operations (Ctrl+arrows, Ctrl+Backspace/Del), Ins overwrite
toggle, opt-in
.bakbackups (set_backup_files). - InputLine select-all-on-focus, Shift+arrow selection, Ins overwrite mode.
Menu::find_hotkey,Validator::complete,Backend::as_any_mut,Desktop::top_view_id/child_by_id/remove_child_by_id,CM_SCROLLBAR_CHANGED,SshAuthPolicy.
[1.3.1] - 2026-06-07¶
Fixed¶
Group::remove_by_id()removed the matching entry from the parallelview_idsvector twice (once insideremove(), then again directly), which corrupted the view-id ↔ child alignment —child_by_id()could then miss an untouched view, and removing the last child panicked with an out-of-bounds index. It now delegates toremove(), which already keepschildrenandview_idsin lock-step.
[1.3.0] - 2026-06-07¶
Added¶
- Screen capture shortcuts — two consolidated global shortcuts, both capturing the whole screen:
- F12 → ASCII (ANSI-colored) dump to a timestamped
screen-YYYYMMDD-HHMMSS.ansfile (Application::dump_screen_ansi(), wrappingTerminal::dump_screen) - Ctrl+F12 → PNG screenshot to a timestamped
screenshot-YYYYMMDD-HHMMSS.pngfile (Application::take_screenshot()/CM_SCREENSHOT) - PNG Screenshots (
src/core/screenshot.rs) - New
Terminal::save_screenshot_png(path)renders the current screen buffer to a true-color PNG using the embedded Spleen 8x16 bitmap font (BSD-2-Clause; seefonts/Spleen-LICENSE) with crisp integer scaling derived from the terminal's reported font cell height; Turbo Vision box-drawing (single and double, with correct corner joinery), block, shade, arrow, and corner-resize (◢) glyphs are rendered procedurally so frames tile cleanly at any scale - New
Terminal::query_font_pixel_size()reports the current font cell size in pixels (falls back to 8x16 when the terminal does not report pixel dimensions) - Self-contained PNG encoder (no new dependencies): RGB, 8-bit, stored DEFLATE blocks with CRC-32 / Adler-32 checksums
- New
CM_SCREENSHOTcommand, also triggerable from a clickable status-line item or menu entry - Remote Input over TCP (
src/terminal/remote_input.rs) — disabled by default Terminal::enable_remote_input(port)/Application::enable_remote_input(port)start a127.0.0.1-only TCP listener that injects events into the event loop; also enabled without code changes via theTV_REMOTE_KEYSenvironment variable (port number)- Key lines contain whitespace-separated chords in human format (e.g.
CTRL+F12orCTRL+F12 ALT+X), parsed by the new publiccore::event::parse_key_chord(); mouse lines (CLICK x y,RCLICK x y,MCLICK x y) inject button-down/up at 0-indexed cell coordinates. Primarily a testing/automation aid (e.g. triggering Ctrl+F12 on terminals that do not forward that chord) - New
examples/screenshot.rsdemonstrating the screenshot feature and remote-input testing
Removed¶
- The unused Shift+F12 "active view" ASCII dump and its plumbing (
KB_SHIFT_F12, the backendon_screen_dump/on_view_dumpcallbacks, andTerminal::set_active_view_bounds/clear_active_view_bounds). Screen capture is now F12 (ASCII) and Ctrl+F12 (PNG); per-view dumps remain available programmatically viaView::dump_to_file/Terminal::dump_region.
[1.2.0] - 2026-05-04¶
Added¶
- LogWindow (#87): a scrollable log view with a
tracing::Subscriberintegration for capturing structured logs inside the TUI - Multi-editor support: window auto-close,
Desktop::contains_id(), andGroupview_idsynchronization on removal - Editor: trait-level edit operations, atomic paste,
can_undo()/can_redo(), and a cursor position accessor - StatusLine: context-sensitive hint text with truncation, and automatic greying-out of items whose
CommandIdis currently disabled - CI workflow for publishing to crates.io
Fixed¶
- Emoji/wide-character width handling in window titles
CM_REDRAWbroadcast on terminal resize for correct re-layout- ESC+letter (macOS Alt emulation) handling
- Bounds double-offset for nested views (#95) and dialog drag artifacts
- Mouse-drag scroll vanishing on negative delta
- MsgBox word-wraps long messages and left-aligns them
[1.1.1] - 2026-04-10¶
Added¶
- Public Window Palette Selection (#96)
Window::new_with_type(bounds, title, WindowPaletteType)constructor for creating Blue, Cyan, Gray, or Dialog windows directly-
WindowBuilder::palette_type()setter for fluent window construction with any palette type -
Palette-Integrated Syntax Highlighting (#98)
- Syntax highlighting colors now flow through the palette chain (Editor -> Window -> App) instead of being hardcoded to blue background
- Editors in Cyan or Gray windows automatically get syntax colors matching the window background
- Extended CP_APP_COLOR with 33 new entries (positions 64-96) for syntax colors across Blue, Cyan, and Gray backgrounds
- Extended CP_BLUE_WINDOW, CP_CYAN_WINDOW, CP_GRAY_WINDOW from 8 to 19 entries
- Extended CP_EDITOR from 2 to 13 entries (normal, selected, plus 11 syntax token types)
-
New
TokenType::palette_index()method for palette-based color resolution -
Desktop Bring-to-Front API (#103)
Desktop::bring_to_front(ViewId)to bring a specific window to the front of the Z-order-
Group::view_id_at(index)helper for ViewId lookup by index -
HelpWindow UX Improvements (#97)
- Single-click on hyperlinks now follows them (previously required double-click)
- Up/Down arrow keys cycle through visible hyperlinks with automatic scrolling
- Backspace (go_back) restores scroll position and selected hyperlink
- History entries now store full view state (topic, scroll offset, selected link)
Fixed¶
- HelpWindow Click-to-Focus (#97):
HelpWindownow delegatesoptions()andset_options()to the innerWindow, restoringOF_TOP_SELECTso Desktop click-to-focus works correctly - Group View ID Sync:
Group::bring_to_front()andsend_to_back()now keepview_idsin sync withchildrenafter z-order changes, fixing silent lookup bugs
[1.0.2] - 2025-12-15¶
Added¶
- ANSI Escape Sequence Parser (src/core/ansi.rs)
- New
AnsiParserfor parsing ANSI escape sequences from text - New
AnsiImagestruct for storing parsed ANSI art as colored cells - Supports basic 16-color (codes 30-37, 40-47)
- Supports bright colors (codes 90-97, 100-107)
- Supports 256-color palette (
\x1b[38;5;Nm/\x1b[48;5;Nm) - Supports true color RGB (
\x1b[38;2;R;G;Bm/\x1b[48;2;R;G;Bm) - Supports bold/bright attribute (
\x1b[1m) and reset (\x1b[0m) -
Includes comprehensive unit tests for all color modes
-
ANSI Background View (src/views/ansi_background.rs)
- New
AnsiBackgroundview for displaying ANSI art on desktop from_file()method to load ANSI art from text filesfrom_string()method to parse ANSI art from strings- Automatic centering support (horizontal and vertical)
AnsiBackgroundBuilderwith fluent API for construction- Works with existing
examples/logo.txttrue-color ANSI art
Changed¶
- Desktop Logo Example (examples/desktop_logo.rs)
- Now automatically loads ANSI art from
examples/logo.txtif available - Falls back to ASCII art when ANSI file is not found
- Added File menu with "Load ANSI File" and "Load ASCII Art" options
- Updated About dialog to mention ANSI support
[1.0.1] - 2025-12-15¶
Added¶
- F1 Help System for rust_editor (demo/rust_editor.rs)
- F1 key now opens the Help window with context-sensitive help
- Help file located at demo/help/rust_editor.md with comprehensive documentation
- Covers File Menu, Edit Menu, Search Menu, Window Menu operations
-
Includes keyboard shortcuts reference and navigation instructions
-
CyanWindow Owner Type for Palette Remapping (src/views/view.rs, src/views/window.rs)
- Added
OwnerType::CyanWindowenum variant for proper cyan window palette handling - Views inside cyan windows (like HelpWindow) now correctly use CP_CYAN_WINDOW colors
- Updated
map_color()to remap through cyan window palette -
Updated
Window::add()to set CyanWindow owner type for cyan palette windows -
Backspace Navigation in Help Window (src/views/help_window.rs)
- Backspace key now navigates back in help history (alternative to Alt+F1)
-
More intuitive navigation for users accustomed to browser-style back navigation
-
HelpWindow Modal State Support (src/views/help_window.rs, src/app/application.rs)
- Added
get_end_state()andset_end_state()to HelpWindow for proper modal execution - SF_MODAL flag now correctly set on HelpWindow in
show_help_topic() - Modal help windows properly block until closed
Fixed¶
- Help Window Content Positioning (src/views/help_window.rs)
- Fixed HelpViewer bounds calculation in
set_bounds()to use absolute coordinates -
Content no longer appears offset or cut off when help window is displayed
-
Help Text Cyan Background Color (src/core/palette.rs, src/views/button.rs)
- Help viewer now correctly displays with cyan background (classic Borland style)
- Fixed palette chain to properly remap through CP_CYAN_WINDOW
-
Added CyanWindow handling in button.rs for shadow color calculation
-
Exit Command During Modal Help Window (src/app/application.rs)
- Alt+X (quit) now works even when modal help window is open
- Added
self.runningcheck inexec_view()modal loop to respect application quit
Changed¶
- Help Text Foreground Color (src/core/palette.rs)
- Changed normal help text from light gray (0x37) to black (0x30) on cyan
- Improves readability on modern terminals where light gray on cyan has poor contrast
-
Comment added explaining deviation from Borland's original palette
-
CP_HELP_VIEWER Palette (src/core/palette.rs)
- Extended from 3 to 6 color entries for rich text support
- Indices now properly remap through CP_CYAN_WINDOW palette chain
- Supports normal text, links, selected links, bold, italic, and code styles
[0.10.4] - 2025-11-13¶
Fixed¶
- ESC + Letter Keyboard Handling for macOS (src/core/event.rs)
- ESC + letter sequences (within 500ms) now produce identical key codes to Alt + letter
- Fixes issue where ESC + letter would insert the letter into focused input fields instead of triggering shortcuts
- Example: ESC then 'L' now produces KB_ALT_L (0x2600), identical to Alt+L
- Prevents InputLine controls from capturing the letter character
- Menu shortcuts (ESC+F, ESC+E, etc.) continue to work correctly
Added¶
- Complete ALT+letter Key Code Support (src/core/event.rs)
- Added KB_ALT_A through KB_ALT_Z constants with proper PC keyboard scan codes
- Added
char_to_alt_code()helper function for letter-to-ALT-code mapping -
All 26 letters now have defined ALT key codes for consistent shortcut handling
-
Label Keyboard Shortcut Support (src/views/label.rs)
- Labels now handle keyboard shortcuts matching their
~X~hotkey markers - Added OF_POST_PROCESS flag to Label for three-phase event processing
- Added
get_hotkey()method to extract shortcut character from label text - Implemented
handle_event()to detect Alt+letter matches and focus linked controls - Example: Label with "~L~ast Name:" now focuses linked input when Alt+L (or ESC+L) is pressed
-
Matches Borland TLabel behavior for keyboard-driven form navigation
-
Group Focus Management (src/views/group.rs)
- Added
focus_by_view_id()method to focus child views by ViewId - Used by Label to transfer focus to linked InputLine controls
- Enhanced
set_focus_to()with can_focus() check for safety
Changed¶
- Code Cleanup
- Removed redundant KB_ESC_X checks from MenuBar (src/views/menu_bar.rs)
- Removed redundant KB_ESC_X checks from Application (src/app/application.rs)
- Updated imports to use new KB_ALT_S and KB_ALT_V constants
- Menu shortcuts now only check for KB_ALT_X instead of both KB_ALT_X and KB_ESC_X
Documentation¶
- Project Statistics Update
- Updated README.md tokei statistics: 110 files (up from 108), 32,838 lines (up from 31,264)
- Updated README.md test count: 199 tests (up from 198), 190 unit tests (up from 189)
- Updated version to 0.10.4
[0.10.1] - 2025-11-10¶
Added¶
- Runtime Palette Customization
- New
Application::set_palette()method for changing application palette at runtime - Automatically triggers redraw when palette actually changes
- Change detection avoids unnecessary redraws
- Simplifies theme switching API - one method call instead of two
- Example usage:
app.set_palette(Some(dark_palette))- redraw is automatic -
Thread-local storage for custom palettes via
palettes::set_custom_palette() -
Palette Themes Demo (examples/palette_themes_demo.rs - 214 lines)
- Interactive demonstration of 4 different color themes
- Default theme: Classic Borland Turbo Vision colors
- Dark theme: Dark backgrounds with bright text for low-light environments
- High-Contrast theme: Black/white for maximum visibility and accessibility
- Solarized theme: Earth tones inspired by the Solarized color scheme
- Shows how to create custom palettes with 63-byte color arrays
- Demonstrates automatic redraw on palette change
Fixed¶
- Dialog Command Handling (src/views/dialog.rs)
- Fixed: Dialogs now properly handle custom button commands
- Previously only CM_OK, CM_YES, CM_NO, CM_CANCEL closed dialogs
- Now all commands (including custom ones) call
end_modal() -
Enables theme switching buttons and other custom commands to work correctly
-
Syntax Highlighting Import (src/views/syntax.rs)
- Fixed unused import warning for TvColor
- Made TvColor import conditional with
#[cfg(test)] - Only used in test code, not production
Changed¶
- Documentation Updates
- README.md: Added "Runtime Customization" bullet to palette features
- README.md: New "Custom Palettes and Theming" section with code example
- README.md: Updated tokei statistics (108 files, 31,215 lines, 23,546 code)
- README.md: Updated test count (198 tests, up from 194)
- docs/PALETTE_SYSTEM.md: New "Runtime Palette Customization" section (137 lines)
- docs/PALETTE_SYSTEM.md: Documents
Application::set_palette()API with examples - docs/PALETTE_SYSTEM.md: Explains custom palette format (63 bytes, fg<<4|bg encoding)
- docs/PALETTE_SYSTEM.md: Palette layout reference and theme creation examples
Technical Details¶
Color Mapping Flow:
1. User calls app.set_palette(Some(palette))
2. Method compares new palette with current palette
3. If different, calls palettes::set_custom_palette(palette)
4. Sets needs_redraw flag to trigger full redraw
5. Next frame, all views remap colors through new palette
Implementation Benefits:
- Simple API: One method call instead of two (set + redraw)
- Efficient: Only redraws when palette actually changes
- Safe: No redundant redraws on same palette
- Automatic: Users don't need to remember needs_redraw()
Custom Palette Format:
- 63-byte array where each byte encodes: (foreground << 4) | background
- Color values: 0=Black, 1=Blue, 2=Green, 3=Cyan, 4=Red, 5=Magenta, 6=Brown, 7=LightGray, 8=DarkGray, 9=LightBlue, A=LightGreen, B=LightCyan, C=LightRed, D=LightMagenta, E=Yellow, F=White
- Layout: 1-8 Desktop, 9-15 Menu/StatusLine, 16-23 Cyan Window, 24-31 Gray Window, 32-63 Dialog/Controls
[0.10.0] - 2025-11-09¶
Fixed¶
- Palette Remapping System
- Fixed dialog control palette remapping to match Borland Turbo Vision behavior
- Labels now display correct white-on-grey instead of red-on-grey
- Menu selected items show black-on-green instead of white-on-grey
- Menu shortcuts display red-on-grey as in original Borland implementation
- Button shadows render correctly with proper foreground/background swap
- Added
owner_typefield to Button, Label, StaticText, and InputLine - All dialog controls now default to
OwnerType::Dialogfor proper palette remapping - Views with
OwnerType::None(MenuBar, StatusLine) use direct app palette - Views with
OwnerType::Dialogremap indices 1-31 through dialog palette
Added¶
- Palette Regression Tests
- Added 9 comprehensive palette regression tests in
tests/palette_regression_tests.rs - Tests verify Borland-accurate colors for Button, Label, StaticText, InputLine, ScrollBar, MenuBar, and Dialog
- Tests ensure color stability across changes
- All tests pass with visually correct colors
Changed¶
- Moved palette regression tests from
src/core/totests/directory for better organization - Updated
map_color()to respectOwnerTypefor context-aware palette remapping - Fixed CP_MENU_BAR palette to match Borland's original values
[2, 5, 3, 4] - Removed ScrollBar's custom
map_color()implementation (now uses default View trait implementation) - Replaced magic palette indices in StatusLine with named constants
Removed¶
- Deleted obsolete example files:
dialog_example.rs,history.rs,key_test.rs,menu_status_data.rs,quick_start.rs,status_line_demo.rs - Moved
menu_status_data.rstotests/directory
[0.9.2] - 2025-11-08¶
Added¶
- Semi-Transparent Shadows
- Shadows now darken underlying content instead of drawing opaque backgrounds
- Matches Borland Turbo Vision's original VGA-based shadow behavior
- Added
TvColor::to_rgb()for RGB component extraction - Added
TvColor::from_rgb()for closest color matching via Euclidean distance - Added
Attr::darken(factor)method for color darkening (default 50%) - Added
Terminal::read_cell()to read existing buffer content - Completely rewrote
draw_shadow()to use read-modify-write pattern - Preserves underlying characters while darkening colors
- Cross-platform implementation using RGB blending instead of VGA bit manipulation
Changed¶
- Shadow rendering now reads terminal buffer before drawing
- Shadow cells show darkened version of underlying content (semi-transparent effect)
- Updated Rust-vs-Borland comparison document with new shadow implementation details
Technical Details¶
- Darkening uses 50% factor (configurable constant)
- Color matching finds nearest color in 16-color palette using RGB distance
- Falls back to default shadow color for out-of-bounds positions
- No performance impact - shadow rendering is still O(n) where n = shadow size
[0.3.0] - 2025-11-06¶
Added¶
- Real-Time Input Validation System
- Complete birthdate validation in biorhythm example with three validation layers
- RangeValidator for field-level input filtering
- Cross-field date validation (leap years, month lengths, future dates)
- Command set integration for dynamic button enable/disable
- Custom event loop pattern for real-time validation feedback
-
Validation runs after every keystroke with immediate UI updates
-
Command Set Broadcasting Pattern
CM_COMMAND_SET_CHANGEDbroadcast system for global command state changes- Buttons automatically update disabled state via command set queries
- Declarative command management instead of direct widget manipulation
-
Matches Borland Turbo Vision's command enable/disable architecture
-
Enhanced Biorhythm Calculator
- Startup dialog for birthdate input (no default random chart)
- Clean exit on cancel (no orphaned windows)
- Date prefill across dialog invocations
- Centered windows and dialogs accounting for shadow size
- Three-layer validation: RangeValidator, complete date check, command updates
-
Enter key support via event reprocessing
-
Dialog Event Reprocessing
- Fixed Enter key handling in dialogs with default buttons
- Event conversion (KB_ENTER → CM_OK) now properly reprocessed
- Matches Borland's
putEvent()pattern for converted events -
Ensures modal dialogs close correctly when Enter pressed in InputLine
-
Window Centering with Shadow Calculations
- Proper centering accounting for shadow size (2 cols width, 1 row height)
- Menu bar and status line offset calculations for main windows
- Dialog centering for full-screen placement
-
Visual balance maintained across different terminal sizes
-
Comprehensive Documentation
docs/BIORHYTHM-TUTORIAL.md- Narrative blog-style tutorial (822 lines)- Real-world examples and personal discovery stories
- Common patterns and gotchas with DO/DON'T comparisons
- Manual test cases for validation scenarios
- Quick reference section for key patterns
Changed¶
- Biorhythm Example Architecture
- Moved from random initial chart to dialog-first startup flow
- Window creation deferred until after successful date validation
- Custom event loop replaces standard
dialog.execute()for validation needs - Command set pattern used instead of direct button manipulation
Fixed¶
- Enter Key in Modal Dialogs
- Dialog event loop now reprocesses converted command events
- KB_ENTER → CM_OK conversion properly triggers
end_modal() - Matches Borland's event re-queuing behavior
Removed¶
- Unused Downcasting Infrastructure
- Removed
as_any_mut()from View trait (not needed with command set pattern) - Removed Button's
as_any_mut()implementation - Removed
std::any::Anyimports - Cleaner API without unnecessary complexity
Technical Details¶
Real-Time Validation Architecture:
The validation system uses three coordinated layers: 1. RangeValidator - Character-level filtering during typing (1-31 for day, 1-12 for month) 2. Complete Date Validation - Cross-field checks (Feb 31, leap years, future dates) 3. Command Set Updates - Global command enable/disable with broadcast propagation
The custom event loop pattern enables validation after every event:
loop {
draw_and_flush();
if let Some(event) = poll_event() {
dialog.handle_event(&mut event);
if event.what == EventType::Command {
dialog.handle_event(&mut event); // Reprocess converted events
}
validate_and_update_command_state(); // After every event
broadcast_if_changed();
}
if dialog.get_end_state() != 0 { break; }
}
Command Set Pattern vs Direct Manipulation:
Instead of fragile child index access:
// OLD: Direct manipulation (removed)
dialog.child_at_mut(8).downcast_mut::<Button>().set_disabled(true);
Use declarative command state:
Benefits: Scales to multiple buttons, no fragile indices, separates validation from UI structure.
Shadow-Aware Centering:
Windows have shadows (2 cols right, 1 row bottom) that must be included in centering:
let x = (screen_width - (window_width + 2)) / 2; // +2 for shadow
let y = 1 + ((screen_height - 2) - (window_height + 1)) / 2; // +1 for shadow, 1+ for menu
This ensures visual balance - ignoring shadows makes windows appear off-center.
Event Reprocessing Pattern:
When dialogs convert keyboard events to commands, the converted event must be reprocessed:
dialog.handle_event(&mut event); // First pass: KB_ENTER → CM_OK
if event.what == EventType::Command {
dialog.handle_event(&mut event); // Second pass: Process CM_OK
}
Without this, Enter key appears to do nothing in modal dialogs.
[0.2.11] - 2025-11-04¶
Fixed¶
- StatusLine Drawing and Hit Detection - Fixed highlighting extending into separator
- StatusLine now draws leading and trailing spaces around text (matches Borland tstatusl.cc:143-145)
- Selection highlight includes spaces before and after text, but not the separator
- Separator "│ " always drawn in normal color, never highlighted
- Hit detection properly includes leading space and text with trailing space
- Matches Borland TStatusLine drawing and hit detection behavior exactly
Technical Details¶
The StatusLine highlighting bug was caused by not matching Borland's exact drawing pattern. In Borland TStatusLine::drawSelect (tstatusl.cc:143-145), each status item is drawn as: 1. Space before text (in selection color when selected) 2. The text itself (with proper shortcut highlighting) 3. Space after text (in selection color when selected) 4. Separator (always in normal color)
Previously, we were drawing the text directly without surrounding spaces, and the separator was being drawn with the selection color. This caused the selection highlight to extend into the separator. The fix now exactly replicates Borland's drawing sequence.
[0.2.10] - 2025-11-04¶
Added¶
- FileEditor Component (src/views/file_editor.rs)
- Proper implementation of Borland's TFileEditor pattern
- File name tracking and modified flag management
valid(app, command)method for save prompts on close- Load/Save/SaveAs operations with proper file management
- Wraps Editor component with file-specific functionality
-
Ready for future proper architecture implementation
-
Window and Desktop Helper Methods
Window::get_editor_text_if_present()- Extract current editor textWindow::is_editor_modified()- Check if editor has unsaved changesWindow::clear_editor_modified()- Clear modified flag after saveDesktop::get_first_window_as_window()- Get immutable window referenceDesktop::get_first_window_as_window_mut()- Get mutable window reference-
Pragmatic unsafe downcasting helpers for editor demo use case
-
Standard Library Dialog Functions (src/views/msgbox.rs)
message_box_ok()- Simple information message with OK buttonmessage_box_error()- Error message with OK buttonmessage_box_warning()- Warning message with OK buttonconfirmation_box()- Yes/No/Cancel confirmation dialogconfirmation_box_yes_no()- Yes/No confirmation dialogconfirmation_box_ok_cancel()- OK/Cancel confirmation dialogsearch_box()- Search dialog returning Optionsearch_replace_box()- Find/replace dialog returning Option<(String, String)>goto_line_box()- Go to line dialog returning Optionwith validation - Convenience wrappers around existing
message_box()function - Eliminates need for manual dialog construction in common cases
-
Example:
examples/dialogs_demo.rsdemonstrating all dialog types -
Rust Text Editor Demo (demo/rust_editor.rs)
- Full-featured text editor application with Rust syntax highlighting
- File operations: New, Open, Save, Save As (using FileDialog)
- Menu bar: File, Edit, Tools menus with keyboard shortcuts
- Status line: F10 Menu, Ctrl+S Save, Ctrl+F Find
- Close menu item (Ctrl+W) for closing editor window
- Close button (■) in window frame
- Smart dirty flag tracking - only prompts when actually modified
- Save prompts before destructive operations (Close, New, Quit)
- Actual save on "Yes" in confirmation dialog
- Search and Replace dialogs using standard library functions (search_box, search_replace_box, goto_line_box)
- Rust analyzer integration (placeholder for future LSP integration)
- About dialog showing "Lonbard Turbo Rust" on startup
- Empty desktop on startup - user must choose File → New or File → Open
- Comprehensive demonstration of Editor, FileDialog, and standard dialogs
- Documentation: demo/README.md with features, shortcuts, and usage guide
Changed¶
- Rust Editor Cleanup
- Replaced local dialog implementations with standard library functions
- Removed 120+ lines of duplicate dialog code (show_search_dialog, show_replace_dialog, show_goto_line_dialog)
- Simplified show_about_dialog() to use message_box_ok()
-
Removed unused imports (Button, Dialog, InputLine, Label, Rc, RefCell)
-
msgbox.rs Dialog Layout
- Moved dialog text and buttons one row higher for better appearance
-
Improved visual spacing in confirmation dialogs
-
msgbox.rs Command Constants
- Removed duplicate CM_YES and CM_NO definitions
- Now imports CM_YES and CM_NO from core::command module
-
Maintains consistency across entire framework
-
Window CM_CLOSE Event Handling
- Non-modal windows no longer auto-close on CM_CLOSE
- CM_CLOSE event propagates to application level for validation
- Matches Borland's TWindow::close() → valid(cmClose) pattern
-
Applications can intercept and validate before allowing close
-
Rust Editor Save Operations
- Fixed critical bug: now saves actual editor content, not stale state
- Simplified EditorState to only track filename
- Save operations retrieve current text from editor window
- Clear modified flag after successful save
- CM_SAVE no longer recreates window (performance optimization)
- CMD_SAVE_AS only recreates window on success (to update title)
Fixed¶
- StatusLine Event Handling - Critical bug where StatusLine was completely non-functional
- Changed from OF_POST_PROCESS to OF_PRE_PROCESS to match Borland behavior (tstatusl.cc:33)
- Added status_line.handle_event() call in rust_editor event loop
- StatusLine now properly handles mouse clicks and keyboard shortcuts
- Items now generate commands when clicked or shortcuts pressed
- StatusLine Hit Detection - Fixed "one off to the right" highlighting issue
- First item now includes leading space (position 0) in hit area
- All items include first separator space after text (matches Borland's inc=2)
- Subsequent items don't include previous separator in their hit area
- Hit detection now matches Borland TStatusLine behavior (tstatusl.cc:204)
- Editor Content Not Saved - Critical bug where saves would write initial content instead of current edits
- Close Button Not Prompting - Frame's close button now properly triggers save confirmation
- Always Prompting on Close - Now only prompts when editor is actually modified
- Save on Confirmation - "Yes" button in save dialog now actually saves the file
Technical Details¶
The FileEditor component provides the proper Borland TFileEditor pattern with encapsulated file management and validation. The Window/Desktop helpers enable pragmatic downcasting for the editor demo while maintaining type safety. The rust_editor now properly synchronizes editor content with file operations.
StatusLine Event Processing: The StatusLine bug was caused by using OF_POST_PROCESS instead of OF_PRE_PROCESS. In Borland's TStatusLine (tstatusl.cc:33), the status line sets options |= ofPreProcess to ensure it gets first chance at events. This allows it to intercept function keys and mouse clicks before they reach the focused view. The fix also required adding the status_line.handle_event() call in the event loop's pre-process phase, matching Borland's TGroup::handleEvent() three-phase architecture.
The standard library dialog functions provide a cleaner API for common dialog patterns. Instead of manually constructing Dialog with StaticText and Button components, applications can now use simple function calls:
Before (47 lines):
let mut dialog = Dialog::new(bounds, "Save Changes?");
let text = StaticText::new_centered(...);
dialog.add(Box::new(text));
let yes_button = Button::new(..., CM_YES, true);
dialog.add(Box::new(yes_button));
// ... more buttons ...
let result = dialog.execute(app);
After (1 line):
The new input dialog functions follow the same simple pattern:
// Search dialog
if let Some(search_text) = search_box(&mut app, "Search") {
// User entered search text
}
// Find and replace dialog
if let Some((find, replace)) = search_replace_box(&mut app, "Replace") {
// User entered both find and replace text
}
// Go to line dialog with validation
if let Some(line_num) = goto_line_box(&mut app, "Go to Line") {
// User entered valid line number
}
The rust_editor demo showcases a complete application built with Turbo Vision for Rust, demonstrating best practices for: - Application structure with event loops - Desktop/Window management - Menu systems with cascading submenus - File I/O with FileDialog integration - Modal dialog patterns - Syntax highlighting with Editor component - Status line with keyboard shortcuts
This editor serves as a reference implementation for building complete TUI applications.
[0.2.9] - 2025-11-04¶
Fixed¶
- MenuBox Mouse Interaction (CRITICAL BUG FIX - Borland Compatibility)
- Root Cause: MenuBox executed commands on MouseDown instead of MouseUp, inconsistent with Borland Turbo Vision
- Impact: Menu items executed before mouse was fully released, preventing proper drag-selection
- Fix: Following Borland tmenuvie.cc:215-222, MouseDown now only tracks selection, MouseUp executes commands
-
Result: Menu behavior now matches original Turbo Vision exactly
-
MenuBox ESC/ESC ESC Handling
- Both KB_ESC and KB_ESC_ESC now properly close popup menus
-
Returns command 0 to signal cancellation matching Borland behavior (tmenuvie.cc:264-268)
-
Submenu Auto-Popup Removed (Borland Compatibility)
- Issue: Submenus were appearing automatically on hover or right arrow navigation
- Fix: Following Borland tmenuvie.cc:333-349, submenus now only show on explicit action:
- Press Enter on submenu item
- Click (MouseUp) on submenu item
- KB_RIGHT now only navigates to next top-level menu, doesn't open submenus
-
Matches original Turbo Vision behavior perfectly
-
Validator Demo Dialog Height
- Increased dialog height from 30 to 34 lines to properly display all fields and buttons
Added¶
- MenuBar Cascading Submenu Support
- Added
show_cascading_submenu()method to display nested submenus - Added
check_cascading_submenu()public method for event loop integration - MenuBox positioned to right of parent dropdown menu
- Proper keyboard (Enter) and mouse (Click) activation
-
Full support for multi-level menu hierarchies
-
Extended Menu Example (examples/menu.rs)
- File menu now includes Recent Files submenu (3 sample files, Clear Recent option)
- Edit menu added with Cut/Copy/Paste and Preferences submenu
- Preferences submenu contains General, Appearance, and Keyboard Shortcuts
- Right-click popup menu on desktop with New File, Open File, Properties
- Comprehensive demonstration of all menu features
-
Status line shows "Right-Click Popup" hint
-
MenuBar MouseUp Event Handling
- Added proper MouseUp event handling matching Borland behavior
- Executes commands only when mouse released on selected item
- Handles submenu activation on click-release
Changed¶
- Validator Demo Unified
- Removed initial menu selection dialog
- All validators (Filter, Range, Picture) now shown in single comprehensive dialog
- Organized into clear sections with dynamic layout
- Filter & Range Validators section with 4 different validator types
- Picture Mask Validators section with phone, date, and product code examples
Technical Details¶
- Borland Compatibility: All menu changes verified against local-only/borland-tvision source code
- Files Modified:
- src/views/menu_box.rs (mouse handling, ESC handling)
- src/views/menu_bar.rs (cascading menus, MouseUp support, removed auto-popup)
- examples/menu.rs (extended with submenus and popup menu)
- examples/validator_demo.rs (unified dialog, removed menu)
[0.2.8] - 2025-11-03¶
Fixed¶
- Keyboard Modifiers Lost in Event System (CRITICAL BUG FIX)
- Root Cause:
Terminal::poll_event()was creating Event withEvent::keyboard(key_code)which lost modifiers from crossterm's KeyEvent - Impact: ALL keyboard modifiers (Shift, Ctrl, Alt) were being stripped, making Shift+Arrow selection completely non-functional
- Discovery: Through debug testing, found crossterm correctly sent SHIFT but Editor received KeyModifiers(0x0)
- Fix: Changed Terminal to preserve
key.modifierswhen creating Event structure -
Result: Shift+Arrow keys now work perfectly with visible cyan selection highlighting
-
Editor Selection Visibility
- Added
is_position_selected()helper method to check if character is in selection - Modified
draw()to apply EDITOR_SELECTED color (black on cyan) to selected text - Selection highlighting works with both plain text and syntax-highlighted code
-
Multi-line selections fully supported
-
Window ESC ESC Handling
- Window now handles both KB_ESC and KB_ESC_ESC to close modal windows
- Matches expected Turbo Vision behavior for double-ESC
Added¶
- examples/key_test.rs - Diagnostic tool to test keyboard input directly from crossterm
- Shows raw KeyCode and KeyModifiers for debugging
- Useful for verifying terminal keyboard behavior
Changed¶
- Test Suite: 178 tests (all passing)
- Code Size: 16,030 lines total, 12,239 lines of code
[0.2.7] - 2025-11-03¶
Fixed¶
- Editor Text Selection with Shift+Arrow Keys (CRITICAL BUG FIX)
- Root Cause: Event structure didn't track keyboard modifiers (Shift, Ctrl, Alt). Editor had hardcoded
shift_pressed = falsewith TODO comment - Impact: Shift+Arrow keys, Shift+Home, Shift+End didn't create text selections
- Fix: Added
key_modifiersfield to Event structure, updated Editor to check for SHIFT modifier -
Features Now Working:
- Shift+Arrow keys create text selection
- Shift+Home selects from cursor to start of line
- Shift+End selects from cursor to end of line
- Shift+PgUp/PgDn select full pages
- Moving without Shift clears selection (expected behavior)
-
Button Broadcast Handling (CRITICAL BUG FIX)
- Root Cause: Disabled buttons were checking disabled state before processing broadcasts, causing them to return early and never receive CM_COMMAND_SET_CHANGED broadcasts
- Impact: Buttons that started disabled (e.g., Cut, Copy, Paste when clipboard empty) would stay disabled forever, breaking the command set system
- Fix: Moved broadcast handling to the top of
Button::handle_event(), before disabled check - Verification: Confirmed implementation matches Borland's original behavior (tbutton.cc:196, tview.cc:486, tbutton.cc:255-262)
- Documentation: Added detailed comments referencing Borland source code line numbers
-
Fixed command_set_demo - Enable/Disable buttons now properly update button states
-
Dialog Event Handling
- Fixed Dialog to accept ANY command as end-modal signal, not just CM_OK/CM_CANCEL/CM_YES/CM_NO
- Matches Borland behavior where any command reaching the dialog ends the modal loop
-
Fixed editor_demo and validator_demo menu dialogs with custom command IDs
-
Editor Demo Modal Windows
- Wrapped all editor demos in modal Windows so they can be used interactively
- Added ESC key handling to Window for modal windows (calls end_modal on ESC press)
- Fixed editor_demo to use Window with SF_MODAL flag for all four demo modes
Added¶
- Regression Tests for Button Broadcasts (7 new tests)
- test_disabled_button_receives_broadcast_and_becomes_enabled (main regression test)
- test_enabled_button_receives_broadcast_and_becomes_disabled
- test_button_creation_with_disabled_command
- test_button_creation_with_enabled_command
- test_disabled_button_ignores_keyboard_events
- test_disabled_button_ignores_mouse_clicks
- test_broadcast_does_not_clear_event
Changed¶
- Test Suite: 178 tests (was 171) - all passing
- Code Size: 16,030 lines total, 12,239 lines of code (was 15,845 / 12,134)
[0.2.6] - 2025-11-03¶
Added¶
- Syntax Highlighting System (~450 lines, 7 tests)
- SyntaxHighlighter trait (src/views/syntax.rs)
- Extensible architecture for language-specific highlighting
- Token-based coloring system (Keywords, Strings, Comments, Numbers, etc.)
- Line-by-line highlighting with efficient token generation
- Methods:
language(),highlight_line(), multi-line context support
- TokenType enum - 11 token types with default color mappings
- Keywords (Yellow), Strings (LightRed), Comments (LightCyan)
- Numbers (LightMagenta), Operators (White), Types (LightGreen)
- Functions (Cyan), Preprocessor (LightCyan), etc.
- RustHighlighter - Built-in Rust syntax highlighting
- Recognizes Rust keywords (fn, let, if, for, match, etc.)
- String and character literals with escape sequences
- Line comments (//) and block comments (/ /)
- Numeric literals (decimal, hex, float)
- Type names (i32, String, custom types)
- Operators and special characters
- PlainTextHighlighter - No-op highlighter for plain text
-
Editor Integration
set_highlighter()- Attach syntax highlighter to Editorclear_highlighter()- Remove highlightinghas_highlighter()- Check if highlighting is enabled- Automatic per-token color rendering in draw method
- Preserves all existing Editor functionality (search/replace, undo/redo, etc.)
-
TPXPictureValidator (Picture Mask Validator) (~360 lines, 11 tests)
- PictureValidator (src/views/picture_validator.rs - 255 lines, 8 tests)
- Validates and formats input according to picture masks
- Matches Borland's TPXPictureValidator from validate.h
- Mask characters:
#- Digit (0-9)@- Alpha (A-Z, a-z)!- Any character*- Optional section marker- Literals - Must match exactly (e.g.,
/,-,(,)) - Methods:
new(),format(),set_auto_format() - Auto-formatting mode inserts literals automatically as user types
- Example masks:
"(###) ###-####"- Phone number: (555) 123-4567"##/##/####"- Date: 12/25/2023"@@@@-####"- Product code: ABCD-1234- Implements Validator trait for InputLine integration
- Helper function:
picture_validator()- Creates ValidatorRef
Examples¶
- editor_demo.rs - Comprehensive editor demonstration (290 lines)
- Menu-driven interface with 4 demonstrations:
- Basic editing (undo/redo/clipboard operations)
- Search and replace functionality
- Syntax highlighting (Rust code with colored tokens)
- File I/O operations (load/save)
- Consolidates previous examples: file_editor.rs, full_editor.rs, syntax_highlighting.rs
- Shows all Editor features in one interactive demo
- validator_demo.rs - All validator types demonstration (320 lines)
- Menu-driven interface with 2 demonstrations:
- FilterValidator and RangeValidator (character filtering, numeric ranges, hex numbers)
- PictureValidator (phone numbers, dates, product codes with format masks)
- Consolidates previous examples: validator_demo.rs, picture_validator.rs
- Shows all validation patterns with interactive examples
Changed¶
- Examples reorganization - Reduced from 19 to 16 examples by consolidation
- Removed: file_editor.rs, full_editor.rs, syntax_highlighting.rs (→ editor_demo.rs)
- Removed: picture_validator.rs (→ validator_demo.rs)
- Updated examples/README.md with new structure and descriptions
Technical Details¶
Syntax Highlighting implements a token-based coloring system that works efficiently with the Editor's line-by-line rendering. Each line is parsed into tokens (keyword, string, comment, etc.) with start/end positions. The Editor's draw method iterates through tokens and applies colors accordingly. The system is extensible - new languages can be added by implementing the SyntaxHighlighter trait.
Design Patterns: - Hook-based architecture for language extensions - Token type abstraction for color mapping customization - Line-by-line processing for efficiency - Optional multi-line state tracking for block comments - Integrates seamlessly with existing Editor features
TPXPictureValidator provides input formatting and validation using Borland's picture mask pattern. Unlike character filtering (FilterValidator) or range validation (RangeValidator), picture masks define the exact format of input including literal characters. The validator can auto-format input by inserting literals (like parentheses, slashes, dashes) as the user types, or validate completed input against the mask pattern.
Design Patterns:
- Matches Borland's TPXPictureValidator architecture
- Integrates with InputLine via Validator trait
- Supports both auto-format and validation-only modes
- Optional sections with * marker (partially implemented)
- Real-time validation during typing
Reference: Borland Turbo Vision tvalidat.cc, validate.h (picture validators)
Test Coverage¶
- Syntax Highlighting: 7 new tests
- Token type colors, plain text, Rust keywords, strings, comments, numbers, types
- Picture Validator: 11 new tests
- Phone mask, date mask, format functions, alpha mask, optional sections, partial input
- Total Tests: 171 tests passing (up from 154)
[0.2.5] - 2025-11-03¶
Added¶
- Help System (Phase 9 - 867 lines, 22 tests)
- HelpFile (src/views/help_file.rs - 302 lines, 7 tests)
- Parses markdown files into help topics with # Title {#topic-id} format
- Cross-reference support via Text markdown links
- Methods:
get_topic(),get_default_topic(),get_topic_ids(),reload() - Human-readable format replacing Borland's binary TPH files
- HelpViewer (src/views/help_viewer.rs - 286 lines, 4 tests)
- Displays help topic content with scrolling support
- Keyboard navigation: Up/Down, PgUp/PgDn, Home/End
- Optional vertical scrollbar for long topics
- Focus-aware coloring (HELP_NORMAL, HELP_FOCUSED)
- Uses DrawBuffer for efficient rendering
- HelpWindow (src/views/help_window.rs - 157 lines, 4 tests)
- Modal help window wrapper around HelpViewer
- Methods:
show_topic(),show_default_topic(),execute() - ESC key closes help window
- Delegates to Window for frame and modal behavior
- HelpContext (src/views/help_context.rs - 122 lines, 7 tests)
- Maps context IDs (u16) to help topic IDs (String)
- Methods:
register(),get_topic(),has_context(),unregister() - Foundation for F1 context-sensitive help support
- Example: examples/help_system.rs demonstrating help topics and navigation
-
Sample help file: examples/help.md with 6 topics and cross-references
-
Color Palette:
HELP_NORMAL: Black on LightGray for unfocused help textHELP_FOCUSED: Black on White for focused help text
Technical Details¶
The Help System implements Borland's context-sensitive help architecture using modern markdown format instead of proprietary binary TPH files. Key advantages:
Markdown Format: - Human-readable and easy to author - Version control friendly (plain text diffs) - No special tools required for editing - Cross-platform compatible - Can be generated from other documentation
Architecture: - HelpFile parses markdown on load, building a HashMap of topics - Topics identified by {#topic-id} in heading: # Welcome {#welcome} - Cross-references via standard markdown links: See also - HelpViewer provides scrollable display with keyboard navigation - HelpWindow wraps viewer in a modal window for display - HelpContext enables F1-style context-sensitive help
Design Patterns:
- Matches Borland's THelpFile, THelpViewer, THelpWindow patterns
- Uses Rc
Reference: Borland Turbo Vision help.h and help system architecture
[0.2.3] - 2025-11-03¶
Added¶
- TEditWindow (src/views/edit_window.rs - 169 lines, 3 tests)
- Window wrapper around Editor for ready-to-use editor windows
- Delegates file operations:
load_file(),save_file(),save_as(),get_filename() - Provides editor access methods:
editor(),editor_mut(),is_modified() - Automatically adjusts editor bounds when window is resized
- Implements View trait with proper event routing to both Window and Editor
-
Matches Borland's TEditWindow pattern from teditor.h
-
TLookupValidator (src/views/lookup_validator.rs - 255 lines, 8 tests)
- Validates input against a list of valid values
- Supports case-sensitive mode via
new()and case-insensitive vianew_case_insensitive() - Helper methods:
add_value(),remove_value(),contains(),set_case_sensitive() - Implements Validator trait for InputLine integration
- Allows all characters during typing, validates on completion
-
Matches Borland's TLookupValidator pattern from validate.h
-
OS Clipboard Integration (src/core/clipboard.rs - enhanced with arboard 3.3)
- Added system clipboard integration via arboard crate
- Fallback strategy: attempts OS clipboard first, falls back to in-memory
- Cross-platform support (macOS, Linux, Windows)
- Functions:
set_clipboard(),get_clipboard(),has_clipboard_content(),clear_clipboard() - Editor can now copy/paste to/from system clipboard (Ctrl+C, Ctrl+X, Ctrl+V)
- Graceful degradation on platforms without clipboard support
Changed¶
- Documentation: Updated MISSING_FEATURES.md progress tracking
- Marked TFileCollection and TDirCollection as obsolete (use Vec
) - Updated summary: 29 missing components (down from 35), 648 hours remaining
- HIGH Priority: COMPLETE (0 hours remaining)
- Added Phase 7+ improvements section documenting recent work
-
Updated statistics: 134 tests passing (up from 126)
-
Cargo.toml: Added arboard 3.3 dependency for OS clipboard support
Technical Details¶
TEditWindow provides a complete editor window solution by composing a Window with an Editor. It matches Borland's TEditWindow pattern where the editor fills the window interior and the window provides the frame and title bar. The implementation properly handles View trait delegation, routing draw and event calls to both the Window (for frame) and Editor (for content).
TLookupValidator implements Borland's validation pattern for restricting input to predefined values. Unlike FilterValidator (character-by-character) or RangeValidator (numeric), LookupValidator validates the complete string against a list. This is useful for dropdowns, enum values, or any constrained input set.
OS Clipboard integration uses the arboard crate to access the system clipboard across platforms. The fallback strategy ensures the application works even when OS clipboard access fails, maintaining the in-memory clipboard as a reliable backup.
Reference: Borland's TEditWindow (teditor.h), TLookupValidator (validate.h), and clipboard integration patterns.
[0.2.2] - 2025-11-03¶
Fixed¶
- Editor UTF-8 Support: Critical bug fixes for proper UTF-8 character handling
- Fixed crash when pressing DELETE/BACKSPACE on multi-byte UTF-8 characters
- Added
char_to_byte_idx()helper to convert character positions to byte indices - Fixed
delete_char(),backspace(),insert_char()to use byte indices for string operations - Fixed
apply_action()undo/redo to handle UTF-8 correctly - Fixed
clamp_cursor()to use character count instead of byte length - Fixed
get_selection_text()to convert character positions to byte indices - Fixed
delete_selection_internal()string slicing for UTF-8 - Fixed
insert_text_internal()to use byte indices forinsert_str() - Fixed
insert_newline()string slicing to use byte indices - Fixed
select_all()to count characters not bytes - Fixed
max_line_length()to count characters for scrollbar calculations - Fixed find operations to count characters for cursor positioning
- Fixed KB_END key handler to use character count
-
Added safety checks to delete operations
-
Editor Cursor Rendering: Fixed two-cursor display bug
- Fixed
update_cursor()to useget_content_area()instead ofbounds - Cursor now correctly positioned when editor has scrollbars and indicator
-
Previously showed two cursors: one at correct position, one offset by indicator height
-
ScrollBar: Fixed division by zero crash
- Added validation in
set_params()to ensuremax_val >= min_val - Added safety check in
get_pos()to handlerange <= 0orsize <= 0 - Prevents crash when content becomes smaller than viewport
- Prevents crash from invalid scrollbar parameters
Added¶
- full_editor example: Comprehensive editor demonstration with search/replace
- Shows editor with scrollbars, indicator, and sample text for testing
- Sample text includes patterns for testing case-sensitive/whole-word search
-
Added panic logging to capture crashes with full backtrace to debug log
-
editor_test example: Minimal editor test for debugging
Technical Details¶
The Editor was incorrectly mixing character indices (used for cursor position tracking) with byte indices (required by Rust's String::remove() and String::insert() methods). In UTF-8 encoding:
- ASCII characters are 1 byte each
- Many Unicode characters (accented letters, emojis, CJK) are 2-4 bytes each
When the editor tried to delete or insert at position cursor.x (a character index) using String::remove(cursor.x) (which expects a byte index), it would panic with "byte index is not a char boundary" on any multi-byte character.
The fix adds proper character-to-byte index conversion throughout the editor, ensuring all string manipulation uses byte indices while cursor tracking continues to use character positions.
The two-cursor bug occurred because update_cursor() used self.bounds while draw() used get_content_area(). When an indicator is added (via with_scrollbars_and_indicator()), the content area starts 1 row below the bounds, causing the terminal cursor to be positioned incorrectly.
The scrollbar division by zero occurred when max_val < min_val, making (max_val - min_val + 1) <= 0. This could happen when content shrinks below viewport size or parameters are set incorrectly.
[0.2.1] - 2025-11-03¶
Added¶
- Input Validators: Comprehensive input validation system matching Borland's TValidator architecture
- New
Validatortrait withis_valid(),is_valid_input(),error(), andvalid()methods FilterValidator: Validates input against allowed character set (e.g., digits only)RangeValidator: Validates numeric input within min/max range- Support for decimal, hexadecimal (0x prefix), and octal (0 prefix) number formats
- Real-time validation: invalid characters rejected as user types
- Final validation: check complete input before accepting
ValidatorReftype alias:Rc<RefCell<dyn Validator>>for shared validator references
Changed¶
- InputLine: Enhanced with validator support
- Added
with_validator()constructor to create InputLine with validator - Added
set_validator()method to attach validator after construction - Added
validate()method to check current input validity - Character insertion now checks
is_valid_input()before accepting - Matches Borland's
TInputLinewithTValidatorattachment pattern
Examples¶
- validator_demo.rs: New example demonstrating input validation
- Field 1: Digits only (FilterValidator with "0123456789")
- Field 2: Number 0-100 (RangeValidator for positive range)
- Field 3: Number -50 to 50 (RangeValidator for mixed range)
- Field 4: Hex 0x00-0xFF (RangeValidator with hex support)
- Shows real-time rejection of invalid characters
- Displays validation results when OK is clicked
Technical Details¶
This implements Borland Turbo Vision's validator architecture from validate.h and tvalidat.cc. The Validator trait provides the base validation interface, with FilterValidator implementing character filtering (matching TFilterValidator from tfilterv.cc) and RangeValidator implementing numeric range validation (matching TRangeValidator from trangeva.cc).
The InputLine checks validators in two contexts:
1. During typing (is_valid_input()): Rejects invalid characters immediately
2. Final validation (is_valid()): Checks complete input when accepting
RangeValidator supports multiple number formats: - Decimal: "123", "-45" - Hexadecimal: "0xFF", "0xAB" - Octal: "077" (63 decimal), "0100" (64 decimal)
This matches Borland's get_val() and get_uval() functions from trangeva.cc:59-69.
Reference: Borland's TValidator architecture in validate.h, tvalidat.cc, tfilterv.cc, and trangeva.cc.
[0.2.0] - 2025-11-03¶
Added¶
- Broadcast Enhancement: Added owner-aware broadcast method to Group
- New
broadcast()method takes optionalowner_indexparameter - Prevents broadcast echo back to the originating view
- Matches Borland's
message()function pattern from tvutil.h - Enables focus-list navigation and sophisticated command routing patterns
- Foundation for future inter-view communication features
Fixed¶
- Menu Example: Fixed OK button command to use CM_OK instead of 0
- Buttons in menu.rs dialogs now properly close when clicked
- Added CM_OK to imports
- Dialog's handle_event now correctly recognizes CM_OK command
Technical Details¶
The Group::broadcast() method implements Borland's message pattern where broadcasts can skip the originator. This prevents circular event loops and enables proper implementation of focus navigation commands (like Ctrl+Tab to cycle through siblings without the current view receiving its own broadcast).
The method signature is broadcast(&mut self, event: &mut Event, owner_index: Option<usize>) where owner_index identifies the child that originated the broadcast. This child will be skipped when distributing the event to all children.
Reference: Borland's void *message(TView *receiver, ...) in tvutil.h and TGroup::forEach pattern in tgroup.cc:675-689.
[0.1.10] - 2025-11-03¶
Added¶
- Event Re-queuing System: Implemented Borland's putEvent() pattern for deferred event processing
- Added
put_event()method to Terminal - Added
pending_eventfield to Terminal struct - Events can now be re-queued for processing in next iteration
- Matches Borland's
TProgram::putEvent()andTProgram::pendingfrom tprogram.cc - Enables complex event transformation chains and command generation patterns
Changed¶
- Terminal: Enhanced
poll_event()to check pending events first - Pending events are processed before polling for new input
- Matches Borland's
TProgram::getEvent()behavior (tprogram.cc:154-194) - Event queue is FIFO - pending event delivered on next poll
- Supports Borland-style event flow patterns
Technical Details¶
This completes the event architecture trilogy started in v0.1.9. While three-phase processing handles HOW events flow through views, event re-queuing handles WHEN events are processed. The put_event() method allows views to:
- Generate new events for next iteration (e.g., converting mouse clicks to commands)
- Defer complex event processing
- Implement modal dialog patterns where unhandled events bubble up
- Match Borland's event generation patterns from status line and buttons
The pending event is checked first in poll_event(), ensuring re-queued events take priority over new input. This matches the exact behavior of TProgram::getEvent() which checks pending.what != evNothing before reading new events.
[0.1.9] - 2025-11-03¶
Added¶
- Three-Phase Event Processing: Implemented Borland's three-phase event handling architecture
- Phase 1 (PreProcess): Views with
OF_PRE_PROCESSflag get first chance at events - Phase 2 (Focused): Currently focused view processes event
- Phase 3 (PostProcess): Views with
OF_POST_PROCESSflag get last chance - Enables proper event interception patterns matching Borland's TGroup::handleEvent()
Buttonnow usesOF_POST_PROCESSto intercept Space/Enter when not focusedStatusLinenow usesOF_POST_PROCESSto monitor all key presses- Added
options()andset_options()methods to View trait
Changed¶
- Group: Enhanced
handle_event()with three-phase processing for keyboard/command events - Mouse events continue to use positional routing (no three-phase)
- Keyboard and Command events now flow through PreProcess → Focused → PostProcess
- Matches Borland's
focusedEventsvspositionalEventsdistinction -
Each phase checks if event was handled (EventType::Nothing) before continuing
-
Button: Now implements
options()withOF_POST_PROCESSflag - Buttons can intercept their hotkeys even when not focused
-
Matches Borland's button behavior from tbutton.cc
-
StatusLine: Now implements
options()withOF_POST_PROCESSflag - Status line monitors all key presses in post-process phase
- Enables status line to handle function keys globally
-
Matches Borland's TStatusLine architecture from tstatusl.cc
-
View trait: Added
options()andset_options()methods - Default implementation returns 0 (no special processing)
- Views can set
OF_PRE_PROCESSorOF_POST_PROCESSflags - Foundation for advanced event routing patterns
Technical Details¶
This implements the critical architectural pattern from Borland's TGroup::handleEvent() (tgroup.cc:342-369). The three-phase system allows views to intercept events before or after the focused view processes them. This is essential for: - Buttons responding to Space/Enter even when another control is focused - Status line handling function keys globally - Modal dialogs intercepting Esc/F10 regardless of focus
The implementation distinguishes between focusedEvents (keyboard/command) which use three-phase processing, and positionalEvents (mouse) which route directly to the view under the cursor.
[0.1.8] - 2025-11-03¶
Added¶
- Status Line Hot Spots: Status line items now have visual feedback and improved interaction
- Mouse hover highlighting: items change color when mouse hovers over them
- Hover color: White on Green (matching button style) for better visibility
- Dedicated
draw_select()method to render items with selection state - Context-sensitive hint display:
set_hint()method to show help text on status line - Improved mouse tracking during clicks for better user feedback
- New
StatusLine::item_mouse_is_in()helper to detect which item mouse is over - New example:
status_line_demo.rsshowcasing all status line improvements
Changed¶
- StatusLine: Enhanced with hover state tracking and hint system
- Added
selected_item: Option<usize>field to track hovered item - Added
hint_text: Option<String>field for context-sensitive help - Improved
handle_event()with mouse move detection for hover effects - Hint text displayed on right side when available and space permits
-
Matches Borland's
TStatusLine::drawSelect()pattern from tstatusl.cc -
Color Palette: Added new status line selection colors
STATUS_SELECTED: White on Green for selected status itemsSTATUS_SELECTED_SHORTCUT: Yellow on Green for shortcuts in selected items- Provides clear visual feedback matching button color scheme
Technical Details¶
This implements Borland Turbo Vision's status line hot spot pattern. The status line now provides visual feedback when the user hovers over items, matching the behavior of TStatusLine::drawSelect() in the original implementation. The hint system allows displaying context-sensitive help text on the status line, which can be updated based on the focused control or current application state. This is a step toward full context-sensitive help support planned for v0.3.0.
[0.1.7] - 2025-11-03¶
Added¶
- Keyboard Shortcuts in Menus: Menu items now display keyboard shortcuts right-aligned
- New
MenuItem::new_with_shortcut()constructor to specify shortcut text - Shortcuts displayed right-aligned in dropdown menus (e.g., "Ctrl+O", "F3", "Alt+X")
- Menu width automatically adjusts to accommodate shortcuts
- Matches Borland's
TMenuItem::keyCodedisplay pattern
Changed¶
- MenuItem: Enhanced with optional
shortcutfield for display purposes - Shortcut text is purely visual - shows users what keys to press
- Improves menu polish and user experience
- Follows desktop UI conventions for shortcut display
Technical Details¶
This implements Borland Turbo Vision's menu shortcut display pattern. Menu items can now show keyboard shortcuts right-aligned, similar to modern desktop applications. The implementation calculates menu width based on both item text and shortcut length, ensuring proper alignment and visual polish. Shortcuts are currently display-only - actual global shortcut handling would require application-level key routing.
[0.1.6] - 2025-11-03¶
Added¶
- Window Resize Support: Windows can now be resized by dragging the bottom-right corner
- Click and drag the bottom-right corner (last 2 columns, last row) to resize
- Minimum size constraints prevent windows from becoming too small (16x6 minimum)
- All child views automatically update during resize
- Efficient redrawing using union rect pattern (same as window movement)
- Matches Borland's
TWindowresize behavior fromtwindow.ccandtframe.cc
Changed¶
- Frame: Enhanced mouse event handling to detect resize corner clicks
- Bottom-right corner detection:
mouse.x >= size.x - 2 && mouse.y >= size.y - 1 - New
SF_RESIZINGstate flag to track resize operations -
Matches Borland's
TFrame::handleEvent()pattern (tframe.cc:214-219) -
Window: Added resize drag logic and size constraints
- Tracks resize offset from bottom-right corner during drag
- Applies minimum size limits (16 wide, 6 tall) matching Borland's
minWinSize - Updates frame and interior bounds during resize
- Prevents resizing smaller than minimum dimensions
Technical Details¶
This implements Borland Turbo Vision's window resizing architecture. The Frame detects resize corner clicks and sets the SF_RESIZING flag. The Window handles mouse move events during resize, calculating new size while respecting minimum size constraints from sizeLimits(). Child views are automatically repositioned through the set_bounds() cascade, and efficient redrawing uses the union rect pattern to minimize screen updates.
[0.1.5] - 2025-11-03¶
Added¶
- Double-click Detection: Implemented proper double-click detection for mouse events
- Added timing and position tracking to Terminal (
last_click_time,last_click_pos) - Detects double-clicks within 500ms at the same position
MouseEvent.double_clickfield now properly set byTerminal::convert_mouse_event()- Matches expected desktop UI behavior for quick successive clicks
Changed¶
- ListBox: Updated to trigger selection command on double-click instead of repeated single clicks
- Double-clicking an item in ListBox now immediately triggers the
on_select_command - Single clicks select items without triggering the command
-
Matches Borland's
TListViewerpattern:if (event.mouse.doubleClick) selectItem(focused) -
FileDialog: Automatically benefits from ListBox double-click support
- Double-clicking files now opens them immediately (no need to click OK button)
- Double-clicking folders navigates into them
- Improves user experience with modern expected behavior
Technical Details¶
This implements double-click detection based on Borland Turbo Vision's MouseEventType.doubleClick field. The implementation tracks click timing using Instant and checks that consecutive clicks occur within 500ms at the same position. This pattern matches modern desktop UI conventions while maintaining compatibility with Borland's event-driven architecture.
[0.1.4] - 2025-11-02¶
Changed¶
- Refactored Modal Execution Architecture: Completely redesigned how modal dialogs work to match Borland Turbo Vision's architecture
- Moved event loop from
DialogtoGrouplevel (matching Borland'sTGroup::execute()) Groupnow hasexecute(),end_modal(), andget_end_state()methodsDialog::execute()now implements its own event loop that callsDialog::handle_event()for proper polymorphic behavior- Dialog handles its own drawing because it's not on the desktop
- Fixed modal dialog hang bugs related to event loop and end state checking
- This change eliminates window movement trails and provides correct modal behavior
Added¶
- Architectural Documentation: Created
local-only/ARCHITECTURAL-FINDINGS.mddocumenting: - How Borland's event loop architecture works (studied original C++ source)
- Differences between C++ inheritance and Rust composition patterns
- Why the event loop belongs in Group, not Dialog
- Bug fixes and design decisions
- Comparison of Borland's TGroup::execute() with the Rust implementation
Fixed¶
- Modal Dialog Trails: Fixed issue where moving modal dialogs left visual trails on screen
- Dialog Hang Bug #1: Fixed infinite loop where
end_statecheck was inside event handling block - Dialog Hang Bug #2: Fixed polymorphism issue where
Group::handle_event()was called instead ofDialog::handle_event() - Application::get_event(): Now properly draws desktop before returning events, preventing trails
Technical Details¶
This release implements Borland Turbo Vision's proven architecture for modal execution. The key insight from studying the original Borland C++ source code (in local-only/borland-tvision/) is that the event loop belongs in TGroup, not in individual dialog types. In Borland:
// TGroup::execute() - the ONE event loop (tgroup.cc:182-195)
ushort TGroup::execute() {
do {
endState = 0;
do {
TEvent e;
getEvent(e); // Get event from owner chain
handleEvent(e); // Virtual dispatch to TDialog::handleEvent
} while(endState == 0);
} while(!valid(endState));
return endState;
}
Our Rust implementation adapts this pattern:
- Group has execute() with event loop and end_state field
- Dialog::execute() implements the loop pattern but calls Dialog::handle_event() for polymorphism
- Dialog::handle_event() calls window.end_modal() when commands occur
- Drawing happens in the loop because dialogs aren't on the desktop
See local-only/ARCHITECTURAL-FINDINGS.md for complete analysis.
0.1.3 - 2025-11-02¶
Added¶
- Scroll Wheel Support: Mouse wheel scrolling now works in ListBox, Memo, and TextView components
- Wheel up scrolls content upward (moves selection/cursor up)
- Wheel down scrolls content downward (moves selection/cursor down)
- Only responds when mouse is within the component's bounds
- Implemented by adding
MouseWheelUpandMouseWheelDownevent types to the event system -
Terminal now converts crossterm's
ScrollUpandScrollDownevents to internal event types -
Window Closing Support: Non-modal windows can now be properly closed
- Click close button on non-modal windows to remove them from desktop
- Modal dialogs: close button converts
CM_CLOSEtoCM_CANCEL - Non-modal windows: close button sets
SF_CLOSEDflag, removed by Desktop on next frame - Matches Borland's
TWindow::handleEvent()behavior (twindow.cc lines 124-138) - Added
SF_CLOSEDflag (0x1000) to mark windows for removal - Desktop automatically removes closed windows after event handling
Fixed¶
- TextView Indicator: Indicator now updates properly when scrolling with mouse wheel or keyboard
Technical Details¶
Scroll Wheel: This implements modern mouse wheel support that wasn't present in the original Borland Turbo Vision (which predated mouse wheels). The implementation follows the framework's event-driven architecture:
- Added event type constants EV_MOUSE_WHEEL_UP (0x0010) and EV_MOUSE_WHEEL_DOWN (0x0020)
- Updated EV_MOUSE mask to 0x003F to include wheel events
- Each scrollable component checks mouse position before handling wheel events
- Wheel events are cleared after handling to prevent propagation
Window Closing: Adapts Borland's architecture for Rust's ownership model:
- Borland uses CLY_destroy(this) to remove views from owner
- Rust uses SF_CLOSED flag since views can't remove themselves from parent Vec
- Window::handle_event() sets flag on CM_CLOSE (non-modal) or converts to CM_CANCEL (modal)
- Desktop::remove_closed_windows() removes flagged windows after event handling
- Group::remove() handles child removal and focus tracking
0.1.2 - 2025-11-02¶
Added¶
- Z-Order Management: Non-modal windows can now be brought to the front by clicking on them, matching Borland Turbo Vision's
TGroup::selectView()behavior. - Modal Window Support: Modal dialogs (like
Dialog::execute()) now properly block interaction with background windows. When a modal dialog is present, clicking background windows has no effect. - Menu Borders and Shadows: Dropdown menus now display with single-line borders and shadows, matching Borland's TMenuBox styling:
- Single-line box drawing characters (
┌─┐,│,└─┘,├─┤) - 2x1 shadow (2 cells wide on right, 1 cell tall on bottom)
- Verified against original Borland Turbo Vision source code
- Window Overlap Test: New
window_modal_overlap_testexample demonstrating z-order management with three overlapping non-modal windows.
Fixed¶
- Mouse Event Z-Order: Fixed mouse event handling to search in reverse z-order (top-most view first), preventing background views from capturing events intended for foreground windows.
- Upward Dragging: Fixed issue where windows could not be dragged upward. Windows can now be dragged in all directions by sending mouse events to dragging windows even when the mouse moves outside their bounds.
Changed¶
- Group::bring_to_front(): Added method to reorder children in z-order, automatically updating focused index.
- Desktop Event Handling: Desktop now manages z-order changes on mouse clicks and enforces modal blocking when modal windows are present.
- Dialog Modal Flag:
Dialog::execute()now automatically sets and clears theSF_MODALflag, making all executed dialogs modal by default.
Technical Details¶
This release implements Borland Turbo Vision's window management architecture:
- Z-Order: Children vector index represents z-order (higher index = on top)
- Modal Scope: Top-most window with SF_MODAL flag captures all events
- Border Drawing: Uses Borland's frameChars pattern for consistent styling
- Shadow Rendering: Matches Borland's shadowSize = {2, 1} and rendering algorithm
0.1.1 - 2025-11-02¶
Fixed¶
- Window dragging trails: Fixed visual corruption when dragging windows. Modal dialogs now properly redraw the desktop background on each frame, matching Borland Turbo Vision's
TProgram::getEvent()pattern where the entire screen is redrawn before polling for events.
Changed¶
- Desktop architecture: Desktop now uses a
Backgroundview as its first child (matching Borland'sTDeskTopwithTBackground), ensuring proper z-order rendering. - FileDialog execution:
FileDialog::execute()now takes anApplicationreference and redraws the desktop before drawing the dialog, following Borland's modal dialog pattern.
Technical Details¶
The fix addresses a fundamental architectural issue where modal dialogs had their own event loops that only redrawed themselves, not the desktop background. This caused visible trails when windows moved. The solution follows Borland Turbo Vision's pattern where getEvent() triggers a full screen redraw before returning events to modal views.
0.1.0 - 2025-11-02¶
Added¶
Core System¶
- Event-driven architecture with keyboard and command-based event routing
- Complete drawing system with color support (16-color palette with attribute system)
- Geometry primitives with absolute and relative positioning
- Focus management with Tab navigation and keyboard shortcuts
- Modal dialog execution system
- Cross-platform terminal I/O abstraction layer built on crossterm
UI Components¶
- Dialog: Dialog boxes with frames and close buttons
- Button: Interactive buttons with keyboard shortcuts and mouse support
- StaticText: Text labels with centered text support
- InputLine: Single-line text input fields
- Menu: Menu bar with dropdown menus and mouse support
- StatusLine: Status bar with clickable items
- Desktop: Desktop manager for window management
- ScrollBar: Vertical and horizontal scrollbars with mouse support
- Scroller: Base class for scrollable views
- Indicator: Position/status display widget
- TextView: Scrollable text viewer with line numbers
- CheckBox: Checkbox controls with mouse support
- RadioButton: Radio button groups with mouse support
- ListBox: List selection widget with mouse and keyboard navigation
- Memo: Multi-line text editor with basic editing capabilities
- FileDialog: Full-featured file selection dialog with directory navigation
Input & Navigation¶
- Full keyboard support with arrow keys, Tab, Enter, Escape
- Mouse support including:
- Button clicks and hover effects
- Menu interaction
- Status bar clicks
- Dialog close buttons
- ListBox item selection
- Scrollbar interaction
- Keyboard shortcuts for quick access
Application Framework¶
- Application class with event loop
- Terminal initialization and cleanup
- Resource management
Documentation¶
- Comprehensive README with quick start guide
- Module overview documentation
- Example programs demonstrating framework usage
Known Limitations¶
- Full text editor with search/replace not yet implemented (basic editing available in Memo)