pax_global_header00006660000000000000000000000064147747512110014523gustar00rootroot0000000000000052 comment=750d395c05e09975d87c16a6eb6e2392db64ee75 editor-0.1.80/000077500000000000000000000000001477475121100130775ustar00rootroot00000000000000editor-0.1.80/.gitignore000066400000000000000000000000071477475121100150640ustar00rootroot00000000000000_build editor-0.1.80/AUTHORS000066400000000000000000000001201477475121100141400ustar00rootroot00000000000000Christoph Hueffelmann Martin Hostettler editor-0.1.80/COPYING000066400000000000000000000024721477475121100141370ustar00rootroot00000000000000Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. editor-0.1.80/NEWS.md000066400000000000000000000315461477475121100142060ustar00rootroot00000000000000# Changelog chr editor ====================== ## 0.1.80 (2025-04-07) * New Upstream Release termpaint (0.3.1) * New Upstream Release tuiwidgests (0.2.2) * Add QCommandLineOption -s to setup inline mode. * Add SearchDialog: Add F3 to search. * Add tests/meson.build and make it adjustable via the build option -Dtests=true * File: After copying or press ESC, the select mode (F4) is ended. * Extends meson by the function to set rpath with -Drpath=. * Add Line Marker support. ## 0.1.79 (2024-06-01) * Fix: meson: Add timeout 180 for debian riscv64 tests. ## 0.1.78 (2024-04-17) * Fix: Delete Key wirh MultiCursor * Fix Test: Include catch2 header from system and respect CATCH3 * Add meson.build: Run tests in verbose mode when supported. * Add meson.build: Register tests executable * Add meson.build: Install manpages * Add +/searchword to start seach on open file ## 0.1.77 (2024-02-26) * Fix: visibility help message ## 0.1.76 (2023-12-27) * Fix missing ifdef SYNTAX_HIGHLIGHTING for file commands ## 0.1.75 (2023-12-23) * Debian Relese 0.1.75 * Rename: editor executable to chr * Add QStandardPaths for ~/.config/chr and ~/.cache/chr/chr.json * Rename: configuration file options to use snake case. ## 0.1.74 (2023-12-23) * Fix: Editor,File: Make sure to update search count after replace or replace all (closes: #9) * Fix: Make selection of trailing spaces visible again when using syntax highlighting. * Fix: copy and paste error * Fix: inverted parameter usage in updateSettings * Add: Commandline and Config Option: -w WordWrap|WrapAnywhere|NoWrap (Close: #19) * Add: SearchDialog: Add more escape sequences that are translated. * Add a minimal help (quick start) * Switch default for highlight_bracket to true. * Remove: Ctrl-K "cutline" edit command. ## 0.1.73 (2023-11-06) * Fix: Scrol Window with wrap long lines * Fix: Opening files that cannot be read * Fix: Up/Down on first and last line * Add: SaveDialog can handel file path * Rename: DOSMode to crlf Mode * Migrate: Cursor to TuiWidges * Migrate: Dukument to TuiWidges * Migrate: File to TuiWidges ## 0.1.72 (2023-09-20) * Add: Syntex Heileiting (Add -Dsyntax_highlighting=true) * Fix: Search Dialog interaction * Fix: Search Regex Support * Add: Search Multiline * Fix: move multicursor on paste text. * Update man ## 0.1.71 (2023-08-01) * Fix: StatusBar * Fix: Change terminal title on f6 * Fix: Change context for F3 and Shift-F3 shortcuts to window. * Preparation for syntax heileiting and multi cursor. ## 0.1.70 (2023-04-11) * Fix: stdin * Fix: menu for copy paste if you change the activ window. * Fix: search WrapAround. * Fix: ScrollBar color. * Fix: scrollin long lines. * Add: character border. ## 0.1.69 (2023-03-27) * Fix: gotoline when starting the editor. * Fix: SearchDialog: Remove duplicated mnemonic. * Fix: StatusBar::paintEvent: change unsafe icon in status bar * Fix: File::insertText: cursor position after insert text with new lines. * Fix: Scrollbar: color when characters ar printed on the bar. * Add: git commit id in version number. See chr -v. * Add: ctrl + d support, do delete a single line. * Add: ctrl +,e, v and Ctrl + e, h shortcuts for tile vertical and horizontal. * Add: When displaying the version number, the Git-Version is now also displayed. * Add: About dialog. * Refactor: Document, File, FileWindow and TextCursor. ## 0.1.68 (2023-01-28) * Fix: OpenDialog * Fix: CloseDialog * New Upstream Release TuiWidgets ## 0.1.67 (2022-06-02) * Fix: File: The first field in an empty row is now also highlighted to show it visually. * Fix: File: Fix crash due to recently added delayed terminal attachment in tui widgets. * Add: File::paintEvent: Mark the line number bold at the current cursor position. third party: * Switch to tui widgets version of window layout (WindowLayout -> Tui::ZWindowLayout) * Switch to use ZVBoxLayout instead of VBoxLayout. And also for ZHBoxLayout. ## 0.1.65 (2022-04-07) * Fix: SaveDialog * Doc: Update manpage ## 0.1.64 (2022-02-12) * Fix: Toolkit: Avoid infinite loop with wrapping, visual spaces and width 0. * Fix: OpenDialog path. * Ref: Categorize file type. ## 0.1.63 (2021-12-22) * Fix: File: of by one if selcet all. * Fix: File: Scroll up and down and move curso. * Fix: Shift + insert shortcuts. * Fix: Remove: Unused and broken shortcuts. * Fix: Opendialog: change to dir without backslash at end of path. * Fix: File::copy: Do nothing in multi insert mode. * Add: WordWrap Support. * Add: WrapDialog. ## 0.1.62 (2021-11-24) * Fix: TextLayout * Fix: InsertCharacter: fix insert all chars. fix priview box. * Fix: SaveDialog: sort tab order. * Fix: File: fix viewWidth without shift line numbers * Fix: File: fix doLyout without wrap option. * Fix: Editor: Add pendingKeySequence handler. * Fix: File: fix singel char for double press home * Add: FileWindow: Add windows options for manual placement, close and return to automatic placement. * Add: FileWindow: Use closeSkipCheck in closeRequested handling. * Add: FileWindow: Adjust visible edges based on position. * Add: FileWindow: Save vertical scrollbar as member. * Add: FileWindow: connect add contextobject. * Add: ZWindow::AutomaticOption to all dialogs. Also Add move to OverwriteDialog. ## 0.1.61 (2021-11-09) * Add: multi document interface (mdi) * Add: Multi window sub menue * Add: Adjust file window edge for horizontal tiling * Add: Allow manually moving file windows ## 0.1.60 (2021-10-13) * Add Formatting dialog. ## 0.1.59 (2021-10-10) * Fix: Update statusbar if file save as. * Round Trip: QSaveFile does not take over the user and group. * Add: OpenDialog should be started in the same folder as the active file. * Add: Show hidden files in save as dialog. * Add: Eat Space Befroe Tab. * Add: Move and close options to dialogs. * Add: Open and Save Dialog switched to FileModel. ## 0.1.58 (2021-10-06) * Fix: Search and Repace Dialog do not crash if file not exist. * Fix: and refector: UndoStaps on inital or file open. * Fix: If file is ro, now can by save as in close dialog. * Fix: FileWindow: Delete close confirm dialog when exit is selected too. ## 0.1.57 (2021-09-23) * Fix: File watcher after reload file. * Fix: Save and Save as in all windows. * Add Sorrt Seleced Lines Support. * Add Save as OverwriteDialog. * SaveDialog: Sort DirsFirst. * ConfirmSave: Different message for confirm types. * Enable reload in menu if file has been externally changed. ## 0.1.56 (2021-09-07) * Fix WrapLongLines with Linenumbers. * Multi Window HVF Suppot. ## 0.1.55 (2024-08-24) * Multi Window Suppot. ## 0.1.53 (2021-07-20) * Fix SaveDialog: Save Path. * Fix Safedialog: Redundant multiple separators. * Add ConfirmDialog for Reload. * Add Menu shortcuts. ## 0.1.52 (2021-06-11) * Fix: OpenDialog: Folders cannot be opened. * Fix: The behavior of no line at the end of the file. * Add: Use default placement for dialogs. * Add: Editor::facet: Require exact match for clipboard facet. ## 0.1.51 (2021-06-04) * Fix: F-Keys trigger delselect. * Add: hidden checkbox, fix sort order when refresh. * Adapt to listview changes. * Update: Statusbar. ## 0.1.50 (2021-05-08) * Add: Theam support * Upstream fixes ## 0.1.49 (2021-02-07) * [ edr ] Typos in the manual corrected. * Add: Alt+Shift+S for sort selected lines ## 0.1.48 (2021-01-18) * Add: regex support in search and replace * Fix: jumping when entering search queries * Fix: F6 brings active windows to the foreground. * Update: Manual ## 0.1.47 (2021-01-11) * Fix: check if clipboard is empty ## 0.1.46 (2021-01-07) * Update termpaint: flush: leave terminal with reset SGR state. * Rename playground to inputevents. Thu, 07 Jan 2021 20:26:27 +0100 ## 0.1.45 (2020-11-18) * Fix: BlockSelect Tab * Fix: Ctrl + Key ## 0.1.44 (2020-11-16) * Add: BlockSelect * Add: Alt+X commandline * Fix: KDE Konsole * Hotfix: 4 Byte Char * Update: Manual ## 0.1.43 (2020-10-29) * Hotfix: Cousor position can not be smaller than zero * Fix: Colors in status bar adjusted for special characters * Shift tab can only be applied to complete lines. Therefore it can always be executed * Fix: select from bottem to top for Tab/ShiftTab and Selected KeyUp/Down to selectLines methode * Add option: select_cursor_position_x0 ## 0.1.42 (2020-10-14) * Fix termpaint: Fix surface_vanish_char access beyond rightmost character of surface. ## 0.1.41 (2020-10-14) * Add Line Number support * Add menu: Select Mode * Fix InputBox half cut char * Fix cursor position during reload * Fix QFileSystemWatcher::removePaths: list is empty ## 0.1.40 (2020-10-12) * The status bar is colored yellow if a file has changed outside the editor * Data input by stdin can now be interrupted in the menu * Files can now be reloaded from the menu * Copy and paste can now also be used in the search dialog ## 0.1.39 (2020-10-08) * Add playground, detect and keyboardcollector to deb files ## 0.1.38 (2020-10-06) * Moved the search in a thread * Add select mode with F4 for consoles with suppressed shift key ## 0.1.37 (2020-10-03) * faster insert log lines * detect nullbyte char delivered by stdin * fix: also accept numpad enter as enter key ## 0.1.36 (2020-09-18) * Add loggin after editor was closed * Create directory for status information if not exist ## 0.1.35 (2020-09-06) * Round trip for ZOC terminal ## 0.1.34 (2020-07-14) * Add tab to spaces converter in Tab Dialog * Add Insert Character dialog to add utf-8 character * Add DOS Mode in Save As Dialog ## 0.1.33 (2020-07-04) * Display special characters * Edit files with parts that are not utf-8 encoded (open / save without further changes should not break the file) * Saves the last refuelled position in ~/.cash/chr.json * Fix: open no newline at end of file * Fix: status bar for new file ## 0.1.31 (2020-06-08) * Fix: add group undo * Fix: in delSelect ## 0.1.30 (2020-05-28) * Fix search and replace, if nothing is found, nothing must be selected * Fix Typo ## 0.1.29 (2020-05-27) * Set window title with * if modified change * Update manual ## 0.1.28 (2020-05-24) * Add ReplaceDialog ## 0.1.27 (2020-05-23) * Add live search support * Bugfixes ## 0.1.26 (2020-04-18) * Bugfix: View last line ## 0.1.25 (2020-03-31) * The clipboard must not be deleted if nothing is selected. ## 0.1.24 (2020-03-25) * Highlight Bracket ## 0.1.23 (2020-03-23) * Default value for crl+f * Paste replaces the selected text * With horizontal scroling, the position of the cursor is stored temporarily and characters of different lengths are noted. * fix: termlib ## 0.1.22 (2020-03-12) * Moved lines should not be selected * show RW or RO status in statusbar * Added distinction between highlighted and focus ListView. ## 0.1.21 (2020-03-14) * Intercept copy and paste errors that contain special characters "·,→,¶" * Show RW status in statusbar. ## 0.1.20 (2020-02-27) * fix alert box * update manual ## 0.1.19 (2020-02-22) * add folloing support with option -f * add multi press pos1 * add no newlien support ## 0.1.18 (2020-02-20) * add support read of stdin statusbar support, append mode and following mode for stdin update manual ## 0.1.17 (2020-01-20) * Add German manual ## 0.1.16 (2020-01-19) * Fix manual path ## 0.1.15 (2020-01-10) * Search Privius * Fix Search first line * Selected tab support ## 0.1.14 (2020-01-10) * Open SearchBox with Crl+F, find the next element with F3 * Color find element * New background scrolbars for mous mode copy * Change window style ## 0.1.13 (2019-11-14) * add first version of search function ## 0.1.12 (2019-11-04) * File: fix scrolling with wraplines in the visibile area * File: Base calculation of horizontal scrollbar only on currently visible lines. * File: fix bigfile size ## 0.1.11 (2019-10-24) * new pipline * termlib fixes ## 0.1.10 (2019-10-09) * remove wrong rpmbuild dir * add manpages support ## 0.1.9 (2019-10-08) * new termlib fixes * debian compat == 11 ## 0.1.8 (2019-09-15) * Don't get confused by std in/out/err where isatty is true but not opened for read and write. * File::delSelect remove selected linebrake ## 0.1.7 (2019-09-04) * update libtui ## 0.1.6 (2019-07-27) * rework undostaps ## 0.1.5 (2019-06-17) * fix horizontel scroling * add delet word ## 0.1.4 (2019-04-10) * set curser shape and color ## 0.1.3 (2019-02-25) * new color 8bit for pterm and co * new color for vt * new color for formatingChar * fix option +n for line number ## 0.1.2 (2019-02-05) * Bugfix crash at print message and quit on open big files * Menue color in SW Mode * Add Shortcut STRG+q ## 0.1.1 (2018-11-28) * Initial release. editor-0.1.80/README.md000066400000000000000000000433751477475121100143720ustar00rootroot00000000000000[![License](https://img.shields.io/badge/License-Boost_1.0-lightblue.svg)](/COPYING) A terminal based text editor. Keyboard shortcuts are similar to the default editors in Gnome, KDE and other desktop environments. This is to ease workflows alternating between GUIs and terminal. The look and feel is a blend of modern GUI editors and late 90s PC text mode editors (e.g. Turbo Vision based or edit.com) adapted to fit into terminal based workflows. It has been written from scratch using [Tui Widget](https://tuiwidgets.namepad.de/) ![Screenshots](https://blog.chr.istoph.de/wp-content/uploads/chr.edit_20230920.png) ## Contribute This project is always open for contributions and welcomes merge requests. Take a look at our [issue tracker](https://github.com/istoph/editor/issues) for open issues. ### Technologies / Dependencies Dependencies in Debian trixie: ``` build-essential meson ninja-build pkg-config qt5-qmake qttools5-dev-tools qtbase5-dev libtermpaint-dev libtuiwidgets-dev libposixsignalmanager-dev ``` For third-party syntex highlighting you need the compile option: `-Dsyntax_highlighting=true` and the following additional dependencies: ``` libkf5syntaxhighlighting-dev libkf5syntaxhighlighting-tools cmake ``` Additional options are: * `-Dtests=true` for switching build tests on and off. * `-Dsystem-catch2=enable` to use catch as a system library (only use for tests). See also [Tui Widgets](https://tuiwidgets.namepad.de/) and [Termpaint](https://termpaint.namepad.de/) ``` git clone https://github.com/istoph/editor cd editor meson setup _build meson compile -C _build meson install -C _build ``` For more details see: [doc](https://github.com/istoph/editor/tree/main/doc/build/README.md) ## Doku ``` man(1) chr man page man(1) NAME chr - chr is a terminal based editor SYNOPSIS Usage: chr [options] [[+line[,char]] file …] [[+/searchword] file …] [/directory …] Options: -h, --help Displays help on commandline options. --help-all Displays help in‐ cluding Qt specific options. -v, --version Displays version information. -l, --line-number The line numbers are displayed -b, --big-file Open bigger files than 100MB -w, --wrap-lines Wrap log lines (NoWrap Default) --attributesfile Safe file for at‐ tributes, default ~/.cache/chr/chr.json -c, --config Load customized con‐ fig file. The default if it exist is ~/.config/chr --syntax-highlighting-theme Name of syntax-highlighting- theme, you can list in‐ stalled themes with: kate-syntax- highlighter --list-themes --disable-syntax disable syntax high‐ lighting -s, --size Size in lines or for Automatic detec‐ tion of the size of the lines with which the editor is displayed on the console. Arguments: [[+line[,char]] file …] Optional is the line number and position. Sev‐ eral files can be opened in multiple windows. [[+/searchword] file …] A search word can be set. [/directory …] Or a directory can be specified to search in the open dialog. DESCRIPTION Chr is a terminal based text editor. Keyboard shortcuts are similar to the default editors in Gnome, KDE and other desktop environments. This is to ease workflows alternating be‐ tween GUIs and terminal. The look and feel is a blend of modern GUI editors and late 90s PC text mode editors (e.g. Turbo Vision based or edit.com) adapted to fit into terminal based workflows. Quick Start The important operations can be invoked from the menu. The menu can be opened with F10 or with Alt together with the highlighted letter of the menu item (e.g. Alt + f). In dialogs Tab is used to navigate between the elements. Use F6 to nav‐ igate between windows/dialogs. Text can be marked in most terminals with Shift + arrow key. Ctrl + c is used for copying. Paste with Ctrl + v. Changes can be saved with Ctrl + s and the editor can be exited with Ctrl + q. SHORT CUTS Shift + Cursor Selects text Ctrl + a Selects all text in the document Ctrl + c / Ctrl + Insert Copies the selected text into the clipboard Ctrl + d Deletes the current line Ctrl + e, up/down/left/right Switches the active window. Use the arrow keys to specify the direc‐ tion for the next active window. Ctrl + e, Ctrl + up/down/left/right Change window size Ctrl + e, q Close an active document Ctrl + f Open the search dialog Ctrl + Backspace Delete a word (left from cursor position) Ctrl + r Open the replace dialog Ctrl + n Creates a window with an empty document Ctrl + m Creates a line marker at the left edge of the line to find lines again. (Ctrl + , oder Ctrl + .) This key combination does not work with all terminals. Ctrl + q Quits the editor Ctrl + Shift + q Close an active document (the key combination does not work in vte, see: Ctrl + e, q) Ctrl + s Save (or save as for new documents) Ctrl + v / Shift + Insert Inserts the contents of the clipboard at the current cursor position. Ctrl + x / Shift + Delete Cuts out the selected text and moves it to the clipboard Ctrl + y Redos an action that has been undone Ctrl + z Undoes an action Ctrl + Shift + up Moves the current selection or line upwards Ctrl + Shift + down Moves the current selection or line down Ctrl + Left Jump a word to the left Ctrl + Shift + Left Selects a word to the left Ctrl + Right Jump a word to the right Ctrl + Shift Right Selects a word to the right Alt + - Open the window menu Alt + Shift + up/down/left/right Marks the text in blocks. Inserting the clipboard duplicates the text per line. If an equal number of lines is marked as to be inserted, the lines from the clipboard will be distributed across the selected lines. Alt + Shift + S Sort the selected lines (lexicographical by code-point) Alt + x Opens a command line. Type "help" for help. Tab / Shift + Tab Indents a selected block by a tab stop or remove one level of inden‐ tion F3 / Shift + F3 Find the next or previously search element F4 Toggles the selection mode to allow selecting text in terminals where marking with Shift + arrow keys does not work F6 / Shift + F6 Change active window, with Shift in reverse order ESC Closes an active dialog menu or action. MENU File New Opens a new an empty unnamed document. Open Opens a file dialog to select a file to be opened. Save Saves the current status of the file. If the save path is not yet spec‐ ified, the "Save as ..." dialog is opened. Save as... A storage location to save the file to can be selected here via a file dialog. Reload Reloads the current file. All changes are discarded. Close Closes the active window. Quit Closes the editor. If there is a file open that has not yet been saved, the Save dialog will be opened first. Edit Cut, Copy, Paste, Select all Text can be selected using the arrow keys while holding down the Shift key. The entire text can be selected with Select all. This selected text can then be copied using Copy or cut using Cut. With Paste, this text can be inserted again at the current cursor position. If there is text in the clipboard before copying (or cutting), it will be replaced. These functions use an internal clipboard that contains different con‐ tent than the clipboard used in the terminal as copy and paste com‐ mands, as the editor cannot access the system clipboard. Delete Line Deletes the entire line. Select Mode Toggles the selection mode to allow selecting text in terminals where marking with Shift + arrow keys does not work. Undo, Redo With Undo or CTRL + z, edits can be undone. With Redo or CTRL + y the undo can be undone again. Search Use Search or Ctrl + f to open the search dialog. Enter a search term in the "Find" field. You can refine the search using the options. If live search is activated, the first matching result is automatically selected while the search term is being entered. If the text document is active, you can press F3 to jump to the next result or Shift + F3 to jump to the previous result. Search Next Jump to the next match for the current search term. Search Previous Jump to the previous match for the current search term. Replace With Replace or CTRL + r the Replace dialog is opened. Enter a search term in the "Find" field. In the field "Replace" the word to be in‐ serted is specified. "Next" jumps to the next match for the current search term. With "Replace" the current match is replaced. With "All" all occurrences of the search term are replaced at once. Insert Character... Opens a dialog in which a character code (Unicode codepoint) of a spe‐ cial character to be inserted can be entered. Goto To jump to a line, open a Goto Line dialog under "Goto". Marker Creates a line marker in the left margin to quickly find lines again when reviewing. Use Ctrl + , or Ctrl + . to jump to the next marker. On quit the list of markers is saved in chr.json, so that it can be re‐ stored when the file is opened. Sort Selected Lines Sort the selected lines (lexicographical by code-point). Options Tab settings Opens the Tab settings dialog. Here the settings for a tab can be made. You can choose between tab (\t) and space. You can also set the width of the indention. The default settings can also be set in the ~/.con‐ fig/chr file. Here you can specify: "tabsize=8" or "tab=false" for spaces. Line Number Shows the line number on the left side of the editor. The default set‐ tings can also be made in the ~/.config/chr file. Here you can specify: "line_number=true". Formatting In the Formatting dialog, "Formatting Characters", "Color Tabs" and "Color Spacs at end of line" can be switched on and off. The "Formatting characters" marks spaces with a dot: "·" end of line (\n) with a "¶" and the end of the file with: "♦". With "Color Tabs" tabs are colorized. The tab border is made darker. "Color Spaces at end of line" is used to spaces mark at the end of the line in red. In the configuration file: ~/.config/chr the behavior can be influenced with the option "formatting_characters=true", "color_tabs=true", "color_space_end=true". Wrap long lines Selects if lines that are wider than the window are displayed clipped or wrapped.. It can be wrapped at the word boundary or hard at the end of the line. This behavior can be influenced by the option "wrap_lines=WordWrap" or "wrap_lines=WrapAnywhere" in the ~/.config/chr file. In addition, the option "Display Right Margin at Column" can be used to specify a numerical value above which the background color is darkened. This value can also be set with the configuration option: "right_mar‐ gin_hint=80" in ~/.config/chr. Stop Input Pipe Reading from a pipe is interrupted. The standard input file descriptor is closed. Highlight Brackets If active and the cursor is on a bracket the bracket at the cursor po‐ sition and the matching other bracket are highlighted. The following opening and closing brackets can be highlighted when the cursor moves over them. With the option "highlight_bracket=true" this behavior can be influenced in the ~/.config/chr. Supported bracket types are: [{(<>)}]. Syntax Highlighting If the editor has been compiled with the "SyntaxHighlighting" feature, syntax highlighting is generally available. The language is automati‐ cally detected when a file is opened and displayed in the status bar. If required, it can also be switched on and off or adjusted via the syntax highlighting dialog. Syntax highlighting can also be deactivated in this dialog. The theme can be customized via the command line switch "--syntax-high‐ lighting-theme". The editor comes with the themes "chr-bluebg" and "chr-blackbg". If required, a theme from the list that can be displayed with "kate-syntax-highlighter --list-themes" can be used. With the op‐ tion "syntax_highlighting_theme=chr-bluebg" the theme can be set in ~/.config/chr. Syntax highlighting can be switched off via the command line using "--disable-syntax" when the editor is started. With the option "dis‐ able_syntax=true" the theme can be set in ~/.config/chr. Theme It opens the dialog for selecting a theme. The Classic (blue) or the Dark (black and white) mode is available. With the option "theme=clas‐ sic" or "theme=dark", this can be set in the ~/.config/chr. Window Next, Previous Switches the active window, with Shift in reverse order. (See F6) Tile Vertically, Horizontally, Fullscreen Selects how multiple open documents are shown. Vertical and horizontal distribute the available space across the docu‐ ments. When Fullscreen is selected only one document is shown at once. (See F6) CUSTOM CONFIG The editor loads a configuration file from ~/.config/chr (if avail‐ able). (If the environment variable $XDG_CONFIG_HOME is set, then from $XDG_CONFIG_HOME/chr) In addition to the options documented above, the following options are available: eat_space_before_tabs This option is only active if tab=false is set. If this option is active and the Tab key is pressed while the cursor is in the indentation at the beginning of a line, the indentation is ex‐ tended to the next tab position. attributes_file Specifies the path of the file in which the cursor and scroll position of files opened in the past is saved. Default config There is a default config (~/.config/chr) where the following options can be set. attributes_file="/home/user/.cache/chr/chr.json" color_space_end=false color_tabs=false disable_syntax=false eat_space_before_tabs=true formatting_characters=false highlight_bracket=true line_number=false logfile="" right_margin_hint=0 syntax_highlighting_theme="chr-bluebg" tab=false tab_size=4 theme="classic" wrap_lines="NoWrap" FILES ~/.config/chr Your personal chr initializations. ~/.cache/chr/chr.json History about the changed files. This is where cursor positions are stored. BUGS Errors in this software can be reported via the bugtracker on https://github.com/istoph/editor. AUTHOR Christoph Hüffelmann Martin Hostettler 0.1.80 06 Apr 2025 man(1) ``` ## License This software is licensed under the [Boost Software License 1.0](/COPYING) editor-0.1.80/doc/000077500000000000000000000000001477475121100136445ustar00rootroot00000000000000editor-0.1.80/doc/build/000077500000000000000000000000001477475121100147435ustar00rootroot00000000000000editor-0.1.80/doc/build/Dockerfile.alpine:3.20000066400000000000000000000021051477475121100205770ustar00rootroot00000000000000FROM alpine:3.20 RUN apk add build-base meson git python3 ninja qt5-qtbase-dev cmake syntax-highlighting5-dev # build lib termpaint RUN git clone https://github.com/termpaint/termpaint && \ cd termpaint && \ meson setup _build -Dttyrescue-fexec-blob=false -Dprefix=$HOME/opt/tuiwidgets-prefix && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build lib tuiwidgets RUN git clone https://github.com/tuiwidgets/tuiwidgets && \ cd tuiwidgets && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/pkgconfig meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix -Drpath=$HOME/opt/tuiwidgets-prefix/lib/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build editor chr RUN git clone https://github.com/istoph/editor && \ cd editor && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/pkgconfig meson setup _build -Dsyntax_highlighting=true -Drpath=$HOME/opt/tuiwidgets-prefix/lib/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # debug info RUN chr -v && \ chr -h editor-0.1.80/doc/build/Dockerfile.archlinux000066400000000000000000000022051477475121100207300ustar00rootroot00000000000000FROM archlinux:latest RUN pacman -Syu --noconfirm base-devel gcc meson git qt5-base qt5-tools ninja python pkg-config syntax-highlighting5 # build lib termpaint RUN git clone https://github.com/termpaint/termpaint && \ cd termpaint && \ meson setup _build -Dttyrescue-fexec-blob=false -Dprefix=$HOME/opt/tuiwidgets-prefix && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build lib tuiwidgets RUN git clone https://github.com/tuiwidgets/tuiwidgets && \ cd tuiwidgets && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/pkgconfig meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix -Drpath=$HOME/opt/tuiwidgets-prefix/lib/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. RUN pacman -Syu --noconfirm cmake # build editor chr RUN git clone https://github.com/istoph/editor && \ cd editor && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/pkgconfig meson setup _build -Dsyntax_highlighting=true -Drpath=$HOME/opt/tuiwidgets-prefix/lib/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # debug info RUN chr -v && \ chr -h editor-0.1.80/doc/build/Dockerfile.debian:11000066400000000000000000000026001477475121100204100ustar00rootroot00000000000000FROM debian:bullseye RUN apt update; apt install -y build-essential git ninja-build pkg-config qt5-qmake qttools5-dev-tools qtbase5-dev libkf5syntaxhighlighting-dev libkf5syntaxhighlighting-tools cmake # meson 0.59.0 is need RUN apt install -y python3 && \ git clone https://github.com/mesonbuild/meson.git && \ ln -s /meson/meson.py /usr/local/bin/meson # build lib termpaint RUN git clone https://github.com/termpaint/termpaint && \ cd termpaint && \ meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build lib tuiwidgets RUN git clone https://github.com/tuiwidgets/tuiwidgets && \ cd tuiwidgets && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/pkgconfig meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix -Drpath=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build editor chr RUN git clone https://github.com/istoph/editor && \ cd editor && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/pkgconfig meson setup _build -Dsyntax_highlighting=true -Drpath=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # debug info RUN chr -v && \ chr -h editor-0.1.80/doc/build/Dockerfile.debian:12000066400000000000000000000023441477475121100204160ustar00rootroot00000000000000FROM debian:bookworm RUN apt update; apt install -y build-essential git meson ninja-build pkg-config qt5-qmake qttools5-dev-tools qtbase5-dev libkf5syntaxhighlighting-dev libkf5syntaxhighlighting-tools cmake # build lib termpaint RUN git clone https://github.com/termpaint/termpaint && \ cd termpaint && \ meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build lib tuiwidgets RUN git clone https://github.com/tuiwidgets/tuiwidgets && \ cd tuiwidgets && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/pkgconfig meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix -Drpath=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build editor chr RUN git clone https://github.com/istoph/editor && \ cd editor && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/pkgconfig meson setup _build -Dsyntax_highlighting=true -Drpath=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # debug info RUN chr -v && \ chr -h editor-0.1.80/doc/build/Dockerfile.debian:sid000066400000000000000000000023231477475121100207500ustar00rootroot00000000000000FROM debian:sid RUN apt update; apt install -y gcc git meson ninja-build pkg-config qt5-qmake qttools5-dev-tools qtbase5-dev libkf5syntaxhighlighting-dev libkf5syntaxhighlighting-tools cmake # build lib termpaint RUN git clone https://github.com/termpaint/termpaint && \ cd termpaint && \ meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build lib tuiwidgets RUN git clone https://github.com/tuiwidgets/tuiwidgets && \ cd tuiwidgets && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/pkgconfig meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix -Drpath=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build editor chr RUN git clone https://github.com/istoph/editor && \ cd editor && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/pkgconfig meson setup _build -Dsyntax_highlighting=true -Drpath=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # debug info RUN chr -v && \ chr -h editor-0.1.80/doc/build/Dockerfile.fedora:41000066400000000000000000000017711477475121100204410ustar00rootroot00000000000000FROM quay.io/fedora/fedora:41 RUN dnf -y upgrade && \ dnf -y install devscripts git meson ninja-build pkgconfig qt5-qtbase-devel kf5-syntax-highlighting-devel # build lib termpaint RUN git clone https://github.com/termpaint/termpaint && \ cd termpaint && \ meson setup _build -Dprefix=$HOME/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build lib tuiwidgets RUN git clone https://github.com/tuiwidgets/tuiwidgets && \ cd tuiwidgets && \ PKG_CONFIG_PATH=$HOME/lib64/pkgconfig meson setup _build -Dprefix=$HOME/ -Drpath=$HOME/lib64 && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build editor chr RUN git clone https://github.com/istoph/editor && \ cd editor && \ PKG_CONFIG_PATH=$HOME/lib64/pkgconfig meson setup _build -Dsyntax_highlighting=true -Drpath=$HOME/lib64 && \ meson compile -C _build && \ meson install -C _build && \ cd .. RUN find / | grep chr # debug info RUN chr -v && \ chr -h editor-0.1.80/doc/build/Dockerfile.fedora:42000066400000000000000000000017711477475121100204420ustar00rootroot00000000000000FROM quay.io/fedora/fedora:42 RUN dnf -y upgrade && \ dnf -y install devscripts git meson ninja-build pkgconfig qt5-qtbase-devel kf5-syntax-highlighting-devel # build lib termpaint RUN git clone https://github.com/termpaint/termpaint && \ cd termpaint && \ meson setup _build -Dprefix=$HOME/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build lib tuiwidgets RUN git clone https://github.com/tuiwidgets/tuiwidgets && \ cd tuiwidgets && \ PKG_CONFIG_PATH=$HOME/lib64/pkgconfig meson setup _build -Dprefix=$HOME/ -Drpath=$HOME/lib64 && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build editor chr RUN git clone https://github.com/istoph/editor && \ cd editor && \ PKG_CONFIG_PATH=$HOME/lib64/pkgconfig meson setup _build -Dsyntax_highlighting=true -Drpath=$HOME/lib64 && \ meson compile -C _build && \ meson install -C _build && \ cd .. RUN find / | grep chr # debug info RUN chr -v && \ chr -h editor-0.1.80/doc/build/Dockerfile.fedora:43000066400000000000000000000017711477475121100204430ustar00rootroot00000000000000FROM quay.io/fedora/fedora:43 RUN dnf -y upgrade && \ dnf -y install devscripts git meson ninja-build pkgconfig qt5-qtbase-devel kf5-syntax-highlighting-devel # build lib termpaint RUN git clone https://github.com/termpaint/termpaint && \ cd termpaint && \ meson setup _build -Dprefix=$HOME/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build lib tuiwidgets RUN git clone https://github.com/tuiwidgets/tuiwidgets && \ cd tuiwidgets && \ PKG_CONFIG_PATH=$HOME/lib64/pkgconfig meson setup _build -Dprefix=$HOME/ -Drpath=$HOME/lib64 && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build editor chr RUN git clone https://github.com/istoph/editor && \ cd editor && \ PKG_CONFIG_PATH=$HOME/lib64/pkgconfig meson setup _build -Dsyntax_highlighting=true -Drpath=$HOME/lib64 && \ meson compile -C _build && \ meson install -C _build && \ cd .. RUN find / | grep chr # debug info RUN chr -v && \ chr -h editor-0.1.80/doc/build/Dockerfile.ubuntu:20.04000066400000000000000000000026161477475121100207410ustar00rootroot00000000000000FROM ubuntu:20.04 RUN apt update; DEBIAN_FRONTEND=noninteractive apt install -y build-essential git ninja-build pkg-config qt5-qmake qttools5-dev-tools qtbase5-dev libqt5concurrent5 libkf5syntaxhighlighting-dev libkf5syntaxhighlighting-tools cmake # meson 0.59.0 is need RUN git clone https://github.com/mesonbuild/meson.git && \ ln -s /meson/meson.py /usr/local/bin/meson # build lib termpaint RUN git clone https://github.com/termpaint/termpaint && \ cd termpaint && \ meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build lib tuiwidgets RUN git clone https://github.com/tuiwidgets/tuiwidgets && \ cd tuiwidgets && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/pkgconfig meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix -Drpath=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build editor chr RUN git clone https://github.com/istoph/editor && \ cd editor && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/pkgconfig meson setup _build -Dsyntax_highlighting=true -Drpath=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # debug info RUN chr -v && \ chr -h editor-0.1.80/doc/build/Dockerfile.ubuntu:22.04000066400000000000000000000024001477475121100207320ustar00rootroot00000000000000FROM ubuntu:22.04 RUN apt update; DEBIAN_FRONTEND=noninteractive apt install -y build-essential git meson ninja-build pkg-config qt5-qmake qttools5-dev-tools qtbase5-dev libkf5syntaxhighlighting-dev libkf5syntaxhighlighting-tools cmake # build lib termpaint RUN git clone https://github.com/termpaint/termpaint && \ cd termpaint && \ meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build lib tuiwidgets RUN git clone https://github.com/tuiwidgets/tuiwidgets && \ cd tuiwidgets && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/pkgconfig meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix -Drpath=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build editor chr RUN git clone https://github.com/istoph/editor && \ cd editor && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/pkgconfig meson setup _build -Dsyntax_highlighting=true -Drpath=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # debug info RUN chr -v && \ chr -h editor-0.1.80/doc/build/Dockerfile.ubuntu:24.04000066400000000000000000000023771477475121100207510ustar00rootroot00000000000000FROM ubuntu:24.04 RUN apt update; DEBIAN_FRONTEND=noninteractive apt install -y build-essential git meson ninja-build pkg-config qt5-qmake qttools5-dev-tools qtbase5-dev libkf5syntaxhighlighting-dev libkf5syntaxhighlighting-tools cmake # build lib termpaint RUN git clone https://github.com/termpaint/termpaint && \ cd termpaint && \ meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build lib tuiwidgets RUN git clone https://github.com/tuiwidgets/tuiwidgets && \ cd tuiwidgets && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/pkgconfig meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix -Drpath=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build editor chr RUN git clone https://github.com/istoph/editor && \ cd editor && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/pkgconfig meson setup _build -Dsyntax_highlighting=true -Drpath=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # debug info RUN chr -v && \ chr -h editor-0.1.80/doc/build/Dockerfile.ubuntu:25.04000066400000000000000000000024001477475121100207350ustar00rootroot00000000000000FROM ubuntu:25.04 RUN apt update; DEBIAN_FRONTEND=noninteractive apt install -y build-essential git meson ninja-build pkg-config qt5-qmake qttools5-dev-tools qtbase5-dev libkf5syntaxhighlighting-dev libkf5syntaxhighlighting-tools cmake # build lib termpaint RUN git clone https://github.com/termpaint/termpaint && \ cd termpaint && \ meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build lib tuiwidgets RUN git clone https://github.com/tuiwidgets/tuiwidgets && \ cd tuiwidgets && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/pkgconfig meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix -Drpath=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # build editor chr RUN git clone https://github.com/istoph/editor && \ cd editor && \ PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/pkgconfig meson setup _build -Dsyntax_highlighting=true -Drpath=$HOME/opt/tuiwidgets-prefix/lib/$(uname -m)-linux-gnu/ && \ meson compile -C _build && \ meson install -C _build && \ cd .. # debug info RUN chr -v && \ chr -h editor-0.1.80/doc/build/README.md000066400000000000000000000040261477475121100162240ustar00rootroot00000000000000### How to build To build the editor on your system, you need a set of dependencies: * meson (>= 0.59.0) https://github.com/mesonbuild/meson * ninja-build https://github.com/ninja-build/ninja * pkg-config https://www.freedesktop.org/wiki/Software/pkg-config/ * qmake (>5.11.3) https://doc.qt.io/qt-5/qmake-manual.html * qtbase (>5.11.3) https://code.qt.io/cgit/qt/qtbase.git/log/?h=5.15 * termpaint (>= 0.3.0) https://github.com/termpaint/termpaint docs: https://termpaint.namepad.de/latest/ * posixsignalmanager (>= 0.3) https://github.com/textshell/posixsignalmanager/ * tuiwidgets (>= 0.2.1) https://github.com/tuiwidgets/tuiwidgets docs: https://tuiwidgets.namepad.de/latest/ For syntax highlighting: * libkf5syntaxhighlighting https://api.kde.org/frameworks/syntax-highlighting/html/index.html For building the tests without bundled copy: * catch https://github.com/catchorg/Catch2 ## How to build the editor # build termpaint library ``` git clone https://github.com/termpaint/termpaint cd termpaint meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix meson compile -C _build meson install -C _build cd .. ``` # build tuiwidgets and posixsignalmanager library ``` git clone https://github.com/tuiwidgets/tuiwidgets cd tuiwidgets PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/x86_64-linux-gnu/pkgconfig meson setup _build -Dprefix=$HOME/opt/tuiwidgets-prefix -Drpath=$HOME/opt/tuiwidgets-prefix/ lib/x86_64-linux-gnu/ meson compile -C _build meson install -C _build cd .. ``` # build chr editor ``` git clone https://github.com/istoph/editor cd editor PKG_CONFIG_PATH=$HOME/opt/tuiwidgets-prefix/lib/x86_64-linux-gnu/pkgconfig meson setup _build -Dsyntax_highlighting=true -Drpath=$HOME/opt/tuiwidgets-prefix/lib/x86_64-linux-gnu/ meson compile -C _build meson install -C _build cd .. ``` ## Dockerfiles These Dockerfiles are for testing whether the build still works on your distro. Or you can see how it works. https://github.com/istoph/editor/tree/main/doc/build/ ``` docker build -f Dockerfile.debian\:sid -t sid . docker run -it sid chr ``` editor-0.1.80/doc/install/000077500000000000000000000000001477475121100153125ustar00rootroot00000000000000editor-0.1.80/doc/install/README.md000066400000000000000000000015261477475121100165750ustar00rootroot00000000000000### Install from distro respositorys # Debian 13 Trixie ``` sudo apt install chr ``` # Ubuntu >= 24.10 ``` sudo apt install chr ``` # Ubuntu 22.04 - 24.04 (unofficial) See [PPA](https://launchpad.net/~chr-istoph/+archive/ubuntu/chr) ``` sudo apt install software-properties-common sudo add-apt-repository ppa:chr-istoph/chr sudo apt install chr ``` # Fedora and Rhel (unofficial) ``` dnf install -y 'dnf-command(copr)' dnf copr enable -y qsx42/tuiwidgets dnf install -y chr ``` # Gentoo (unofficial) ``` emerge -q dev-vcs/git pkgdev app-eselect/eselect-repository mkdir -p /etc/portage/repos.conf cat < /etc/portage/repos.conf/eselect-repo.conf [gentoo-chr] location = /var/db/repos/gentoo-chr auto-sync = yes sync-type = git sync-uri = https://github.com/istoph/gentoo-chr.git EOF emaint sync -r gentoo-chr emerge -a app-editors/chr ``` editor-0.1.80/manpages/000077500000000000000000000000001477475121100146725ustar00rootroot00000000000000editor-0.1.80/manpages/chr.1000066400000000000000000000336011477475121100155330ustar00rootroot00000000000000.\" SPDX-License-Identifier: BSL-1.0 .\" Manpage for chr .\" Make pull requests at: https://github.com/istoph/editor or create an issue for error corrections. .TH man 1 "06 Apr 2025" "0.1.80" "chr man page" .SH NAME chr \- chr is a terminal based editor .SH SYNOPSIS Usage: chr [options] [[+line[,char]] file …] [[+/searchword] file …] [/directory …] Options: -h, --help Displays help on commandline options. --help-all Displays help including Qt specific options. -v, --version Displays version information. -l, --line-number The line numbers are displayed -b, --big-file Open bigger files than 100MB -w, --wrap-lines Wrap log lines (NoWrap Default) --attributesfile Safe file for attributes, default ~/.cache/chr/chr.json -c, --config Load customized config file. The default if it exist is ~/.config/chr --syntax-highlighting-theme Name of syntax-highlighting-theme, you can list installed themes with: kate-syntax-highlighter --list-themes --disable-syntax disable syntax highlighting -s, --size Size in lines or for Automatic detection of the size of the lines with which the editor is displayed on the console. Arguments: [[+line[,char]] file …] Optional is the line number and position. Several files can be opened in multiple windows. [[+/searchword] file …] A search word can be set. [/directory …] Or a directory can be specified to search in the open dialog. .SH DESCRIPTION Chr is a terminal based text editor. Keyboard shortcuts are similar to the default editors in Gnome, KDE and other desktop environments. This is to ease workflows alternating between GUIs and terminal. The look and feel is a blend of modern GUI editors and late 90s PC text mode editors (e.g. Turbo Vision based or edit.com) adapted to fit into terminal based workflows. .SH Quick Start The important operations can be invoked from the menu. The menu can be opened with \fBF10\fP or with Alt together with the highlighted letter of the menu item (e.g. \fBAlt + f\fP). In dialogs \fBTab\fP is used to navigate between the elements. Use \fBF6\fP to navigate between windows/dialogs. Text can be marked in most terminals with Shift + arrow key. \fBCtrl + c\fP is used for copying. Paste with \fBCtrl + v\fP. Changes can be saved with \fBCtrl + s\fP and the editor can be exited with \fBCtrl + q\fP. .SH SHORT CUTS Shift + Cursor Selects text Ctrl + a Selects all text in the document Ctrl + c / Ctrl + Insert Copies the selected text into the clipboard Ctrl + d Deletes the current line Ctrl + e, up/down/left/right Switches the active window. Use the arrow keys to specify the direction for the next active window. Ctrl + e, Ctrl + up/down/left/right Change window size Ctrl + e, q Close an active document Ctrl + f Open the search dialog Ctrl + Backspace Delete a word (left from cursor position) Ctrl + r Open the replace dialog Ctrl + n Creates a window with an empty document Ctrl + m Creates a line marker at the left edge of the line to find lines again. (Ctrl + , oder Ctrl + .) This key combination does not work with all terminals. Ctrl + q Quits the editor Ctrl + Shift + q Close an active document (the key combination does not work in vte, see: Ctrl + e, q) Ctrl + s Save (or save as for new documents) Ctrl + v / Shift + Insert Inserts the contents of the clipboard at the current cursor position. Ctrl + x / Shift + Delete Cuts out the selected text and moves it to the clipboard Ctrl + y Redos an action that has been undone Ctrl + z Undoes an action Ctrl + Shift + up Moves the current selection or line upwards Ctrl + Shift + down Moves the current selection or line down Ctrl + Left Jump a word to the left Ctrl + Shift + Left Selects a word to the left Ctrl + Right Jump a word to the right Ctrl + Shift Right Selects a word to the right Alt + - Open the window menu Alt + Shift + up/down/left/right Marks the text in blocks. Inserting the clipboard duplicates the text per line. If an equal number of lines is marked as to be inserted, the lines from the clipboard will be distributed across the selected lines. Alt + Shift + S Sort the selected lines (lexicographical by code-point) Alt + x Opens a command line. Type "help" for help. Tab / Shift + Tab Indents a selected block by a tab stop or remove one level of indention F3 / Shift + F3 Find the next or previously search element F4 Toggles the selection mode to allow selecting text in terminals where marking with Shift + arrow keys does not work F6 / Shift + F6 Change active window, with Shift in reverse order ESC Closes an active dialog menu or action. .SH MENU .SH File .SS New Opens a new an empty unnamed document. .SS Open Opens a file dialog to select a file to be opened. .SS Save Saves the current status of the file. If the save path is not yet specified, the "Save as ..." dialog is opened. .SS Save as... A storage location to save the file to can be selected here via a file dialog. .SS Reload Reloads the current file. All changes are discarded. .SS Close Closes the active window. .SS Quit Closes the editor. If there is a file open that has not yet been saved, the Save dialog will be opened first. .SH Edit .SS Cut, Copy, Paste, Select all Text can be selected using the arrow keys while holding down the Shift key. The entire text can be selected with \fBSelect all\fP. This selected text can then be copied using \fBCopy\fP or cut using \fBCut\fP. With \fBPaste\fP, this text can be inserted again at the current cursor position. If there is text in the clipboard before copying (or cutting), it will be replaced. These functions use an internal clipboard that contains different content than the clipboard used in the terminal as copy and paste commands, as the editor cannot access the system clipboard. .SS Delete Line Deletes the entire line. .SS Select Mode Toggles the selection mode to allow selecting text in terminals where marking with Shift + arrow keys does not work. .SS Undo, Redo With \fBUndo\fP or CTRL + z, edits can be undone. With \fBRedo\fP or CTRL + y the undo can be undone again. .SS Search Use Search or Ctrl + f to open the search dialog. Enter a search term in the "Find" field. You can refine the search using the options. If live search is activated, the first matching result is automatically selected while the search term is being entered. If the text document is active, you can press F3 to jump to the next result or Shift + F3 to jump to the previous result. .SS Search Next Jump to the next match for the current search term. .SS Search Previous Jump to the previous match for the current search term. .SS Replace With Replace or CTRL + r the Replace dialog is opened. Enter a search term in the "Find" field. In the field "Replace" the word to be inserted is specified. "Next" jumps to the next match for the current search term. With "Replace" the current match is replaced. With "All" all occurrences of the search term are replaced at once. .SS Insert Character... Opens a dialog in which a character code (Unicode codepoint) of a special character to be inserted can be entered. .SS Goto To jump to a line, open a Goto Line dialog under "Goto". .SS Marker Creates a line marker in the left margin to quickly find lines again when reviewing. Use Ctrl + , or Ctrl + . to jump to the next marker. On quit the list of markers is saved in chr.json, so that it can be restored when the file is opened. .SS Sort Selected Lines Sort the selected lines (lexicographical by code-point). .SH Options .SS Tab settings Opens the Tab settings dialog. Here the settings for a tab can be made. You can choose between tab (\\t) and space. You can also set the width of the indention. The default settings can also be set in the ~/.config/chr file. Here you can specify: "tabsize=8" or "tab=false" for spaces. .SS Line Number Shows the line number on the left side of the editor. The default settings can also be made in the ~/.config/chr file. Here you can specify: "line_number=true". .SS Formatting In the Formatting dialog, "Formatting Characters", "Color Tabs" and "Color Spacs at end of line" can be switched on and off. The "Formatting characters" marks spaces with a dot: "·" end of line (\\n) with a "¶" and the end of the file with: "♦". With "Color Tabs" tabs are colorized. The tab border is made darker. "Color Spaces at end of line" is used to spaces mark at the end of the line in red. In the configuration file: ~/.config/chr the behavior can be influenced with the option "formatting_characters=true", "color_tabs=true", "color_space_end=true". .SS Wrap long lines Selects if lines that are wider than the window are displayed clipped or wrapped.. It can be wrapped at the word boundary or hard at the end of the line. This behavior can be influenced by the option "wrap_lines=WordWrap" or "wrap_lines=WrapAnywhere" in the ~/.config/chr file. In addition, the option "Display Right Margin at Column" can be used to specify a numerical value above which the background color is darkened. This value can also be set with the configuration option: "right_margin_hint=80" in ~/.config/chr. .SS Stop Input Pipe Reading from a pipe is interrupted. The standard input file descriptor is closed. .SS Highlight Brackets If active and the cursor is on a bracket the bracket at the cursor position and the matching other bracket are highlighted. The following opening and closing brackets can be highlighted when the cursor moves over them. With the option "highlight_bracket=true" this behavior can be influenced in the ~/.config/chr. Supported bracket types are: \fB[{(<>)}]\fP. .SS Syntax Highlighting If the editor has been compiled with the "SyntaxHighlighting" feature, syntax highlighting is generally available. The language is automatically detected when a file is opened and displayed in the status bar. If required, it can also be switched on and off or adjusted via the syntax highlighting dialog. Syntax highlighting can also be deactivated in this dialog. The theme can be customized via the command line switch "--syntax-highlighting-theme". The editor comes with the themes "chr-bluebg" and "chr-blackbg". If required, a theme from the list that can be displayed with "kate-syntax-highlighter --list-themes" can be used. With the option "syntax_highlighting_theme=chr-bluebg" the theme can be set in ~/.config/chr. Syntax highlighting can be switched off via the command line using "--disable-syntax" when the editor is started. With the option "disable_syntax=true" the theme can be set in ~/.config/chr. .SS Theme It opens the dialog for selecting a theme. The Classic (blue) or the Dark (black and white) mode is available. With the option "theme=classic" or "theme=dark", this can be set in the ~/.config/chr. .SH Window .SS Next, Previous Switches the active window, with Shift in reverse order. (See F6) .SS Tile Vertically, Horizontally, Fullscreen Selects how multiple open documents are shown. Vertical and horizontal distribute the available space across the documents. When Fullscreen is selected only one document is shown at once. (See F6) .SH CUSTOM CONFIG The editor loads a configuration file from \fB~/.config/chr\fP (if available). (If the environment variable \fB$XDG_CONFIG_HOME\fP is set, then from \fB$XDG_CONFIG_HOME/chr\fP) In addition to the options documented above, the following options are available: .SS eat_space_before_tabs This option is only active if \fBtab=false\fP is set. If this option is active and the Tab key is pressed while the cursor is in the indentation at the beginning of a line, the indentation is extended to the next tab position. .SS attributes_file Specifies the path of the file in which the cursor and scroll position of files opened in the past is saved. .SH Default config There is a default config (~/.config/chr) where the following options can be set. .EX attributes_file="/home/user/.cache/chr/chr.json" color_space_end=false color_tabs=false disable_syntax=false eat_space_before_tabs=true formatting_characters=false highlight_bracket=true line_number=false logfile="" right_margin_hint=0 syntax_highlighting_theme="chr-bluebg" tab=false tab_size=4 theme="classic" wrap_lines="NoWrap" .EE .SH FILES ~/.config/chr Your personal chr initializations. ~/.cache/chr/chr.json History about the changed files. This is where cursor positions are stored. .SH BUGS Errors in this software can be reported via the bugtracker on https://github.com/istoph/editor. .SH AUTHOR Christoph Hüffelmann Martin Hostettler editor-0.1.80/manpages/chr.de.1000066400000000000000000000365731477475121100161350ustar00rootroot00000000000000.\" SPDX-License-Identifier: BSL-1.0 .\" Manpage für chr .\" Mach Pull Requests auf: https://github.com/istoph/editor oder erstelle ein Issue für Fehlerkorrekturen. .TH man 1 "06 Apr 2025" "0.1.80" "chr man page" .SH NAME chr \- chr ist ein terminal-basierter Editor .SH SYNOPSIS Usage: chr [options] [[+line[,char]] file …] [[+/searchword] file …] [/directory …] Options: -h, --help Displays help on commandline options. --help-all Displays help including Qt specific options. -v, --version Displays version information. -l, --line-number The line numbers are displayed -b, --big-file Open bigger files than 100MB -w, --wrap-lines Wrap log lines (NoWrap Default) --attributesfile Safe file for attributes, default ~/.cache/chr/chr.json -c, --config Load customized config file. The default if it exist is ~/.config/chr --syntax-highlighting-theme Name of syntax-highlighting-theme, you can list installed themes with: kate-syntax-highlighter --list-themes --disable-syntax disable syntax highlighting -s, --size Size in lines or for Automatic detection of the size of the lines with which the editor is displayed on the console. Arguments: [[+line[,char]] file …] Optional is the line number and position. Several files can be opened in multiple windows. [[+/searchword] file …] A search word can be set. [/directory …] Or a directory can be specified to search in the open dialog. .SH DESCRIPTION chr ist ein terminal-basierter Texteditor. Die Tastenkombinationen ähneln denen der Standard-Editoren in Gnome, KDE und anderen Desktopumgebungen. Dies soll die wechselnde Verwendung von GUI und Terminal Editoren erleichtern. Das Erscheinungsbild und Verhalten ist eine Kombination aus modernen GUI-Editoren und PC-Textmodus-Editoren der späten 90er Jahre (z. B. Turbo Vision oder edit.com), angepasst an terminalbasierte Arbeitsabläufe. .SH Schnellstart Die wichtigen Operationen sind im Menü aufrufbar. Das Menü kann mit \fBF10\fP oder mit Alt zusammen mit dem hervorgehobenen Buchstaben des Menüpunktes geöffnet werden (z. B. \fBAlt + f\fP). In Dialogen wird mit \fBTab\fP zwischen den Elementen navigiert und \fBF6\fP navigiert zwischen Fenster/Dialogen. Text kann in den meisten Terminals mit Shift + Pfeiltaste markiert werden. Zum Kopieren wird \fBCtrl + c\fP verwendet. Einfügen mit \fBCtrl + v\fP. Änderungen können mit \fBCtrl + s\fP gespeichert werden und der Editor mit \fBCtrl + q\fP verlassen werden. .SH SHORT CUTS Shift + Cursor Markiert Text Ctrl + a Markiert den gesamten Text Ctrl + c / Ctrl + Insert Kopiert den markierten Text in den Zwischenablage Ctrl + d Löscht die aktuelle Zeile Ctrl + e, hoch/runter/links/rechts Wechselt das aktive Fenster. Mit den Pfeiltasten gibt man die Richtung des nächsten aktiven Fensters an Ctrl + e, Ctrl + hoch/runter/links/rechts Fenstergröße verändern Ctrl + e, q Schließt das aktive dokument Ctrl + f Öffnet den Search-Dialog für die Suche Ctrl + Backspace Löscht ein Wort (links vom Cursor) Ctrl + r Öffnet den Replace-Dialog zum Suchen und Ersetzen Ctrl + n Erstellt ein neues Fenster mit einem leeren Textdokument Ctrl + m Erstellt einen Line Marker am linken Zeilenrand, um Zeilen wiederzufinden. (Ctrl + , oder Ctrl + .) Diese Tastenkombination funktioniert nicht mit allen Terminals. Ctrl + q Programm verlassen Ctrl + Shift + q Schließt das aktive Dokument (die Tastenkombination funktioniert aber nicht in vte, nutze dafür Ctrl + e, q) Ctrl + s Speichern oder Speichern unter... Ctrl + v / Shift + Insert Fügt den Inhalt der Zwischenablage an der Cursorposition ein Ctrl + x / Shift + Delete Schneidet den markierten Text aus und verschiebt ihn in die Zwischenablage Ctrl + y Stellt eine rückgängig gemachte Aktion wieder her Ctrl + z Macht eine Aktion rückgängig Ctrl + Shift + up Verschiebt die aktuelle Auswahl oder Zeile nach oben Ctrl + Shift + down Verschiebt die aktuelle Auswahl oder Zeile nach unten Ctrl + Left Springt ein Wort nach links Ctrl + Shift + Left Markiert ein Wort nach links Ctrl + Right Springt ein Wort nach rechts Ctrl + Shift Right Markiert ein Wort nach rechts Alt + - Öffnet das Fenster-Menü Alt + Shift + hoch/runter/links/rechts Markiert den Text in Blöcken. Das Einfügen der Zwischenablage dupliziert den Text je Zeile. Stimmt beim Einfügen die Anzahl von Zeilen in der Zwischenablage mit der Anzahl der markierten Zeilen überein, werden die Zeilen aus der Zwischenablage auf die markierten Zeilen verteilt. Alt + Shift + S Markierte Zeilen werden alphabetisch (lexikografisch nach Codepoint) sortiert Alt + x Öffnet eine Kommandozeile. Für weitere Hilfe "help" eintippen Tab / Shift + Tab Rückt einen markierten Block um einen Tabulator ein oder entfernt diesen F3 / Shift + F3 Springt zum nächsten oder vorherigen Suchwort F4 Wechselt den Markierungsmodus, um das Markieren in Terminals, in denen Markierung mit Shift + Pfeiltasten nicht funktioniert, zu ermöglichen F6 / Shift + F6 Wechselt das aktive Fenster, mit Shift in umgekehrter Reihenfolge Esc Schließt einen aktiven Dialog, ein Menü oder beendet eine Aktion .SH Menu .SH File .SS New Erstellt ein neues Fenster mit einem leeren Textdokument. .SS Open Öffnet einen Dateidialog, um eine zu öffnende Datei auszuwählen. .SS Save Speichert den aktuellen Stand der Datei. Sollte der Speicherpfad noch nicht angegeben sein, wird "Save as..." ausgeführt. .SS Save as... Öffnet einen Dateidialog, um einen Speicherort aktuellen Stand des Textdokuments auszuwählen und speichert den aktuellen Stand. .SS Reload Lädt die aktuelle Datei neu. Dabei werden alle Änderungen verworfen. .SS Close Schließt das aktive Fenster. .SS Quit Beendet den Editor. Sollte noch ein ungespeichertes Textdokument geöffnet sein, wird zuvor der Speichern-Dialog aufgerufen. .SH Edit .SS Cut, Copy, Paste, Select all Mit den Pfeiltasten und dem gleichzeitigen gedrückt halten der Shifttaste kann Text markiert werden. Der gesamte Text kann mit \fBSelect all\fP markiert werden. Dieser markierte Text kann dann mittels \fBCopy\fP kopiert oder mit \fBCut\fP ausgeschnitten werden. Mit \fBPaste\fP kann dieser Text an der aktuellen Cursorposition wieder eingefügt werden. Befindet sich vor dem Kopieren (oder Ausscheiden) Text in der Zwischenablage, so wird dieser ersetzt. Diese Funktionen verwenden eine interne Zwischenablage, die unterschiedlichen Inhalt enthält als die ggf. im Terminal als Copy und Paste Befehle verwendete Zwischenablage, da der Editor die System-Zwischenablage nicht zugreifen kann. .SS Delete Line Die gesamte Zeile wird gelöscht. .SS Select Mode Wechselt den Markierungsmodus, um das Markieren in Terminals, in denen Markierung mit Shift + Pfeiltasten nicht funktioniert, zu ermöglichen. .SS Undo, Redo Mit \fBUndo\fP oder Ctrl + z können Eingaben rückgängig gemacht werden. Mit \fBRedo\fP oder Ctrl + y können rückgängig gemachte Änderungen wiederhergestellt werden. .SS Search Mit Search oder Ctrl + f wird der Suchen-Dialog geöffnet. Unter "Find" gibt man ein Suchwort ein. Über die Optionen kann man die Suche verfeinern. Ist Livesuche aktiviert, so wird während der Eingabe des Suchbegriffs automatisch das erste passende Ergebnis ausgewählt. Ist das Textdokument aktiv, kann mit F3 zur nächste bzw. mit Shift + F3 zur vorherigen Fundstelle gesprungen werden. .SS Search Next Springt zur nächsten Fundstelle des aktuellen Suchbegriffs. .SS Search Previous Springt zur vorherigen Fundstelle des aktuellen Suchbegriffs. .SS Replace Mit Replace oder Ctrl + r wird der "Ersetzen"-Dialog geöffnet. Im Feld "Find" wird das Suchwort angegeben. Im Feld "Replace" wird das Wort angegeben, das eingefügt werden soll. Mit "Next" wird die nächste Fundstelle gesucht. Mit "Replace" wird das Suchwort ersetzt. Mit "All" werden alle Fundstellen ersetzt. .SS Insert Character... Öffnet einen Dialog, in dem ein Zeichencode (Unicode codepoint) eines einzufügenden Sonderzeichens eingegeben werden kann. .SS Goto Öffnet einen Dialog, um zu einer Zeile zu springen. .SS Marker Erstellt am linken Rand einen Line Marker, um Zeilen bei der Durchsicht schnell wiederzufinden. Mithilfe von Ctrl + , oder Ctrl + . wird an den jeweils nächsten Marker gesprungen. Die Liste von Markern wird beim Beenden in chr.json gespeichert, um sie, beim Öffnen der Datei, wiederherzustellen. .SS Sort Selected Lines Markierte Zeilen werden alphabetisch (lexikografisch nach Codepoint) sortiert. .SH Options .SS Tab settings Öffnet den Tab-Settings-Dialog. Hier können die Einstellungen für die Einrückung vorgenommen werden. Es kann zwischen Tab (\\t) und Leerzeichen gewählt werden. Zudem kann die Breite der Einrückungen festgelegt werden. Die Standardeinstellungen können auch in der ~/.config/chr Datei vorgenommen werden. Hier kann: "tab_size=8" oder "tab=false" für Leerzeichen angegeben werden. .SS Line Number Schaltet die Darstellung der Zeilennummern auf der linken Seite des Editors ein. Die Standardeinstellungen können auch in der ~/.config/chr Datei vorgenommen werden. Hier kann: "line_number=true" angegeben werden. .SS Formatting Im Formatting-Dialog können "Formatting Characters", "Color Tabs" und "Color Spacs at end of line" ein und ausgestaltet werden. "Formatting characters" kennzeichnen Leerzeichen mit einem Punkt: "·", Zeilenenden (\\n) durch ein "¶" und das Ende der Datei mit: "♦". Mit "Color Tabs" werden Tabs farblich hervorgehoben. Hierbei wird die Tabgrenze dunkler dargestellt. Mit "Color Spacs at end of line" werden Leerzeichen am Ende der Zeile rot markiert. In der Konfigurationsdatei: ~/.config/chr kann mit der Option "formatting_characters=true", "color_tabs=true", "color_space_end=true" das Verhalten eingestellt werden. .SS Wrap long lines Hier kann eingestellt werden, ob Zeilen, die breiter als das Fenster sind, abgeschnitten oder umgebrochen dargestellt werden. Es kann an der Wortgrenze oder am Zeilenende hart umgebrochen werden. Diese Verhalten kann über die Option "wrap_lines=WordWrap" oder "wrap_lines=WrapAnywhere" in der ~/.config/chr Datei beeinflusst werden. Zudem kann mit der Option: "Display Right Margin at Column" ein numerischer Wert angegeben werden, ab dem die Hintergrundfarbe dunkel gefärbt wird. Dieser Wert lest sich auch mit der Konfigurationsoption: "right_margin_hint=80" in der ~/.config/chr einstellen. .SS Stop Input Pipe Einlesen von einer pipe wird unterbrochen. Der Standard-Eingabedatei-Deskriptor wird geschlossen. .SS Highlight Brackets Wenn aktiv und der Cursor auf einer Klammer steht, wird die Klammer an der Cursorposition und die zugehörige andere Klammer hervorgehoben. Mit der Option "highlight_bracket=false" kann dieses Verhalten in der ~/.config/chr eingestellt werden. Unterstützte Klammertypen sind: \fB[{(<>)}]\fP. .SS Syntax Highlighting Wenn der Editor mit dem Feature "SyntaxHighlighting" compiliert wurden, steht das Syntax Highlighting generell zur Verfügung. Die Sprache wird beim Öffnen einer Datei automatisch erkannt und in der Statusbar angezeigt. Bei Bedarf kann diese aber auch über das Syntax Highlighting Dialog ein uns aus bzw. angepasst werden. In diesem Dialog kann das Syntax Highlighting auch deaktiviert werden. Über die command line kann "--syntax-highlighting-theme" kann der Theme angepasst werden. Der Editor bringt bereits die Themes "chr-bluebg" und "chr-blackbg" mit. Bei Bedarf kann ein Theme aus der Liste, die mit "kate-syntax-highlighter --list-themes" anzeigbar ist, benutzt werden. Mit der Option "syntax_highlighting_theme=chr-bluebg" kann der Theme in der ~/.config/chr eingestellt werden. Über die command line kann mittels "--disable-syntax" das Syntax Highlighting beim Starten des Editors ausgeschaltet werden. Mit der Option "disable_syntax=true" kann der Theme in der ~/.config/chr eingestellt werden. .SS Theme Es öffnet den Theme-Dialog zum auswählen eines Theme. Es steht der "Classic" (Blau) oder der "Dark" (schwarz weiß) Theme zur Verfügung. Mit der Option "theme=classic" oder "theme=dark", kann dies in der ~/.config/chr eingestellt werden. .SH Window .SS Next, Previous Wechselt das aktive Fenster, mit Shift in umgekehrter Reihenfolge. (Siehe F6) .SS Tile Vertically, Horizontally, Fullscreen Wählt aus, wie mehrere offene Textdokumente angezeigt werden. Vertikal und Horizontal teilen den verfügbaren Platz automatisch auf die Dokumentenfenster auf. Wird Fullscreen gewählt, ist jeweils nur ein Dokumentenfenster gleichzeitig sichtbar. (Siehe F6) .SH Konfigurationsdatei Der Editor lädt (falls vorhanden) eine Konfigurationsdatei aus \fB~/.config/chr\fP. (Wenn die Environmentvariable \fB$XDG_CONFIG_HOME\fP gesetzt ist, dann aus \fB$XDG_CONFIG_HOME/chr\fP) Zusätzlich zu den oben dokumentieren Optionen sind folgende Optionen verfügbar: .SS eat_space_before_tabs Diese Option ist nur aktiv, wenn \fBtab=false\fP gesetzt ist. Ist diese Option aktiv und wird die Tab-Taste gedrückt, während der Cursor in der Einrückung am Anfang einer Zeile steht, so wird die Einrückung auf die nächste Tabposition erweitert. .SS attributes_file Gibt den Pfad der Datei an, in der die Cursor- und Scrollposition in der Vergangenheit geöffneter Dateien gespeichert wird. .SH Default config Es gibt eine default Config (~/.config/chr) in der folgenden Optionen gesetzt werden können. .EX attributes_file="/home/user/.cache/chr/chr.json" color_space_end=false color_tabs=false disable_syntax=false eat_space_before_tabs=true formatting_characters=false highlight_bracket=true line_number=false logfile="" right_margin_hint=0 syntax_highlighting_theme="chr-bluebg" tab=false tab_size=4 theme="classic" wrap_lines="NoWrap" .EE .SH FILES ~/.config/chr Your personal chr initializations. ~/.cache/chr/chr.json History über die geänderten Dateien. Hierin werden Positionen von Cursor gespeichert. .SH BUGS Fehler in dieser Software können über den Bugtracker auf https://github.com/istoph/editor gemeldet werden. .SH AUTHOR Christoph Hüffelmann Martin Hostettler editor-0.1.80/meson.build000066400000000000000000000025221477475121100152420ustar00rootroot00000000000000# SPDX-License-Identifier: BSL-1.0 project('editor', ['c', 'cpp'], default_options : ['buildtype=debugoptimized']) if meson.get_compiler('cpp').get_id() == 'gcc' add_project_arguments('-fvisibility=hidden', '-fvisibility-inlines-hidden', language: 'cpp') endif add_project_arguments('-std=gnu17', language : 'c') add_project_arguments('-Wall', '-Wextra', language: 'c') add_project_arguments('-Werror=return-type', '-Werror=implicit-function-declaration', language: 'c') add_project_arguments('-std=gnu++17', language : 'cpp') add_project_arguments('-Wall', '-Wextra', language: 'cpp') add_project_arguments('-Werror=return-type', language: 'cpp') qt5 = import('qt5') qt5_dep = dependency('qt5', modules: ['Core', 'Concurrent']) tuiwidgets_dep = dependency('TuiWidgets', version: '>= 0.2.1') if tuiwidgets_dep.version().version_compare('>= 0.2.2') add_project_arguments('-DHAS_TUIWIDGETS_0_2_2', language: 'cpp') endif posixsignalmanager_dep = dependency('PosixSignalManager') if get_option('syntax_highlighting') syntax_dep = declare_dependency(dependencies:dependency('KF5SyntaxHighlighting'), compile_args: ['-DSYNTAX_HIGHLIGHTING']) syntax_qrc = qt5.compile_resources(sources: 'syntax.qrc') else syntax_dep = [] syntax_qrc = [] endif install_man('manpages/chr.1') install_man('manpages/chr.de.1', locale: 'de') subdir('src') editor-0.1.80/meson_options.txt000066400000000000000000000005671477475121100165440ustar00rootroot00000000000000# SPDX-License-Identifier: BSL-1.0 option('rpath', type : 'string', value : '') option('syntax_highlighting', type: 'boolean', value: false, description: 'enable syntax highlighting (needs KF5SyntaxHighlighting)') option('system-catch2', type : 'feature', value : 'disabled') option('tests', type : 'boolean', value : true) option('version', type : 'string', value : 'git') editor-0.1.80/src/000077500000000000000000000000001477475121100136665ustar00rootroot00000000000000editor-0.1.80/src/aboutdialog.cpp000066400000000000000000000033151477475121100166660ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "aboutdialog.h" #include #include #include #include #include AboutDialog::AboutDialog(Tui::ZWidget *parent) : Tui::ZDialog(parent) { setOptions(Tui::ZWindow::CloseOption | Tui::ZWindow::MoveOption | Tui::ZWindow::AutomaticOption); setFocus(); setWindowTitle("About chr"); setContentsMargins({ 1, 1, 1, 1}); Tui::ZVBoxLayout *vbox = new Tui::ZVBoxLayout(); setLayout(vbox); vbox->setSpacing(1); Tui::ZLabel *nameLabel = new Tui::ZLabel(this); nameLabel->setText(QCoreApplication::applicationVersion()); vbox->addWidget(nameLabel); Tui::ZLabel *authorLabel = new Tui::ZLabel(this); authorLabel->setText("Authors: Christoph Hüffelmann and Martin Hostettler"); vbox->addWidget(authorLabel); Tui::ZLabel *licenseLabel = new Tui::ZLabel(this); licenseLabel->setText("License: Boost Software License - Version 1.0"); vbox->addWidget(licenseLabel); #ifdef SYNTAX_HIGHLIGHTING Tui::ZLabel *moduleLabel = new Tui::ZLabel(this); moduleLabel->setText("Enabled Features: SyntaxHighlighting"); vbox->addWidget(moduleLabel); #endif Tui::ZLabel *githubLabel = new Tui::ZLabel(this); githubLabel->setText("Bugtracker: https://github.com/istoph/editor"); vbox->addWidget(githubLabel); Tui::ZHBoxLayout *hbox1 = new Tui::ZHBoxLayout(); hbox1->addStretch(); Tui::ZButton *okButton = new Tui::ZButton(this); okButton->setText("OK"); okButton->setDefault(true); hbox1->addWidget(okButton); vbox->add(hbox1); QObject::connect(okButton, &Tui::ZButton::clicked, this, &AboutDialog::deleteLater); } editor-0.1.80/src/aboutdialog.h000066400000000000000000000003561477475121100163350ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef ABOUTDIALOG_H #define ABOUTDIALOG_H #include class AboutDialog : public Tui::ZDialog { Q_OBJECT public: AboutDialog(Tui::ZWidget *parent); }; #endif // ABOUTDIALOG_H editor-0.1.80/src/alert.cpp000066400000000000000000000021761477475121100155070ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "alert.h" #include #include Alert::Alert(Tui::v0::ZWidget *parent) : Tui::ZDialog(parent) { setFocusPolicy(Tui::StrongFocus); } QString Alert::markup() const { return _styledText.markup(); } void Alert::setMarkup(QString m) { _styledText.setMarkup(m); } void Alert::paintEvent(Tui::v0::ZPaintEvent *event) { Tui::ZDialog::paintEvent(event); _styledText.setBaseStyle({getColor("control.fg"), getColor("control.bg")}); //_styledText.setWidth(geometry().width() - 4); auto *painter = event->painter(); //_styledText.write(painter, 2, 2, geometry().width() - 4, geometry().height() - 4); _styledText.write(painter, 2, 2, 47); painter->writeWithColors(3, geometry().height() - 1, " Press ESC or Enter to dismiss ", getColor("control.fg"), getColor("control.bg")); } void Alert::keyEvent(Tui::v0::ZKeyEvent *event) { if (event->key() == Qt::Key_Escape || event->key() == Qt::Key_Enter) { deleteLater(); } else { Tui::ZDialog::keyEvent(event); } } editor-0.1.80/src/alert.h000066400000000000000000000007721477475121100151540ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef ALERT_H #define ALERT_H #include #include #include class Alert : public Tui::ZDialog { Q_OBJECT public: explicit Alert(Tui::ZWidget *parent); public: QString markup() const; void setMarkup(QString m); protected: void paintEvent(Tui::ZPaintEvent *event) override; void keyEvent(Tui::ZKeyEvent *event) override; private: Tui::ZStyledTextLine _styledText; }; #endif // ALERT_H editor-0.1.80/src/attributes.cpp000066400000000000000000000122031477475121100165560ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "attributes.h" #include #include #include #include #include #include #include Attributes::Attributes() { Attributes(""); } Attributes::Attributes(QString attributesFile) { setAttributesFile(attributesFile); } bool Attributes::readAttributes() { if (_attributesFile.isEmpty()) { return false; } if (_loaded) { return true; } QFile fileRead(_attributesFile); if (!fileRead.open(QIODevice::ReadOnly | QIODevice::Text)) { //qDebug() << "File open error"; return false; } QString val = fileRead.readAll(); fileRead.close(); QJsonDocument jd = QJsonDocument::fromJson(val.toUtf8()); _attributeObject = jd.object(); _loaded = true; return true; } Tui::ZDocumentCursor::Position Attributes::getAttributesCursorPosition(QString filename) { if (readAttributes()) { QJsonObject data = _attributeObject.value(filename).toObject(); // Convert from old attributes format, can be removed in the future if (data.contains("cursorPositionX") && data.contains("cursorPositionY")) { return {data.value("cursorPositionX").toInt(), data.value("cursorPositionY").toInt()}; } return {data.value("curOff").toInt(), data.value("curLine").toInt()}; } return {0, 0}; } int Attributes::getAttributesScrollCol(QString filename) { if (readAttributes()) { if (!_attributeObject.isEmpty() && _attributeObject.contains(filename)) { QJsonObject data = _attributeObject.value(filename).toObject(); return data.value("sCol").toInt(); } } return 0; } int Attributes::getAttributesScrollLine(QString filename) { if (readAttributes()) { if (!_attributeObject.isEmpty() && _attributeObject.contains(filename)) { QJsonObject data = _attributeObject.value(filename).toObject(); return data.value("sLine").toInt(); } } return 0; } int Attributes::getAttributesScrollFine(QString filename) { if (readAttributes()) { if (!_attributeObject.isEmpty() && _attributeObject.contains(filename)) { QJsonObject data = _attributeObject.value(filename).toObject(); return data.value("sFine").toInt(); } } return 0; } QList Attributes::getAttributesLineMarker(QString filename) { QList lineMarker; if (readAttributes()) { if (!_attributeObject.isEmpty() && _attributeObject.contains(filename)) { QJsonObject data = _attributeObject.value(filename).toObject(); QJsonArray jsonArray = data["lineMarker"].toArray(); for (const QJsonValue &value : jsonArray) { lineMarker.append(value.toInt()); } } } return lineMarker; } bool Attributes::writeAttributes(QString filename, Tui::ZDocumentCursor::Position cursorPosition, int scrollPositionColumn, int scrollPositionLine, int scrollPositionFineLine, QList lineMarkers) { QFileInfo filenameInfo(filename); if (!filenameInfo.exists() || _attributesFile.isEmpty()) { return false; } readAttributes(); const auto [cursorCodeUnit, cursorLine] = cursorPosition; QJsonObject data; data.insert("curOff", cursorCodeUnit); data.insert("curLine", cursorLine); data.insert("sCol", scrollPositionColumn); data.insert("sLine", scrollPositionLine); data.insert("sFine", scrollPositionFineLine); QJsonArray jsonArray; for (int i = 0; i < lineMarkers.size(); i++) { jsonArray.append(lineMarkers.at(i)); } data.insert("lineMarker", jsonArray); _attributeObject.insert(filenameInfo.absoluteFilePath(), data); QJsonDocument jsonDoc; jsonDoc.setObject(_attributeObject); //Save QDir d = QFileInfo(_attributesFile).absoluteDir(); QString absolute = d.absolutePath(); if (!QDir(absolute).exists()) { if (QDir().mkdir(absolute)) { qWarning("%s%s", "can not create directory: ", absolute.toUtf8().data()); return false; } } QSaveFile file(_attributesFile); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qWarning("%s%s", "can not save attributes file: ", _attributesFile.toUtf8().data()); return false; } file.write(jsonDoc.toJson()); file.commit(); return true; } void Attributes::setAttributesFile(QString attributesFile) { if (!attributesFile.isEmpty() && !attributesFile.isNull()) { QFileInfo file(attributesFile); QString dirPath = file.path(); QFileInfo dirInfo(dirPath); if (file.isReadable() || dirInfo.isWritable()) { _attributesFile = file.absoluteFilePath(); } else { static std::once_flag warningPrinted; std::call_once(warningPrinted, [&] { qWarning("attributesFile '%s' is not readable or writable", attributesFile.toUtf8().data()); }); _attributesFile = ""; } } else { _attributesFile = ""; } } QString Attributes::attributesFile() { return _attributesFile; } editor-0.1.80/src/attributes.h000066400000000000000000000017451477475121100162340ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef ATTRIBUTES_H #define ATTRIBUTES_H #include #include "Tui/ZDocumentCursor.h" #include "qjsonobject.h" #include class Attributes { public: Attributes(); Attributes(QString attributesFile); bool readAttributes(); Tui::ZDocumentCursor::Position getAttributesCursorPosition(QString filename); bool writeAttributes(QString filename, Tui::ZDocumentCursor::Position cursorPosition, int scrollPositionColumn, int scrollPositionLine, int scrollPositionFineLine, QList lineMarkers); void setAttributesFile(QString attributesFile); QString attributesFile(); int getAttributesScrollCol(QString filename); int getAttributesScrollLine(QString filename); int getAttributesScrollFine(QString filename); QList getAttributesLineMarker(QString filename); private: bool _loaded = false; QJsonObject _attributeObject; QString _attributesFile; }; #endif // ATTRIBUTES_H editor-0.1.80/src/commandlinewidget.cpp000066400000000000000000000034511477475121100200670ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "commandlinewidget.h" #include #include #include #include #include CommandLineWidget::CommandLineWidget(Tui::v0::ZWidget *parent) : Tui::ZWidget(parent) { setMaximumSize(Tui::tuiMaxSize, 1); setSizePolicyH(Tui::SizePolicy::Expanding); setSizePolicyV(Tui::SizePolicy::Fixed); cmdEntry.setParent(this); auto pal = cmdEntry.palette(); pal.setColors({ { "lineedit.focused.bg", Tui::Colors::black}, { "lineedit.focused.fg", Tui::Colors::lightGray}, { "lineedit.bg", Tui::Colors::black}, { "lineedit.fg", Tui::Colors::lightGray}}); cmdEntry.setPalette(pal); } void CommandLineWidget::setCmdEntryText(QString text) { cmdEntry.setText(text); } QSize CommandLineWidget::sizeHint() const { return { 0, 1 }; } void CommandLineWidget::paintEvent(Tui::ZPaintEvent *event) { Tui::ZColor bg = Tui::Colors::black; Tui::ZColor fg = Tui::Colors::lightGray; auto *painter = event->painter(); painter->clear(fg, bg); painter->writeWithColors(0, 0, "> ", fg, bg); } void CommandLineWidget::keyEvent(Tui::ZKeyEvent *event) { if (event->key() == Qt::Key_Escape) { releaseKeyboard(); cmdEntry.setText(""); dismissed(); } else if (event->key() == Qt::Key_Enter && event->modifiers() == 0) { releaseKeyboard(); QString cmd = cmdEntry.text(); cmdEntry.setText(""); execute(cmd); } else { cmdEntry.event(event); } } void CommandLineWidget::pasteEvent(Tui::ZPasteEvent *event) { cmdEntry.event(event); } void CommandLineWidget::resizeEvent(Tui::ZResizeEvent *event) { cmdEntry.setGeometry({2, 0, event->size().width() - 2, 1}); ZWidget::resizeEvent(event); } editor-0.1.80/src/commandlinewidget.h000066400000000000000000000013001477475121100175230ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef COMMANDLINEWIDGET_H #define COMMANDLINEWIDGET_H #include #include class CommandLineWidget : public Tui::ZWidget { Q_OBJECT public: CommandLineWidget(Tui::ZWidget *parent); void setCmdEntryText(QString text); public: QSize sizeHint() const override; signals: void dismissed(); void execute(QString command); protected: void paintEvent(Tui::ZPaintEvent *event); void keyEvent(Tui::ZKeyEvent *event) override; void pasteEvent(Tui::ZPasteEvent *event) override; void resizeEvent(Tui::ZResizeEvent *event) override; private: Tui::ZInputBox cmdEntry; }; #endif // COMMANDLINEWIDGET_H editor-0.1.80/src/confirmsave.cpp000066400000000000000000000053161477475121100167130ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "confirmsave.h" #include #include #include #include ConfirmSave::ConfirmSave(Tui::ZWidget *parent, QString filename, Type type, bool saveable) : Tui::ZDialog(parent) { setOptions(Tui::ZWindow::MoveOption | Tui::ZWindow::AutomaticOption); QString title, nosave, save, mainLabel; if (type == Close) { title = "Close"; nosave = "Discard"; if (saveable) { save = "Save"; } else { save = "Save as"; } mainLabel = "Save changes to \"" + filename + "\"?"; } else if (type == CloseUnnamed) { title = "Close"; nosave = "Discard"; save = "Save as"; mainLabel = "Save changes before closing?"; } else if (type == Reload) { title = "Reload"; nosave = "Discard and Reload"; // `save` is not used in this mode save = ""; mainLabel = "Discard changes to \"" + filename + "\"?"; } else if (type == QuitUnnamed) { title = "Exit"; nosave = "Quit"; save = "Save as and Quit"; mainLabel = "Save changes before closing?"; } else { title = "Exit"; nosave = "Quit"; if (saveable) { save = "Save and Quit"; } else { save = "Save as and Quit"; } mainLabel = "Save changes to \"" + filename + "\"?"; } //Dialog Box setWindowTitle(title); setContentsMargins({1, 1, 1, 1}); Tui::ZVBoxLayout *vbox = new Tui::ZVBoxLayout(); setLayout(vbox); vbox->setSpacing(1); //Label Tui::ZLabel *l1 = new Tui::ZLabel(this); l1->setText(mainLabel); vbox->addWidget(l1); vbox->addStretch(); Tui::ZHBoxLayout* hbox = new Tui::ZHBoxLayout(); hbox->addStretch(); //Cancel Tui::ZButton *bCancel = new Tui::ZButton(this); if (type == Reload) { bCancel->setDefault(true); bCancel->setFocus(); } bCancel->setText("Cancel"); hbox->addWidget(bCancel); //Discard Tui::ZButton *bDiscard = new Tui::ZButton(this); bDiscard->setText(nosave); hbox->addWidget(bDiscard); hbox->setSpacing(1); //Save if (type != Reload) { Tui::ZButton *bSave = new Tui::ZButton(this); bSave->setText(save); bSave->setDefault(true); bSave->setFocus(); hbox->addWidget(bSave); QObject::connect(bSave, &Tui::ZButton::clicked, this, &ConfirmSave::saveSelected); } vbox->add(hbox); QObject::connect(bCancel, &Tui::ZButton::clicked, this, &ConfirmSave::rejected); QObject::connect(bDiscard, &Tui::ZButton::clicked, this, &ConfirmSave::discardSelected); } editor-0.1.80/src/confirmsave.h000066400000000000000000000006551477475121100163610ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef CONFIRMSAVE_H #define CONFIRMSAVE_H #include class ConfirmSave : public Tui::ZDialog { public: enum Type { Reload, Close, CloseUnnamed, Quit, QuitUnnamed }; Q_OBJECT public: explicit ConfirmSave(Tui::ZWidget *parent, QString filename, Type type, bool saveable); signals: void saveSelected(); void discardSelected(); }; #endif // CONFIRMSAVE_H editor-0.1.80/src/dlgfilemodel.cpp000066400000000000000000000035541477475121100170300ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "dlgfilemodel.h" DlgFileModel::DlgFileModel(const QDir &dir) : AbstractTableModelTrackBy(1), _dir(dir) { _watcher.addPath(dir.absolutePath()); QObject::connect(&_watcher, &QFileSystemWatcher::directoryChanged, this, &DlgFileModel::update); update(); } void DlgFileModel::update() { QFileInfoList list; if (_displayHidden) { _dir.setFilter(QDir::AllEntries | QDir::System | QDir::Hidden); } else { _dir.setFilter(QDir::AllEntries | QDir::System ); } _dir.setSorting(QDir::DirsFirst | QDir::Name | QDir::SortFlag::IgnoreCase); list = _dir.entryInfoList(); QVector newData; if (_dir.path() != "/") { Row row; row.key = ""; QMap column; column[Qt::DisplayRole] = "../"; row.columns.append(std::move(column)); newData.append(row); } for (int i = 0; i < list.size(); ++i) { const QFileInfo &fileInfo = list.at(i); Row row; row.key = fileInfo.absoluteFilePath(); QMap column; if (fileInfo.fileName() != "." && fileInfo.fileName() != "..") { if (fileInfo.isDir()) { column[Qt::DisplayRole] = fileInfo.fileName() + "/"; } else { column[Qt::DisplayRole] = fileInfo.fileName(); } row.columns.append(std::move(column)); newData.append(row); } } setData(newData); } void DlgFileModel::setDisplayHidden(bool displayHidden) { if (_displayHidden == displayHidden) { return; } _displayHidden = displayHidden; update(); } void DlgFileModel::setDirectory(const QDir &dir) { if (_dir == dir) { return; } _watcher.removePath(_dir.absolutePath()); _dir = dir; _watcher.addPath(_dir.absolutePath()); update(); } editor-0.1.80/src/dlgfilemodel.h000066400000000000000000000010441477475121100164650ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef DLGFILEMODEL_H #define DLGFILEMODEL_H #include #include #include class DlgFileModel : public Tui::Misc::AbstractTableModelTrackBy { Q_OBJECT public: DlgFileModel(const QDir &dir); public: void update(); void setDisplayHidden(bool displayHidden); void setDirectory(const QDir &dir); private: QDir _dir; QFileSystemWatcher _watcher; bool _displayHidden = false; }; #endif // DLGFILEMODEL_H editor-0.1.80/src/edit.cpp000066400000000000000000001142141477475121100153220ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "edit.h" #include #include #include #include #include #include #include #include #include #include #include "aboutdialog.h" #include "confirmsave.h" #include "formattingdialog.h" #include "gotoline.h" #include "insertcharacter.h" #include "opendialog.h" Editor::Editor() { } Editor::~Editor() { // Delete children here manually, instead of leaving it to QObject, // to avoid children observing already destructed parent. for (QObject *child : children()) { delete child; } } void Editor::setupUi() { ensureCommandManager(); Tui::ZMenubar *menu = new Tui::ZMenubar(this); menu->setItems({ { "File", "", {}, { { "New", "Ctrl-N", "New", {}}, { "Open...", "Ctrl-O", "Open", {}}, { "Save", "Ctrl-S", "Save", {}}, { "Save as...", "", "SaveAs", {}}, { "Reload", "", "Reload", {}}, { "Close", "", "Close", {}}, {}, { "Quit", "Ctrl-Q", "Quit", {}} } }, { "Edit", "", "", { { "Cut", "Ctrl-X", "Cut", {}}, { "Copy", "Ctrl-C", "Copy", {}}, { "Paste", "Ctrl-V", "Paste", {}}, { "Select all", "Ctrl-A", "Selectall", {}}, { "Delete Line", "Ctrl-D", "DeleteLine", {}}, { "Select Mode", "F4", "SelectMode", {}}, {}, { "Undo", "Ctrl-Z", "Undo", {}}, { "Redo", "Ctrl-Y", "Redo", {}}, {}, { "Search", "Ctrl-F", "Search", {}}, { "Search Next", "F3", "Search Next", {}}, { "Search Previous", "Shift-F3", "Search Previous", {}}, { "Replace", "Ctrl-R", "Replace", {}}, {}, { "Insert Character...", "", "InsertCharacter", {}}, {}, { "Goto Line", "Ctrl-G", "Gotoline", {}}, { "Set Marker", "Ctrl-M", "addLineMarker", {}}, // { "Marker", "", "", { //{ "Set Marker", "Ctrl-M", "addLineMarker", {}}, //{ "Goto Next Marker", "Ctrl-,", "nextLineMarker", {}}, //{ "Goto Previous Marker", "Ctrl-.", "previousLineMarker", {}}, // }}, {}, { "Sort Selcted Lines", "Alt-Shift-S", "SortSelectedLines", {}} } }, { "Options", "", {}, { { "Tab settings", "", "Tab", {}}, { "Line Number", "", "LineNumber", {}}, { "Formatting", "", "Formatting", {}}, { "Wrap long lines", "", "Wrap", {}}, // { "Following (standard input)", "", "Following", {}}, { "Stop Input Pipe", "", "StopInputPipe", {}}, { "Highlight Brackets", "", "Brackets", {}}, { "Syntax Highlighting", "", "SyntaxHighlighting", {}}, { "Theme", "", "Theme", {}} } }, {"Window", this, [this] { return createWindowMenu(); }}, { "Help", "", {}, { { "Quickstart", "F1", "Help", {}}, { "Terminal diagnostics", "", "TerminalDiagnostics", {}}, { "About", "", "About", {}} } } }); //New QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forShortcut("n"), this, Qt::ApplicationShortcut), &Tui::ZShortcut::activated, this, [this] { newFile(); }); QObject::connect(new Tui::ZCommandNotifier("New", this), &Tui::ZCommandNotifier::activated, this, [this] { newFile(); }); //Open QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forShortcut("o"), this, Qt::ApplicationShortcut), &Tui::ZShortcut::activated, this, [this] { openFileMenu(); }); QObject::connect(new Tui::ZCommandNotifier("Open", this), &Tui::ZCommandNotifier::activated, this, [this] { openFileMenu(); } ); //Quit QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forShortcut("q"), this, Qt::ApplicationShortcut), &Tui::ZShortcut::activated, this, &Editor::quit); QObject::connect(new Tui::ZCommandNotifier("Quit", this), &Tui::ZCommandNotifier::activated, this, &Editor::quit); //Search QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forShortcut("f"), this, Qt::ApplicationShortcut), &Tui::ZShortcut::activated, this, &Editor::searchDialog); _cmdSearch = new Tui::ZCommandNotifier("Search", this); QObject::connect(_cmdSearch, &Tui::ZCommandNotifier::activated, this, &Editor::searchDialog); //Replace QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forShortcut("r"), this, Qt::ApplicationShortcut), &Tui::ZShortcut::activated, this, &Editor::replaceDialog); _cmdReplace = new Tui::ZCommandNotifier("Replace", this); QObject::connect(_cmdReplace, &Tui::ZCommandNotifier::activated, this, &Editor::replaceDialog); //InsertCharacter _cmdInsertCharacter = new Tui::ZCommandNotifier("InsertCharacter", this); QObject::connect(_cmdInsertCharacter, &Tui::ZCommandNotifier::activated, this, [this] { QObject::connect(new InsertCharacter(this), &InsertCharacter::characterSelected, this, [this] (QString str) { if (_file) { _file->insertText(str); } }); } ); //Goto Line auto gotoLine = [this] { QObject::connect(new GotoLine(this), &GotoLine::lineSelected, this, [this] (QString line) { if (_file) { _file->gotoLine(line); } }); }; QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forShortcut("g"), this, Qt::ApplicationShortcut), &Tui::ZShortcut::activated, this, gotoLine); _cmdGotoLine = new Tui::ZCommandNotifier("Gotoline", this); QObject::connect(_cmdGotoLine, &Tui::ZCommandNotifier::activated, this, gotoLine); //Marker _cmdLineMarker = new Tui::ZCommandNotifier("addLineMarker", this); QObject::connect(_cmdLineMarker, &Tui::ZCommandNotifier::activated, this, [this] { if (_file) { _file->toggleLineMarker(); } }); _cmdNextLineMarker = new Tui::ZCommandNotifier("nextLineMarker", this); QObject::connect(_cmdNextLineMarker, &Tui::ZCommandNotifier::activated, this, [this] { if (_file) { _file->gotoNextLineMarker(); } }); _cmdPreviousLineMarker = new Tui::ZCommandNotifier("previousLineMarker", this); QObject::connect(_cmdPreviousLineMarker, &Tui::ZCommandNotifier::activated, this, [this] { if (_file) { _file->gotoPreviousLineMarker(); } }); //Sort Seleced Lines _cmdSortSelectedLines = new Tui::ZCommandNotifier("SortSelectedLines", this); QObject::connect(_cmdSortSelectedLines, &Tui::ZCommandNotifier::activated, this, [this] { if (_file) { _file->sortSelecedLines(); } }); //Options _cmdTab = new Tui::ZCommandNotifier("Tab", this); QObject::connect(_cmdTab, &Tui::ZCommandNotifier::activated, this, [this] { if (_tabDialog) { _tabDialog->raise(); _tabDialog->setVisible(true); _tabDialog->placeFocus()->setFocus(); } else { _tabDialog = new TabDialog(this); if (_file) { _tabDialog->updateSettings(_file->useTabChar(), _file->tabStopDistance(), _file->eatSpaceBeforeTabs()); } QObject::connect(_tabDialog, &TabDialog::convert, this, [this] (bool useTabs, int indentSize) { if (_file) { _file->setUseTabChar(useTabs); _file->setTabStopDistance(indentSize); _file->convertTabsToSpaces(); } }); QObject::connect(_tabDialog, &TabDialog::settingsChanged, this, [this] (bool useTabs, int indentSize, bool eatSpaceBeforeTabs) { if (_file) { _file->setUseTabChar(useTabs); _file->setTabStopDistance(indentSize); _file->setEatSpaceBeforeTabs(eatSpaceBeforeTabs); } }); } } ); _cmdLineNumbers = new Tui::ZCommandNotifier("LineNumber", this); QObject::connect(_cmdLineNumbers, &Tui::ZCommandNotifier::activated, [this] { if (_file) { _file->toggleShowLineNumbers(); } } ); _cmdFormatting = new Tui::ZCommandNotifier("Formatting", this); QObject::connect(_cmdFormatting, &Tui::ZCommandNotifier::activated, this, [this] { FormattingDialog *_formattingDialog = new FormattingDialog(this); if (_file) { _formattingDialog->updateSettings(_file->formattingCharacters(), _file->colorTabs(), _file->colorTrailingSpaces()); } else { _formattingDialog->updateSettings(_initialFileSettings.formattingCharacters, _initialFileSettings.colorTabs, _initialFileSettings.colorSpaceEnd); } QObject::connect(_formattingDialog, &FormattingDialog::settingsChanged, this, [this] (bool formattingCharacters, bool colorTabs, bool colorSpaceEnd) { if (_file) { _file->setFormattingCharacters(formattingCharacters); _file->setColorTabs(colorTabs); _file->setColorTrailingSpaces(colorSpaceEnd); } else { _initialFileSettings.formattingCharacters = formattingCharacters; _initialFileSettings.colorTabs = colorTabs; _initialFileSettings.colorSpaceEnd = colorSpaceEnd; } }); } ); _cmdBrackets = new Tui::ZCommandNotifier("Brackets", this); QObject::connect(_cmdBrackets, &Tui::ZCommandNotifier::activated, this, [this] { if (_file) { _file->setHighlightBracket(!_file->highlightBracket()); } } ); #ifdef SYNTAX_HIGHLIGHTING _cmdSyntaxHighlight = new Tui::ZCommandNotifier("SyntaxHighlighting", this); QObject::connect(_cmdSyntaxHighlight, &Tui::ZCommandNotifier::activated, this, [this] { if (_syntaxHighlightDialog) { _syntaxHighlightDialog->raise(); _syntaxHighlightDialog->setVisible(true); _syntaxHighlightDialog->placeFocus()->setFocus(); } else { _syntaxHighlightDialog = new SyntaxHighlightDialog(this); QObject::connect(_syntaxHighlightDialog, &SyntaxHighlightDialog::settingsChanged, this, [this] (bool enable, QString lang) { if (_file) { _file->setSyntaxHighlightingActive(enable); _file->setSyntaxHighlightingLanguage(lang); } }); } if (_file) { _syntaxHighlightDialog->updateSettings(_file->syntaxHighlightingActive(), _file->syntaxHighlightingLanguage()); } } ); #endif QObject::connect(new Tui::ZCommandNotifier("Theme", this), &Tui::ZCommandNotifier::activated, this, [this] { new ThemeDialog(this); } ); QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forMnemonic("x"), this, Qt::ApplicationShortcut), &Tui::ZShortcut::activated, this, &Editor::showCommandLine); _cmdNextWindow = new Tui::ZCommandNotifier("NextWindow", this); QObject::connect(_cmdNextWindow, &Tui::ZCommandNotifier::activated, this, [this] { activateNextWindow(); } ); _cmdPreviousWindow = new Tui::ZCommandNotifier("PreviousWindow", this); QObject::connect(_cmdPreviousWindow, &Tui::ZCommandNotifier::activated, this, [this] { activatePreviousWindow(); } ); _cmdTileVert = new Tui::ZCommandNotifier("TileVert", this); QObject::connect(_cmdTileVert, &Tui::ZCommandNotifier::activated, this, [this] { _mdiLayout->setMode(MdiLayout::LayoutMode::TileV); } ); QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, "v", {}), this, Qt::ApplicationShortcut), &Tui::ZShortcut::activated, this, [this] { _mdiLayout->setMode(MdiLayout::LayoutMode::TileV); } ); _cmdTileHorz = new Tui::ZCommandNotifier("TileHorz", this); QObject::connect(_cmdTileHorz, &Tui::ZCommandNotifier::activated, this, [this] { _mdiLayout->setMode(MdiLayout::LayoutMode::TileH); } ); QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, "h", {}), this, Qt::ApplicationShortcut), &Tui::ZShortcut::activated, this, [this] { _mdiLayout->setMode(MdiLayout::LayoutMode::TileH); } ); _cmdTileFull = new Tui::ZCommandNotifier("TileFull", this); QObject::connect(_cmdTileFull, &Tui::ZCommandNotifier::activated, this, [this] { _mdiLayout->setMode(MdiLayout::LayoutMode::Base); } ); ensureWindowCommands(24); enableFileCommands(false); auto openHelp = [this] { if (_helpDialog.isNull()) { _helpDialog = new Help(this); } else { _helpDialog->raise(); _helpDialog->setVisible(true); } }; QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forKey(Tui::Key_F1), this, Qt::ApplicationShortcut), &Tui::ZShortcut::activated, this, openHelp); QObject::connect(new Tui::ZCommandNotifier("Help", this), &Tui::ZCommandNotifier::activated, this, openHelp); QObject::connect(new Tui::ZCommandNotifier("About", this), &Tui::ZCommandNotifier::activated, this, [this] { new AboutDialog(this); } ); QObject::connect(new Tui::ZCommandNotifier("TerminalDiagnostics", this), &Tui::ZCommandNotifier::activated, this, [this] { new Tui::ZTerminalDiagnosticsDialog(this); } ); // Background setFillChar(u'▒'); _commandLineWidget = new CommandLineWidget(this); _commandLineWidget->setVisible(false); QObject::connect(_commandLineWidget, &CommandLineWidget::dismissed, this, &Editor::commandLineDismissed); QObject::connect(_commandLineWidget, &CommandLineWidget::execute, this, &Editor::commandLineExecute); _statusBar = new StatusBar(this); Tui::ZVBoxLayout *rootLayout = new Tui::ZVBoxLayout(); setLayout(rootLayout); rootLayout->addWidget(menu); _mdiLayout = new MdiLayout(); rootLayout->add(_mdiLayout); rootLayout->addWidget(_commandLineWidget); rootLayout->addWidget(_statusBar); } void Editor::terminalChanged() { setupUi(); QObject::connect(terminal(), &Tui::ZTerminal::focusChanged, this, [this] { Tui::ZWidget *w = terminal()->focusWidget(); while (w && !qobject_cast(w)) { w = w->parentWidget(); } if (qobject_cast(w)) { _win = qobject_cast(w); if (_file != _win->getFileWidget()) { _file = _win->getFileWidget(); enableFileCommands(true); if (_tabDialog) { _tabDialog->updateSettings(_file->useTabChar(), _file->tabStopDistance(), _file->eatSpaceBeforeTabs()); } if (_syntaxHighlightDialog) { _syntaxHighlightDialog->updateSettings(_file->syntaxHighlightingActive(), _file->syntaxHighlightingLanguage()); } } _mux.selectInput(_win); } }); Tui::ZPendingKeySequenceCallbacks pending; pending.setPendingSequenceStarted([this] { _pendingKeySequenceTimer.setInterval(2000); _pendingKeySequenceTimer.setSingleShot(true); _pendingKeySequenceTimer.start(); }); pending.setPendingSequenceFinished([this] (bool matched) { (void)matched; _pendingKeySequenceTimer.stop(); if (_pendingKeySequence) { _pendingKeySequence->deleteLater(); _pendingKeySequence = nullptr; } }); terminal()->registerPendingKeySequenceCallbacks(pending); QObject::connect(&_pendingKeySequenceTimer, &QTimer::timeout, this, [this] { _pendingKeySequence = new Tui::ZWindow(this); auto *layout = new Tui::ZWindowLayout(); _pendingKeySequence->setLayout(layout); layout->setCentralWidget(new Tui::ZTextLine("Incomplete key sequence. Press 2nd key.", _pendingKeySequence)); _pendingKeySequence->setGeometry({{0,0}, _pendingKeySequence->sizeHint()}); _pendingKeySequence->setDefaultPlacement(Qt::AlignCenter); }); setupSearchDialogs(); for (auto &action: _startActions) { action(); } } FileWindow *Editor::createFileWindow() { FileWindow *win = new FileWindow(this); File *file = win->getFileWidget(); _mux.connect(win, file, &File::cursorPositionChanged, this, &Editor::updateTerminalHeight); _mux.connect(win, file, &File::cursorPositionChanged, _statusBar, &StatusBar::cursorPosition, 0, 0, 0, 0); _mux.connect(win, file, &File::scrollPositionChanged, _statusBar, &StatusBar::scrollPosition, 0, 0); _mux.connect(win, file, &File::selectCharLines, _statusBar, &StatusBar::setSelectCharLines, 0, 0); _mux.connect(win, file, &File::modifiedChanged, _statusBar, &StatusBar::setModified, false); _mux.connect(win, win, &FileWindow::readFromStandadInput, _statusBar, &StatusBar::readFromStandardInput, false); //_mux.connect(win, win, &FileWindow::followStandadInput, _statusBar, &StatusBar::followStandardInput, false); _mux.connect(win, file, &File::followStandardInputChanged, _statusBar, &StatusBar::followStandardInput, false); _mux.connect(win, file, &File::writableChanged, _statusBar, &StatusBar::setWritable, true); _mux.connect(win, file->document(), &Tui::ZDocument::crLfModeChanged, _statusBar, &StatusBar::crlfMode, false); _mux.connect(win, file, &File::selectModeChanged, _statusBar, &StatusBar::modifiedSelectMode, false); _mux.connect(win, file, &File::searchCountChanged, _statusBar, &StatusBar::searchCount, -1); _mux.connect(win, file, &File::searchTextChanged, _statusBar, &StatusBar::searchText, QString()); _mux.connect(win, file, &File::searchVisibleChanged, _statusBar, &StatusBar::searchVisible, false); _mux.connect(win, file, &File::overwriteModeChanged, _statusBar, &StatusBar::overwrite, false); _mux.connect(win, win, &FileWindow::fileChangedExternally, _statusBar, &StatusBar::fileHasBeenChangedExternally, false); _mux.connect(win, file, &File::syntaxHighlightingEnabledChanged, _statusBar, &StatusBar::syntaxHighlightingEnabled, false); _mux.connect(win, file, &File::syntaxHighlightingLanguageChanged, _statusBar, &StatusBar::language, QString()); _allWindows.append(win); ensureWindowCommands(_allWindows.size()); QObject::connect(win, &FileWindow::backingFileChanged, this, [this, win] (QString filename) { QMutableMapIterator iter(_nameToWindow); while (iter.hasNext()) { iter.next(); if (iter.value() == win) { if (iter.key() == filename) { // Nothing changed return; } else { iter.remove(); break; } } } if (filename.size()) { Q_ASSERT(!_nameToWindow.contains(filename)); _nameToWindow[filename] = win; } }); QObject::connect(win, &QObject::destroyed, this, [this, win] { _mux.removeInput(win); // remove from _nameToWindow map QMutableMapIterator iter(_nameToWindow); while (iter.hasNext()) { iter.next(); if (iter.value() == win) { iter.remove(); break; } } if (_win == win) { _win = nullptr; _file = nullptr; } _allWindows.removeAll(win); if (_allWindows.size() == 0) { enableFileCommands(false); } }); if (_win) { file->setTabStopDistance(_file->tabStopDistance()); file->setShowLineNumbers(_file->showLineNumbers()); file->setUseTabChar(_file->useTabChar()); file->setEatSpaceBeforeTabs(_file->eatSpaceBeforeTabs()); file->setFormattingCharacters(_file->formattingCharacters()); file->setColorTabs(_file->colorTabs()); file->setColorTrailingSpaces(_file->colorTrailingSpaces()); win->setWrap(_file->wordWrapMode()); file->setRightMarginHint(_file->rightMarginHint()); file->setHighlightBracket(_file->highlightBracket()); file->setAttributesFile(_file->attributesFile()); file->setSyntaxHighlightingTheme(_initialFileSettings.syntaxHighlightingTheme); file->setSyntaxHighlightingActive(_file->syntaxHighlightingActive()); } else { file->setTabStopDistance(_initialFileSettings.tabSize); file->setShowLineNumbers(_initialFileSettings.showLineNumber); file->setUseTabChar(_initialFileSettings.tabOption); file->setEatSpaceBeforeTabs(_initialFileSettings.eatSpaceBeforeTabs); file->setFormattingCharacters(_initialFileSettings.formattingCharacters); file->setColorTabs(_initialFileSettings.colorTabs); file->setColorTrailingSpaces(_initialFileSettings.colorSpaceEnd); win->setWrap(_initialFileSettings.wrap); file->setRightMarginHint(_initialFileSettings.rightMarginHint); file->setHighlightBracket(_initialFileSettings.highlightBracket); file->setAttributesFile(_initialFileSettings.attributesFile); file->setSyntaxHighlightingTheme(_initialFileSettings.syntaxHighlightingTheme); file->setSyntaxHighlightingActive(!_initialFileSettings.disableSyntaxHighlighting); } return win; } QVector Editor::createWindowMenu() { QVector subitems = { {"Next", "F6", "NextWindow", {}}, {"Previous", "Shift-F6", "PreviousWindow", {}}, {"Tile Vertically", "", "TileVert", {}}, {"Tile Horizontally", "", "TileHorz", {}}, {"Tile Fullscreen", "", "TileFull", {}}, }; if (_allWindows.size()) { subitems.append(Tui::ZMenuItem{}); for (int i = 0; i < _allWindows.size(); i++) { if (i < 9) { subitems.append(Tui::ZMenuItem{_allWindows[i]->getFileWidget()->getFilename(), QStringLiteral("Alt-%0").arg(QString::number(i + 1)), QStringLiteral("SwitchToWindow%0").arg(QString::number(i + 1)), {}}); } else { subitems.append(Tui::ZMenuItem{_allWindows[i]->getFileWidget()->getFilename(), "", QStringLiteral("SwitchToWindow%0").arg(QString::number(i + 1)), {}}); } } } return subitems; } void Editor::setupSearchDialogs() { auto searchCancled = [this] { if (_file) { _file->setSearchText(""); _file->clearSelection(); } }; auto searchCaseSensitiveChanged = [this](bool value) { if (_file) { if (value) { _file->setSearchCaseSensitivity(Qt::CaseSensitive); } else { _file->setSearchCaseSensitivity(Qt::CaseInsensitive); } } }; auto searchWrapChanged = [this](bool value) { if (_file) { if (value) { _file->setSearchWrap(true); } else { _file->setSearchWrap(false); } } }; auto searchForwardChanged = [this](bool value) { if (_file) { _file->setSearchDirection(value); } }; auto liveSearch = [this](QString text, bool forward) { if (_file) { if (text.size() == 0) { _file->setSearchText(""); _file->clearSelection(); } else { _file->setSearchText(text); _file->setSearchDirection(forward); if(forward) { _file->setCursorPosition({_file->cursorPosition().codeUnit - text.size(), _file->cursorPosition().line}); } else { _file->setCursorPosition({_file->cursorPosition().codeUnit + text.size(), _file->cursorPosition().line}); } _file->runSearch(false); } } }; auto searchNext = [this](QString text, bool forward) { if (_file) { _file->setSearchText(text); _file->setSearchDirection(forward); _file->runSearch(false); } }; auto regex = [this] (bool regex) { if (_file) { _file->setRegex(regex); } }; _searchDialog = new SearchDialog(this); QObject::connect(_searchDialog, &SearchDialog::searchCanceled, this, searchCancled); QObject::connect(_searchDialog, &SearchDialog::searchCaseSensitiveChanged, this, searchCaseSensitiveChanged); QObject::connect(_searchDialog, &SearchDialog::searchWrapChanged, this, searchWrapChanged); QObject::connect(_searchDialog, &SearchDialog::searchDirectionChanged, this, searchForwardChanged); QObject::connect(_searchDialog, &SearchDialog::liveSearch, this, liveSearch); QObject::connect(_searchDialog, &SearchDialog::searchFindNext, this, searchNext); QObject::connect(_searchDialog, &SearchDialog::searchRegexChanged, this, regex); _replaceDialog = new SearchDialog(this, true); QObject::connect(_replaceDialog, &SearchDialog::searchCanceled, this, searchCancled); QObject::connect(_replaceDialog, &SearchDialog::searchCaseSensitiveChanged, this, searchCaseSensitiveChanged); QObject::connect(_replaceDialog, &SearchDialog::searchWrapChanged, this, searchWrapChanged); QObject::connect(_replaceDialog, &SearchDialog::searchDirectionChanged, this, searchForwardChanged); QObject::connect(_replaceDialog, &SearchDialog::liveSearch, this, liveSearch); QObject::connect(_replaceDialog, &SearchDialog::searchFindNext, this, searchNext); QObject::connect(_replaceDialog, &SearchDialog::searchRegexChanged, this, regex); QObject::connect(_replaceDialog, &SearchDialog::searchReplace, this, [this] (QString text, QString replacement, bool forward) { if (_file) { if (_file->isSearchMatchSelected()) { _file->setReplaceText(replacement); _file->replaceSelected(); } _file->setSearchDirection(forward); _file->setSearchText(text); _file->runSearch(false); } }); QObject::connect(_replaceDialog, &SearchDialog::searchReplaceAll, this, [this] (QString text, QString replacement) { if (_file) { _file->replaceAll(text, replacement); } }); } void Editor::ensureWindowCommands(int count) { if (count <= _windowCommandsCreated) { return; } for (int i = _windowCommandsCreated; i < count; i++) { auto code = [this, windowNumber = i] { if (windowNumber < _allWindows.size()) { auto *win = _allWindows[windowNumber]; raiseOnActivate(win); auto *widget = win->placeFocus(); if (widget) { widget->setFocus(); } } }; QObject::connect(new Tui::ZCommandNotifier(QStringLiteral("SwitchToWindow%0").arg(QString::number(i + 1)), this), &Tui::ZCommandNotifier::activated, this, code); if (i < 9) { QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forMnemonic(QString::number(i + 1)), this, Qt::ApplicationShortcut), &Tui::ZShortcut::activated, this, code); } } _windowCommandsCreated = count; } void Editor::enableFileCommands(bool enable) { _cmdInsertCharacter->setEnabled(enable); _cmdGotoLine->setEnabled(enable); _cmdLineMarker->setEnabled(enable); _cmdNextLineMarker->setEnabled(enable); _cmdPreviousLineMarker->setEnabled(enable); _cmdSortSelectedLines->setEnabled(enable); _cmdTab->setEnabled(enable); _cmdLineNumbers->setEnabled(enable); // _cmdFormatting works without active document and should be kept enabled _cmdBrackets->setEnabled(enable); #ifdef SYNTAX_HIGHLIGHTING _cmdSyntaxHighlight->setEnabled(enable); #endif _cmdTileVert->setEnabled(enable); _cmdTileHorz->setEnabled(enable); _cmdTileFull->setEnabled(enable); _cmdSearch->setEnabled(enable); _cmdReplace->setEnabled(enable); } void Editor::searchDialog() { if (_file) { _searchDialog->open(); _searchDialog->setSearchText(_file->selectedText()); } } void Editor::replaceDialog() { if (_replaceDialog) { _replaceDialog->open(); _replaceDialog->setSearchText(_file->selectedText()); } } void Editor::setTerminalHeightMode(TerminalMode mode) { terminalMode = mode; } void Editor::updateTerminalHeight() { #ifdef HAS_TUIWIDGETS_0_2_2 if (terminalMode == TerminalMode::modeAuto) { _mdiLayout->setMode(MdiLayout::LayoutMode::Base); if (_file) { int tmp = _file->visualLineCount() + 4; if(tmp > terminal()->height()) { terminal()->setInlineHeight(tmp); } } } #endif } FileWindow *Editor::newFile(QString filename) { FileWindow *win = createFileWindow(); if (filename.size()) { win->getFileWidget()->newText(filename); } _mdiLayout->addWindow(win); win->getFileWidget()->setFocus(); return win; } void Editor::openFileMenu() { if (_file) { openFileDialog(_file->getFilename()); } else { openFileDialog(); } } void Editor::gotoLineInCurrentFile(QString lineInfo) { if (_file) { _file->gotoLine(lineInfo); } } void Editor::followInCurrentFile(bool follow) { if (_win) { _win->setFollow(follow); } } void Editor::watchPipe() { if (_win) { _win->watchPipe(); } } void Editor::setStartActions(std::vector> actions) { _startActions = actions; } FileWindow* Editor::openFile(QString fileName) { QFileInfo filenameInfo(fileName); QString absFileName = filenameInfo.absoluteFilePath(); if (_nameToWindow.contains(absFileName)) { _nameToWindow.value(absFileName)->getFileWidget()->setFocus(); return _nameToWindow.value(absFileName); } if (_win && !_win->getFileWidget()->isModified() && _win->getFileWidget()->getFilename() == "NEWFILE") { _win->openFile(fileName); return _win; } else { FileWindow *win = createFileWindow(); _mdiLayout->addWindow(win); win->getFileWidget()->setFocus(); win->openFile(fileName); return win; } } void Editor::openFileDialog(QString path) { OpenDialog *openDialog = new OpenDialog(this, path); QObject::connect(openDialog, &OpenDialog::fileSelected, this, [this] (QString fileName) { openFile(fileName); }); } void Editor::quitImpl(int i) { if (i >= _allWindows.size()) { // delete all windows so they can signal work in QtConcurrent to quit also. for (auto* win: _allWindows) { win->deleteLater(); } QCoreApplication::instance()->quit(); return; } auto handleNext = [this, i] { quitImpl(i + 1); }; auto *win = _allWindows[i]; auto *file = win->getFileWidget(); file->writeAttributes(); if (file->isModified()) { ConfirmSave *quitDialog = new ConfirmSave(this, file->getFilename(), file->isNewFile() ? ConfirmSave::QuitUnnamed : ConfirmSave::Quit, file->getWritable()); QObject::connect(quitDialog, &ConfirmSave::discardSelected, this, [quitDialog,handleNext] { quitDialog->deleteLater(); handleNext(); }); QObject::connect(quitDialog, &ConfirmSave::saveSelected, this, [this, quitDialog,handleNext,win] { quitDialog->deleteLater(); win->saveOrSaveas([handleNext](bool ok) { if (ok) { handleNext(); } }); }); QObject::connect(quitDialog, &ConfirmSave::rejected, this, [quitDialog] { quitDialog->deleteLater(); }); } else { handleNext(); } } void Editor::quit() { if (_allWindows.isEmpty()) { QCoreApplication::instance()->quit(); return; } quitImpl(0); } void Editor::setTheme(Theme theme) { _theme = theme; if (theme == Theme::dark) { Tui::ZPalette tmpPalette = Tui::ZPalette::black(); tmpPalette.setColors({ {"chr.fgbehindText", { 0xdd, 0xdd, 0xdd}}, {"chr.trackBgColor", { 0x80, 0x80, 0x80}}, {"chr.thumbBgColor", { 0x69, 0x69, 0x69}}, {"chr.trackFgColor", Tui::Colors::brightWhite}, {"chr.linenumberFg", { 0xdd, 0xdd, 0xdd}}, {"chr.linenumberBg", { 0x22, 0x22, 0x22}}, {"chr.statusbarBg", Tui::Colors::darkGray}, {"chr.editFg", {0xff, 0xff, 0xff}}, {"chr.editBg", {0x0, 0x0, 0x0}}, }); setPalette(tmpPalette); if (_initialFileSettings.syntaxHighlightingTheme == "chr-blackbg" || _initialFileSettings.syntaxHighlightingTheme == "chr-bluebg") { _initialFileSettings.syntaxHighlightingTheme = "chr-blackbg"; } } else if (theme == Theme::classic) { Tui::ZPalette tmpPalette = Tui::ZPalette::classic(); tmpPalette.setColors({ {"chr.fgbehindText", { 0xdd, 0xdd, 0xdd}}, {"chr.trackBgColor", { 0, 0, 0x80}}, {"chr.thumbBgColor", { 0, 0, 0xd9}}, {"chr.trackFgColor", Tui::Colors::brightWhite}, {"chr.linenumberFg", { 0xdd, 0xdd, 0xdd}}, {"chr.linenumberBg", { 0, 0, 0x80}}, {"chr.statusbarBg", {0, 0xaa, 0xaa}}, {"chr.editFg", {0xff, 0xff, 0xff}}, {"chr.editBg", {0, 0, 0xaa}}, {"root.fg", {0xaa, 0xaa, 0xaa}}, {"root.bg", {0, 0, 0xaa}} }); setPalette(tmpPalette); if (_initialFileSettings.syntaxHighlightingTheme == "chr-blackbg" || _initialFileSettings.syntaxHighlightingTheme == "chr-bluebg") { _initialFileSettings.syntaxHighlightingTheme = "chr-bluebg"; } } for (auto window: _allWindows) { window->getFileWidget()->setSyntaxHighlightingTheme(_initialFileSettings.syntaxHighlightingTheme); } } void Editor::setInitialFileSettings(const Settings &initial) { _initialFileSettings = initial; } void Editor::showCommandLine() { _statusBar->setVisible(false); _commandLineWidget->setVisible(true); _commandLineWidget->grabKeyboard(); } void Editor::commandLineDismissed() { _statusBar->setVisible(true); _commandLineWidget->setVisible(false); } void Editor::commandLineExecute(QString cmd) { commandLineDismissed(); if (cmd.startsWith("screenshot-tpi ")) { QString filename = cmd.split(" ").value(1); if (filename.size()) { connect(terminal(), &Tui::ZTerminal::afterRendering, this, [this, filename, terminal=terminal()] { terminal->grabCurrentImage().save(filename); disconnect(terminal, &Tui::ZTerminal::afterRendering, this, 0); }); } } else if (cmd == "help") { _commandLineWidget->setCmdEntryText("suspend shell fullscreen nofullscreen incsize"); showCommandLine(); } else if (cmd == "suspend") { ::raise(SIGTSTP); } else if (cmd == "shell") { auto term = terminal(); term->pauseOperation(); // ignoring result because this is a interactive shell (void)!system(qgetenv("SHELL")); term->unpauseOperation(); } #ifdef HAS_TUIWIDGETS_0_2_2 else if (cmd == "incsize") { terminal()->setInlineHeight(terminal()->inlineHeight() + 1); } else if (cmd == "fullscreen") { terminal()->setInline(false); } else if (cmd == "nofullscreen") { terminal()->setInline(true); } #endif } editor-0.1.80/src/edit.h000066400000000000000000000101711477475121100147640ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef EDIT_H #define EDIT_H #include #include #include "commandlinewidget.h" #include "file.h" #include "filewindow.h" #include "help.h" #include "mdilayout.h" #include "searchdialog.h" #include "statemux.h" #include "statusbar.h" #include "syntaxhighlightdialog.h" #include "tabdialog.h" #include "themedialog.h" class Settings { public: int tabSize = 4; bool tabOption = false; bool eatSpaceBeforeTabs = true; bool formattingCharacters = false; bool colorSpaceEnd = false; bool colorTabs = false; Tui::ZTextOption::WrapMode wrap = Tui::ZTextOption::NoWrap; bool highlightBracket = true; bool showLineNumber = false; QString attributesFile; int rightMarginHint = 0; QString syntaxHighlightingTheme; bool disableSyntaxHighlighting = false; }; class Editor : public Tui::ZRoot { Q_OBJECT public: enum class Theme { classic, dark }; enum class TerminalMode { modeFullscreen, modeManual, modeAuto }; public: Editor(); ~Editor(); public: void setTheme(Theme theme); void setInitialFileSettings(const Settings &initial); void windowTitle(QString filename); FileWindow* openFile(QString fileName); void openFileDialog(QString path = ""); FileWindow* newFile(QString filename = ""); void openFileMenu(); void gotoLineInCurrentFile(QString lineInfo); void followInCurrentFile(bool follow=true); void watchPipe(); void setStartActions(std::vector> actions); void setTerminalHeightMode(TerminalMode mode); public slots: void showCommandLine(); void updateTerminalHeight(); private slots: void commandLineDismissed(); void commandLineExecute(QString cmd); protected: void terminalChanged() override; private: void setupUi(); void setupSearchDialogs(); void ensureWindowCommands(int count); void enableFileCommands(bool enable); FileWindow *createFileWindow(); QVector createWindowMenu(); void quit(); void quitImpl(int i); void searchDialog(); void replaceDialog(); private: File *_file = nullptr; FileWindow *_win = nullptr; MdiLayout *_mdiLayout = nullptr; StateMux _mux; QMap _nameToWindow; QVector _allWindows; Tui::ZCommandNotifier *_cmdSearch = nullptr; SearchDialog *_searchDialog = nullptr; Tui::ZCommandNotifier *_cmdReplace = nullptr; SearchDialog *_replaceDialog = nullptr; ThemeDialog *_themeDialog = nullptr; QPointer _tabDialog = nullptr; Tui::ZCommandNotifier *_cmdSyntaxHighlight = nullptr; QPointer _syntaxHighlightDialog = nullptr; StatusBar *_statusBar = nullptr; CommandLineWidget *_commandLineWidget = nullptr; Theme _theme = Theme::classic; Settings _initialFileSettings; QPointer _helpDialog; Tui::ZCommandNotifier *_cmdTab = nullptr; Tui::ZWindow *_optionTab = nullptr; Tui::ZWindow *_fileOpen = nullptr; Tui::ZWindow *_fileGotoLine = nullptr; Tui::ZCommandNotifier *_cmdInsertCharacter = nullptr; Tui::ZCommandNotifier *_cmdGotoLine = nullptr; Tui::ZCommandNotifier *_cmdLineMarker = nullptr; Tui::ZCommandNotifier *_cmdNextLineMarker = nullptr; Tui::ZCommandNotifier *_cmdPreviousLineMarker = nullptr; Tui::ZCommandNotifier *_cmdSortSelectedLines = nullptr; Tui::ZCommandNotifier *_cmdLineNumbers = nullptr; Tui::ZCommandNotifier *_cmdFormatting = nullptr; Tui::ZCommandNotifier *_cmdBrackets = nullptr; Tui::ZCommandNotifier *_cmdTileVert = nullptr; Tui::ZCommandNotifier *_cmdTileHorz = nullptr; Tui::ZCommandNotifier *_cmdTileFull = nullptr; Tui::ZCommandNotifier *_cmdNextWindow = nullptr; Tui::ZCommandNotifier *_cmdPreviousWindow = nullptr; int _tab = 8; Tui::ZWindow *_pendingKeySequence = nullptr; QTimer _pendingKeySequenceTimer; std::vector> _startActions; int _windowCommandsCreated = 0; TerminalMode terminalMode = TerminalMode::modeFullscreen; }; #endif // EDIT_H editor-0.1.80/src/file.cpp000066400000000000000000003053641477475121100153240ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "file.h" #include #include #include #include #include #include #ifdef SYNTAX_HIGHLIGHTING #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "attributes.h" #include "searchcount.h" // User Data values for ZFormatRange ranges. #define FR_UD_SELECTION 1 #define FR_UD_LIVE_SEARCH 2 #define FR_UD_SYNTAX 3 File::File(Tui::ZTextMetrics textMetrics, Tui::ZWidget *parent) : ZTextEdit(textMetrics, parent) { _lineMarker = std::make_unique(); setInsertCursorStyle(Tui::CursorStyle::Underline); setOverwriteCursorStyle(Tui::CursorStyle::Block); setTabChangesFocus(false); initText(); registerCommandNotifiers(Qt::WindowShortcut); _cmdSearchNext = new Tui::ZCommandNotifier("Search Next", this, Qt::WindowShortcut); QObject::connect(_cmdSearchNext, &Tui::ZCommandNotifier::activated, this, [this] {runSearch(false);}); _cmdSearchNext->setEnabled(false); QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forKey(Qt::Key_F3, Qt::NoModifier), this, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this] { runSearch(false); }); _cmdSearchPrevious = new Tui::ZCommandNotifier("Search Previous", this, Qt::WindowShortcut); QObject::connect(_cmdSearchPrevious, &Tui::ZCommandNotifier::activated, this, [this] {runSearch(true);}); _cmdSearchPrevious->setEnabled(false); QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forKey(Qt::Key_F3, Qt::ShiftModifier), this, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this] { runSearch(true); }); QObject::connect(document(), &Tui::ZDocument::lineMarkerChanged, this, [this](const Tui::ZDocumentLineMarker *marker) { if ((_blockSelectEndLine && marker == &*_blockSelectEndLine)) { // Recalculate the scroll position: // * In case any editing from outside of this class moved our block selection cursor // Internal changes trigger these signals as well, that should be safe, as long as adjustScrollPosition // does not change the scroll position when called again as long as neither document nor the cursor or // block selection cursor is changed inbetween. adjustScrollPosition(); } if (_blockSelectEndLine && marker == &*_blockSelectEndLine) { // When in block selection mode, the block selection end line marker is what we expose as cursor position // line, so send an update out. const auto [cursorCodeUnit, cursorLine, cursorColumn] = cursorPositionOrBlockSelectionEnd(); int utf8PositionX = document()->line(cursorLine).left(cursorCodeUnit).toUtf8().size(); cursorPositionChanged(cursorColumn, cursorCodeUnit, utf8PositionX, cursorLine); } }); #ifdef SYNTAX_HIGHLIGHTING qRegisterMetaType(); QObject::connect(document(), &Tui::ZDocument::contentsChanged, this, [this] { updateSyntaxHighlighting(false); }); #endif } void File::emitCursorPostionChanged() { const auto [cursorCodeUnit, cursorLine, cursorColumn] = cursorPositionOrBlockSelectionEnd(); int utf8CodeUnit = document()->line(cursorLine).leftRef(cursorCodeUnit).toUtf8().size(); cursorPositionChanged(cursorColumn, cursorCodeUnit, utf8CodeUnit, cursorLine); if (_stdin && document()->lineCount() - 1 == cursorLine) { _followMode = true; followStandardInputChanged(true); } else { _followMode = false; followStandardInputChanged(false); } } #ifdef SYNTAX_HIGHLIGHTING void File::updateSyntaxHighlighting(bool force = false) { if (force) { document()->setLineUserData(0, nullptr); } if (!syntaxHighlightingActive() || !_syntaxHighlightDefinition.isValid() || !_syntaxHighlightingTheme.isValid()) { return; } Tui::ZDocumentSnapshot snapshot = document()->snapshot(); SyntaxHighlightingSignalForwarder *forwarder = new SyntaxHighlightingSignalForwarder(); forwarder->moveToThread(nullptr); // enable later pull to worker thread QObject::connect(forwarder, &SyntaxHighlightingSignalForwarder::updates, this, &File::ingestSyntaxHighlightingUpdates); QtConcurrent::run([forwarder, snapshot, &highlighter=_syntaxHighlightExporter] { forwarder->moveToThread(QThread::currentThread()); Updates updates; updates.documentRevision = snapshot.revision(); auto sendData = [&] { forwarder->updates(updates); }; auto update = [&](int line, std::shared_ptr &newData) { updates.data.append(newData); updates.lines.append(line); if (updates.data.size() > 100) { sendData(); updates.data.clear(); updates.lines.clear(); } }; KSyntaxHighlighting::State state; for (int line = 0; line < snapshot.lineCount(); line++) { if (!snapshot.isUpToDate()) { // Abandon work, the document has changed delete forwarder; return; } auto userData = std::static_pointer_cast(snapshot.lineUserData(line)); if (!userData || userData->stateBegin != state || userData->lineRevision != snapshot.lineRevision(line)) { auto newData = std::make_shared(); newData->stateBegin = state; auto res = highlighter.highlightLineWrap(snapshot.line(line), state); newData->stateEnd = state = std::get<0>(res); newData->highlights = std::get<1>(res); newData->lineRevision = snapshot.lineRevision(line); update(line, newData); } else { state = userData->stateEnd; } } if (updates.data.size()) { sendData(); } delete forwarder; }); } void File::syntaxHighlightDefinition() { if (_syntaxHighlightDefinition.isValid()) { _syntaxHighlightExporter.setTheme(_syntaxHighlightingTheme); _syntaxHighlightExporter.setDefinition(_syntaxHighlightDefinition); _syntaxHighlightExporter.defBg = getColor("chr.editBg"); _syntaxHighlightExporter.defFg = getColor("chr.editFg"); _syntaxHighlightingLanguage = _syntaxHighlightDefinition.name(); syntaxHighlightingLanguageChanged(_syntaxHighlightingLanguage); } } void File::ingestSyntaxHighlightingUpdates(Updates updates) { if (updates.documentRevision != document()->revision()) { // Lines numbers might have changed (by insertion or deletion of lines), we can't use this update return; } const int visibleLinesStart = scrollPositionLine(); const int visibleLinesEnd = visibleLinesStart + geometry().height(); bool needRepaint = false; for (int i = 0; i < updates.lines.size(); i++) { const int line = updates.lines[i]; if (line < document()->lineCount() && document()->lineRevision(line) == updates.data[i]->lineRevision) { document()->setLineUserData(line, updates.data[i]); if (visibleLinesStart <= line && line <= visibleLinesEnd) { needRepaint = true; } } } if (needRepaint) { update(); } } std::tuple> HighlightExporter::highlightLineWrap(const QString &text, const KSyntaxHighlighting::State &state) { std::lock_guard lock{mutex}; highlights.clear(); auto newState = highlightLine(text, state); return {newState, this->highlights}; } void HighlightExporter::applyFormat(int offset, int length, const KSyntaxHighlighting::Format &format) { Tui::ZTextAttributes attr; if (format.isBold(theme())) { attr |= Tui::ZTextAttribute::Bold; } if (format.isItalic(theme())) { attr |= Tui::ZTextAttribute::Italic; } if (format.isUnderline(theme())) { attr |= Tui::ZTextAttribute::Underline; } if (format.isStrikeThrough(theme())) { attr |= Tui::ZTextAttribute::Strike; } auto convert = [](QColor q) { return Tui::ZColor::fromRgb(q.red(), q.green(), q.blue()); }; Tui::ZColor fg = format.hasTextColor(theme()) ? convert(format.textColor(theme())) : defFg; Tui::ZColor bg = format.hasBackgroundColor(theme()) ? convert(format.backgroundColor(theme())) : defBg; Tui::ZTextStyle style(fg, bg, attr); highlights.append(Tui::ZFormatRange(offset, length, style, {Tui::Colors::darkGray, bg}, FR_UD_SYNTAX)); } #endif void File::setSyntaxHighlightingTheme(QString themeName) { #ifdef SYNTAX_HIGHLIGHTING _syntaxHighlightingThemeName = themeName; _syntaxHighlightingTheme = _syntaxHighlightRepo.theme(_syntaxHighlightingThemeName); _syntaxHighlightExporter.setTheme(_syntaxHighlightingTheme); _syntaxHighlightExporter.defBg = getColor("chr.editBg"); _syntaxHighlightExporter.defFg = getColor("chr.editFg"); for (int line = 0; line < document()->lineCount(); line++) { document()->setLineUserData(line, nullptr); } updateSyntaxHighlighting(true); #else (void)themeName; #endif } void File::setSyntaxHighlightingLanguage(QString language) { #ifdef SYNTAX_HIGHLIGHTING _syntaxHighlightDefinition = _syntaxHighlightRepo.definitionForName(language); syntaxHighlightDefinition(); // rehighlight updateSyntaxHighlighting(true); #else (void)language; #endif } QString File::syntaxHighlightingLanguage() { return _syntaxHighlightingLanguage; } bool File::syntaxHighlightingActive() { return _syntaxHighlightingActive; } void File::setSyntaxHighlightingActive(bool active) { #ifdef SYNTAX_HIGHLIGHTING _syntaxHighlightingActive = active; syntaxHighlightingEnabledChanged(_syntaxHighlightingActive); if (active) { // rehighlight, highlight have not been updated while syntax highlighting was disabled updateSyntaxHighlighting(true); } update(); #else (void)active; #endif } File::~File() { if (_searchNextFuture) { _searchNextFuture->cancel(); _searchNextFuture.reset(); } _lineMarker->clearMarkers(); } int File::convertTabsToSpaces() { auto undoGroup = startUndoGroup(); int count = 0; Tui::ZTextOption option = textOption(); option.setWrapMode(Tui::ZTextOption::NoWrap); // This changes code unit based positions in many lines. To avoid glitches reset selection and // manually save and restore cursor position. This should no longer be needed when TextCursor // is fixed to be able to keep its position even when edits happen. clearSelection(); const auto [cursorCodeUnit, cursorLine] = cursorPosition(); Tui::ZTextLayout lay = textLayoutForLine(option, cursorLine); int cursorPosition = lay.lineAt(0).cursorToX(cursorCodeUnit, Tui::ZTextLayout::Leading); Tui::ZDocumentCursor cur = makeCursor(); for (int line = 0, found = -1; line < document()->lineCount(); line++) { found = document()->line(line).lastIndexOf("\t"); if (found != -1) { Tui::ZTextLayout lay = textLayoutForLine(option, line); while (found != -1) { int columnStart = lay.lineAt(0).cursorToX(found, Tui::ZTextLayout::Leading); int columnEnd = lay.lineAt(0).cursorToX(found, Tui::ZTextLayout::Trailing); cur.setPosition({found, line}); cur.moveCharacterRight(true); cur.insertText(QString(" ").repeated(columnEnd-columnStart)); count++; found = document()->line(line).lastIndexOf("\t", found); } } } // Restore cursor position lay = textLayoutForLine(option, cursorLine); int newCursorPosition = lay.lineAt(0).xToCursor(cursorPosition); if (newCursorPosition - 1 >= 0) { setCursorPosition({newCursorPosition, cursorLine}); } return count; } void File::setSaveAs(bool state) { _saveAs = state; } bool File::isSaveAs() { return _saveAs; } bool File::setFilename(QString filename) { QFileInfo filenameInfo(filename); if (filenameInfo.isSymLink()) { return setFilename(filenameInfo.symLinkTarget()); } document()->setFilename(filenameInfo.absoluteFilePath()); return true; } QString File::getFilename() { return document()->filename(); } bool File::newText(QString filename) { initText(); if (filename == "") { document()->setFilename("NEWFILE"); setSaveAs(true); } else { setSaveAs(false); setFilename(filename); } modifiedChanged(false); return true; } bool File::stdinText() { document()->setFilename("STDIN"); _stdin = true; initText(); setFollowStandardInput(true); followStandardInputChanged(true); modifiedChanged(true); setSaveAs(true); return true; } bool File::initText() { clear(); return true; } bool File::saveText() { // QSaveFile does not take over the user and group. Therefore this should only be used if // user and group are the same and the editor also runs under this user. QFile file(getFilename()); //QSaveFile file(getFilename()); if (file.open(QIODevice::WriteOnly)) { bool ok = writeTo(&file); ok &= file.flush(); //file.commit(); file.close(); if (!ok) { return false; } modifiedChanged(false); setSaveAs(false); checkWritable(); update(); writeAttributes(); return true; } //TODO vernünfiges error Händling return false; } void File::checkWritable() { writableChanged(getWritable()); } bool File::getWritable() { QFileInfo file(getFilename()); if (file.isWritable()) { return true; } if (file.isSymLink()) { QFileInfo path(file.symLinkTarget()); if (!file.exists() && path.isWritable()) { return true; } return false; } QFileInfo path(file.path()); if (!file.exists() && path.isWritable()) { return true; } return false; } void File::setHighlightBracket(bool hb) { _bracket = hb; } bool File::highlightBracket() { return _bracket; } bool File::writeAttributes() { Attributes a{_attributesFile}; return a.writeAttributes(getFilename(), cursorPosition(), scrollPositionColumn(), scrollPositionLine(), scrollPositionFineLine(), _lineMarker->listMarker()); } void File::setAttributesFile(QString attributesFile) { _attributesFile = attributesFile; } QString File::attributesFile() { return _attributesFile; } bool File::openText(QString filename) { setFilename(filename); QFile file(getFilename()); if (file.open(QIODevice::ReadOnly)) { initText(); Attributes a{_attributesFile}; Tui::ZDocumentCursor::Position initialPosition = a.getAttributesCursorPosition(getFilename()); const bool ok = readFrom(&file, initialPosition); file.close(); if (!ok) { return false; } if (getWritable()) { setSaveAs(false); } else { setSaveAs(true); } checkWritable(); modifiedChanged(false); setScrollPosition(a.getAttributesScrollCol(getFilename()), a.getAttributesScrollLine(getFilename()), a.getAttributesScrollFine(getFilename())); QList lm = a.getAttributesLineMarker(getFilename()); _lineMarker->clearMarkers(); // delete all old markers before adding a new one for(int i = 0; i < lm.size(); i++) { _lineMarker->addMarker(document(), lm.at(i)); } adjustScrollPosition(); #ifdef SYNTAX_HIGHLIGHTING _syntaxHighlightDefinition = _syntaxHighlightRepo.definitionForFileName(getFilename()); syntaxHighlightDefinition(); #endif return true; } return false; } void File::cutline() { clearSelection(); Tui::ZDocumentCursor cursor = textCursor(); cursor.moveToStartOfLine(); cursor.moveToEndOfLine(true); setTextCursor(cursor); cut(); updateCommands(); adjustScrollPosition(); } void File::deleteLine() { Tui::ZDocumentCursor cursor = textCursor(); auto undoGroup = document()->startUndoGroup(&cursor); if (cursor.hasSelection() || hasBlockSelection() || hasMultiInsert()) { const auto [start, end] = getSelectedLinesSort(); clearSelection(); cursor.setPosition({0,end}); cursor.moveToEndOfLine(); cursor.setPosition({0,start}, true); cursor.removeSelectedText(); } cursor.deleteLine(); setTextCursor(cursor); updateCommands(); adjustScrollPosition(); } void File::copy() { if (hasBlockSelection()) { Tui::ZClipboard *clipboard = findFacet(); const int firstSelectBlockLine = std::min(_blockSelectStartLine->line(), _blockSelectEndLine->line()); const int lastSelectBlockLine = std::max(_blockSelectStartLine->line(), _blockSelectEndLine->line()); const int firstSelectBlockColumn = std::min(_blockSelectStartColumn, _blockSelectEndColumn); const int lastSelectBlockColumn = std::max(_blockSelectStartColumn, _blockSelectEndColumn); QString selectedText; for (int line = firstSelectBlockLine; line < document()->lineCount() && line <= lastSelectBlockLine; line++) { Tui::ZTextLayout laySel = textLayoutForLineWithoutWrapping(line); Tui::ZTextLineRef tlrSel = laySel.lineAt(0); const int selFirstCodeUnitInLine = tlrSel.xToCursor(firstSelectBlockColumn); const int selLastCodeUnitInLine = tlrSel.xToCursor(lastSelectBlockColumn); selectedText += document()->line(line).mid(selFirstCodeUnitInLine, selLastCodeUnitInLine - selFirstCodeUnitInLine); if (line != lastSelectBlockLine) { selectedText += "\n"; } } clipboard->setContents(selectedText); } else if (ZTextEdit::hasSelection()) { Tui::ZClipboard *clipboard = findFacet(); clipboard->setContents(ZTextEdit::selectedText()); setSelectMode(false); } } void File::paste() { auto undoGroup = startUndoGroup(); Tui::ZClipboard *clipboard = findFacet(); if (clipboard->contents().size()) { insertText(clipboard->contents()); adjustScrollPosition(); } document()->clearCollapseUndoStep(); } void File::gotoLine(QString pos) { int lineNumber = -1, lineChar = 0; if (pos.mid(0,1) == "+") { pos = pos.mid(1); } QStringList list1 = pos.split(','); if (list1.count() > 0) { lineNumber = list1[0].toInt() -1; } if (list1.count() > 1) { lineChar = list1[1].toInt() -1; } setCursorPosition({lineChar, lineNumber}); } bool File::formattingCharacters() const { return _formattingCharacters; } void File::setFormattingCharacters(bool formattingCharacters) { _formattingCharacters = formattingCharacters; update(); } void File::setRightMarginHint(int hint) { if (hint < 1) { _rightMarginHint = 0; } else { _rightMarginHint = hint; } update(); } int File::rightMarginHint() const { return _rightMarginHint; } bool File::colorTabs() const { return _colorTabs; } void File::setColorTabs(bool colorTabs) { _colorTabs = colorTabs; update(); } bool File::colorTrailingSpaces() { return _colorTrailingSpaces; } void File::setColorTrailingSpaces(bool colorSpaceEnd) { _colorTrailingSpaces = colorSpaceEnd; update(); } void File::setEatSpaceBeforeTabs(bool eat) { _eatSpaceBeforeTabs = eat; } bool File::eatSpaceBeforeTabs() { return _eatSpaceBeforeTabs; } QPair File::getSelectedLinesSort() { auto lines = getSelectedLines(); return {std::min(lines.first, lines.second), std::max(lines.first, lines.second)}; } QPair File::getSelectedLines() { int startY; int endeY; if (hasBlockSelection() || hasMultiInsert()) { startY = _blockSelectStartLine->line(); endeY = _blockSelectEndLine->line(); } else { const auto [startCodeUnit, startLine] = anchorPosition(); const auto [endCodeUnit, endLine] = cursorPosition(); startY = startLine; endeY = endLine; if (startLine < endLine) { if (endCodeUnit == 0) { endeY--; } } else if (endLine < startLine) { if (startCodeUnit == 0) { startY--; } } } return {std::max(0, startY), std::max(0, endeY)}; } void File::selectLines(int startY, int endY) { clearSelection(); if (startY > endY) { setSelection({document()->lineCodeUnits(startY), startY}, {0, endY}); } else { setSelection({0, startY}, {document()->lineCodeUnits(endY), endY}); } } void File::clearAdvancedSelection() { if (_currentSearchMatch) { _currentSearchMatch.reset(); } if (_blockSelect) { disableBlockSelection(); } } bool File::canCut() { return hasBlockSelection() || ZTextEdit::hasSelection(); } bool File::canCopy() { return hasBlockSelection() || ZTextEdit::hasSelection(); } QString File::selectedText() { QString selectText = ""; if (hasBlockSelection()) { const int firstSelectBlockLine = std::min(_blockSelectStartLine->line(), _blockSelectEndLine->line()); const int lastSelectBlockLine = std::max(_blockSelectStartLine->line(), _blockSelectEndLine->line()); const int firstSelectBlockColumn = std::min(_blockSelectStartColumn, _blockSelectEndColumn); const int lastSelectBlockColumn = std::max(_blockSelectStartColumn, _blockSelectEndColumn); for (int line = firstSelectBlockLine; line < document()->lineCount() && line <= lastSelectBlockLine; line++) { Tui::ZTextLayout laySel = textLayoutForLineWithoutWrapping(line); Tui::ZTextLineRef tlrSel = laySel.lineAt(0); const int selFirstCodeUnitInLine = tlrSel.xToCursor(firstSelectBlockColumn); const int selLastCodeUnitInLine = tlrSel.xToCursor(lastSelectBlockColumn); selectText += document()->line(line).mid(selFirstCodeUnitInLine, selLastCodeUnitInLine - selFirstCodeUnitInLine); if (line != lastSelectBlockLine) { selectText += "\n"; } } } else { selectText = ZTextEdit::selectedText(); } return selectText; } bool File::hasSelection() const { return hasBlockSelection() || hasMultiInsert() || ZTextEdit::hasSelection(); } bool File::removeSelectedText() { Tui::ZDocumentCursor cursor = textCursor(); auto undoGroup = document()->startUndoGroup(&cursor); if (!hasBlockSelection() && !hasMultiInsert() && !ZTextEdit::hasSelection()) { return false; } if (_blockSelect) { if (hasBlockSelection()) { blockSelectRemoveSelectedAndConvertToMultiInsert(); } const int newCursorLine = _blockSelectEndLine->line(); const int newCursorColumn = _blockSelectStartColumn; Tui::ZTextLayout lay = textLayoutForLineWithoutWrapping(newCursorLine); Tui::ZTextLineRef tlr = lay.lineAt(0); _blockSelect = false; _blockSelectStartLine.reset(); _blockSelectEndLine.reset(); _blockSelectStartColumn = _blockSelectEndColumn = -1; setCursorPosition({tlr.xToCursor(newCursorColumn), newCursorLine}); } else { cursor.removeSelectedText(); setTextCursor(cursor); } adjustScrollPosition(); updateCommands(); return true; } bool File::hasBlockSelection() const { return _blockSelect && _blockSelectStartColumn != _blockSelectEndColumn; } bool File::hasMultiInsert() const { return _blockSelect && _blockSelectStartColumn == _blockSelectEndColumn; } void File::activateBlockSelection() { // pre-condition: _blockSelect = false _blockSelect = true; const auto [cursorCodeUnit, cursorLine] = cursorPosition(); _blockSelectStartLine.emplace(document(), cursorLine); _blockSelectEndLine.emplace(document(), cursorLine); Tui::ZTextLayout lay = textLayoutForLineWithoutWrapping(cursorLine); Tui::ZTextLineRef tlr = lay.lineAt(0); _blockSelectStartColumn = _blockSelectEndColumn = tlr.cursorToX(cursorCodeUnit, Tui::ZTextLayout::Leading); } void File::disableBlockSelection() { // pre-condition: _blockSelect = true _blockSelect = false; // just to be sure const int cursorLine = std::min(_blockSelectEndLine->line(), document()->lineCount() - 1); const int cursorColumn = _blockSelectEndColumn; _blockSelectStartLine.reset(); _blockSelectEndLine.reset(); _blockSelectStartColumn = _blockSelectEndColumn = -1; Tui::ZTextLayout lay = textLayoutForLineWithoutWrapping(cursorLine); Tui::ZTextLineRef tlr = lay.lineAt(0); setCursorPosition({tlr.xToCursor(cursorColumn), cursorLine}); } void File::blockSelectRemoveSelectedAndConvertToMultiInsert() { const int firstSelectBlockLine = std::min(_blockSelectStartLine->line(), _blockSelectEndLine->line()); const int lastSelectBlockLine = std::max(_blockSelectStartLine->line(), _blockSelectEndLine->line()); const int firstSelectBlockColumn = std::min(_blockSelectStartColumn, _blockSelectEndColumn); const int lastSelectBlockColumn = std::max(_blockSelectStartColumn, _blockSelectEndColumn); for (int line = firstSelectBlockLine; line < document()->lineCount() && line <= lastSelectBlockLine; line++) { Tui::ZTextLayout laySel = textLayoutForLineWithoutWrapping(line); Tui::ZTextLineRef tlrSel = laySel.lineAt(0); const int selFirstCodeUnitInLine = tlrSel.xToCursor(firstSelectBlockColumn); const int selLastCodeUnitInLine = tlrSel.xToCursor(lastSelectBlockColumn); Tui::ZDocumentCursor cur = makeCursor(); cur.setPosition({selFirstCodeUnitInLine, line}); cur.setPosition({selLastCodeUnitInLine, line}, true); cur.removeSelectedText(); } _blockSelectStartColumn = _blockSelectEndColumn = firstSelectBlockColumn; } template void File::multiInsertForEachCursor(int flags, F f) { // pre-condition: hasMultiInsert() = true const int firstSelectBlockLine = std::min(_blockSelectStartLine->line(), _blockSelectEndLine->line()); const int lastSelectBlockLine = std::max(_blockSelectStartLine->line(), _blockSelectEndLine->line()); const int column = _blockSelectStartColumn; bool endSkipped = false; int fallbackColumn = _blockSelectStartColumn; for (int line = firstSelectBlockLine; line < document()->lineCount() && line <= lastSelectBlockLine; line++) { Tui::ZTextLayout laySel = textLayoutForLineWithoutWrapping(line); Tui::ZTextLineRef tlrSel = laySel.lineAt(0); const int codeUnitInLine = tlrSel.xToCursor(column); Tui::ZDocumentCursor cur = makeCursor(); cur.setPosition({codeUnitInLine, line}); bool skip = false; if (tlrSel.width() < column) { if ((flags & mi_add_spaces)) { cur.insertText(QString(" ").repeated(column - tlrSel.width())); } else if (flags & mi_skip_short_lines) { skip = true; } } if (!skip) { f(cur); Tui::ZTextLayout layNew = textLayoutForLineWithoutWrapping(line); Tui::ZTextLineRef tlrNew = layNew.lineAt(0); fallbackColumn = tlrNew.cursorToX(cur.position().codeUnit, Tui::ZTextLayout::Leading); if (line == _blockSelectEndLine->line()) { _blockSelectStartColumn = _blockSelectEndColumn = fallbackColumn; } } else { if (line == _blockSelectEndLine->line()) { endSkipped = true; } } } if (endSkipped) { _blockSelectStartColumn = _blockSelectEndColumn = fallbackColumn; } } void File::multiInsertDeletePreviousCharacter() { // pre-condition: hasMultiInsert() = true multiInsertForEachCursor(mi_skip_short_lines, [&](Tui::ZDocumentCursor &cur) { const auto [cursorCodeUnit, cursorLine] = cur.position(); if (cursorCodeUnit > 0) { cur.deletePreviousCharacter(); } }); } void File::multiInsertDeletePreviousWord() { // pre-condition: hasMultiInsert() = true multiInsertForEachCursor(mi_skip_short_lines, [&](Tui::ZDocumentCursor &cur) { const auto [cursorCodeUnit, cursorLine] = cur.position(); if (cursorCodeUnit > 0) { cur.deletePreviousWord(); } }); } void File::multiInsertDeleteCharacter() { // pre-condition: hasMultiInsert() = true multiInsertForEachCursor(mi_skip_short_lines, [&](Tui::ZDocumentCursor &cur) { const auto [cursorCodeUnit, cursorLine] = cur.position(); if (cursorCodeUnit < document()->lineCodeUnits(cursorLine)) { cur.deleteCharacter(); } }); } void File::multiInsertDeleteWord() { // pre-condition: hasMultiInsert() = true multiInsertForEachCursor(mi_skip_short_lines, [&](Tui::ZDocumentCursor &cur) { const auto [cursorCodeUnit, cursorLine] = cur.position(); if (cursorCodeUnit < document()->lineCodeUnits(cursorLine)) { cur.deleteWord(); } }); } void File::multiInsertInsert(const QString &text) { // pre-condition: hasMultiInsert() = true multiInsertForEachCursor(mi_add_spaces, [&](Tui::ZDocumentCursor &cur) { cur.insertText(text); }); } void File::toggleLineMarker() { if (_lineMarker->hasMarker(cursorPosition().line)) { _lineMarker->removeMarker(cursorPosition().line); } else { _lineMarker->addMarker(document(), cursorPosition().line); } update(); } void File::gotoNextLineMarker() { int line = _lineMarker->nextMarker(cursorPosition().line); if (line >= 0) { setCursorPosition({0, line}); } } void File::gotoPreviousLineMarker() { int line = _lineMarker->previousMarker(cursorPosition().line); if (line >= 0) { setCursorPosition({0, line}); } } bool File::hasLineMarker() const { return _lineMarker->hasMarker(); } bool File::hasLineMarker(int line) const { return _lineMarker->hasMarker(line); } int File::lineMarkerBorderWidth() const { if (hasLineMarker()) { if (lineNumberBorderWidth() > 0) { return 1; } } else { return 0; } return 2; } int File::allBordersWidth() const { return lineNumberBorderWidth() + lineMarkerBorderWidth(); } void File::setSearchText(QString searchText) { int gen = ++(*searchGeneration); _searchText = searchText; searchTextChanged(_searchText); if (searchText == "") { _cmdSearchNext->setEnabled(false); _cmdSearchPrevious->setEnabled(false); setSearchVisible(false); return; } else { _cmdSearchNext->setEnabled(true); _cmdSearchPrevious->setEnabled(true); setSearchVisible(true); } if (_searchRegex || _searchText.contains('\n')) { // SearchCount currently does not support regular expression and does not support multi line matches, // just disable the search count display in these cases for now. searchCountChanged(-1); } else { SearchCountSignalForwarder *searchCountSignalForwarder = new SearchCountSignalForwarder(); QObject::connect(searchCountSignalForwarder, &SearchCountSignalForwarder::searchCount, this, &File::searchCountChanged); QtConcurrent::run([searchCountSignalForwarder](Tui::ZDocumentSnapshot snap, QString searchText, Qt::CaseSensitivity caseSensitivity, int gen, std::shared_ptr> searchGen) { SearchCount sc; QObject::connect(&sc, &SearchCount::searchCount, searchCountSignalForwarder, &SearchCountSignalForwarder::searchCount); sc.run(snap, searchText, caseSensitivity, gen, searchGen); searchCountSignalForwarder->deleteLater(); }, document()->snapshot(), _searchText, _searchCaseSensitivity, gen, searchGeneration); } } void File::setSearchCaseSensitivity(Qt::CaseSensitivity searchCaseSensitivity) { _searchCaseSensitivity = searchCaseSensitivity; update(); } void File::setSearchVisible(bool visible) { _searchVisible = visible; searchVisibleChanged(visible); update(); } bool File::searchVisible() { return _searchVisible; } void File::setReplaceText(QString replaceText) { _replaceText = replaceText; } bool File::isSearchMatchSelected() { return _currentSearchMatch.has_value(); } void File::setRegex(bool reg) { _searchRegex = reg; } void File::setSearchWrap(bool wrap) { _searchWrap = wrap; update(); } bool File::searchWrap() { return _searchWrap; } void File::setSearchDirection(bool searchDirection) { _searchDirectionForward = searchDirection; } void File::toggleShowLineNumbers() { setShowLineNumbers(!showLineNumbers()); } void File::setFollowStandardInput(bool follow) { _followMode = follow; } void File::replaceSelected() { if (!_currentSearchMatch || hasBlockSelection() || hasMultiInsert()) { return; } if (ZTextEdit::hasSelection()) { QString text; if (_searchRegex) { bool esc = false; for (QChar ch: _replaceText) { if (esc) { if (ch >= '1' && ch <= '9') { const int captureNumber = (ch.unicode() - '0'); if (std::holds_alternative(*_currentSearchMatch)) { text += std::get(*_currentSearchMatch).regexCapture(captureNumber); } else if (std::holds_alternative(*_currentSearchMatch)) { text += std::get(*_currentSearchMatch).regexCapture(captureNumber); } } else if (ch == '\\') { text += '\\'; } esc = false; } else { if (ch == '\\') { esc = true; } else { text += ch; } } } } else { text = _replaceText; } Tui::ZDocumentCursor cursor = textCursor(); auto undoGroup = document()->startUndoGroup(&cursor); cursor.insertText(text); setTextCursor(cursor); adjustScrollPosition(); } } void File::runSearch(bool direction) { if (_searchText != "") { setSearchVisible(true); if (_searchNextFuture) { _searchNextFuture->cancel(); _searchNextFuture.reset(); } const bool effectiveDirection = direction ^ _searchDirectionForward; Tui::ZDocument::FindFlags flags; if (_searchCaseSensitivity == Qt::CaseSensitive) { flags |= Tui::ZDocument::FindFlag::FindCaseSensitively; } if (_searchWrap) { flags |= Tui::ZDocument::FindFlag::FindWrap; } if (!effectiveDirection) { flags |= Tui::ZDocument::FindFlag::FindBackward; } auto watcher = new QFutureWatcher(); QObject::connect(watcher, &QFutureWatcher::finished, this, [this, watcher, effectiveDirection] { if (!watcher->isCanceled()) { Tui::ZDocumentFindAsyncResult res = watcher->future().result(); if (res.anchor() != res.cursor()) { // has a match? clearAdvancedSelection(); if (selectMode()) { if (effectiveDirection) { setCursorPosition(res.cursor(), true); } else { setCursorPosition(res.anchor(), true); } } else { setSelection(res.anchor(), res.cursor()); } //TODO: //res.wrapped(); _currentSearchMatch.emplace(res); updateCommands(); const auto [currentCodeUnit, currentLine] = cursorPosition(); setScrollPosition(scrollPositionColumn(), std::max(0, currentLine - 1), 0); adjustScrollPosition(); } } watcher->deleteLater(); }); if (_searchRegex) { _searchNextFuture.emplace(document()->findAsync(QRegularExpression(_searchText), textCursor(), flags)); } else { _searchNextFuture.emplace(document()->findAsync(_searchText, textCursor(), flags)); } watcher->setFuture(*_searchNextFuture); } } int File::replaceAll(QString searchText, QString replaceText) { setSearchText(searchText); setReplaceText(replaceText); // Get rid of block selections and multi insert. clearSelection(); if (searchText.isEmpty()) { return 0; } Tui::ZDocumentCursor cursor = textCursor(); auto undoGroup = document()->startUndoGroup(&cursor); int counter = 0; Tui::ZDocument::FindFlags flags; if (_searchCaseSensitivity == Qt::CaseSensitive) { flags |= Tui::ZDocument::FindFlag::FindCaseSensitively; } cursor.setPosition({0, 0}); while (true) { Tui::ZDocumentCursor found = cursor; auto match = _currentSearchMatch; if (_searchRegex) { Tui::ZDocumentFindResult details = document()->findSyncWithDetails(QRegularExpression(_searchText), cursor, flags); match = details; found = details.cursor(); } else { found = document()->findSync(_searchText, cursor, flags); match = std::monostate(); } if (!found.hasSelection()) { // has no match? break; } setSelection(found.anchor(), found.position()); _currentSearchMatch = match; replaceSelected(); cursor = textCursor(); counter++; } const auto [currentCodeUnit, currentLine] = cursorPosition(); if (currentLine - 1 > 0) { setScrollPosition(scrollPositionColumn(), currentLine - 1, 0); } adjustScrollPosition(); // Update search count setSearchText(searchText); return counter; } Tui::ZTextOption File::textOption() const { Tui::ZTextOption option; option.setWrapMode(wordWrapMode()); option.setTabStopDistance(tabStopDistance()); Tui::ZTextOption::Flags flags; if (formattingCharacters()) { flags |= Tui::ZTextOption::ShowTabsAndSpaces; } if (colorTabs()) { flags |= Tui::ZTextOption::ShowTabsAndSpacesWithColors; if (!useTabChar()) { option.setTabColor([] (int pos, int size, int hidden, const Tui::ZTextStyle &base, const Tui::ZTextStyle &formating, const Tui::ZFormatRange* range) -> Tui::ZTextStyle { (void)formating; (void)size; if (range && range->userData() == FR_UD_SELECTION) { if (pos == hidden) { return { range->format().foregroundColor(), {0xff, 0x80, 0xff} }; } return { range->format().foregroundColor(), {0xff, 0xb0, 0xff} }; } if (range && range->userData() == FR_UD_LIVE_SEARCH) { return {Tui::Colors::darkGray, {0xff, 0xdd, 0}, Tui::ZTextAttribute::Bold}; } if (pos == hidden) { return { base.foregroundColor(), {base.backgroundColor().red() + 0x60, base.backgroundColor().green(), base.backgroundColor().blue()} }; } return { base.foregroundColor(), {base.backgroundColor().red() + 0x40, base.backgroundColor().green(), base.backgroundColor().blue()} }; }); } else { option.setTabColor([] (int pos, int size, int hidden, const Tui::ZTextStyle &base, const Tui::ZTextStyle &formating, const Tui::ZFormatRange* range) -> Tui::ZTextStyle { (void)formating; (void)size; if (range && range->userData() == FR_UD_SELECTION) { if (pos == hidden) { return { range->format().foregroundColor(), {0x80, 0xff, 0xff} }; } return { range->format().foregroundColor(), {0xb0, 0xff, 0xff} }; } if (range && range->userData() == FR_UD_LIVE_SEARCH) { return {Tui::Colors::darkGray, {0xff, 0xdd, 0}, Tui::ZTextAttribute::Bold}; } if (pos == hidden) { return { base.foregroundColor(), {base.backgroundColor().red(), base.backgroundColor().green() + 0x60, base.backgroundColor().blue()} }; } return { base.foregroundColor(), {base.backgroundColor().red(), base.backgroundColor().green() + 0x40, base.backgroundColor().blue()} }; }); } } option.setFlags(flags); return option; } bool File::highlightBracketFind() { QString openBracket = "{[(<"; QString closeBracket = "}])>"; if (highlightBracket()) { const auto [cursorCodeUnit, cursorLine] = cursorPosition(); if (cursorCodeUnit < document()->lineCodeUnits(cursorLine)) { for (int i = 0; i < openBracket.size(); i++) { if (document()->line(cursorLine)[cursorCodeUnit] == openBracket[i]) { int y = 0; int counter = 0; int startX = cursorCodeUnit + 1; for (int line = cursorLine; y++ < rect().height() && line < document()->lineCount(); line++) { for (; startX < document()->lineCodeUnits(line); startX++) { if (document()->line(line)[startX] == openBracket[i]) { counter++; } else if (document()->line(line)[startX] == closeBracket[i]) { if (counter > 0) { counter--; } else { _bracketPosition.line = line; _bracketPosition.codeUnit = startX; return true; } } } startX = 0; } } if (document()->line(cursorLine)[cursorCodeUnit] == closeBracket[i]) { int counter = 0; int startX = cursorCodeUnit - 1; for (int line = cursorLine; line >= 0;) { for (; startX >= 0; startX--) { if (document()->line(line)[startX] == closeBracket[i]) { counter++; } else if (document()->line(line)[startX] == openBracket[i]) { if (counter > 0) { counter--; } else { _bracketPosition.line = line; _bracketPosition.codeUnit = startX; return true; } } } if(--line >= 0) { startX = document()->lineCodeUnits(line) - 1; } } } } } } _bracketPosition.line = -1; _bracketPosition.codeUnit = -1; return false; } void File::paintEvent(Tui::ZPaintEvent *event) { Tui::ZColor fg = getColor("chr.editFg"); Tui::ZColor bg = getColor("chr.editBg"); setCursorColor(fg.redOrGuess(), fg.greenOrGuess(), fg.blueOrGuess()); highlightBracketFind(); Tui::ZColor marginMarkBg = [](Tui::ZColorHSV base) { return Tui::ZColor::fromHsv(base.hue(), base.saturation(), base.value() * 0.75); }(bg.toHsv()); std::optional leftOfMarginBuffer; std::optional painterLeftOfMargin; auto scrollPositionColumns = scrollPositionColumn(); const int leftBordersWidth = lineNumberBorderWidth() + lineMarkerBorderWidth(); auto *painter = event->painter(); if (_rightMarginHint) { painter->clearRect(0, 0, -scrollPositionColumns + leftBordersWidth + _rightMarginHint, rect().height(), fg, bg); painter->clearRect(-scrollPositionColumns + leftBordersWidth + _rightMarginHint, 0, Tui::tuiMaxSize, rect().height(), fg, marginMarkBg); // One extra column to account for double wide character at last position leftOfMarginBuffer.emplace(terminal(), _rightMarginHint + 1, 1); painterLeftOfMargin.emplace(leftOfMarginBuffer->painter()); } else { painter->clear(fg, bg); } Tui::ZTextOption option = textOption(); Tui::ZTextOption optionCursorAtEndOfLine = option; if (colorTrailingSpaces()) { option.setFlags(optionCursorAtEndOfLine.flags() | Tui::ZTextOption::ShowTabsAndSpacesWithColors); option.setTrailingWhitespaceColor([] (const Tui::ZTextStyle &base, const Tui::ZTextStyle &formating, const Tui::ZFormatRange* range) -> Tui::ZTextStyle { (void)formating; if (range && range->userData() == FR_UD_SELECTION) { return { range->format().foregroundColor(), {0xff, 0x80, 0x80} }; } return { base.foregroundColor(), {0x80, 0, 0} }; }); } const int firstSelectBlockLine = _blockSelect ? std::min(_blockSelectStartLine->line(), _blockSelectEndLine->line()) : 0; const int lastSelectBlockLine = _blockSelect ? std::max(_blockSelectStartLine->line(), _blockSelectEndLine->line()) : 0; const int firstSelectBlockColumn = std::min(_blockSelectStartColumn, _blockSelectEndColumn); const int lastSelectBlockColumn = std::max(_blockSelectStartColumn, _blockSelectEndColumn); Tui::ZDocumentCursor::Position startSelectCursor(-1, -1); Tui::ZDocumentCursor::Position endSelectCursor(-1, -1); Tui::ZDocumentCursor cursor = textCursor(); if (cursor.hasSelection()) { startSelectCursor = cursor.selectionStartPos(); endSelectCursor = cursor.selectionEndPos(); } QVector highlights; const Tui::ZTextStyle base{fg, bg}; const Tui::ZTextStyle baseInMargin{fg, marginMarkBg}; const Tui::ZTextStyle formatingChar{Tui::Colors::darkGray, bg}; const Tui::ZTextStyle formatingCharInMargin{Tui::Colors::darkGray, marginMarkBg}; const Tui::ZTextStyle selected{Tui::Colors::darkGray, fg, Tui::ZTextAttribute::Bold}; const Tui::ZTextStyle multiInsertChar{fg, Tui::Colors::lightGray, Tui::ZTextAttribute::Blink | Tui::ZTextAttribute::Italic}; const Tui::ZTextStyle multiInsertFormatingChar{Tui::Colors::darkGray, Tui::Colors::lightGray, Tui::ZTextAttribute::Blink}; const Tui::ZTextStyle selectedFormatingChar{Tui::Colors::darkGray, fg}; const auto [cursorCodeUnit, cursorLineReal] = cursor.position(); const int cursorLine = _blockSelect ? _blockSelectEndLine->line() : cursorLineReal; QString strlinenumber; int y = -scrollPositionFineLine(); int tmpLastLineWidth = 0; for (int line = scrollPositionLine(); y < rect().height() && line < document()->lineCount(); line++) { const bool multiIns = hasMultiInsert(); const bool cursorAtEndOfCurrentLine = [&, cursorCodeUnit=cursorCodeUnit] { if (_blockSelect) { if (multiIns && firstSelectBlockLine <= line && line <= lastSelectBlockLine) { auto testLayout = textLayoutForLineWithoutWrapping(line); auto testLayoutLine = testLayout.lineAt(0); return testLayoutLine.width() == firstSelectBlockColumn; } return false; } else { return line == cursorLine && document()->lineCodeUnits(cursorLine) == cursorCodeUnit; } }(); Tui::ZTextLayout lay = cursorAtEndOfCurrentLine ? textLayoutForLine(optionCursorAtEndOfLine, line) : textLayoutForLine(option, line); // highlights highlights.clear(); #ifdef SYNTAX_HIGHLIGHTING if (syntaxHighlightingActive()) { if (document()->lineUserData(line)) { auto extraData = std::static_pointer_cast(document()->lineUserData(line)); if (line == cursorLine && extraData->lineRevision != document()->lineRevision(line)) { // avoid glitches when using the cursor to edit lines // the state can still be stale, but much more edits can be done without visible glitches // with stale state. highlights += std::get<1>(_syntaxHighlightExporter.highlightLineWrap(document()->line(line), extraData->stateBegin)); } else { highlights += extraData->highlights; } } } #endif // search matches if (searchVisible() && _searchText != "") { int found = -1; if (_searchRegex) { QRegularExpression rx(_searchText); if (rx.isValid()) { if (_searchCaseSensitivity == Qt::CaseInsensitive) { rx.setPatternOptions(QRegularExpression::PatternOption::CaseInsensitiveOption); } QRegularExpressionMatchIterator i = rx.globalMatch(document()->line(line)); while (i.hasNext()) { QRegularExpressionMatch match = i.next(); if (match.capturedLength() > 0) { highlights.append(Tui::ZFormatRange{match.capturedStart(), match.capturedLength(), {Tui::Colors::darkGray, {0xff, 0xdd, 0}, Tui::ZTextAttribute::Bold}, selectedFormatingChar, FR_UD_LIVE_SEARCH}); } } } } else { while ((found = document()->line(line).indexOf(_searchText, found + 1, _searchCaseSensitivity)) != -1) { highlights.append(Tui::ZFormatRange{found, _searchText.size(), {Tui::Colors::darkGray, {0xff, 0xdd, 0}, Tui::ZTextAttribute::Bold}, selectedFormatingChar, FR_UD_LIVE_SEARCH}); } } } if (_bracketPosition.codeUnit >= 0) { if (_bracketPosition.line == line) { highlights.append(Tui::ZFormatRange{_bracketPosition.codeUnit, 1, {Tui::Colors::cyan, bg,Tui::ZTextAttribute::Bold}, selectedFormatingChar}); } if (line == cursorLine) { highlights.append(Tui::ZFormatRange{cursorCodeUnit, 1, {Tui::Colors::cyan, bg,Tui::ZTextAttribute::Bold}, selectedFormatingChar}); } } // selection if (_blockSelect) { if (line >= firstSelectBlockLine && line <= lastSelectBlockLine) { Tui::ZTextLayout laySel = textLayoutForLineWithoutWrapping(line); Tui::ZTextLineRef tlrSel = laySel.lineAt(0); if (firstSelectBlockColumn == lastSelectBlockColumn) { highlights.append(Tui::ZFormatRange{tlrSel.xToCursor(firstSelectBlockColumn), 1, multiInsertChar, multiInsertFormatingChar, FR_UD_SELECTION}); } else { const int selFirstCodeUnitInLine = tlrSel.xToCursor(firstSelectBlockColumn); const int selLastCodeUnitInLine = tlrSel.xToCursor(lastSelectBlockColumn); highlights.append(Tui::ZFormatRange{selFirstCodeUnitInLine, selLastCodeUnitInLine - selFirstCodeUnitInLine, selected, selectedFormatingChar, FR_UD_SELECTION}); } } } else { if (line > startSelectCursor.line && line < endSelectCursor.line) { // whole line highlights.append(Tui::ZFormatRange{0, document()->lineCodeUnits(line), selected, selectedFormatingChar, FR_UD_SELECTION}); } else if (line > startSelectCursor.line && line == endSelectCursor.line) { // selection ends on this line highlights.append(Tui::ZFormatRange{0, endSelectCursor.codeUnit, selected, selectedFormatingChar, FR_UD_SELECTION}); } else if (line == startSelectCursor.line && line < endSelectCursor.line) { // selection starts on this line highlights.append(Tui::ZFormatRange{startSelectCursor.codeUnit, document()->lineCodeUnits(line) - startSelectCursor.codeUnit, selected, selectedFormatingChar, FR_UD_SELECTION}); } else if (line == startSelectCursor.line && line == endSelectCursor.line) { // selection is contained in this line highlights.append(Tui::ZFormatRange{startSelectCursor.codeUnit, endSelectCursor.codeUnit - startSelectCursor.codeUnit, selected, selectedFormatingChar, FR_UD_SELECTION}); } } if (_rightMarginHint && lay.maximumWidth() > _rightMarginHint) { if (lay.lineCount() > leftOfMarginBuffer->height() && leftOfMarginBuffer->height() < rect().height()) { leftOfMarginBuffer.emplace(terminal(), _rightMarginHint + 1, std::min(std::max(leftOfMarginBuffer->height() * 2, lay.lineCount()), rect().height())); painterLeftOfMargin.emplace(leftOfMarginBuffer->painter()); } lay.draw(*painter, {-scrollPositionColumns + leftBordersWidth, y}, baseInMargin, &formatingCharInMargin, highlights); painterLeftOfMargin->clearRect(0, 0, _rightMarginHint + 1, lay.lineCount(), base.foregroundColor(), base.backgroundColor()); lay.draw(*painterLeftOfMargin, {0, 0}, base, &formatingChar, highlights); painter->drawImageWithTiling(-scrollPositionColumns + leftBordersWidth, y, *leftOfMarginBuffer, 0, 0, _rightMarginHint, lay.lineCount(), Tui::ZTilingMode::NoTiling, Tui::ZTilingMode::Put); } else { if (_formattingCharacters) { lay.draw(*painter, {-scrollPositionColumns + leftBordersWidth, y}, base, &formatingChar, highlights); } else { lay.draw(*painter, {-scrollPositionColumns + leftBordersWidth, y}, base, &base, highlights); } } Tui::ZTextLineRef lastLine = lay.lineAt(lay.lineCount()-1); tmpLastLineWidth = lastLine.width(); bool lineBreakSelected = false; if (_blockSelect) { const int lastSelectBlockHighlightColumn = lastSelectBlockColumn + (multiIns ? 1 : 0); if (firstSelectBlockLine <= line && line <= lastSelectBlockLine) { lineBreakSelected = firstSelectBlockColumn <= lastLine.width() && lastLine.width() < lastSelectBlockHighlightColumn; // FIXME this does not work with soft wrapped lines, so disable for now when in a wrapped line if (lay.lineCount() == 1 && lastLine.width() + 1 < lastSelectBlockHighlightColumn) { Tui::ZTextStyle markStyle = selected; if (firstSelectBlockColumn == lastSelectBlockColumn) { markStyle = multiInsertChar; } const int firstColumnAfterLineBreakMarker = std::max(lastLine.width() + 1, firstSelectBlockColumn); painter->clearRect(-scrollPositionColumns + firstColumnAfterLineBreakMarker + leftBordersWidth, y + lastLine.y(), std::max(1, lastSelectBlockColumn - firstColumnAfterLineBreakMarker), 1, markStyle.foregroundColor(), markStyle.backgroundColor()); } } } else { lineBreakSelected = startSelectCursor.line <= line && endSelectCursor.line > line; } if (lineBreakSelected) { if (formattingCharacters()) { Tui::ZTextStyle markStyle = selectedFormatingChar; if (multiIns) { markStyle = multiInsertChar; } painter->writeWithAttributes(-scrollPositionColumns + lastLine.width() + leftBordersWidth, y + lastLine.y(), QStringLiteral("¶"), markStyle.foregroundColor(), markStyle.backgroundColor(), markStyle.attributes()); } else { Tui::ZTextStyle markStyle = selected; if (multiIns) { markStyle = multiInsertChar; } painter->clearRect(-scrollPositionColumns + lastLine.width() + leftBordersWidth, y + lastLine.y(), 1, 1, markStyle.foregroundColor(), markStyle.backgroundColor()); } } else if (formattingCharacters()) { const Tui::ZTextStyle &markStyle = (_rightMarginHint && lastLine.width() > _rightMarginHint) ? formatingCharInMargin : formatingChar; painter->writeWithAttributes(-scrollPositionColumns + lastLine.width() + leftBordersWidth, y + lastLine.y(), QStringLiteral("¶"), markStyle.foregroundColor(), markStyle.backgroundColor(), markStyle.attributes()); } if (cursorLine == line) { if (focus()) { if (_blockSelect) { painter->setCursor(-scrollPositionColumns + leftBordersWidth + _blockSelectEndColumn, y); } else { lay.showCursor(*painter, {-scrollPositionColumns + leftBordersWidth, y}, cursorCodeUnit); } } } // linenumber if (showLineNumbers()) { // Wrapping for (int i = lay.lineCount() - 1; i > 0; i--) { painter->writeWithColors(0, y + i, QString(" ").repeated(leftBordersWidth), getColor("chr.linenumberFg"), getColor("chr.linenumberBg")); } if (hasLineMarker(line)) { strlinenumber = QString::number(line + 1) + QString("*") + QString(" ").repeated(lineNumberBorderWidth() - QString::number(line + 1).size()); } else { strlinenumber = QString::number(line + 1) + QString(" ").repeated(leftBordersWidth - QString::number(line + 1).size()); } int lineNumberY = y; if (y < 0) { strlinenumber.replace(' ', '^'); lineNumberY = 0; } if (line == cursorLine || hasLineMarker(line)) { painter->writeWithAttributes(0, lineNumberY, strlinenumber, getColor("chr.linenumberFg"), getColor("chr.linenumberBg"), Tui::ZTextAttribute::Bold); } else { painter->writeWithColors(0, lineNumberY, strlinenumber, getColor("chr.linenumberFg"), getColor("chr.linenumberBg")); } } else { for (int i = lay.lineCount() - 1; i > 0; i--) { painter->writeWithColors(0, y + i, QString(" ").repeated(lineMarkerBorderWidth()), getColor("chr.linenumberFg"), getColor("chr.linenumberBg")); } if (hasLineMarker(line)) { strlinenumber = QString("*") + QString(" ").repeated(lineMarkerBorderWidth() -1); } else { strlinenumber = QString(" ").repeated(lineMarkerBorderWidth()); } int lineNumberY = y; if (y < 0) { strlinenumber.replace(' ', '^'); lineNumberY = 0; } painter->writeWithAttributes(0, lineNumberY, strlinenumber, getColor("chr.linenumberFg"), getColor("chr.linenumberBg"), Tui::ZTextAttribute::Bold); } y += lay.lineCount(); } if (document()->newlineAfterLastLineMissing()) { if (formattingCharacters() && y < rect().height() && scrollPositionColumns == 0) { const Tui::ZTextStyle &markStyle = (_rightMarginHint && tmpLastLineWidth > _rightMarginHint) ? formatingCharInMargin : formatingChar; painter->writeWithAttributes(-scrollPositionColumns + tmpLastLineWidth + leftBordersWidth, y - 1, "♦", markStyle.foregroundColor(), markStyle.backgroundColor(), markStyle.attributes()); } painter->writeWithAttributes(0 + leftBordersWidth, y, "\\ No newline at end of file", formatingChar.foregroundColor(), formatingChar.backgroundColor(), formatingChar.attributes()); } else { if (formattingCharacters() && y < rect().height() && scrollPositionColumns == 0) { painter->writeWithAttributes(0 + leftBordersWidth, y, "♦", formatingChar.foregroundColor(), formatingChar.backgroundColor(), formatingChar.attributes()); } } } int File::pageNavigationLineCount() const { if (wordWrapMode()) { return std::max(1, geometry().height() - 2); } return std::max(1, geometry().height() - 1); } void File::appendLine(const QString &line) { Tui::ZDocumentCursor cur = makeCursor(); if (document()->lineCount() == 1 && document()->lineCodeUnits(0) == 0) { cur.insertText(line); // We reposition the cursor so that the cursor is not moved in front of the cur coursor. Tui::ZDocumentCursor cursor = textCursor(); cursor.setPosition({0, 0}); setTextCursor(cursor); } else { cur.moveToEndOfDocument(); cur.insertText("\n" + line); } if (_followMode) { Tui::ZDocumentCursor cursor = textCursor(); cursor.setPosition({cursor.position().codeUnit, document()->lineCount() - 1}); setTextCursor(cursor); } adjustScrollPosition(); } void File::insertText(const QString &str) { // TODO das ist kein insertText... Oder vielleicht doch? auto undoGroup = startUndoGroup(); if (_blockSelect) { if (hasBlockSelection()) { blockSelectRemoveSelectedAndConvertToMultiInsert(); } QStringList source = str.split('\n'); if (source.last().isEmpty()) { source.removeLast(); } if (source.size()) { const int firstSelectBlockLine = std::min(_blockSelectStartLine->line(), _blockSelectEndLine->line()); const int lastSelectBlockLine = std::max(_blockSelectStartLine->line(), _blockSelectEndLine->line()); const int column = _blockSelectStartColumn; int sourceLine = 0; for (int line = firstSelectBlockLine; line < document()->lineCount() && line <= lastSelectBlockLine; line++) { if (source.size() != 1 && sourceLine >= source.size()) { break; } Tui::ZTextLayout laySel = textLayoutForLineWithoutWrapping(line); Tui::ZTextLineRef tlrSel = laySel.lineAt(0); Tui::ZDocumentCursor cur = makeCursor(); const int codeUnitInLine = tlrSel.xToCursor(column); cur.setPosition({codeUnitInLine, line}); if (tlrSel.width() < column) { cur.insertText(QString(" ").repeated(column - tlrSel.width())); } cur.insertText(source[sourceLine]); if (source.size() > 1) { sourceLine += 1; if (line == lastSelectBlockLine) { if (sourceLine < source.size()) { // Now sure what do do with the overflowing lines, for now just dump them in the last line for (; sourceLine < source.size(); sourceLine++) { cur.insertText("|" + source[sourceLine]); } } } } else { // keep repeating the one line for all selected lines } if (line == lastSelectBlockLine) { Tui::ZTextLayout layNew = textLayoutForLineWithoutWrapping(line); Tui::ZTextLineRef tlrNew = layNew.lineAt(0); _blockSelectStartColumn = _blockSelectEndColumn = tlrNew.cursorToX(cur.position().codeUnit, Tui::ZTextLayout::Leading); } } } } else { // Inserting might adjust the scroll position, so save it here and restore it later. const int line = scrollPositionLine(); Tui::ZDocumentCursor cursor = textCursor(); cursor.insertText(str); setTextCursor(cursor); setScrollPosition(scrollPositionColumn(), line, scrollPositionFineLine()); } adjustScrollPosition(); } void File::sortSelecedLines() { if (hasBlockSelection() || hasMultiInsert() || ZTextEdit::hasSelection()) { const auto [startLine, endLine] = getSelectedLines(); auto lines = getSelectedLinesSort(); Tui::ZDocumentCursor cursor = textCursor(); document()->sortLines(lines.first, lines.second + 1, &cursor); selectLines(startLine, endLine); } adjustScrollPosition(); } bool File::event(QEvent *event) { if (!parent()) { return ZWidget::event(event); } if (event->type() == Tui::ZEventType::terminalChange()) { // We are not allowed to have the cursor position between characters. Character boundaries depend on the // detected terminal thus reset the position to get the needed adjustment now. Tui::ZDocumentCursor cursor = textCursor(); if (!cursor.atLineStart() && terminal()) { if (_blockSelect) { disableBlockSelection(); } else { setCursorPosition(cursor.position()); } } } return ZWidget::event(event); } bool File::followStandardInput() { return _followMode; } void File::pasteEvent(Tui::ZPasteEvent *event) { QString text = event->text(); if (_formattingCharacters) { text.replace(QString("·"), QString(" ")); text.replace(QString("→"), QString(" ")); text.replace(QString("¶"), QString("")); } text.replace(QString("\r\n"), QString('\n')); text.replace(QString('\r'), QString('\n')); insertText(text); document()->clearCollapseUndoStep(); adjustScrollPosition(); } void File::focusInEvent(Tui::ZFocusEvent *event) { Q_UNUSED(event); updateCommands(); if (_searchText == "") { _cmdSearchNext->setEnabled(false); _cmdSearchPrevious->setEnabled(false); } else { _cmdSearchNext->setEnabled(true); _cmdSearchPrevious->setEnabled(true); } modifiedChanged(isModified()); } bool File::isNewFile() { if (document()->filename() == "NEWFILE") { return true; } return false; } int File::visualLineCount() { if (wordWrapMode() == Tui::ZTextOption::WrapMode::NoWrap) { return document()->lineCount(); } if (geometry().width() <= 0 || geometry().height() <= 0 || !terminal()) { return document()->lineCount(); } const int maxLines = 1024; // TODO: This leaves an empty line, but without for some reason the scroll happens before resizing the inline // display. int visualLines = 1; Tui::ZTextOption option = textOption(); for (int line = 0; line < document()->lineCount(); line++) { Tui::ZTextLayout lineLayout = textLayoutForLine(option, line); visualLines += lineLayout.lineCount(); if (visualLines > maxLines) { break; } } return visualLines; } void File::keyEvent(Tui::ZKeyEvent *event) { auto undoGroup = startUndoGroup(); QString text = event->text(); const bool isAltShift = event->modifiers() == (Qt::AltModifier | Qt::ShiftModifier); const bool isAltCtrlShift = event->modifiers() == (Qt::AltModifier | Qt::ControlModifier | Qt::ShiftModifier); if (event->key() == Qt::Key_Space && event->modifiers() == 0) { text = " "; } auto delAndClearSelection = [this] { //Markierte Zeichen Löschen removeSelectedText(); clearSelection(); adjustScrollPosition(); }; if (event->key() == Qt::Key_Backspace && (event->modifiers() == 0 || event->modifiers() == Qt::ControlModifier)) { disableDetachedScrolling(); setSelectMode(false); if (hasBlockSelection()) { delAndClearSelection(); } else if (hasMultiInsert()) { if (event->modifiers() & Qt::ControlModifier) { multiInsertDeletePreviousWord(); } else { multiInsertDeletePreviousCharacter(); } } else { Tui::ZDocumentCursor cursor = textCursor(); if (event->modifiers() & Qt::ControlModifier) { cursor.deletePreviousWord(); } else { cursor.deletePreviousCharacter(); } setTextCursor(cursor); } updateCommands(); adjustScrollPosition(); } else if (event->key() == Qt::Key_Delete && (event->modifiers() == 0 || event->modifiers() == Qt::ControlModifier)) { disableDetachedScrolling(); setSelectMode(false); if (hasBlockSelection()) { delAndClearSelection(); } else if (hasMultiInsert()) { if (event->modifiers() & Qt::ControlModifier) { multiInsertDeleteWord(); } else { multiInsertDeleteCharacter(); } } else { Tui::ZDocumentCursor cursor = textCursor(); if (event->modifiers() & Qt::ControlModifier) { cursor.deleteWord(); } else { if (cursor.atEnd()) { if (document()->line(cursor.position().line) != "") { document()->setNewlineAfterLastLineMissing(true); cursor.deleteCharacter(); } else { if (cursor.atStart() == cursor.atEnd()) { document()->setNewlineAfterLastLineMissing(true); } } } else { cursor.deleteCharacter(); } } setTextCursor(cursor); } updateCommands(); adjustScrollPosition(); } else if (text.size() && event->modifiers() == 0) { disableDetachedScrolling(); if (_formattingCharacters) { if (text == "·" || text == "→") { text = " "; } else if (event->text() == "¶") { //do not add the character text = ""; delAndClearSelection(); } } if (text.size()) { setSelectMode(false); if (_blockSelect) { if (hasBlockSelection()) { blockSelectRemoveSelectedAndConvertToMultiInsert(); } if (overwriteMode()) { multiInsertDeleteCharacter(); } multiInsertInsert(text); } else { Tui::ZDocumentCursor cursor = textCursor(); // Inserting might adjust the scroll position, so save it here and restore it later. const int line = scrollPositionLine(); if (overwriteMode()) { cursor.overwriteText(text); } else { cursor.insertText(text); } setTextCursor(cursor); setScrollPosition(scrollPositionColumn(), line, scrollPositionFineLine()); } adjustScrollPosition(); } updateCommands(); } else if (event->text() == "S" && (event->modifiers() == Qt::AltModifier || event->modifiers() == (Qt::AltModifier | Qt::ShiftModifier)) && hasSelection()) { disableDetachedScrolling(); // Alt + Shift + s sort selected lines sortSelecedLines(); adjustScrollPosition(); update(); } else if (event->key() == Qt::Key_Left) { disableDetachedScrolling(); if (isAltShift || isAltCtrlShift) { if (!_blockSelect) { activateBlockSelection(); } if (_blockSelectEndColumn > 0) { const auto mode = event->modifiers() & Qt::ControlModifier ? Tui::ZTextLayout::SkipWords : Tui::ZTextLayout::SkipCharacters; Tui::ZTextLayout lay = textLayoutForLineWithoutWrapping(_blockSelectEndLine->line()); Tui::ZTextLineRef tlr = lay.lineAt(0); const int codeUnitInLine = tlr.xToCursor(_blockSelectEndColumn); if (codeUnitInLine != document()->lineCodeUnits(_blockSelectEndLine->line())) { _blockSelectEndColumn = tlr.cursorToX(lay.previousCursorPosition(codeUnitInLine, mode), Tui::ZTextLayout::Leading); } else { _blockSelectEndColumn -= 1; } } } else { if (_blockSelect) { disableBlockSelection(); } Tui::ZDocumentCursor cursor = textCursor(); const bool extendSelection = event->modifiers() & Qt::ShiftModifier || selectMode(); if (event->modifiers() & Tui::ControlModifier) { cursor.moveWordLeft(extendSelection); } else { cursor.moveCharacterLeft(extendSelection); } setTextCursor(cursor); } updateCommands(); adjustScrollPosition(); document()->clearCollapseUndoStep(); } else if (event->key() == Qt::Key_Right) { disableDetachedScrolling(); if (isAltShift || isAltCtrlShift) { if (!_blockSelect) { activateBlockSelection(); } const auto mode = event->modifiers() & Qt::ControlModifier ? Tui::ZTextLayout::SkipWords : Tui::ZTextLayout::SkipCharacters; Tui::ZTextLayout lay = textLayoutForLineWithoutWrapping(_blockSelectEndLine->line()); Tui::ZTextLineRef tlr = lay.lineAt(0); const int codeUnitInLine = tlr.xToCursor(_blockSelectEndColumn); if (tlr.cursorToX(codeUnitInLine, Tui::ZTextLayout::Leading) == _blockSelectEndColumn && codeUnitInLine != document()->lineCodeUnits(_blockSelectEndLine->line())) { _blockSelectEndColumn = tlr.cursorToX(lay.nextCursorPosition(codeUnitInLine, mode), Tui::ZTextLayout::Leading); } else { _blockSelectEndColumn += 1; } } else { if (_blockSelect) { disableBlockSelection(); } Tui::ZDocumentCursor cursor = textCursor(); const bool extendSelection = event->modifiers() & Qt::ShiftModifier || selectMode(); if (event->modifiers() & Tui::ControlModifier) { cursor.moveWordRight(extendSelection); } else { cursor.moveCharacterRight(extendSelection); } setTextCursor(cursor); } updateCommands(); adjustScrollPosition(); document()->clearCollapseUndoStep(); } else if (event->key() == Qt::Key_Down && (event->modifiers() == 0 || event->modifiers() == Qt::ShiftModifier || isAltShift)) { disableDetachedScrolling(); if (isAltShift) { if (!_blockSelect) { activateBlockSelection(); } if (document()->lineCount() - 1 > _blockSelectEndLine->line()) { _blockSelectEndLine->setLine(_blockSelectEndLine->line() + 1); } } else { clearAdvancedSelection(); const bool extendSelection = event->modifiers() & Qt::ShiftModifier || selectMode(); Tui::ZDocumentCursor cursor = textCursor(); cursor.moveDown(extendSelection); setTextCursor(cursor); } updateCommands(); adjustScrollPosition(); document()->clearCollapseUndoStep(); } else if (event->key() == Qt::Key_Up && (event->modifiers() == 0 || event->modifiers() == Qt::ShiftModifier || isAltShift)) { disableDetachedScrolling(); if (isAltShift) { if (!_blockSelect) { activateBlockSelection(); } if (_blockSelectEndLine->line() > 0) { _blockSelectEndLine->setLine(_blockSelectEndLine->line() - 1); } } else { clearAdvancedSelection(); const bool extendSelection = event->modifiers() & Qt::ShiftModifier || selectMode(); Tui::ZDocumentCursor cursor = textCursor(); cursor.moveUp(extendSelection); setTextCursor(cursor); } updateCommands(); adjustScrollPosition(); document()->clearCollapseUndoStep(); } else if (event->key() == Qt::Key_Home && (event->modifiers() == 0 || event->modifiers() == Qt::ShiftModifier || isAltShift)) { disableDetachedScrolling(); if (isAltShift) { if (!_blockSelect) { activateBlockSelection(); } if (_blockSelectEndColumn == 0) { int codeUnitInLine = 0; for (; codeUnitInLine <= document()->lineCodeUnits(_blockSelectEndLine->line()) - 1; codeUnitInLine++) { if (document()->line(_blockSelectEndLine->line())[codeUnitInLine] != ' ' && document()->line(_blockSelectEndLine->line())[codeUnitInLine] != '\t') { break; } } Tui::ZTextLayout lay = textLayoutForLineWithoutWrapping(_blockSelectEndLine->line()); Tui::ZTextLineRef tlr = lay.lineAt(0); _blockSelectEndColumn = tlr.cursorToX(codeUnitInLine, Tui::ZTextLayout::Leading); } else { _blockSelectEndColumn = 0; } } else { clearAdvancedSelection(); const bool extendSelection = event->modifiers() == Qt::ShiftModifier || selectMode(); Tui::ZDocumentCursor cursor = textCursor(); if (cursor.atLineStart()) { cursor.moveToStartIndentedText(extendSelection); } else { cursor.moveToStartOfLine(extendSelection); } setTextCursor(cursor); } updateCommands(); adjustScrollPosition(); document()->clearCollapseUndoStep(); } else if (event->key() == Qt::Key_End && (event->modifiers() == 0 || event->modifiers() == Qt::ShiftModifier || isAltShift)) { disableDetachedScrolling(); if (isAltShift) { if (!_blockSelect) { activateBlockSelection(); } Tui::ZTextLayout lay = textLayoutForLineWithoutWrapping(_blockSelectEndLine->line()); Tui::ZTextLineRef tlr = lay.lineAt(0); _blockSelectEndColumn = tlr.cursorToX(document()->lineCodeUnits(_blockSelectEndLine->line()), Tui::ZTextLayout::Leading); } else { clearAdvancedSelection(); Tui::ZDocumentCursor cursor = textCursor(); if (event->modifiers() == Qt::ShiftModifier || selectMode()) { cursor.moveToEndOfLine(true); } else { cursor.moveToEndOfLine(false); } setTextCursor(cursor); updateCommands(); } adjustScrollPosition(); document()->clearCollapseUndoStep(); } else if (event->key() == Qt::Key_PageDown && (event->modifiers() == 0 || event->modifiers() == Qt::ShiftModifier)) { disableDetachedScrolling(); if (_blockSelect && event->modifiers() == Qt::ShiftModifier) { if (document()->lineCount() > _blockSelectEndLine->line() + pageNavigationLineCount()) { _blockSelectEndLine->setLine(_blockSelectEndLine->line() + pageNavigationLineCount()); } else { _blockSelectEndLine->setLine(document()->lineCount() - 1); } } else { clearAdvancedSelection(); Tui::ZDocumentCursor cursor = textCursor(); // Shift+PageUp/Down does not work with xterm's default settings. const bool extendSelection = event->modifiers() == Qt::ShiftModifier || selectMode(); const int amount = pageNavigationLineCount(); for (int i = 0; i < amount; i++) { cursor.moveDown(extendSelection); } setTextCursor(cursor); } adjustScrollPosition(); document()->clearCollapseUndoStep(); } else if (event->key() == Qt::Key_PageUp && (event->modifiers() == 0 || event->modifiers() == Qt::ShiftModifier)) { disableDetachedScrolling(); if (_blockSelect && event->modifiers() == Qt::ShiftModifier) { if (_blockSelectEndLine->line() > pageNavigationLineCount()) { _blockSelectEndLine->setLine(_blockSelectEndLine->line() - pageNavigationLineCount()); } else { _blockSelectEndLine->setLine(0); } } else { clearAdvancedSelection(); Tui::ZDocumentCursor cursor = textCursor(); // Shift+PageUp/Down does not work with xterm's default settings. const bool extendSelection = event->modifiers() == Qt::ShiftModifier || selectMode(); const int amount = pageNavigationLineCount(); for (int i = 0; i < amount; i++) { cursor.moveUp(extendSelection); } setTextCursor(cursor); } adjustScrollPosition(); document()->clearCollapseUndoStep(); } else if (event->key() == Qt::Key_Enter && (event->modifiers() & ~Qt::KeypadModifier) == 0) { disableDetachedScrolling(); setSelectMode(false); if (_blockSelect) { delAndClearSelection(); } // Inserting might adjust the scroll position, so save it here and restore it later. const int line = scrollPositionLine(); Tui::ZDocumentCursor cursor = textCursor(); cursor.insertText("\n"); setTextCursor(cursor); setScrollPosition(scrollPositionColumn(), line, scrollPositionFineLine()); updateCommands(); adjustScrollPosition(); } else if (event->key() == Qt::Key_Tab && event->modifiers() == 0) { disableDetachedScrolling(); if (_blockSelect) { if (hasBlockSelection()) { blockSelectRemoveSelectedAndConvertToMultiInsert(); } multiInsertForEachCursor(mi_add_spaces, [&](Tui::ZDocumentCursor &cur) { insertTabAt(cur); }); } else if (ZTextEdit::hasSelection()) { // Add one level of indent to the selected lines. const auto [firstLine, lastLine] = getSelectedLinesSort(); const auto [startLine, endLine] = getSelectedLines(); const bool savedSelectMode = selectMode(); clearSelection(); Tui::ZDocumentCursor cur = makeCursor(); for (int line = firstLine; line <= lastLine; line++) { if (document()->lineCodeUnits(line) > 0) { if (useTabChar()) { cur.setPosition({0, line}); cur.insertText(QString("\t")); } else { cur.setPosition({0, line}); cur.insertText(QString(" ").repeated(tabStopDistance())); } } } selectLines(startLine, endLine); setSelectMode(savedSelectMode); adjustScrollPosition(); } else { Tui::ZDocumentCursor cursor = textCursor(); if (_eatSpaceBeforeTabs && !useTabChar()) { // If spaces in front of a tab const auto [cursorCodeUnit, cursorLine] = cursor.position(); int leadingSpace = 0; for (; leadingSpace < document()->lineCodeUnits(cursorLine) && document()->line(cursorLine)[leadingSpace] == ' '; leadingSpace++); if (leadingSpace > cursorCodeUnit) { cursor.setPosition({leadingSpace, cursorLine}); } } // a normal tab insertTabAt(cursor); setTextCursor(cursor); updateCommands(); adjustScrollPosition(); update(); } } else if (event->key() == Qt::Key_Tab && event->modifiers() == Qt::ShiftModifier) { disableDetachedScrolling(); Tui::ZDocumentCursor cursor = textCursor(); // returns current line if no selection is active const auto [firstLine, lastLine] = getSelectedLinesSort(); const auto [startLine, endLine] = getSelectedLines(); const auto [cursorCodeUnit, cursorLine] = cursor.position(); const bool savedSelectMode = selectMode(); const bool reselect = hasMultiInsert() || hasBlockSelection() || cursor.hasSelection(); clearSelection(); int cursorAdjust = 0; for (int line = firstLine; line <= lastLine; line++) { int codeUnitsToRemove = 0; if (document()->lineCodeUnits(line) && document()->line(line)[0] == '\t') { codeUnitsToRemove = 1; } else { while (true) { if (codeUnitsToRemove < document()->lineCodeUnits(line) && codeUnitsToRemove < tabStopDistance() && document()->line(line)[codeUnitsToRemove] == ' ') { codeUnitsToRemove++; } else { break; } } } cursor.setPosition({0, line}); cursor.setPosition({codeUnitsToRemove, line}, true); cursor.removeSelectedText(); if (!reselect && line == cursorLine) { cursorAdjust = codeUnitsToRemove; } } // Update cursor / recreate selection if (!reselect) { setCursorPosition({cursorCodeUnit - cursorAdjust, cursorLine}); } else { selectLines(startLine, endLine); } setSelectMode(savedSelectMode); adjustScrollPosition(); } else if (event->text() == "d" && event->modifiers() == Qt::ControlModifier) { disableDetachedScrolling(); // Ctrl + d -> delete single line deleteLine(); } else if ((event->text() == "m") && event->modifiers() == Qt::ControlModifier) { // Not all terminals support this shortcut. toggleLineMarker(); } else if (event->text() == "," && event->modifiers() == Qt::ControlModifier) { gotoPreviousLineMarker(); } else if (event->text() == "." && event->modifiers() == Qt::ControlModifier) { gotoNextLineMarker(); } else if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_Up) { // Fenster hoch Scrolen detachedScrollUp(); } else if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_Down) { // Fenster runter Scrolen detachedScrollDown(); } else if (event->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier) && event->key() == Qt::Key_Up) { disableDetachedScrolling(); // returns current line if no selection is active const auto [firstLine, lastLine] = getSelectedLinesSort(); if (firstLine > 0) { Tui::ZDocumentCursor cursor = textCursor(); const auto scrollPositionYSave = scrollPositionLine(); const auto [startLine, endLine] = getSelectedLines(); const auto [cursorCodeUnit, cursorLine] = cursor.position(); const bool savedSelectMode = selectMode(); const bool reselect = hasMultiInsert() || hasBlockSelection() || cursor.hasSelection(); clearSelection(); // move lines up document()->moveLine(firstLine - 1, endLine, &cursor); // Update cursor / recreate selection if (!reselect) { setCursorPosition({cursorCodeUnit, cursorLine - 1}); } else { selectLines(startLine - 1, endLine - 1); } setSelectMode(savedSelectMode); setScrollPosition(scrollPositionColumn(), scrollPositionYSave, scrollPositionFineLine()); adjustScrollPosition(); } } else if (event->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier) && event->key() == Qt::Key_Down) { disableDetachedScrolling(); // returns current line if no selection is active const auto [firstLine, lastLine] = getSelectedLinesSort(); if (lastLine < document()->lineCount() - 1) { Tui::ZDocumentCursor cursor = textCursor(); const auto scrollPositionYSave = scrollPositionLine(); const auto [startLine, endLine] = getSelectedLines(); const auto [cursorCodeUnit, cursorLine] = cursor.position(); const bool savedSelectMode = selectMode(); const bool reselect = hasMultiInsert() || hasBlockSelection() || cursor.hasSelection(); clearSelection(); // Move lines down document()->moveLine(lastLine + 1, firstLine, &cursor); // Update cursor / recreate selection if (!reselect) { setCursorPosition({cursorCodeUnit, cursorLine + 1}); } else { selectLines(startLine + 1, endLine + 1); } setSelectMode(savedSelectMode); setScrollPosition(scrollPositionColumn(), scrollPositionYSave, scrollPositionFineLine()); adjustScrollPosition(); } } else if (event->key() == Qt::Key_Escape && event->modifiers() == 0) { disableDetachedScrolling(); setSearchVisible(false); setSelectMode(false); clearAdvancedSelection(); adjustScrollPosition(); } else if (event->key() == Qt::Key_Insert && event->modifiers() == 0) { disableDetachedScrolling(); toggleOverwriteMode(); } else if (event->key() == Qt::Key_F4 && event->modifiers() == 0) { disableDetachedScrolling(); toggleSelectMode(); } else { undoGroup.closeGroup(); ZTextEdit::keyEvent(event); } } std::tuple File::cursorPositionOrBlockSelectionEnd() { if (_blockSelect) { const int cursorLine = _blockSelectEndLine->line(); const int cursorColumn = _blockSelectEndColumn; Tui::ZTextLayout layNoWrap = textLayoutForLineWithoutWrapping(cursorLine); const int cursorCodeUnit = layNoWrap.lineAt(0).xToCursor(cursorColumn); return std::make_tuple(cursorCodeUnit, cursorLine, cursorColumn); } else { const auto [cursorCodeUnit, cursorLine] = cursorPosition(); Tui::ZTextLayout layNoWrap = textLayoutForLineWithoutWrapping(cursorLine); int cursorColumn = layNoWrap.lineAt(0).cursorToX(cursorCodeUnit, Tui::ZTextLayout::Leading); return std::make_tuple(cursorCodeUnit, cursorLine, cursorColumn); } } void File::adjustScrollPosition() { // diff to base class: uses cursorPositionOrBlockSelectionEnd(…) if (geometry().width() <= 0 && geometry().height() <= 0) { return; } int newScrollPositionLine = scrollPositionLine(); int newScrollPositionColumn = scrollPositionColumn(); int newScrollPositionFineLine = scrollPositionFineLine(); if (isDetachedScrolling()) { if (newScrollPositionLine >= document()->lineCount()) { newScrollPositionLine = document()->lineCount() - 1; } setScrollPosition(newScrollPositionColumn, newScrollPositionLine, newScrollPositionFineLine); return; } const auto [cursorCodeUnit, cursorLine, cursorColumn] = cursorPositionOrBlockSelectionEnd(); int viewWidth = geometry().width() - allBordersWidth(); // horizontal scroll position if (wordWrapMode() == Tui::ZTextOption::WrapMode::NoWrap) { if (cursorColumn - newScrollPositionColumn >= viewWidth) { newScrollPositionColumn = cursorColumn - viewWidth + 1; } if (cursorColumn > 0) { if (cursorColumn - newScrollPositionColumn < 1) { newScrollPositionColumn = cursorColumn - 1; } } else { newScrollPositionColumn = 0; } } else { newScrollPositionColumn = 0; } // vertical scroll position if (wordWrapMode() == Tui::ZTextOption::WrapMode::NoWrap) { if (cursorLine >= 0) { if (cursorLine - newScrollPositionLine < 1) { newScrollPositionLine = cursorLine; newScrollPositionFineLine = 0; } } if (cursorLine - newScrollPositionLine >= geometry().height() - 1) { newScrollPositionLine = cursorLine - geometry().height() + 2; } if (document()->lineCount() - newScrollPositionLine < geometry().height() - 1) { newScrollPositionLine = std::max(0, document()->lineCount() - geometry().height() + 1); } } else { Tui::ZTextOption option = textOption(); const int availableLinesAbove = geometry().height() - 2; Tui::ZTextLayout layCursorLayout = textLayoutForLine(option, cursorLine); int linesAbove = layCursorLayout.lineForTextPosition(cursorCodeUnit).lineNumber(); if (linesAbove >= availableLinesAbove) { if (newScrollPositionLine < cursorLine) { newScrollPositionLine = cursorLine; newScrollPositionFineLine = linesAbove - availableLinesAbove; } if (newScrollPositionLine == cursorLine) { if (newScrollPositionFineLine < linesAbove - availableLinesAbove) { newScrollPositionFineLine = linesAbove - availableLinesAbove; } } } else { for (int line = cursorLine - 1; line >= 0; line--) { Tui::ZTextLayout lay = textLayoutForLine(option, line); if (linesAbove + lay.lineCount() >= availableLinesAbove) { if (newScrollPositionLine < line) { newScrollPositionLine = line; newScrollPositionFineLine = (linesAbove + lay.lineCount()) - availableLinesAbove; } if (newScrollPositionLine == line) { if (newScrollPositionFineLine < (linesAbove + lay.lineCount()) - availableLinesAbove) { newScrollPositionFineLine = (linesAbove + lay.lineCount()) - availableLinesAbove; } } //_scrollPositionY = line; //_scrollFineLine = (linesAbove + lay.lineCount()) - availableLinesAbove; break; } linesAbove += lay.lineCount(); } } linesAbove = layCursorLayout.lineForTextPosition(cursorCodeUnit).lineNumber(); if (newScrollPositionLine == cursorLine) { if (linesAbove < newScrollPositionFineLine) { newScrollPositionFineLine = linesAbove; } } else if (newScrollPositionLine > cursorLine) { newScrollPositionLine = cursorLine; newScrollPositionFineLine = linesAbove; } // scroll when window is larger than the document shown (unless scrolled to top) if (newScrollPositionLine && newScrollPositionLine + (geometry().height() - 1) > document()->lineCount()) { int linesCounted = 0; QVector sizes; for (int line = document()->lineCount() - 1; line >= 0; line--) { Tui::ZTextLayout lay = textLayoutForLine(option, line); sizes.append(lay.lineCount()); linesCounted += lay.lineCount(); if (linesCounted >= geometry().height() - 1) { if (newScrollPositionLine > line) { newScrollPositionLine = line; newScrollPositionFineLine = linesCounted - (geometry().height() - 1); } else if (newScrollPositionLine == line && newScrollPositionFineLine > linesCounted - (geometry().height() - 1)) { newScrollPositionFineLine = linesCounted - (geometry().height() - 1); } break; } } } } setScrollPosition(newScrollPositionColumn, newScrollPositionLine, newScrollPositionFineLine); int max=0; for (int i = newScrollPositionLine; i < document()->lineCount() && i < newScrollPositionLine + geometry().height(); i++) { if(max < document()->lineCodeUnits(i)) { max = document()->lineCodeUnits(i); } } scrollRangeChanged(std::max(0, max - viewWidth), std::max(0, document()->lineCount() - geometry().height())); if (hasSelection()) { // TODO consider running this in background auto l = getSelectedLines(); int lines = 1 + std::max(l.second, l.first) - std::min(l.second, l.first); selectCharLines(terminal()->textMetrics().sizeInClusters(selectedText()), lines); } else { selectCharLines(0, 0); } update(); } editor-0.1.80/src/file.h000066400000000000000000000204061477475121100147600ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef FILE_H #define FILE_H #include #include #include #include #include #include #ifdef SYNTAX_HIGHLIGHTING #include #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include "markermanager.h" struct ExtraData : public Tui::ZDocumentLineUserData { #ifdef SYNTAX_HIGHLIGHTING KSyntaxHighlighting::State stateBegin; KSyntaxHighlighting::State stateEnd; #endif QVector highlights; unsigned lineRevision = -1; }; struct Updates { QList> data; QList lines; unsigned documentRevision = 0; }; Q_DECLARE_METATYPE(Updates); class SyntaxHighlightingSignalForwarder : public QObject { Q_OBJECT signals: void updates(Updates); }; #ifdef SYNTAX_HIGHLIGHTING class HighlightExporter : public KSyntaxHighlighting::AbstractHighlighter { public: std::tuple> highlightLineWrap(const QString &text, const KSyntaxHighlighting::State &state); Tui::ZColor defBg; Tui::ZColor defFg; protected: void applyFormat(int offset, int length, const KSyntaxHighlighting::Format &format) override; protected: // The whole object graph of the syntax highlighter is not safe to share across threads... // so we need to use a fine grained lock to avoid blocking the UI thread for too long. std::mutex mutex; QVector highlights; }; #endif class File : public Tui::ZTextEdit { Q_OBJECT public: explicit File(Tui::ZTextMetrics textMetrics, Tui::ZWidget *parent); ~File(); bool setFilename(QString _filename); QString getFilename(); bool saveText(); bool openText(QString filename); void cutline(); void deleteLine(); void copy() override; void paste() override; void gotoLine(QString pos); void toggleLineMarker(); void gotoNextLineMarker(); void gotoPreviousLineMarker(); void setEatSpaceBeforeTabs(bool eat); bool eatSpaceBeforeTabs(); bool formattingCharacters() const; void setFormattingCharacters(bool formattingCharacters); bool colorTabs() const; void setColorTabs(bool colorTabs); bool colorTrailingSpaces(); void setColorTrailingSpaces(bool colorTrailingSpaces); QString selectedText(); bool hasSelection() const; bool hasBlockSelection() const; bool hasMultiInsert() const; bool removeSelectedText(); void appendLine(const QString &line); void insertText(const QString &str); void setSearchText(QString searchText); void setSearchCaseSensitivity(Qt::CaseSensitivity searchCaseSensitivity); void setSearchVisible(bool visible); bool searchVisible(); void setReplaceText(QString replaceText); bool isSearchMatchSelected(); void replaceSelected(); void setHighlightBracket(bool hb); bool highlightBracket(); bool writeAttributes(); void setAttributesFile(QString attributesFile); QString attributesFile(); int convertTabsToSpaces(); int replaceAll(QString searchText, QString replaceText); void setRightMarginHint(int hint); int rightMarginHint() const; bool isNewFile(); int visualLineCount(); void setSyntaxHighlightingTheme(QString themeName); void setSyntaxHighlightingLanguage(QString language); QString syntaxHighlightingLanguage(); void setSyntaxHighlightingActive(bool active); bool syntaxHighlightingActive(); public: void setSearchWrap(bool wrap); bool searchWrap(); void setRegex(bool reg); bool getWritable(); void runSearch(bool direction); void setSearchDirection(bool searchDirection); void toggleShowLineNumbers(); void setSaveAs(bool state); bool isSaveAs(); bool newText(QString filename); bool stdinText(); void sortSelecedLines(); bool event(QEvent *event) override; int allBordersWidth() const override; bool followStandardInput(); public slots: void setFollowStandardInput(bool follow); signals: void followStandardInputChanged(bool follow); void writableChanged(bool rw); void searchCountChanged(int sc); void searchTextChanged(QString searchText); void searchVisibleChanged(bool visible); void selectCharLines(int selectChar, int selectLines); void syntaxHighlightingLanguageChanged(QString language); void syntaxHighlightingEnabledChanged(bool enable); protected: void paintEvent(Tui::ZPaintEvent *event) override; void keyEvent(Tui::ZKeyEvent *event) override; void pasteEvent(Tui::ZPasteEvent *event) override; void focusInEvent(Tui::ZFocusEvent *event) override; void clearAdvancedSelection() override; bool canCut() override; bool canCopy() override; private: bool initText(); void adjustScrollPosition() override; void emitCursorPostionChanged() override; bool hasLineMarker() const; bool hasLineMarker(int line) const; int lineMarkerBorderWidth() const; Tui::ZTextOption textOption() const override; bool highlightBracketFind(); void searchSelect(int line, int found, int length, bool direction); int pageNavigationLineCount() const override; void checkWritable(); QPair getSelectedLinesSort(); QPair getSelectedLines(); void selectLines(int startY, int endY); bool readAttributes(); Tui::ZDocumentCursor::Position getAttributes(); std::tuple cursorPositionOrBlockSelectionEnd(); // block selection void activateBlockSelection(); void disableBlockSelection(); void blockSelectRemoveSelectedAndConvertToMultiInsert(); static const int mi_add_spaces = 1; static const int mi_skip_short_lines = 2; template void multiInsertForEachCursor(int flags, F f); void multiInsertDeletePreviousCharacter(); void multiInsertDeletePreviousWord(); void multiInsertDeleteCharacter(); void multiInsertDeleteWord(); void multiInsertInsert(const QString &text); #ifdef SYNTAX_HIGHLIGHTING void ingestSyntaxHighlightingUpdates(Updates); void updateSyntaxHighlighting(bool force); void syntaxHighlightDefinition(); #endif private: // block selection bool _blockSelect = false; std::optional _blockSelectStartLine; std::optional _blockSelectEndLine; int _blockSelectStartColumn = -1; int _blockSelectEndColumn = -1; bool _eatSpaceBeforeTabs = true; QString _searchText; Qt::CaseSensitivity _searchCaseSensitivity = Qt::CaseSensitivity::CaseSensitive; QString _replaceText; std::optional> _currentSearchMatch; bool _searchWrap = true; bool _searchRegex = false; bool _searchDirectionForward = true; bool _searchVisible = false; std::shared_ptr> searchGeneration = std::make_shared>(); std::optional> _searchNextFuture; bool _followMode = false; bool _stdin = false; Position _bracketPosition; bool _bracket = false; QString _attributesFile; bool _saveAs = true; bool _formattingCharacters = true; int _rightMarginHint = 0; bool _colorTabs = true; bool _colorTrailingSpaces = true; std::unique_ptr _lineMarker; Tui::ZCommandNotifier *_cmdSearchNext = nullptr; Tui::ZCommandNotifier *_cmdSearchPrevious = nullptr; // Syntax highlighting QString _syntaxHighlightingThemeName; QString _syntaxHighlightingLanguage = "None"; bool _syntaxHighlightingActive = false; #ifdef SYNTAX_HIGHLIGHTING KSyntaxHighlighting::Repository _syntaxHighlightRepo; KSyntaxHighlighting::Theme _syntaxHighlightingTheme; KSyntaxHighlighting::Definition _syntaxHighlightDefinition; HighlightExporter _syntaxHighlightExporter; #endif }; #endif // FILE_H editor-0.1.80/src/filecategorize.cpp000066400000000000000000000023571477475121100173750ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "filecategorize.h" #include #include FileCategory fileCategorize(QString input, int maxrecursion) { //STDIN if (input == "-") { return FileCategory::stdin_file; } //DIR QFileInfo fileInfo(input); if (fileInfo.isDir()) { return FileCategory::dir; } if (fileInfo.isSymLink()) { if (maxrecursion > 10) { return FileCategory::invalid_error; } return fileCategorize(fileInfo.symLinkTarget(), ++maxrecursion); } //FILE if (fileInfo.exists() && fileInfo.isFile()) { if (fileInfo.isReadable()) return FileCategory::open_file; else { return FileCategory::invalid_file_not_readable; } } else if (fileInfo.exists()) { return FileCategory::invalid_filetype; } if (fileInfo.absoluteDir().exists()) { QFileInfo dir(fileInfo.absoluteDir().absolutePath()); if (dir.isWritable()) { return FileCategory::new_file; } else { return FileCategory::invalid_dir_not_writable; } } else { return FileCategory::invalid_dir_not_exist; } //return FileCategory::invalid_error; } editor-0.1.80/src/filecategorize.h000066400000000000000000000005731477475121100170400ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef FILECATEGORIZE_H #define FILECATEGORIZE_H #include enum class FileCategory { stdin_file, open_file, new_file, dir, invalid_dir_not_exist, invalid_dir_not_writable, invalid_file_not_readable, invalid_filetype, invalid_error }; FileCategory fileCategorize(QString input, int maxrecursion = 0); #endif // FILECATEGORIZE_H editor-0.1.80/src/filelistparser.cpp000066400000000000000000000023101477475121100174160ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "filelistparser.h" QVector parseFileList(QStringList args) { QVector res; QString pos = "", search = ""; while (!args.empty()) { if (args.first().startsWith("+")) { if(args.first().startsWith("+/")) { if (search != "") { return {}; } search = args.first().right(args.first().length() - 2); args.removeFirst(); } else { if (pos != "") { return {}; } pos = args.first(); //TODO: remove + args.removeFirst(); } continue; } res.append({args.first(), pos, search}); pos = ""; search = ""; args.removeFirst(); } if (!res.empty()) { if (pos != "") { if (res.last().pos != "") { return {}; } res.last().pos = pos; } if (search != "") { if (res.last().search != "") { return {}; } res.last().search = search; } } return res; } editor-0.1.80/src/filelistparser.h000066400000000000000000000005011477475121100170630ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef FILELISTPARSER_H #define FILELISTPARSER_H #include #include #include struct FileListEntry { QString fileName; QString pos; QString search; }; QVector parseFileList(QStringList args); #endif // FILELISTPARSER_H editor-0.1.80/src/filewindow.cpp000066400000000000000000000355451477475121100165550ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "filewindow.h" #include #include #include #include #include "alert.h" #include "confirmsave.h" FileWindow::FileWindow(Tui::ZWidget *parent) : Tui::ZWindow(parent) { setOptions(Tui::ZWindow::CloseOption | Tui::ZWindow::DeleteOnClose | Tui::ZWindow::MoveOption | Tui::ZWindow::ResizeOption | Tui::ZWindow::AutomaticOption); setBorderEdges({ Qt::TopEdge }); _file = new File(terminal()->textMetrics(), this); _scrollbarVertical = new ScrollBar(this); _scrollbarVertical->setTransparent(true); QObject::connect(_file, &File::scrollPositionChanged, _scrollbarVertical, &ScrollBar::scrollPosition); QObject::connect(_file, &File::scrollRangeChanged, _scrollbarVertical, &ScrollBar::positonMax); _scrollbarHorizontal = new ScrollBar(this); QObject::connect(_file, &File::scrollPositionChanged, _scrollbarHorizontal, &ScrollBar::scrollPosition); QObject::connect(_file, &File::scrollRangeChanged, _scrollbarHorizontal, &ScrollBar::positonMax); _scrollbarHorizontal->setTransparent(true); _winLayout = new Tui::ZWindowLayout(); setLayout(_winLayout); _winLayout->setRightBorderWidget(_scrollbarVertical); _winLayout->setRightBorderTopAdjust(-1); _winLayout->setRightBorderBottomAdjust(-1); _winLayout->setBottomBorderWidget(_scrollbarHorizontal); _winLayout->setBottomBorderLeftAdjust(-1); _winLayout->setCentralWidget(_file); QObject::connect(_file, &File::modifiedChanged, this, [this] { updateTitle(); } ); //Wrap QObject::connect(new Tui::ZCommandNotifier("Wrap", this, Qt::WindowShortcut), &Tui::ZCommandNotifier::activated, this, &FileWindow::wrapDialog); //Save QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forShortcut("s"), this, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this] { saveOrSaveas(); }); QObject::connect(new Tui::ZCommandNotifier("Save", this, Qt::WindowShortcut), &Tui::ZCommandNotifier::activated, this, [this] { saveOrSaveas(); } ); //Save As //shortcut does not work in vte, konsole, ... QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forShortcut("S", Qt::ControlModifier | Qt::ShiftModifier), this, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this] { saveFileDialog(); }); QObject::connect(new Tui::ZCommandNotifier("SaveAs", this, Qt::WindowShortcut), &Tui::ZCommandNotifier::activated, this, [this] { saveFileDialog(); }); //Reload _cmdReload = new Tui::ZCommandNotifier("Reload", this, Qt::WindowShortcut); _cmdReload->setEnabled(false); QObject::connect(_cmdReload, &Tui::ZCommandNotifier::activated, this, [this] { if (_file->isModified()) { ConfirmSave *confirmDialog = new ConfirmSave(this->parentWidget(), _file->getFilename(), ConfirmSave::Reload, false); QObject::connect(confirmDialog, &ConfirmSave::discardSelected, [this,confirmDialog] { confirmDialog->deleteLater(); reload(); }); QObject::connect(confirmDialog, &ConfirmSave::rejected, [confirmDialog] { confirmDialog->deleteLater(); }); } else { FileWindow::reload(); } }); //Close //shortcut does not work in vte, konsole, ... QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forShortcut("Q", Qt::ControlModifier | Qt::ShiftModifier), this, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this] { closeRequested(); } ); QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, "q", {}), this, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this] { closeRequested(); } ); QObject::connect(new Tui::ZCommandNotifier("Close", this, Qt::WindowShortcut), &Tui::ZCommandNotifier::activated, this, [this] { closeRequested(); } ); //Edit QObject::connect(new Tui::ZCommandNotifier("Selectall", this, Qt::WindowShortcut), &Tui::ZCommandNotifier::activated, this, [this] { _file->selectAll(); } ); QObject::connect(new Tui::ZCommandNotifier("Cutline", this, Qt::WindowShortcut), &Tui::ZCommandNotifier::activated, this, [this] { _file->cutline(); } ); QObject::connect(new Tui::ZCommandNotifier("DeleteLine", this, Qt::WindowShortcut), &Tui::ZCommandNotifier::activated, this, [this] { _file->deleteLine(); } ); QObject::connect(new Tui::ZCommandNotifier("SelectMode", this, Qt::WindowShortcut), &Tui::ZCommandNotifier::activated, this, [this] { _file->toggleSelectMode(); } ); // File follow and pipe _cmdInputPipe = new Tui::ZCommandNotifier("StopInputPipe", this, Qt::WindowShortcut); _cmdInputPipe->setEnabled(false); QObject::connect(_cmdInputPipe, &Tui::ZCommandNotifier::activated, this, [this] { closePipe(); } ); _cmdFollow = new Tui::ZCommandNotifier("Following", this, Qt::WindowShortcut); _cmdFollow->setEnabled(false); QObject::connect(_cmdFollow, &Tui::ZCommandNotifier::activated, this, [this] { setFollow(!getFollow()); } ); QObject::connect(this, &FileWindow::readFromStandadInput, this, [this](bool enable) { _cmdInputPipe->setEnabled(enable); _cmdFollow->setEnabled(enable); }); _watcher = new QFileSystemWatcher(); QObject::connect(_watcher, &QFileSystemWatcher::fileChanged, this, [this] { fileChangedExternally(true); }); _file->newText(""); backingFileChanged(""); } File *FileWindow::getFileWidget() { return _file; } void FileWindow::setWrap(Tui::ZTextOption::WrapMode wrap) { _file->setWordWrapMode(wrap); if (wrap == Tui::ZTextOption::NoWrap) { _scrollbarHorizontal->setVisible(true); _winLayout->setRightBorderBottomAdjust(-1); } else if (wrap == Tui::ZTextOption::WordWrap) { _scrollbarHorizontal->setVisible(false); _winLayout->setRightBorderBottomAdjust(-2); } else if (wrap == Tui::ZTextOption::WrapAnywhere) { _scrollbarHorizontal->setVisible(false); _winLayout->setRightBorderBottomAdjust(-2); } } bool FileWindow::saveFile(QString filename, std::optional crlfMode) { _file->setFilename(filename); backingFileChanged(_file->getFilename()); watcherRemove(); if (crlfMode.has_value()) { _file->document()->setCrLfMode(*crlfMode); } const bool ok = _file->saveText(); if (ok) { //windowTitle(filename); update(); fileChangedExternally(false); } else { Alert *e = new Alert(parentWidget()); e->setWindowTitle("Error"); e->setMarkup("file could not be saved"); e->setGeometry({15, 5, 50, 5}); e->setDefaultPlacement(Qt::AlignCenter); e->setVisible(true); e->setFocus(); } watcherAdd(); _cmdReload->setEnabled(true); return ok; } WrapDialog *FileWindow::wrapDialog() { WrapDialog *wrapDialog = new WrapDialog(parentWidget(), _file); return wrapDialog; } SaveDialog *FileWindow::saveFileDialog(std::function callback) { SaveDialog *saveDialog = new SaveDialog(parentWidget(), _file); QObject::connect(saveDialog, &SaveDialog::fileSelected, this, [this,callback](const QString &filename, bool crlfMode) { const bool ok = saveFile(filename, crlfMode); if (callback) { callback(ok); } }); return saveDialog; } SaveDialog *FileWindow::saveOrSaveas(std::function callback) { if (_file->isSaveAs()) { SaveDialog *q = saveFileDialog(callback); return q; } else { const bool ok = saveFile(_file->getFilename(), std::nullopt); if (callback) { callback(ok); } return nullptr; } } void FileWindow::newFile(QString filename) { closePipe(); watcherRemove(); _file->newText(filename); if (filename.size()) { backingFileChanged(_file->getFilename()); } else { backingFileChanged(""); } fileChangedExternally(false); watcherAdd(); } void FileWindow::openFile(QString filename) { closePipe(); watcherRemove(); if (!_file->openText(filename)) { Alert *e = new Alert(parentWidget()); e->setWindowTitle("Error"); e->setMarkup("Error while reading file."); e->setGeometry({15, 5, 50, 5}); e->setDefaultPlacement(Qt::AlignCenter); e->setVisible(true); e->setFocus(); } backingFileChanged(_file->getFilename()); fileChangedExternally(false); watcherAdd(); _cmdReload->setEnabled(true); } void FileWindow::reload() { closePipe(); _file->clearSelection(); Tui::ZDocumentCursor::Position cursorPosition = _file->cursorPosition(); watcherRemove(); if (!_file->openText(_file->getFilename())) { Alert *e = new Alert(parentWidget()); e->setWindowTitle("Error"); e->setMarkup("Error while reading file."); e->setGeometry({15, 5, 50, 5}); e->setDefaultPlacement(Qt::AlignCenter); e->setVisible(true); e->setFocus(); } fileChangedExternally(false); _file->setCursorPosition(cursorPosition); watcherAdd(); } void FileWindow::closeEvent(Tui::ZCloseEvent *event) { if (!event->skipChecks().contains("unsaved")) { closeRequested(); event->ignore(); } } void FileWindow::resizeEvent(Tui::ZResizeEvent *event) { updateBorders(); updateTitle(); Tui::ZWindow::resizeEvent(event); } void FileWindow::moveEvent(Tui::ZMoveEvent *event) { updateBorders(); Tui::ZWindow::moveEvent(event); } void FileWindow::updateBorders() { auto * const term = terminal(); if (!term) return; Qt::Edges borders = Qt::TopEdge; QRect g = geometry(); bool touchesLeftEdge = (g.x() == 0); bool touchesRightEdge = (g.right() == term->mainWidget()->geometry().width() - 1); if (!touchesRightEdge || !touchesLeftEdge) { borders |= Qt::LeftEdge; borders |= Qt::RightEdge; _winLayout->setRightBorderTopAdjust(0); _winLayout->setRightBorderBottomAdjust(0); _scrollbarVertical->setTransparent(false); } else { _winLayout->setRightBorderTopAdjust(-1); _winLayout->setRightBorderBottomAdjust(-1); _scrollbarVertical->setTransparent(true); } if (g.bottom() != term->mainWidget()->geometry().height() - 2) { borders |= Qt::BottomEdge; _scrollbarHorizontal->setTransparent(false); _winLayout->setBottomBorderLeftAdjust(borders.testFlag(Qt::LeftEdge) ? 0 : -1); } else { _winLayout->setBottomBorderLeftAdjust(-1); _scrollbarHorizontal->setTransparent(true); } setBorderEdges(borders); } void FileWindow::updateTitle() { if (!terminal()) { return; } QString title = _file->getFilename(); if (_file->isModified()) { title = "*" + title; } terminal()->setTitle("chr - " + title); terminal()->setIconTitle("chr - " + title); Tui::ZTextMetrics metrics = terminal()->textMetrics(); const int maxWidth = geometry().width() - 14; const int width = metrics.sizeInColumns(title); if (width > maxWidth) { auto splitPoint = metrics.splitByColumns(title, width - maxWidth - 1); title = "…" + title.mid(splitPoint.codeUnits); } setWindowTitle(title); } void FileWindow::closeRequested() { _file->writeAttributes(); if (_file->isModified()) { ConfirmSave *closeDialog = new ConfirmSave(parentWidget(), _file->getFilename(), _file->isNewFile() ? ConfirmSave::CloseUnnamed : ConfirmSave::Close, _file->getWritable()); QObject::connect(closeDialog, &ConfirmSave::discardSelected, this, [this, closeDialog] { closeDialog->deleteLater(); deleteLater(); }); QObject::connect(closeDialog, &ConfirmSave::saveSelected, this, [this, closeDialog] { closeDialog->deleteLater(); saveOrSaveas([this](bool ok) { if (ok) { closeSkipCheck({"unsaved"}); } }); }); QObject::connect(closeDialog, &ConfirmSave::rejected, [=]{ closeDialog->deleteLater(); }); } else { deleteLater(); } } void FileWindow::watcherAdd() { QFileInfo filenameInfo(_file->getFilename()); if (filenameInfo.exists()) { _watcher->addPath(_file->getFilename()); } } void FileWindow::watcherRemove() { if (_watcher->files().count() > 0) { _watcher->removePaths(_watcher->files()); } } void FileWindow::closePipe() { if (_pipeSocketNotifier != nullptr && _pipeSocketNotifier->isEnabled()) { _pipeSocketNotifier->setEnabled(false); _pipeSocketNotifier->deleteLater(); _pipeSocketNotifier = nullptr; ::close(0); readFromStandadInput(false); } } void FileWindow::watchPipe() { _pipeSocketNotifier = new QSocketNotifier(0, QSocketNotifier::Type::Read, this); QObject::connect(_pipeSocketNotifier, &QSocketNotifier::activated, this, &FileWindow::inputPipeReadable); _file->stdinText(); readFromStandadInput(true); } void FileWindow::inputPipeReadable(int socket) { char buff[1024]; int bytes = read(socket, buff, 1024); if (bytes == 0) { // EOF if (!_pipeLineBuffer.isEmpty()) { _file->appendLine(Tui::Misc::SurrogateEscape::decode(_pipeLineBuffer)); } _pipeSocketNotifier->deleteLater(); _pipeSocketNotifier = nullptr; } else if (bytes < 0) { // TODO error handling _pipeSocketNotifier->deleteLater(); _pipeSocketNotifier = nullptr; } else { _pipeLineBuffer.append(buff, bytes); int index; while ((index = _pipeLineBuffer.indexOf('\n')) != -1) { _file->appendLine(Tui::Misc::SurrogateEscape::decode(_pipeLineBuffer.left(index))); _pipeLineBuffer = _pipeLineBuffer.mid(index + 1); } _file->modifiedChanged(true); } if (_pipeSocketNotifier == nullptr) { readFromStandadInput(false); } else { readFromStandadInput(true); } } void FileWindow::setFollow(bool follow) { _follow = follow; _file->setFollowStandardInput(getFollow()); followStandadInput(getFollow()); } bool FileWindow::getFollow() { return _follow; } editor-0.1.80/src/filewindow.h000066400000000000000000000036551477475121100162170ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef FILEWINDOW_H #define FILEWINDOW_H #include #include #include #include #include "file.h" #include "savedialog.h" #include "scrollbar.h" #include "wrapdialog.h" class FileWindow : public Tui::ZWindow { Q_OBJECT public: FileWindow(Tui::ZWidget *parent); public: File *getFileWidget(); void setWrap(Tui::ZTextOption::WrapMode wrap); bool saveFile(QString filename, std::optional crlfMode); void newFile(QString filename); void openFile(QString filename); void closePipe(); void watchPipe(); void setFollow(bool follow); bool getFollow(); SaveDialog *saveOrSaveas(std::function callback = {}); signals: void readFromStandadInput(bool activ); void followStandadInput(bool follow); void fileChangedExternally(bool fileChangedExternally); void backingFileChanged(QString filename); protected: void closeEvent(Tui::ZCloseEvent *event) override; void resizeEvent(Tui::ZResizeEvent *event) override; void moveEvent(Tui::ZMoveEvent *event) override; private: void updateBorders(); void updateTitle(); void closeRequested(); SaveDialog *saveFileDialog(std::function callback = {}); WrapDialog *wrapDialog(); void reload(); void watcherAdd(); void watcherRemove(); void inputPipeReadable(int socket); private: File *_file = nullptr; ScrollBar *_scrollbarHorizontal = nullptr; ScrollBar *_scrollbarVertical = nullptr; Tui::ZWindowLayout *_winLayout = nullptr; QFileSystemWatcher *_watcher = nullptr; bool _follow = false; Tui::ZCommandNotifier *_cmdReload = nullptr; Tui::ZCommandNotifier *_cmdFollow = nullptr; Tui::ZCommandNotifier *_cmdInputPipe = nullptr; QSocketNotifier *_pipeSocketNotifier = nullptr; QByteArray _pipeLineBuffer; }; #endif // FILEWINDOW_H editor-0.1.80/src/formattingdialog.cpp000066400000000000000000000054641477475121100177350ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "formattingdialog.h" #include #include #include FormattingDialog::FormattingDialog(Tui::ZWidget *parent) : Tui::ZDialog(parent) { setOptions(Tui::ZWindow::CloseOption | Tui::ZWindow::MoveOption | Tui::ZWindow::AutomaticOption | Tui::ZWindow::DeleteOnClose); setFocus(); setWindowTitle("Formatting"); setContentsMargins({ 1, 1, 1, 1}); Tui::ZVBoxLayout *vbox = new Tui::ZVBoxLayout(); setLayout(vbox); vbox->setSpacing(1); Tui::ZHBoxLayout *hbox1 = new Tui::ZHBoxLayout(); _formattingCharacters = new Tui::ZCheckBox(this); _formattingCharacters->setMarkup("Formatting Characters"); _formattingCharacters->setFocus(); hbox1->addWidget(_formattingCharacters); vbox->add(hbox1); //vbox->addStretch(); Tui::ZHBoxLayout *hbox2 = new Tui::ZHBoxLayout(); _colorTabs = new Tui::ZCheckBox(this); _colorTabs->setMarkup("Color Tabs"); hbox2->addWidget(_colorTabs); vbox->add(hbox2); //vbox->addStretch(); Tui::ZHBoxLayout *hbox3 = new Tui::ZHBoxLayout(); _colorSpaceEnd = new Tui::ZCheckBox(this); _colorSpaceEnd->setMarkup("Color Spaces at end of line"); hbox3->addWidget(_colorSpaceEnd); vbox->add(hbox3); vbox->addStretch(); Tui::ZHBoxLayout *hbox5 = new Tui::ZHBoxLayout(); hbox5->addStretch(); Tui::ZButton *cancelButton = new Tui::ZButton(this); cancelButton->setText("Cancel"); hbox5->addWidget(cancelButton); Tui::ZButton *saveButton = new Tui::ZButton(this); saveButton->setText("Ok"); saveButton->setDefault(true); hbox5->addWidget(saveButton); vbox->add(hbox5); QObject::connect(saveButton, &Tui::ZButton::clicked, this, [this] { Q_EMIT settingsChanged(_formattingCharacters->checkState() == Qt::CheckState::Checked, _colorTabs->checkState() == Qt::CheckState::Checked, _colorSpaceEnd->checkState() == Qt::CheckState::Checked); deleteLater(); }); QObject::connect(cancelButton, &Tui::ZButton::clicked, [this] { deleteLater(); }); } void FormattingDialog::updateSettings(bool formattingCharacters, bool colorTabs, bool colorSpaceEnd) { if (formattingCharacters) { _formattingCharacters->setCheckState(Qt::CheckState::Checked); } else { _formattingCharacters->setCheckState(Qt::CheckState::Unchecked); } if (colorTabs) { _colorTabs->setCheckState(Qt::CheckState::Checked); } else { _colorTabs->setCheckState(Qt::CheckState::Unchecked); } if (colorSpaceEnd) { _colorSpaceEnd->setCheckState(Qt::CheckState::Checked); } else { _colorSpaceEnd->setCheckState(Qt::CheckState::Unchecked); } } editor-0.1.80/src/formattingdialog.h000066400000000000000000000011631477475121100173720ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef FORMATTINGDIALOG_H #define FORMATTINGDIALOG_H #include #include class FormattingDialog : public Tui::ZDialog { Q_OBJECT public: FormattingDialog(Tui::ZWidget *parent); void updateSettings(bool formattingCharacters, bool colorTabs, bool colorSpaceEnd); signals: void settingsChanged(bool formattingCharacters, bool colorTabs, bool colorSpaceEnd); private: Tui::ZCheckBox *_formattingCharacters = nullptr; Tui::ZCheckBox *_colorTabs = nullptr; Tui::ZCheckBox *_colorSpaceEnd = nullptr; }; #endif // FORMATTINGDIALOG_H editor-0.1.80/src/gotoline.cpp000066400000000000000000000031401477475121100162100ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "gotoline.h" #include #include #include #include #include GotoLine::GotoLine(Tui::ZWidget *parent) : Tui::ZDialog(parent) { setOptions(Tui::ZWindow::CloseOption | Tui::ZWindow::DeleteOnClose | Tui::ZWindow::MoveOption | Tui::ZWindow::AutomaticOption); setWindowTitle("Goto Line"); setContentsMargins({ 1, 1, 1, 1}); Tui::ZVBoxLayout *vbox = new Tui::ZVBoxLayout(); setLayout(vbox); vbox->setSpacing(1); Tui::ZHBoxLayout *hbox1 = new Tui::ZHBoxLayout(); Tui::ZLabel *labelGoto = new Tui::ZLabel(this); labelGoto->setText("Goto line: "); hbox1->addWidget(labelGoto); Tui::ZInputBox *inputboxGoto = new Tui::ZInputBox(this); inputboxGoto->setFocus(); hbox1->addWidget(inputboxGoto); vbox->add(hbox1); vbox->addStretch(); Tui::ZHBoxLayout *hbox2 = new Tui::ZHBoxLayout(); Tui::ZButton *buttonCancel = new Tui::ZButton(this); buttonCancel->setGeometry({3, 5, 7, 7}); buttonCancel->setText("Cancel"); hbox2->addWidget(buttonCancel); Tui::ZButton *buttonOK = new Tui::ZButton(this); buttonOK->setGeometry({15, 5, 7, 7}); buttonOK->setText("OK"); buttonOK->setDefault(true); hbox2->addWidget(buttonOK); vbox->add(hbox2); QObject::connect(buttonCancel, &Tui::ZButton::clicked, this, &GotoLine::rejected); QObject::connect(buttonOK, &Tui::ZButton::clicked, this, [this, inputboxGoto] { lineSelected(inputboxGoto->text()); this->deleteLater(); }); } editor-0.1.80/src/gotoline.h000066400000000000000000000004411477475121100156560ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef GOTOLINE_H #define GOTOLINE_H #include class GotoLine : public Tui::ZDialog { public: Q_OBJECT public: explicit GotoLine(Tui::ZWidget *parent); signals: void lineSelected(QString line); }; #endif // GOTOLINE_H editor-0.1.80/src/groupbox.cpp000066400000000000000000000054001477475121100162360ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "groupbox.h" #include #include #include #include #include GroupBox::GroupBox(Tui::ZWidget *parent) : Tui::ZWidget(parent) { } GroupBox::GroupBox(const QString &text, Tui::ZWidget *parent) : GroupBox(parent) { setText(text); } GroupBox::GroupBox(Tui::WithMarkupTag, const QString &markup, Tui::ZWidget *parent) : GroupBox(parent) { setMarkup(markup); } QString GroupBox::text() const { return styledText.text(); } void GroupBox::setText(const QString &t) { styledText.setText(t); update(); } QString GroupBox::markup() const { return styledText.markup(); } void GroupBox::setMarkup(QString m) { styledText.setMarkup(m); if (styledText.mnemonic().size()) { for (Tui::ZShortcut *s : findChildren("", Qt::FindDirectChildrenOnly)) { delete s; } Tui::ZShortcut *s = new Tui::ZShortcut(Tui::ZKeySequence::forMnemonic(styledText.mnemonic()), this); connect(s, &Tui::ZShortcut::activated, this, [this] { Tui::ZWidget *w = placeFocus(); if (w) { w->setFocus(Qt::ShortcutFocusReason); } }); } update(); } QSize GroupBox::sizeHint() const { if (layout()) { QSize sh = layout()->sizeHint() + QSize(1, 1); QMargins cm = contentsMargins(); sh.rwidth() += cm.left() + cm.right(); sh.rheight() += cm.top() + cm.bottom(); sh.rwidth() = std::max(sh.rwidth(), text().size() + 1); return sh.expandedTo(minimumSize()).boundedTo(maximumSize()); } return {text().size() + 1, 1}; } QRect GroupBox::layoutArea() const { QRect r = Tui::ZWidget::layoutArea(); r.adjust(1, 1, 0, 0); return r; } void GroupBox::paintEvent(Tui::ZPaintEvent *event) { Tui::ZTextStyle baseStyle; Tui::ZTextStyle shortcut; auto *painter = event->painter(); auto *term = terminal(); if (!isEnabled()) { baseStyle = {getColor("control.disabled.fg"), getColor("control.bg")}; shortcut = baseStyle; } else { if (term && isAncestorOf(term->focusWidget())) { baseStyle = {getColor("control.focused.fg"), getColor("control.focused.bg")}; painter->writeWithColors(0, 0, "»", baseStyle.foregroundColor(), baseStyle.backgroundColor()); } else { baseStyle = {getColor("control.fg"), getColor("control.bg")}; } shortcut = {getColor("control.shortcut.fg"), getColor("control.shortcut.bg")}; } styledText.setMnemonicStyle(baseStyle, shortcut); styledText.write(painter, 1, 0, geometry().width() - 1); } void GroupBox::keyEvent(Tui::ZKeyEvent *event) { Tui::ZWidget::keyEvent(event); } editor-0.1.80/src/groupbox.h000066400000000000000000000014701477475121100157060ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef GROUPBOX_H #define GROUPBOX_H #include #include class GroupBox : public Tui::ZWidget { Q_OBJECT public: explicit GroupBox(Tui::ZWidget *parent = nullptr); explicit GroupBox(const QString &text, Tui::ZWidget *parent = nullptr); explicit GroupBox(Tui::WithMarkupTag, const QString &markup, Tui::ZWidget *parent = nullptr); public: QString text() const; void setText(const QString &t); QString markup() const; void setMarkup(QString m); QSize sizeHint() const override; QRect layoutArea() const override; protected: void paintEvent(Tui::ZPaintEvent *event) override; void keyEvent(Tui::ZKeyEvent *event) override; private: Tui::ZStyledTextLine styledText; }; #endif // GROUPBOX_H editor-0.1.80/src/help.cpp000066400000000000000000000054451477475121100153320ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "help.h" #include #include #include #include #include #include #include #include "scrollbar.h" static const QString helpText = R"(The important operations can be invoked from the menu. The menu can be opened with F10 or with Alt together with the highlighted letter of the menu item (e.g. Alt + f). In dialogs Tab is used to navigate between the elements. Use F6 to navigate between windows/dialogs. Text can be marked in most terminals with Shift + arrow key. Ctrl + c is used for copying. Paste with Ctrl + v. Changes can be saved with Ctrl + s and the editor can be exited with Ctrl + q. )"; Help::Help(Tui::v0::ZWidget *parent) : Tui::ZDialog(parent) { setFocusPolicy(Tui::StrongFocus); setDefaultPlacement(Qt::AlignBottom | Qt::AlignHCenter, {0, -2}); setGeometry({0, 0, 65, 14}); setPaletteClass({"window", "cyan"}); setWindowTitle("Quick Start"); auto winLayout = new Tui::ZWindowLayout(); setLayout(winLayout); auto scrollbar = new ScrollBar(this); winLayout->setRightBorderWidget(scrollbar); _text = new Tui::ZTextEdit(terminal()->textMetrics(), this); winLayout->setCentralWidget(_text); _text->setReadOnly(true); QObject::connect(_text, &Tui::ZTextEdit::scrollRangeChanged, scrollbar, &ScrollBar::positonMax); QObject::connect(_text, &Tui::ZTextEdit::scrollPositionChanged, scrollbar, &ScrollBar::scrollPosition); _text->setText(helpText); _text->setWordWrapMode(Tui::ZTextOption::WrapMode::WordWrap); _text->setFocusPolicy(Tui::NoFocus); auto pal = _text->palette(); // Change colors to look like e.g. a label. pal.addRules({ {{}, {{Tui::ZPalette::Local, "textedit.bg", "control.bg"}}}, {{}, {{Tui::ZPalette::Local, "textedit.fg", "control.fg"}}} }); _text->setPalette(pal); setFocus(); dynamic_cast(facet(Tui::ZWindowFacet::staticMetaObject))->setExtendViewport(true); } void Help::paintEvent(Tui::v0::ZPaintEvent *event) { Tui::ZDialog::paintEvent(event); auto *painter = event->painter(); painter->writeWithColors(3, geometry().height() - 1, " Press ESC or Enter to dismiss ", getColor("control.fg"), getColor("control.bg")); } void Help::keyEvent(Tui::v0::ZKeyEvent *event) { if (event->key() == Tui::Key_Escape || event->key() == Tui::Key_Enter) { deleteLater(); } else if (event->key() == Tui::Key_Up) { _text->detachedScrollUp(); } else if (event->key() == Tui::Key_Down) { _text->detachedScrollDown(); } else { Tui::ZDialog::keyEvent(event); } } editor-0.1.80/src/help.h000066400000000000000000000006521477475121100147720ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef HELP_H #define HELP_H #include #include #include class Help : public Tui::ZDialog { Q_OBJECT public: explicit Help(Tui::ZWidget *parent); protected: void paintEvent(Tui::ZPaintEvent *event) override; void keyEvent(Tui::ZKeyEvent *event) override; private: Tui::ZTextEdit *_text = nullptr; }; #endif // HELP_H editor-0.1.80/src/insertcharacter.cpp000066400000000000000000000107401477475121100175550ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "insertcharacter.h" #include #include #include #include #include #include InsertCharacter::InsertCharacter(Tui::ZWidget *parent) : Tui::ZDialog(parent) { setOptions(Tui::ZWindow::CloseOption | Tui::ZWindow::DeleteOnClose | Tui::ZWindow::MoveOption | Tui::ZWindow::AutomaticOption); setWindowTitle("Insert Character"); setContentsMargins({1, 1, 1, 1}); Tui::ZVBoxLayout *vbox = new Tui::ZVBoxLayout(); setLayout(vbox); vbox->setSpacing(1); Tui::ZHBoxLayout *hbox1 = new Tui::ZHBoxLayout(); Tui::ZLabel *hexLabel = new Tui::ZLabel(this); hexLabel->setText("Hex: 0x"); hbox1->addWidget(hexLabel); Tui::ZInputBox *hexInputBox = new Tui::ZInputBox(this); hexInputBox->setFocus(); hbox1->addWidget(hexInputBox); vbox->add(hbox1); vbox->addStretch(); Tui::ZHBoxLayout *hbox2 = new Tui::ZHBoxLayout(); Tui::ZLabel *intLabel = new Tui::ZLabel(this); intLabel->setText("Decimal: "); hbox2->addWidget(intLabel); Tui::ZInputBox *intInputBox = new Tui::ZInputBox(this); hbox2->addWidget(intInputBox); vbox->add(hbox2); vbox->addStretch(); Tui::ZHBoxLayout *hbox3 = new Tui::ZHBoxLayout(); Tui::ZInputBox *preview = new Tui::ZInputBox(this); Tui::ZPalette tmpPalette = Tui::ZPalette::classic(); tmpPalette.setColors({{"lineedit.bg", Tui::Colors::lightGray}, {"lineedit.fg", Tui::Colors::black}}); preview->setPalette(tmpPalette); preview->setText(" Preview: "); preview->setEnabled(false); hbox3->addWidget(preview); vbox->add(hbox3); Tui::ZHBoxLayout *hbox4 = new Tui::ZHBoxLayout(); Tui::ZButton *cancelBtn = new Tui::ZButton(this); cancelBtn->setGeometry({3, 5, 7, 7}); cancelBtn->setText("Cancel"); hbox4->addWidget(cancelBtn); Tui::ZButton *insertButton = new Tui::ZButton(this); insertButton->setGeometry({15, 5, 7, 7}); insertButton->setText("Insert"); insertButton->setDefault(true); insertButton->setEnabled(false); hbox4->addWidget(insertButton); vbox->add(hbox4); QObject::connect(hexInputBox, &Tui::ZInputBox::textChanged, this, [this,hexInputBox,preview,intInputBox,cancelBtn,insertButton] { if (hexInputBox->focus()) { _codepoint = hexInputBox->text().toInt(&_check, 16); if (_check && _codepoint >= 0 && _codepoint <= 0x10FFFF) { preview->setText(" Preview: " + intToChar(_codepoint)); intInputBox->setText(QString::number(_codepoint, 10).toUpper()); cancelBtn->setDefault(false); insertButton->setDefault(true); insertButton->setEnabled(true); } else { preview->setText(" Preview: ERROR"); insertButton->setEnabled(false); insertButton->setDefault(false); cancelBtn->setDefault(true); } } }); QObject::connect(intInputBox, &Tui::ZInputBox::textChanged, this, [this,hexInputBox,preview,intInputBox,cancelBtn,insertButton] { if (intInputBox->focus()) { _codepoint = intInputBox->text().toInt(&_check, 10); if (_check && _codepoint >= 0 && _codepoint <= 0x10FFFF) { preview->setText(" Preview: " + intToChar(_codepoint)); hexInputBox->setText(QString::number(_codepoint, 16).toUpper()); cancelBtn->setDefault(false); insertButton->setDefault(true); insertButton->setEnabled(true); } else { preview->setText(" Preview: ERROR"); insertButton->setEnabled(false); insertButton->setDefault(false); cancelBtn->setDefault(true); } } }); QObject::connect(cancelBtn, &Tui::ZButton::clicked, this, &InsertCharacter::rejected); QObject::connect(insertButton, &Tui::ZButton::clicked, [this] { if (_check) { Q_EMIT characterSelected(intToChar(_codepoint)); } deleteLater(); }); } void InsertCharacter::rejected() { deleteLater(); } QString InsertCharacter::intToChar(int i) { if (i < 0 || i > 0x10FFFF) { return ""; } else if (i <= 0xFFFF) { return QString(1, QChar(i)); } else if (i <= 0x10FFFF){ return (QString(1, QChar::highSurrogate(i)) + QString(1, QChar::lowSurrogate(i))); } return ""; } editor-0.1.80/src/insertcharacter.h000066400000000000000000000006411477475121100172210ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef INSERTCHARACTER_H #define INSERTCHARACTER_H #include class InsertCharacter : public Tui::ZDialog { Q_OBJECT public: InsertCharacter(Tui::ZWidget *parent); void rejected(); signals: void characterSelected(QString); private: QString intToChar(int i); int _codepoint = 0; bool _check = false; }; #endif // INSERTCHARACTER_H editor-0.1.80/src/main.cpp000066400000000000000000000363351477475121100153300ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include #include #include #include #include #include #include #include #include #include #include #include "edit.h" #include "filecategorize.h" #include "filelistparser.h" #include "version_git.h" #include "version.h" int main(int argc, char **argv) { #ifdef SYNTAX_HIGHLIGHTING Q_INIT_RESOURCE(syntax); #endif QCoreApplication app(argc, argv); QCoreApplication::setApplicationName("chr"); QString version; if (QString(VERSION_NUMBER) != "git" && QString(GIT_VERSION_ID) != "000000") { version = "Version: "+ QString(VERSION_NUMBER) + " Git-Hash: " + QString(GIT_VERSION_ID); } else if (QString(VERSION_NUMBER) != "git") { version = "Version: "+ QString(VERSION_NUMBER); } else { version = "Git-Hash: "+ QString(GIT_VERSION_ID); } QCoreApplication::setApplicationVersion(version); QCommandLineParser parser; parser.setApplicationDescription("chr"); parser.addHelpOption(); parser.addVersionOption(); // line number QCommandLineOption lineNumberOption({"l", "line-number"}, QCoreApplication::translate("main", "The line numbers are displayed")); parser.addOption(lineNumberOption); /* Currently broken // append mode QCommandLineOption append({"a", "append"}, QCoreApplication::translate("main", "Only with read from standard input, then append to a file"), QCoreApplication::translate("main", "file")); parser.addOption(append); */ /* maybe no longer needed and clashes with cursor position based automatic toggling of follow mode // follow mode QCommandLineOption follow({"f", "follow"}, QCoreApplication::translate("main", "Only with read from standard input, then follow the input stream")); parser.addOption(follow); */ // big file QCommandLineOption bigOption({"b", "big-file"}, QCoreApplication::translate("main", "Open bigger files than 100MB")); parser.addOption(bigOption); // wrap log lines QCommandLineOption wraplines({"w", "wrap-lines"}, QCoreApplication::translate("main", "Wrap log lines (NoWrap Default)"), QCoreApplication::translate("main", "WordWrap|WrapAnywhere|NoWrap")); parser.addOption(wraplines); // safe attributes QCommandLineOption attributesfileOption("attributesfile", QCoreApplication::translate("main", "Safe file for attributes, default ~/.cache/chr/chr.json"), QCoreApplication::translate("main", "config")); parser.addOption(attributesfileOption); // config file QCommandLineOption configOption({"c", "config"}, QCoreApplication::translate("main", "Load customized config file. The default if it exist is ~/.config/chr"), QCoreApplication::translate("main", "config")); parser.addOption(configOption); // syntax #ifdef SYNTAX_HIGHLIGHTING QCommandLineOption syntaxHighlightingTheme("syntax-highlighting-theme", QCoreApplication::translate("main", "Name of syntax-highlighting-theme, you can list installed themes with: kate-syntax-highlighter --list-themes"), QCoreApplication::translate("main", "name")); parser.addOption(syntaxHighlightingTheme); QCommandLineOption disableSyntaxHighlighting("disable-syntax", QCoreApplication::translate("main", "disable syntax highlighting")); parser.addOption(disableSyntaxHighlighting); #endif #ifdef HAS_TUIWIDGETS_0_2_2 // height (bonsai mode) QCommandLineOption sizeOption({"s", "size"}, QCoreApplication::translate("main", "Size in lines or for Automatic detection of the size of the lines with which the editor is displayed on the console."), QCoreApplication::translate("main", "N|auto")); parser.addOption(sizeOption); #endif // goto line parser.addPositionalArgument("[[+line[,char]] file …]", QCoreApplication::translate("main", "Optional is the line number and position. Several files can be opened in multiple windows.")); // search on open file parser.addPositionalArgument("[[+/searchword] file …]", QCoreApplication::translate("main", "A search word can be set.")); // open directory parser.addPositionalArgument("[/directory …]", QCoreApplication::translate("main", "Or a directory can be specified to search in the open dialog.")); parser.process(app); parser.parse(QCoreApplication::arguments()); const QStringList args = parser.positionalArguments(); QTextStream out(stdout); // START EDITOR Tui::ZTerminal terminal; Editor *root = new Editor(); #ifdef HAS_TUIWIDGETS_0_2_2 terminal.setInlineHeight(7); if (parser.isSet(sizeOption)) { bool isInt = false; int size = parser.value(sizeOption).toInt(&isInt); if (parser.value(sizeOption) == "auto") { terminal.setInline(true); root->setTerminalHeightMode(Editor::TerminalMode::modeAuto); } else if (isInt && size > 0) { terminal.setInline(true); terminal.setInlineHeight(size); root->setTerminalHeightMode(Editor::TerminalMode::modeManual); } else { terminal.setInline(true); root->setTerminalHeightMode(Editor::TerminalMode::modeManual); } } else { root->setTerminalHeightMode(Editor::TerminalMode::modeFullscreen); } #endif terminal.setMainWidget(root); // READ CONFIG FILE AND SET DEFAULT OPTIONS QString configDir = ""; if (parser.isSet(configOption)) { configDir = parser.value(configOption); } else { // default config QString userConfigPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); configDir = userConfigPath + "/chr"; } QString attributesfile = ""; if (parser.isSet(attributesfileOption)) { attributesfile = parser.value(attributesfileOption); } // default settings QSettings * qsettings = new QSettings(configDir, QSettings::IniFormat); QString logfile = qsettings->value("log_file", "").toString(); if (logfile.isEmpty()) { QLoggingCategory::defaultCategory()->setEnabled(QtDebugMsg, false); Tui::ZSimpleStringLogger::install(); } else { Tui::ZSimpleFileLogger::install(logfile); } int tabsize = qsettings->value("tab_size", "4").toInt(); Settings settings; settings.tabSize = tabsize; bool tab = qsettings->value("tab", "false").toBool(); settings.tabOption = tab; bool eatSpaceBeforeTabs = qsettings->value("eat_space_before_tabs", "true").toBool(); settings.eatSpaceBeforeTabs = eatSpaceBeforeTabs; bool ln = qsettings->value("line_number", "0").toBool() || qsettings->value("linenumber", "0").toBool(); settings.showLineNumber = ln || parser.isSet(lineNumberOption); settings.formattingCharacters = qsettings->value("formatting_characters", "0").toBool(); settings.colorTabs = qsettings->value("color_tabs", "0").toBool(); settings.colorSpaceEnd = qsettings->value("color_space_end", "0").toBool(); bool bigfile = qsettings->value("big_file", "false").toBool(); QString defaultSyntaxHighlightingTheme; QString theme = qsettings->value("theme", "classic").toString(); if (theme.toLower() == "dark" || theme.toLower() == "black") { root->setTheme(Editor::Theme::dark); defaultSyntaxHighlightingTheme = "chr-blackbg"; } else { root->setTheme(Editor::Theme::classic); defaultSyntaxHighlightingTheme = "chr-bluebg"; } #ifdef SYNTAX_HIGHLIGHTING settings.syntaxHighlightingTheme = qsettings->value("syntax_highlighting_theme", defaultSyntaxHighlightingTheme).toString(); if (parser.isSet(syntaxHighlightingTheme)) { settings.syntaxHighlightingTheme = parser.value(syntaxHighlightingTheme); } settings.disableSyntaxHighlighting = qsettings->value("disable_syntax", "false").toBool(); if (parser.isSet(disableSyntaxHighlighting)) { settings.disableSyntaxHighlighting = true; } #endif // default cache file const QString userConfigPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); QString attributesfileDefault = qsettings->value("attributes_file", userConfigPath + "/chr.json").toString(); if (attributesfile.isEmpty()) { QDir dir(QFileInfo(attributesfileDefault).absolutePath()); // Users often use editors with su or other ways that might leave us with a mismatch between // paths in environment variables and the actual user id the program runs under. // Refuse to default attributesfile if the directory user does not match the current user. auto requiredUid = geteuid(); QFileInfo fi(dir.absolutePath()); while (!fi.exists()) { fi = QFileInfo(fi.path()); } if (fi.ownerId() != requiredUid) { qDebug() << "File history disabled due to directory owner mismatch:" << fi.absoluteFilePath(); } else { if (!dir.exists() && !dir.mkpath(".")) { qDebug() << "Error can't create dir:" << dir.absolutePath(); } else { attributesfile = attributesfileDefault; } } } settings.attributesFile = attributesfile; QString wl = parser.value(wraplines).toLower(); if (wl == "") { wl = qsettings->value("wrap_lines", "false").toString().toLower(); } if (wl == QString("WordWrap").toLower() || wl == "true") { settings.wrap = Tui::ZTextOption::WordWrap; } else if (wl == QString("WrapAnywhere").toLower()) { settings.wrap = Tui::ZTextOption::WrapAnywhere; } else { settings.wrap = Tui::ZTextOption::NoWrap; } settings.rightMarginHint = qsettings->value("right_margin_hint", "0").toInt(); bool hb = qsettings->value("highlight_bracket", "true").toBool(); settings.highlightBracket = hb; root->setInitialFileSettings(settings); qDebug("%i chr starting", (int)QCoreApplication::applicationPid()); static QtMessageHandler oldHandler = nullptr; oldHandler = qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &context, const QString &msg) { oldHandler(type, context, msg); StatusBar::notifyQtLog(); }); // OPEN FILE std::vector> actions; if (args.empty()) { actions.push_back([root] { root->newFile(""); }); } else { QVector fles = parseFileList(args); if (fles.empty()) { out << "Got file offset without file name.\n"; return 0; } for (FileListEntry fle: fles) { FileCategory filecategory = fileCategorize(fle.fileName); if (filecategory == FileCategory::stdin_file) { /* if (parser.isSet(append) && (fileCategorize(parser.value(append)) == FileCategory::new_file || fileCategorize(parser.value(append)) == FileCategory::open_file ) ) { actions.push_back([root, name=parser.value(append)] { root->openFile(name); }); } else { */ actions.push_back([root, pos=fle.pos, search=fle.search] { // TODO: the search is not yet implemented FileWindow* win = root->newFile(""); if (search != "") { win->getFileWidget()->setSearchText(search); win->getFileWidget()->runSearch(false); } if (pos != "") { win->getFileWidget()->gotoLine(pos); } }); //} actions.push_back([root] { root->watchPipe(); }); } else if (filecategory == FileCategory::dir || filecategory == FileCategory::invalid_file_not_readable) { QFileInfo fileInfo(fle.fileName); QString tmp = fileInfo.absoluteFilePath(); actions.push_back([root, tmp] { root->openFileDialog(tmp); }); } else if (filecategory == FileCategory::new_file) { QFileInfo fileInfo(fle.fileName); actions.push_back([root, name=fileInfo.absoluteFilePath()] { root->newFile(name); }); } else if (filecategory == FileCategory::open_file) { QFileInfo fileInfo(fle.fileName); int maxMB = 100; if (fileInfo.size() / 1024 / 1024 >= maxMB && !parser.isSet(bigOption) && !bigfile) { out << "The file is bigger then " << maxMB << "MB (" << fileInfo.size() / 1024 / 1024 << "MB). Please start with -b for big files.\n"; return 0; } actions.push_back([root, name=fileInfo.absoluteFilePath(), pos=fle.pos, search=fle.search] { FileWindow* win = root->openFile(name); if (search != "") { win->getFileWidget()->setSearchText(search); win->getFileWidget()->runSearch(false); } if (pos != "") { win->getFileWidget()->gotoLine(pos); } }); } else if (filecategory == FileCategory::invalid_filetype) { out << "File type cannot be opened.\n"; return 1; } else if (filecategory == FileCategory::invalid_dir_not_exist) { out << "Directory not exist, cannot be opened.\n"; return 1; } else if (filecategory == FileCategory::invalid_dir_not_writable) { out << "Directory not writable, cannot be opened.\n"; return 1; } else { out << "Invalid error.\n"; return 255; } } } /* if (parser.isSet(follow)) { actions.push_back([root] { root->followInCurrentFile(); }); } */ root->setStartActions(actions); QObject::connect(&terminal, &Tui::ZTerminal::terminalConnectionLost, [=] { //TODO file save //qDebug("%i terminalConnectionLost", (int)QCoreApplication::applicationPid()); QCoreApplication::quit(); }); std::unique_ptr sigIntNotifier; if (!PosixSignalManager::isCreated()) { PosixSignalManager::create(); } sigIntNotifier = std::unique_ptr(new PosixSignalNotifier(SIGINT)); QObject::connect(sigIntNotifier.get(), &PosixSignalNotifier::activated, [] { //qDebug("%i SIGINT", (int)QCoreApplication::applicationPid()); QCoreApplication::quit(); }); app.exec(); if (Tui::ZSimpleStringLogger::getMessages().size() > 0) { terminal.pauseOperation(); printf("\e[90m%s\e[0m", Tui::ZSimpleStringLogger::getMessages().toUtf8().data()); } return 0; } editor-0.1.80/src/markermanager.cpp000066400000000000000000000044041477475121100172100ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "markermanager.h" #include #include MarkerManager::MarkerManager() { } void MarkerManager::addMarker(Tui::ZDocument *doc, int line) { markers.emplace_back(std::make_unique(doc, line)); } void MarkerManager::removeMarker(int line) { markers.erase(std::remove_if(markers.begin(), markers.end(), [line](const auto& marker) { return marker->line() == line; }), markers.end()); } bool MarkerManager::hasMarker() { return !markers.empty(); } bool MarkerManager::hasMarker(int line) { return std::any_of(markers.begin(), markers.end(), [line](const auto& marker) { return marker->line() == line; }); } std::vector MarkerManager::listMarkerIntern() { std::vector list; list.reserve(markers.size()); for (const auto& marker : markers) { list.push_back(marker->line()); } std::sort(list.begin(), list.end()); return list; } QList MarkerManager::listMarker() { std::vector vec = listMarkerIntern(); QList list; for (int value : vec) { list.append(value); } return list; } int MarkerManager::nextMarker(int line) { std::vector list = listMarkerIntern(); auto it = std::upper_bound(list.begin(), list.end(), line); if (it != list.end()) { return *it; } else { auto it = std::min_element(list.begin(), list.end()); if (it != list.end()) { return *it; } } return -1; } int MarkerManager::previousMarker(int line) { std::vector list = listMarkerIntern(); auto it = std::lower_bound(list.begin(), list.end(), line); if (it != list.begin()) { if (*(it - 1) < line) { return *(it - 1); } else { if(!list.empty()) { return list.back(); } } } else { if (!list.empty()) { return list.back(); } } return -1; } void MarkerManager::clearMarkers() { markers.clear(); } MarkerManager::~MarkerManager() { clearMarkers(); } editor-0.1.80/src/markermanager.h000066400000000000000000000012161477475121100166530ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef MARKERMANAGER_H #define MARKERMANAGER_H #include #include #include class MarkerManager { public: explicit MarkerManager(); void addMarker(Tui::ZDocument *doc, int line); void removeMarker(int line); bool hasMarker(); bool hasMarker(int line); QList listMarker(); int nextMarker(int line); int previousMarker(int line); void clearMarkers(); ~MarkerManager(); private: std::vector listMarkerIntern(); std::vector> markers; }; #endif // MARKERMANAGER_H editor-0.1.80/src/mdilayout.cpp000066400000000000000000000433221477475121100164050ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "mdilayout.h" #include #include #include MdiLayout::MdiLayout() { } MdiLayout::~MdiLayout() { } void MdiLayout::addWindow(Tui::ZWidget *w) { Tui::ZWindowFacet *windowFacet = static_cast(w->facet(Tui::ZWindowFacet::staticMetaObject)); if (windowFacet) { windowFacet->setContainer(&windowContainer); windowFacet->setManuallyPlaced(false); } double sum = 0.; for (const Item &item: _items) { sum += item.weight; } double newWeight = _items.size() ? sum / _items.size() : 1; _items.push_back({w, newWeight}); QObject::connect(w, &QObject::destroyed, this, [this, w] { QMutableVectorIterator it{_items}; while (it.hasNext()) { it.next(); if (it.value().item == w) { it.remove(); } } relayout(); }); QObject::connect(new Tui::ZShortcut( Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, Qt::Key_Up, Qt::ControlModifier), w, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this, w] { setInteractiveUp(w); } ); QObject::connect(new Tui::ZShortcut( Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, Qt::Key_Down, Qt::ControlModifier), w, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this, w] { setInteractiveDown(w); } ); QObject::connect(new Tui::ZShortcut( Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, Qt::Key_Left, Qt::ControlModifier), w, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this, w] { setInteractiveLeft(w); } ); QObject::connect(new Tui::ZShortcut( Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, Qt::Key_Right, Qt::ControlModifier), w, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this, w] { setInteractiveRight(w); } ); QObject::connect(new Tui::ZShortcut( Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, Qt::Key_Up, {}), w, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this, w] { if (_mode == LayoutMode::TileV) { auto *prev = prevWindow(w); if (prev) { setFocus(prev); prev->raise(); } } } ); QObject::connect(new Tui::ZShortcut( Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, Qt::Key_Down, {}), w, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this, w] { if (_mode == LayoutMode::TileV) { auto *next = nextWindow(w); if (next) { setFocus(next); next->raise(); } } } ); QObject::connect(new Tui::ZShortcut( Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, Qt::Key_Left, {}), w, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this, w] { if (_mode == LayoutMode::TileH) { auto *prev = prevWindow(w); if (prev) { setFocus(prev); prev->raise(); } } } ); QObject::connect(new Tui::ZShortcut( Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, Qt::Key_Right, {}), w, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this, w] { if (_mode == LayoutMode::TileH) { auto *next = nextWindow(w); if (next) { setFocus(next); next->raise(); } } } ); QObject::connect(new Tui::ZShortcut( Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, Qt::Key_Up, Qt::ControlModifier | Qt::ShiftModifier), w, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this, w] { if (_mode == LayoutMode::TileV) { swapPrevWindow(w); } } ); QObject::connect(new Tui::ZShortcut( Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, Qt::Key_Down, Qt::ControlModifier | Qt::ShiftModifier), w, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this, w] { if (_mode == LayoutMode::TileV) { swapNextWindow(w); } } ); QObject::connect(new Tui::ZShortcut( Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, Qt::Key_Left, Qt::ControlModifier | Qt::ShiftModifier), w, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this, w] { if (_mode == LayoutMode::TileH) { swapPrevWindow(w); } } ); QObject::connect(new Tui::ZShortcut( Tui::ZKeySequence::forShortcutSequence("e", Qt::ControlModifier, Qt::Key_Right, Qt::ControlModifier | Qt::ShiftModifier), w, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this, w] { if (_mode == LayoutMode::TileH) { swapNextWindow(w); } } ); relayout(); } void MdiLayout::setMode(MdiLayout::LayoutMode mode) { _mode = mode; relayout(); } void MdiLayout::setGeometry(QRect r) { height = r.height(); width = r.width(); if (_mode == LayoutMode::Base) { for (const Item &item: _items) { auto *const childWidget = item.item; Tui::ZWindowFacet *windowFacet = static_cast(childWidget->facet(Tui::ZWindowFacet::staticMetaObject)); if (windowFacet) { if (windowFacet->container() == &windowContainer && !windowFacet->isManuallyPlaced()) { childWidget->setGeometry(r); } } else { childWidget->setGeometry(r); } } } else { QVector fit; double sum = 0; for (Item &item: _items) { auto *const childWidget = item.item; Tui::ZWindowFacet *windowFacet = static_cast(childWidget->facet(Tui::ZWindowFacet::staticMetaObject)); if (windowFacet) { if (windowFacet->container() == &windowContainer && !windowFacet->isManuallyPlaced()) { fit.append(&item); sum += item.weight; } } else { fit.append(&item); sum += item.weight; } } if (fit.size() == 0) { return; } if (sum == 0) { return; } if (_mode == LayoutMode::TileV) { double factor = r.height() / sum; double y = r.top(); for (int i = 0; i < fit.size(); i++) { Item *item = fit[i]; double h = item->weight * factor; int height = static_cast(y + h) - static_cast(y); if (i + 1 == fit.size() && static_cast(y) + height <= r.bottom()) { height += 1; } item->item->setGeometry({r.left(), static_cast(y), r.width(), height}); item->lastLayoutSize = height; if (item->item == interactiveSizeSelected) { if (interactiveSizeEdge == Qt::TopEdge) { overlay.setGeometry({r.left() + r.width() / 4, static_cast(y) - 1, r.width() / 2, 3}); } else { overlay.setGeometry({r.left() + r.width() / 4, static_cast(y + h) - 1, r.width() / 2, 3}); } } y += h; } } if (_mode == LayoutMode::TileH) { double factor = r.width() / sum; double x = r.left(); for (int i = 0; i < fit.size(); i++) { Item *item = fit[i]; double w = item->weight * factor; int width = static_cast(x + w) - static_cast(x); if (i + 1 == fit.size() && static_cast(x) + width <= r.bottom()) { width += 1; } item->item->setGeometry({static_cast(x), r.top(), width, r.height()}); item->lastLayoutSize = width; if (item->item == interactiveSizeSelected) { if (interactiveSizeEdge == Qt::LeftEdge) { overlay.setGeometry({static_cast(x) - 1, r.top() + r.height() / 4, 3, r.height() / 2}); } else { overlay.setGeometry({static_cast(x + w) - 1, r.top() + r.height() / 4, 3, r.height() / 2}); } } x += w; } } } } void MdiLayout::removeWidgetRecursively(Tui::ZWidget *widget) { QMutableVectorIterator it{_items}; while (it.hasNext()) { it.next(); if (it.value().item == widget) { it.remove(); } } relayout(); } QSize MdiLayout::sizeHint() const { return {1, 1}; } Tui::SizePolicy MdiLayout::sizePolicyH() const { return Tui::SizePolicy::Expanding; } Tui::SizePolicy MdiLayout::sizePolicyV() const { return Tui::SizePolicy::Expanding; } void MdiLayout::setInteractiveUp(Tui::ZWidget *w) { if (_mode == LayoutMode::TileH || _items.size() == 0 || _items.first().item == w) { return; } interactiveSizeSelected = w; interactiveSizeEdge = Qt::TopEdge; overlay.setVertical(true); overlay.setParent(qobject_cast(parent()->parent())); relayout(); w->grabKeyboard([this] (QEvent *event) { interactiveHandler(event); }); } void MdiLayout::setInteractiveDown(Tui::ZWidget *w) { if (_mode == LayoutMode::TileH || _items.size() == 0 || _items.last().item == w) { return; } interactiveSizeSelected = w; interactiveSizeEdge = Qt::BottomEdge; overlay.setVertical(true); overlay.setParent(qobject_cast(parent()->parent())); relayout(); w->grabKeyboard([this] (QEvent *event) { interactiveHandler(event); }); } void MdiLayout::setInteractiveLeft(Tui::ZWidget *w) { if (_mode == LayoutMode::TileV || _items.size() == 0 || _items.first().item == w) { return; } interactiveSizeSelected = w; interactiveSizeEdge = Qt::LeftEdge; overlay.setVertical(false); overlay.setParent(qobject_cast(parent()->parent())); relayout(); w->grabKeyboard([this] (QEvent *event) { interactiveHandler(event); }); } void MdiLayout::setInteractiveRight(Tui::ZWidget *w) { if (_mode == LayoutMode::TileV || _items.size() == 0 || _items.last().item == w) { return; } interactiveSizeSelected = w; interactiveSizeEdge = Qt::RightEdge; overlay.setVertical(false); overlay.setParent(qobject_cast(parent()->parent())); relayout(); w->grabKeyboard([this] (QEvent *event) { interactiveHandler(event); }); } void MdiLayout::interactiveHandler(QEvent *event) { if (event->type() == Tui::ZEventType::key()) { auto *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Enter) { interactiveSizeSelected->releaseKeyboard(); interactiveSizeSelected = nullptr; overlay.setParent(nullptr); relayout(); } else if (keyEvent->key() == Qt::Key_Escape) { interactiveSizeSelected->releaseKeyboard(); interactiveSizeSelected = nullptr; overlay.setParent(nullptr); relayout(); } else { if (interactiveSizeEdge == Qt::TopEdge) { if (keyEvent->key() == Qt::Key_Up) { increaseWeight(interactiveSizeSelected, height); } else if (keyEvent->key() == Qt::Key_Down) { decreaseWeight(interactiveSizeSelected, height); } } else if (interactiveSizeEdge == Qt::BottomEdge) { if (keyEvent->key() == Qt::Key_Up) { auto w = nextWindow(interactiveSizeSelected); if (w) { increaseWeight(w, height); } } else if (keyEvent->key() == Qt::Key_Down) { auto w = nextWindow(interactiveSizeSelected); if (w) { decreaseWeight(w, height); } } } else if (interactiveSizeEdge == Qt::LeftEdge) { if (keyEvent->key() == Qt::Key_Left) { increaseWeight(interactiveSizeSelected, width); } else if (keyEvent->key() == Qt::Key_Right) { decreaseWeight(interactiveSizeSelected, width); } } else if (interactiveSizeEdge == Qt::RightEdge) { if (keyEvent->key() == Qt::Key_Left) { auto w = nextWindow(interactiveSizeSelected); if (w) { increaseWeight(w, width); } } else if (keyEvent->key() == Qt::Key_Right) { auto w = nextWindow(interactiveSizeSelected); if (w) { decreaseWeight(w, width); } } } } } } void MdiLayout::increaseWeight(Tui::ZWidget *w, int extend) { double sum = 0; for (Item &item: _items) { sum += item.weight; } double adjust = sum / (extend ? extend : 1); Item *prevItem = nullptr; for (Item &item: _items) { if (item.item == w) { if (prevItem && prevItem->lastLayoutSize > 2) { prevItem->weight -= adjust; item.weight += adjust; } } prevItem = &item; } relayout(); } void MdiLayout::decreaseWeight(Tui::ZWidget *w, int extend) { double sum = 0; for (Item &item: _items) { sum += item.weight; } double adjust = sum / (extend ? extend : 1); Item *prevItem = nullptr; for (Item &item: _items) { if (item.item == w) { if (prevItem && item.lastLayoutSize > 2) { prevItem->weight += adjust; item.weight -= adjust; } } prevItem = &item; } relayout(); } Tui::ZWidget *MdiLayout::prevWindow(Tui::ZWidget *w) { Tui::ZWidget *prev = nullptr; for (Item &item: _items) { if (item.item == w) { return prev; } prev = item.item; } return nullptr; } Tui::ZWidget *MdiLayout::nextWindow(Tui::ZWidget *w) { bool found = false; for (Item &item: _items) { if (found) { return item.item; } if (item.item == w) { found = true; } } return nullptr; } void MdiLayout::swapPrevWindow(Tui::ZWidget *w) { Item *prev = nullptr; for (Item &item: _items) { if (item.item == w && prev) { using namespace std; swap(item, *prev); relayout(); break; } prev = &item; } } void MdiLayout::swapNextWindow(Tui::ZWidget *w) { Item *found = nullptr; for (Item &item: _items) { if (found) { using namespace std; swap(item, *found); relayout(); break; } if (item.item == w) { found = &item; } } } void MdiLayout::setFocus(Tui::ZWidget *w) { w = w->placeFocus(); if (w) { w->setFocus(Qt::FocusReason::ActiveWindowFocusReason); } } SizerOverlay::SizerOverlay(Tui::ZWidget *parent) : Tui::ZWidget(parent) { setGeometry({0, 0, 3, 3}); } void SizerOverlay::paintEvent(Tui::ZPaintEvent *event) { auto painter = event->painter(); auto bg = Tui::Colors::blue; auto fg = Tui::Colors::brightGreen; if (_vertical) { if (geometry().width() > 6) { painter->writeWithColors(0, 0, "↑↑↑", fg, bg); //painter->writeWithColors(0, 1, "|||", fg, bg); painter->writeWithColors(0, 2, "↓↓↓", fg, bg); painter->writeWithColors(geometry().width() - 3, 0, "↑↑↑", fg, bg); //painter->writeWithColors(geometry().width() - 3, 1, "|||", fg, bg); painter->writeWithColors(geometry().width() - 3, 2, "↓↓↓", fg, bg); } else { painter->writeWithColors(geometry().width() / 2 - 1, 0, "↑↑↑", fg, bg); //painter->writeWithColors(geometry().width() / 2 - 1, 1, "|||", fg, bg); painter->writeWithColors(geometry().width() / 2 - 1, 2, "↓↓↓", fg, bg); } } else { if (geometry().height() > 6) { painter->writeWithColors(0, 0, "←-→", fg, bg); //painter->writeWithColors(0, 1, "←-→", fg, bg); painter->writeWithColors(0, 2, "←-→", fg, bg); painter->writeWithColors(0, geometry().height() - 3, "←-→", fg, bg); //painter->writeWithColors(0, geometry().height() - 2, "←-→", fg, bg); painter->writeWithColors(0, geometry().height() - 1, "←-→", fg, bg); } else { painter->writeWithColors(0, geometry().height() / 2 - 1, "←-→", fg, bg); //painter->writeWithColors(0, geometry().height() / 2 + 0, "←-→", fg, bg); painter->writeWithColors(0, geometry().height() / 2 + 1, "←-→", fg, bg); } } } void SizerOverlay::setVertical(bool v) { _vertical = v; } editor-0.1.80/src/mdilayout.h000066400000000000000000000036431477475121100160540ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef MDILAYOUT_H #define MDILAYOUT_H #include #include #include #include #include class SizerOverlay : public Tui::ZWidget { Q_OBJECT public: SizerOverlay(Tui::ZWidget* parent = nullptr); public: void paintEvent(Tui::ZPaintEvent *event) override; void setVertical(bool v); private: bool _vertical = true; }; class MdiLayout : public Tui::ZLayout { Q_OBJECT public: enum class LayoutMode { Base, TileV, TileH }; public: MdiLayout(); ~MdiLayout() override; public: void addWindow(Tui::ZWidget *w); void setMode(LayoutMode _mode); public: void setGeometry(QRect r) override; void removeWidgetRecursively(Tui::ZWidget *widget) override; QSize sizeHint() const override; Tui::SizePolicy sizePolicyH() const override; Tui::SizePolicy sizePolicyV() const override; private: void setInteractiveUp(Tui::ZWidget *w); void setInteractiveDown(Tui::ZWidget *w); void setInteractiveLeft(Tui::ZWidget *w); void setInteractiveRight(Tui::ZWidget *w); void interactiveHandler(QEvent *event); void increaseWeight(Tui::ZWidget *w, int extend); void decreaseWeight(Tui::ZWidget *w, int extend); Tui::ZWidget *prevWindow(Tui::ZWidget *w); Tui::ZWidget *nextWindow(Tui::ZWidget *w); void swapPrevWindow(Tui::ZWidget *w); void swapNextWindow(Tui::ZWidget *w); void setFocus(Tui::ZWidget *w); private: struct Item { Tui::ZWidget *item; double weight = 1; int lastLayoutSize = 0; }; QVector _items; LayoutMode _mode = LayoutMode::TileV; Tui::ZWindowContainer windowContainer; QPointer interactiveSizeSelected; Qt::Edge interactiveSizeEdge = Qt::BottomEdge; int height = 1; int width = 1; SizerOverlay overlay; }; #endif // MDILAYOUT_H editor-0.1.80/src/meson.build000066400000000000000000000045531477475121100160370ustar00rootroot00000000000000# SPDX-License-Identifier: BSL-1.0 main = ['main.cpp'] #ide:editable-filelist editor_sources = [ 'aboutdialog.cpp', 'alert.cpp', 'attributes.cpp', 'commandlinewidget.cpp', 'confirmsave.cpp', 'dlgfilemodel.cpp', 'edit.cpp', 'file.cpp', 'filecategorize.cpp', 'filelistparser.cpp', 'filewindow.cpp', 'formattingdialog.cpp', 'gotoline.cpp', 'groupbox.cpp', 'help.cpp', 'insertcharacter.cpp', 'markermanager.cpp', 'mdilayout.cpp', 'opendialog.cpp', 'overwritedialog.cpp', 'savedialog.cpp', 'scrollbar.cpp', 'searchcount.cpp', 'searchdialog.cpp', 'statemux.cpp', 'statusbar.cpp', 'syntaxhighlightdialog.cpp', 'tabdialog.cpp', 'themedialog.cpp', 'wrapdialog.cpp', ] #ide:editable-filelist editor_headers = [ 'aboutdialog.h', 'alert.h', 'attributes.h', 'commandlinewidget.h', 'confirmsave.h', 'dlgfilemodel.h', 'edit.h', 'file.h', 'filecategorize.h', 'filewindow.h', 'formattingdialog.h', 'gotoline.h', 'groupbox.h', 'help.h', 'insertcharacter.h', 'markermanager.h', 'mdilayout.h', 'opendialog.h', 'overwritedialog.h', 'savedialog.h', 'scrollbar.h', 'searchcount.h', 'searchdialog.h', 'statusbar.h', 'syntaxhighlightdialog.h', 'tabdialog.h', 'themedialog.h', 'wrapdialog.h', ] vcs_dep = vcs_tag(command : ['git', 'rev-parse', '--short', 'HEAD'], fallback: '000000', input:'version_git.h.in', output:'version_git.h', replace_string:'%GIT_VERSION%') conf_data = configuration_data() conf_data.set_quoted('VERSION_NUMBER', get_option('version')) configure_file( input: 'version.h.in', output: 'version.h', configuration: conf_data ) editor_lib = static_library('editor', [editor_sources, syntax_qrc], qt5.preprocess(moc_headers: editor_headers, moc_sources: editor_sources, include_directories: include_directories('.')), include_directories: include_directories('.'), dependencies : [qt5_dep, tuiwidgets_dep, posixsignalmanager_dep, syntax_dep]) executable('chr', main, vcs_dep, install_rpath: get_option('rpath'), install : true, include_directories: include_directories('.'), link_with: editor_lib, dependencies : [qt5_dep, tuiwidgets_dep, posixsignalmanager_dep, syntax_dep]) verbose_kwargs = {} if meson.version().version_compare('>=0.62') verbose_kwargs += {'verbose': true} endif if get_option('tests') subdir('tests') endif editor-0.1.80/src/opendialog.cpp000066400000000000000000000060651477475121100165220ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "opendialog.h" #include OpenDialog::OpenDialog(Tui::ZWidget *parent, QString path) : Tui::ZDialog(parent) { setOptions(Tui::ZWindow::CloseOption | Tui::ZWindow::DeleteOnClose | Tui::ZWindow::MoveOption | Tui::ZWindow::AutomaticOption); setDefaultPlacement(Qt::AlignCenter); setGeometry({0, 0, 50, 15}); setWindowTitle("Open File"); setContentsMargins({1, 1, 2, 1}); if (path.size()) { QFileInfo fi(path); if (fi.isDir() && path.right(1) != "/") { fi.setFile(path + "/"); } _dir.setPath(fi.absolutePath()); } _dir.makeAbsolute(); _curentPath = new Tui::ZLabel(this); _curentPath->setGeometry({2, 2, 45, 1}); _hiddenCheckBox = new Tui::ZCheckBox(Tui::withMarkup, "hidden", this); _hiddenCheckBox->setGeometry({3, 12, 13, 1}); _model = std::make_unique(_dir); _model->setDisplayHidden(_hiddenCheckBox->checkState() == Qt::CheckState::Checked); _folder = new Tui::ZListView(this); _folder->setGeometry({3, 3, 44, 8}); _folder->setFocus(); _folder->setModel(_model.get()); _cancelButton = new Tui::ZButton(this); _cancelButton->setGeometry({25, 12, 12, 1}); _cancelButton->setText("Cancel"); _okButton = new Tui::ZButton(this); _okButton->setGeometry({37, 12, 10, 1}); _okButton->setText("Open"); _okButton->setDefault(true); QObject::connect(_folder->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this] { filenameChanged(_folder->currentItem()); }); QObject::connect(_folder, &Tui::ZListView::enterPressed, this, [this](int selected) { (void)selected; if (_okButton->isEnabled()) { userInput(_folder->currentItem()); } }); QObject::connect(_hiddenCheckBox, &Tui::ZCheckBox::stateChanged, this, [this] { _model->setDisplayHidden(_hiddenCheckBox->checkState() == Qt::CheckState::Checked); }); QObject::connect(_okButton, &Tui::ZButton::clicked, this, [this] { userInput(_folder->currentItem()); }); QObject::connect(_cancelButton, &Tui::ZButton::clicked, this, &OpenDialog::rejected); refreshFolder(); } void OpenDialog::filenameChanged(QString filename) { QFileInfo datei(_dir.path() + "/" + filename); _okButton->setEnabled(datei.isReadable()); } void OpenDialog::refreshFolder() { _model->setDirectory(_dir); _curentPath->setText(_dir.absolutePath().right(44)); _folder->setCurrentIndex(_model->index(0, 0)); } void OpenDialog::userInput(QString filename) { if (QFileInfo(_dir.filePath(filename)).isDir()) { if (QFileInfo(_dir.filePath(filename)).isSymLink()) { _dir.setPath(_dir.filePath(QFileInfo(_dir.filePath(filename)).symLinkTarget())); } else { _dir.setPath(_dir.filePath(filename)); } _dir.makeAbsolute(); refreshFolder(); } else { fileSelected(_dir.filePath(filename)); rejected(); } } void OpenDialog::rejected() { deleteLater(); } editor-0.1.80/src/opendialog.h000066400000000000000000000016711477475121100161650ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef OPENDIALOG_H #define OPENDIALOG_H #include #include #include #include #include #include #include #include "dlgfilemodel.h" class OpenDialog : public Tui::ZDialog { Q_OBJECT public: OpenDialog(Tui::ZWidget *parent, QString path = ""); public slots: void rejected(); signals: void fileSelected(QString filename); private: void filenameChanged(QString filename); void userInput(QString filename); void refreshFolder(); private: Tui::ZLabel *_curentPath = nullptr; Tui::ZInputBox *_filenameText = nullptr; Tui::ZListView *_folder = nullptr; Tui::ZCheckBox *_hiddenCheckBox = nullptr; Tui::ZButton *_okButton = nullptr; Tui::ZButton *_cancelButton = nullptr; QDir _dir; std::unique_ptr _model; }; #endif // OPENDIALOG_H editor-0.1.80/src/overwritedialog.cpp000066400000000000000000000025121477475121100176000ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "overwritedialog.h" #include #include #include OverwriteDialog::OverwriteDialog(Tui::ZWidget *parent, QString fileName) : Tui::ZDialog(parent) { setOptions(Tui::ZWindow::MoveOption | Tui::ZWindow::AutomaticOption | Tui::ZWindow::DeleteOnClose); setContentsMargins({1, 1, 2, 1}); setWindowTitle("Overwrite?"); Tui::ZVBoxLayout *vbox = new Tui::ZVBoxLayout(); setLayout(vbox); vbox->setSpacing(1); { Tui::ZTextLine *tl = new Tui::ZTextLine("Overwrite the existing file?", this); vbox->addWidget(tl); Tui::ZTextLine *tl2 = new Tui::ZTextLine(fileName, this); vbox->addWidget(tl2); } { Tui::ZHBoxLayout *hbox = new Tui::ZHBoxLayout(); hbox->setSpacing(2); _cancelButton = new Tui::ZButton(this); _cancelButton->setText("Cancel"); hbox->addWidget(_cancelButton); _okButton = new Tui::ZButton(this); _okButton->setText("Overwrite"); _okButton->setDefault(true); hbox->addWidget(_okButton); vbox->add(hbox); } QObject::connect(_cancelButton, &Tui::ZButton::clicked, this, [this] { confirm(false); }); QObject::connect(_okButton, &Tui::ZButton::clicked, this, [this] { confirm(true); }); } editor-0.1.80/src/overwritedialog.h000066400000000000000000000006651477475121100172540ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef OVERWRITEDIALOG_H #define OVERWRITEDIALOG_H #include #include class OverwriteDialog : public Tui::ZDialog { Q_OBJECT public: OverwriteDialog(Tui::ZWidget *parent, QString fileName); signals: void confirm(bool confirm); private: Tui::ZButton *_okButton = nullptr; Tui::ZButton *_cancelButton = nullptr; }; #endif // OVERWRITEDIALOG_H editor-0.1.80/src/savedialog.cpp000066400000000000000000000203261477475121100165130ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "savedialog.h" #include #include #include "overwritedialog.h" SaveDialog::SaveDialog(Tui::ZWidget *parent, File *file) : Tui::ZDialog(parent) { setDefaultPlacement(Qt::AlignCenter); setGeometry({0, 0, 50, 15}); setOptions(Tui::ZWindow::CloseOption | Tui::ZWindow::DeleteOnClose | Tui::ZWindow::MoveOption | Tui::ZWindow::AutomaticOption); setWindowTitle("Save as..."); setContentsMargins({1, 1, 2, 1}); _currentPath = new Tui::ZLabel(this); _currentPath->setGeometry({2, 2, 45, 1}); if (!file->isNewFile()) { QFileInfo fileInfo(file->getFilename()); _dir.setPath(fileInfo.path()); } _model = std::make_unique(_dir); _folder = new Tui::ZListView(this); _folder->setGeometry({3, 3, 44, 6}); _folder->setFocus(); _folder->setModel(_model.get()); _filenameText = new Tui::ZInputBox(this); _filenameText->setGeometry({3, 10, 44, 1}); if (!file->isNewFile()) { QFileInfo fileInfo(file->getFilename()); _filenameText->setText(fileInfo.fileName()); } _hiddenCheckBox = new Tui::ZCheckBox(Tui::withMarkup, "hidden", this); _hiddenCheckBox->setGeometry({3, 11, 13, 1}); _model->setDisplayHidden(_hiddenCheckBox->checkState() == Qt::CheckState::Checked); _crlf = new Tui::ZCheckBox(Tui::withMarkup, "CRLF Mode", this); _crlf->setGeometry({3, 12, 16, 1}); if (file->document()->crLfMode()) { _crlf->setCheckState(Qt::Checked); } else { _crlf->setCheckState(Qt::Unchecked); } _cancelButton = new Tui::ZButton(this); _cancelButton->setGeometry({25, 12, 12, 1}); _cancelButton->setText("Cancel"); _saveButton = new Tui::ZButton(this); _saveButton->setGeometry({37, 12, 10, 1}); _saveButton->setText("Save"); _saveButton->setDefault(true); _saveButton->setEnabled(false); Tui::ZPalette p; p.setColors({{"control.bg", Tui::Colors::lightGray}, {"control.fg", Tui::Colors::red}}); _noteText = new Tui::ZLabel(this); _noteText->setGeometry({2, 9, 45, 1}); _noteText->setPalette(p); QObject::connect(_folder, &Tui::ZListView::enterPressed, this, [this](int selected) { (void)selected; userInput(_folder->currentItem()); }); QObject::connect(_filenameText, &Tui::ZInputBox::textChanged, this, &SaveDialog::filenameChanged); QObject::connect(_hiddenCheckBox, &Tui::ZCheckBox::stateChanged, this, [this] { _model->setDisplayHidden(_hiddenCheckBox->checkState() == Qt::CheckState::Checked); }); QObject::connect(_cancelButton, &Tui::ZButton::clicked, this, &SaveDialog::rejected); QObject::connect(this, &Tui::ZDialog::rejected, this, &SaveDialog::rejected); QObject::connect(_saveButton, &Tui::ZButton::clicked, this, &SaveDialog::saveFile); QRect r = geometry(); r.moveCenter(terminal()->mainWidget()->geometry().center()); setGeometry(r); refreshFolder(); if (_filenameText->text().size()) { filenameChanged(_filenameText->text()); } } void SaveDialog::saveFile() { QString fileName = _dir.absoluteFilePath(_filenameText->text()); QFileInfo fileInfo(fileName); if (fileInfo.isDir()) { _previewDir.reset(); _dir.setPath(fileName); refreshFolder(); _filenameText->setText(""); return; } if (_dir.exists(fileName)) { OverwriteDialog *overwriteDialog = new OverwriteDialog(parentWidget(), fileName); overwriteDialog->setFocus(); QObject::connect(overwriteDialog, &OverwriteDialog::confirm, this, [this, overwriteDialog, fileName](bool value) { if (value) { fileSelected(fileName, _crlf->checkState() == Qt::Checked); deleteLater(); } overwriteDialog->deleteLater(); } ); } else { fileSelected(fileName, _crlf->checkState() == Qt::Checked); deleteLater(); } } void SaveDialog::filenameChanged(QString filename) { if (filename.isEmpty()) { if (_previewDir) { _previewDir.reset(); refreshFolder(); } _noteText->setText(""); _saveButton->setText("Save"); _saveButton->setEnabled(false); return; } if (!filename.contains('/')) { filename = _dir.absolutePath() + QString('/') + filename; if (_previewDir) { _previewDir.reset(); refreshFolder(); } } else { QFileInfo fileInfo(_dir.filePath(QFileInfo(filename).path())); if (fileInfo.isDir()) { _previewDir.emplace(fileInfo.absoluteFilePath()); refreshFolder(); filename = QFileInfo(_dir.filePath(filename)).absoluteFilePath(); } else { _noteText->setText("directory does not exist: " + fileInfo.absoluteFilePath()); _saveButton->setText("Save"); _saveButton->setEnabled(false); return; } } // Loop to easyly catch symlink loops for (int cycle = 0; cycle < 100; cycle++) { QFileInfo fileInfo(filename); if (filename.isEmpty()) { _saveButton->setText("Save"); _saveButton->setEnabled(false); return; } else if (fileInfo.isDir()) { _noteText->setText(""); _saveButton->setText("Open"); _saveButton->setEnabled(true); return; } else if (fileInfo.isFile()) { // existing file if (fileInfo.isSymLink()) { filename = fileInfo.symLinkTarget(); continue; } else if (fileInfo.isWritable()) { _saveButton->setText("Save"); _saveButton->setEnabled(true); return; } _noteText->setText("no permission to write file"); } else { // to create a new file if (fileInfo.isSymLink()) { filename = fileInfo.symLinkTarget(); continue; } else { QFileInfo parent(fileInfo.path()); if (parent.isWritable()) { _saveButton->setText("Save"); _saveButton->setEnabled(true); _noteText->setText(""); return; } else { _noteText->setText("no permission to create file"); } } } _saveButton->setText("Save"); _saveButton->setEnabled(false); return; } // too many symlinks, bail out _noteText->setText("symlink loop"); _saveButton->setText("Save"); _saveButton->setEnabled(false); } void SaveDialog::refreshFolder() { if (_previewDir) { _model->setDirectory(*_previewDir); _currentPath->setText((*_previewDir).absolutePath().right(34) + " (preview)"); _folder->setCurrentIndex(_model->index(0, 0)); } else { _model->setDirectory(_dir); _currentPath->setText(_dir.absolutePath().right(44)); _folder->setCurrentIndex(_model->index(0, 0)); } } void SaveDialog::userInput(QString filename) { if (_previewDir) { _dir = *_previewDir; _previewDir.reset(); _currentPath->setText(_dir.absolutePath().right(44)); } const bool isDirectory = QFileInfo(_dir.filePath(filename)).isDir(); if (isDirectory) { QString tmp = filename.left(filename.size() - 1); const bool isSymlink = QFileInfo(_dir.filePath(tmp)).isSymLink(); if (isSymlink) { _dir.setPath(_dir.filePath(QFileInfo(_dir.filePath(tmp)).symLinkTarget())); } else { _dir.setPath(_dir.filePath(filename)); } _dir.makeAbsolute(); if (_filenameText->text().contains('/')) { _filenameText->setText(_filenameText->text().section('/', -1)); } else { filenameChanged(_filenameText->text()); } refreshFolder(); } else { _filenameText->setText(filename); _filenameText->setFocus(); } } void SaveDialog::rejected() { deleteLater(); } editor-0.1.80/src/savedialog.h000066400000000000000000000022231477475121100161540ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef SAVEDIALOG_H #define SAVEDIALOG_H #include #include #include #include #include #include #include #include "dlgfilemodel.h" #include "file.h" class SaveDialog : public Tui::ZDialog { Q_OBJECT public: SaveDialog(Tui::ZWidget *parent, File *file = nullptr); public slots: void saveFile(); void rejected(); signals: void fileSelected(QString filename, bool crlfMode); private: void filenameChanged(QString filename); bool _filenameChanged(QString filename); void userInput(QString filename); void refreshFolder(); private: Tui::ZLabel *_currentPath = nullptr; Tui::ZInputBox *_filenameText = nullptr; Tui::ZListView *_folder = nullptr; Tui::ZCheckBox *_hiddenCheckBox = nullptr; Tui::ZCheckBox *_crlf = nullptr; Tui::ZButton *_cancelButton = nullptr; Tui::ZButton *_saveButton = nullptr; Tui::ZLabel *_noteText = nullptr; QDir _dir; std::optional _previewDir; std::unique_ptr _model; }; #endif // SAVEDIALOG_H editor-0.1.80/src/scrollbar.cpp000066400000000000000000000106111477475121100163540ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "scrollbar.h" #include #include #include #include ScrollBar::ScrollBar(Tui::ZWidget *parent) : Tui::ZWidget(parent) { QObject::connect(&_autoHide, &QTimer::timeout, this, &ScrollBar::autoHideExpired); } void ScrollBar::paintEvent(Tui::ZPaintEvent *event) { auto *painter = event->painter(); Tui::ZColor controlfg = getColor("scrollbar.control.fg"); Tui::ZColor controlbg = getColor("scrollbar.control.bg"); Tui::ZColor fg = getColor("chr.trackFgColor"); Tui::ZColor bg = getColor("scrollbar.bg"); Tui::ZColor fgbehindText = getColor("chr.fgbehindText"); Tui::ZColor trackBgColor = getColor("chr.trackBgColor"); Tui::ZColor thumbBgColor = getColor("chr.thumbBgColor"); int thumbHeight; int trackBarPosition = 0; int trackBarSize; int currentPosition; int maxPosition; if (geometry().width() == 1) { trackBarSize = this->geometry().height() - 2; currentPosition = _scrollPositionY; maxPosition = _positionMaxY; } else { trackBarSize = this->geometry().width() - 2; currentPosition = _scrollPositionX; maxPosition = _positionMaxX; } if (maxPosition <= 0) { thumbHeight = trackBarSize; } else if (maxPosition == 1) { thumbHeight = trackBarSize - 1; } else if (maxPosition == 2) { thumbHeight = trackBarSize - 2; } else { thumbHeight = (trackBarSize - 2) - (maxPosition - 2) / 10; if (thumbHeight < 1) { thumbHeight = 1; } } if (currentPosition == 0 || maxPosition <= 0) { trackBarPosition = 0; } else if (currentPosition == maxPosition) { trackBarPosition = trackBarSize - thumbHeight; } else { trackBarPosition = 1 + ((double)(trackBarSize - 1) - thumbHeight) * ((double)currentPosition / (maxPosition)); } if (geometry().width() == 1) { if (!_transparent) { painter->clear(fg, bg); } else { for (int i = 0; i < geometry().height(); i++) { painter->setBackground(0, i, trackBgColor); painter->setForeground(0, i, fg); } } int y = 1 + trackBarPosition; painter->writeWithColors(0, 0, "↑", controlfg, controlbg); for (int i = 0; i < thumbHeight; i++) { if (!_transparent) { painter->writeWithColors(0, y, "▓", controlfg, controlbg); } else { painter->setBackground(0, y, thumbBgColor); painter->setForeground(0, y, fgbehindText); } y += 1; } painter->writeWithColors(0, trackBarSize + 1, "↓", controlfg, controlbg); } else { if (!_transparent) { painter->clear(fg, bg); } else { for (int i = 0; i < geometry().width(); i++) { painter->setBackground(i, 0, trackBgColor); painter->setForeground(i, 0, fgbehindText); } } int x = 1 + trackBarPosition; painter->writeWithColors(0, 0, "←", controlfg, controlbg); for (int i = 0; i < thumbHeight; i++) { if (!_transparent) { painter->writeWithColors(x, 0, "▓", controlfg, controlbg); } else { painter->setBackground(x, 0, thumbBgColor); painter->setForeground(x, 0, fgbehindText); } x += 1; } painter->writeWithColors(trackBarSize + 1, 0, "→", controlfg, controlbg); } } void ScrollBar::autoHideExpired() { setVisible(false); } bool ScrollBar::transparent() const { return _transparent; } void ScrollBar::setTransparent(bool transparent) { _transparent = transparent; } void ScrollBar::scrollPosition(int x, int y) { if (_autoHideEnabled && _scrollPositionY != y) { setVisible(true); _autoHide.start(); } _scrollPositionX = x; _scrollPositionY = y; } void ScrollBar::positonMax(int x, int y) { _positionMaxX = x; _positionMaxY = y; } void ScrollBar::setAutoHide(bool val) { if (_autoHideEnabled == val) { return; } _autoHideEnabled = val; if (_autoHideEnabled) { _autoHide.setInterval(1000); _autoHide.setSingleShot(true); _autoHide.start(); } else { _autoHide.stop(); setVisible(true); } } editor-0.1.80/src/scrollbar.h000066400000000000000000000013601477475121100160220ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef SCROLLBAR_H #define SCROLLBAR_H #include #include class ScrollBar : public Tui::ZWidget { Q_OBJECT public: ScrollBar(Tui::ZWidget *parent); bool transparent() const; public slots: void scrollPosition(int x, int y); void positonMax(int x, int y); void setAutoHide(bool val); void setTransparent(bool transparent); protected: void paintEvent(Tui::ZPaintEvent *event); private: void autoHideExpired(); private: int _scrollPositionX = 0; int _scrollPositionY = 0; int _positionMaxX = 0; int _positionMaxY = 0; QTimer _autoHide; bool _autoHideEnabled = false; bool _transparent = false; }; #endif // SCROLLBAR_H editor-0.1.80/src/searchcount.cpp000066400000000000000000000010351477475121100167070ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "searchcount.h" #include SearchCount::SearchCount() { } void SearchCount::run(Tui::ZDocumentSnapshot snap, QString searchText, Qt::CaseSensitivity caseSensitivity, int gen, std::shared_ptr> searchGen) { int found = 0; for (int line = 0; line < snap.lineCount(); line++) { if (gen != *searchGen) { return; } found += snap.line(line).count(searchText, caseSensitivity); searchCount(found); } } editor-0.1.80/src/searchcount.h000066400000000000000000000011131477475121100163510ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef SEARCHCOUNT_H #define SEARCHCOUNT_H #include #include #include #include class SearchCount : public QObject { Q_OBJECT public: explicit SearchCount(); void run(Tui::ZDocumentSnapshot snap, QString searchText, Qt::CaseSensitivity caseSensitivity, int gen, std::shared_ptr> searchGen); signals: void searchCount(int sc); }; class SearchCountSignalForwarder : public QObject { Q_OBJECT signals: void searchCount(int count); }; #endif // SEARCHCOUNT_H editor-0.1.80/src/searchdialog.cpp000066400000000000000000000352111477475121100170210ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "searchdialog.h" #include #include #include #include #include #include #include #include "groupbox.h" SearchDialog::SearchDialog(Tui::ZWidget *parent, bool replace) : Tui::ZDialog(parent) { setOptions(Tui::ZWindow::CloseOption | Tui::ZWindow::MoveOption | Tui::ZWindow::ResizeOption | Tui::ZWindow::AutomaticOption); setDefaultPlacement(Qt::AlignBottom | Qt::AlignHCenter, {0, -2}); _replace = replace; setVisible(false); setContentsMargins({1, 1, 2, 1}); Tui::ZWindowLayout *wl = new Tui::ZWindowLayout(); setLayout(wl); if (_replace) { setWindowTitle("Replace"); } else { setWindowTitle("Search"); } Tui::ZVBoxLayout *vbox = new Tui::ZVBoxLayout(); Tui::ZLabel *labelFind; vbox->setSpacing(1); { Tui::ZHBoxLayout *hbox = new Tui::ZHBoxLayout(); hbox->setSpacing(2); labelFind = new Tui::ZLabel(Tui::withMarkup, "Find", this); hbox->addWidget(labelFind); _searchText = new Tui::ZInputBox(this); labelFind->setBuddy(_searchText); hbox->addWidget(_searchText); vbox->add(hbox); } if (_replace) { Tui::ZHBoxLayout *hbox = new Tui::ZHBoxLayout(); hbox->setSpacing(2); Tui::ZLabel *labelReplace = new Tui::ZLabel(Tui::withMarkup, "Replace", this); hbox->addWidget(labelReplace); _replaceText = new Tui::ZInputBox(this); labelReplace->setBuddy(_replaceText); hbox->addWidget(_replaceText); labelFind->setMinimumSize(labelReplace->sizeHint()); vbox->add(hbox); } { Tui::ZHBoxLayout *hbox = new Tui::ZHBoxLayout(); hbox->setSpacing(3); { ZWidget *gbox1 = new ZWidget(this); Tui::ZVBoxLayout *nbox = new Tui::ZVBoxLayout(); gbox1->setLayout(nbox); _caseMatchBox = new Tui::ZCheckBox(Tui::withMarkup, "Match case sensitive", gbox1); nbox->addWidget(_caseMatchBox); _escapeSequenceRadio = new Tui::ZRadioButton(Tui::withMarkup, "Text with escapes", gbox1); _escapeSequenceRadio->setChecked(true); nbox->addWidget(_escapeSequenceRadio); _wordMatchRadio = new Tui::ZRadioButton(Tui::withMarkup, "Match entire word only", gbox1); nbox->addWidget(_wordMatchRadio); _regexMatchRadio = new Tui::ZRadioButton(Tui::withMarkup, "Regular expression", gbox1); nbox->addWidget(_regexMatchRadio); _plainTextRadio = new Tui::ZRadioButton(Tui::withMarkup, "Plain text", gbox1); nbox->addWidget(_plainTextRadio); hbox->addWidget(gbox1); } { ZWidget *gbox2 = new ZWidget(this); Tui::ZVBoxLayout *nbox = new Tui::ZVBoxLayout(); gbox2->setLayout(nbox); _forwardRadio = new Tui::ZRadioButton(Tui::withMarkup, "Forward", gbox2); _forwardRadio->setChecked(true); nbox->addWidget(_forwardRadio); _backwardRadio = new Tui::ZRadioButton(Tui::withMarkup, "Backward", gbox2); nbox->addWidget(_backwardRadio); nbox->addSpacing(1); _liveSearchBox = new Tui::ZCheckBox(Tui::withMarkup, "Live search", gbox2); _liveSearchBox->setCheckState(Qt::Checked); nbox->addWidget(_liveSearchBox); _wrapBox = new Tui::ZCheckBox(Tui::withMarkup, "Wrap around", gbox2); _wrapBox->setCheckState(Qt::Checked); nbox->addWidget(_wrapBox); hbox->addWidget(gbox2); } hbox->addStretch(); vbox->add(hbox); } vbox->addStretch(); { Tui::ZHBoxLayout *hbox = new Tui::ZHBoxLayout(); hbox->setSpacing(1); hbox->addStretch(); _findNextBtn = new Tui::ZButton(Tui::withMarkup, "Next", this); _findNextBtn->setDefault(true); hbox->addWidget(_findNextBtn); if (_replace) { _replaceBtn = new Tui::ZButton(Tui::withMarkup, "Replace", this); _replaceAllBtn = new Tui::ZButton(Tui::withMarkup, "All", this); hbox->addWidget(_replaceBtn); hbox->addWidget(_replaceAllBtn); } _cancelBtn = new Tui::ZButton(Tui::withMarkup, "Close", this); hbox->addWidget(_cancelBtn); vbox->add(hbox); } wl->setCentral(vbox); if (_replace) { setGeometry({0, 0, 55, 14}); setMinimumSize(48, 14); setMaximumSize(Tui::tuiMaxSize, 14); } else { setGeometry({0, 0, 55, 12}); setMinimumSize(48, 12); setMaximumSize(Tui::tuiMaxSize, 12); } QObject::connect(_searchText, &Tui::ZInputBox::textChanged, this, [this] (const QString& newText) { _findNextBtn->setEnabled(newText.size()); if (_replaceBtn) { _replaceBtn->setEnabled(newText.size()); } if (_replaceAllBtn) { _replaceAllBtn->setEnabled(newText.size()); } if (_liveSearchBox->checkState() == Qt::Checked) { emitAllConditions(); emitLiveSearch(); } }); QObject::connect(_findNextBtn, &Tui::ZButton::clicked, this, [this] { emitAllConditions(); Q_EMIT searchFindNext(translateSearch(_searchText->text()), _forwardRadio->checked()); }); if (_replaceBtn) { QObject::connect(_replaceBtn, &Tui::ZButton::clicked, this, [this] { emitAllConditions(); Q_EMIT searchReplace(translateSearch(_searchText->text()), translateReplace(_replaceText->text()), _forwardRadio->checked()); }); } if (_replaceAllBtn) { QObject::connect(_replaceAllBtn, &Tui::ZButton::clicked, this, [this] { emitAllConditions(); Q_EMIT searchReplaceAll(translateSearch(_searchText->text()), translateReplace(_replaceText->text())); }); } QObject::connect(_cancelBtn, &Tui::ZButton::clicked, this, [this] { Q_EMIT searchCanceled(); setVisible(false); }); QObject::connect(_caseMatchBox, &Tui::ZCheckBox::stateChanged, this, [this] { emitAllConditions(); }); QObject::connect(_escapeSequenceRadio, &Tui::ZRadioButton::toggled, this, [this] { if (!_escapeSequenceRadio->checked()) { return; } emitAllConditions(); if (_liveSearchBox->checkState() == Qt::Checked) { emitLiveSearch(); } }); QObject::connect(_wordMatchRadio, &Tui::ZRadioButton::toggled, this, [this] { if (!_wordMatchRadio->checked()) { return; } emitAllConditions(); if (_liveSearchBox->checkState() == Qt::Checked) { emitLiveSearch(); } }); QObject::connect(_regexMatchRadio, &Tui::ZRadioButton::toggled, this, [this] { if (!_regexMatchRadio->checked()) { return; } emitAllConditions(); if (_liveSearchBox->checkState() == Qt::Checked) { emitLiveSearch(); } }); QObject::connect(_plainTextRadio, &Tui::ZRadioButton::toggled, this, [this] { if (!_plainTextRadio->checked()) { return; } emitAllConditions(); if (_liveSearchBox->checkState() == Qt::Checked) { emitLiveSearch(); } }); QObject::connect(_wrapBox, &Tui::ZCheckBox::stateChanged, this, [this] { emitAllConditions(); }); QObject::connect(_forwardRadio, &Tui::ZRadioButton::toggled, this, [this] { emitAllConditions(); }); QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forKey(Qt::Key_F3, Qt::NoModifier), this, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this] { Q_EMIT searchFindNext(translateSearch(_searchText->text()), _forwardRadio->checked()); }); QObject::connect(new Tui::ZShortcut(Tui::ZKeySequence::forKey(Qt::Key_F3, Qt::ShiftModifier), this, Qt::WindowShortcut), &Tui::ZShortcut::activated, this, [this] { Q_EMIT searchFindNext(translateSearch(_searchText->text()), !_forwardRadio->checked()); }); } void SearchDialog::setSearchText(QString text) { if (text != "") { if (_searchText->text() == "" || !_regexMatchRadio->checked()) { if (_escapeSequenceRadio->checked()) { text.replace('\n', "\\n"); text.replace('\t', "\\t"); text.replace('\\', "\\\\"); } _searchText->setText(text); _searchText->textChanged(text); } } } void SearchDialog::setReplace(bool replace) { _replace = replace; } void SearchDialog::emitAllConditions() { Q_EMIT searchCaseSensitiveChanged(_caseMatchBox->checkState() == Tui::CheckState::Checked); Q_EMIT searchRegexChanged(_regexMatchRadio->checked() || _wordMatchRadio->checked()); Q_EMIT searchDirectionChanged(_forwardRadio->checked()); Q_EMIT searchWrapChanged(_wrapBox->checkState() == Tui::CheckState::Checked); } void SearchDialog::emitLiveSearch() { Q_EMIT liveSearch(translateSearch(_searchText->text()), _forwardRadio->checked()); } QString SearchDialog::translateSearch(const QString &in) { QString text = in; if (_escapeSequenceRadio->checked()) { text = applyEscapeSequences(text, false); } else if (_wordMatchRadio->checked()) { text = "\\b" + QRegularExpression::escape(text) + "\\b"; } return text; } QString SearchDialog::translateReplace(const QString &in) { QString result = in; if (_escapeSequenceRadio->checked() || _regexMatchRadio->checked()) { result = applyEscapeSequences(_replaceText->text(), _regexMatchRadio->checked()); } else if (_wordMatchRadio->checked()) { // whole word match is internally a regex replace, but we want to disable escape sequences in this mode. result.replace("\\", "\\\\"); } return result; } QString SearchDialog::applyEscapeSequences(const QString &text, bool forRegexReplacementText) { QString result; result.reserve(text.size()); auto isHexDigit = [&](int index) { QChar digitChar = text[index]; return (digitChar >= '0' && digitChar <= '9') || (digitChar >= 'a' && digitChar <= 'f') || (digitChar >= 'A' && digitChar <= 'F'); }; auto insertChar = [&](int codepoint) { if (forRegexReplacementText && codepoint == '\\') { result += "\\\\"; } else { result += QChar(codepoint); } }; for (int i = 0; i < text.size(); i++) { const QChar startChar = text[i]; if (startChar == '\\' && i + 1 < text.size()) { const QChar nextChar = text[i + 1]; if (nextChar == '0') { // octal escape sequence int value = 0; if (i + 2 >= text.size()) { i += 1; result += QChar(0); } else { QChar digitChar = text[i + 2]; if (digitChar >= '0' && digitChar <= '3') { value = digitChar.unicode() - '0'; if (i + 3 >= text.size()) { i += 2; insertChar(value); } else { digitChar = text[i + 3]; if (digitChar >= '0' && digitChar <= '7') { value = value * 8 + (digitChar.unicode() - '0'); if (i + 4 >= text.size()) { i += 3; insertChar(value); } else { digitChar = text[i + 4]; if (digitChar >= '0' && digitChar <= '7') { value = value * 8 + (digitChar.unicode() - '0'); i += 4; insertChar(value); } else { i += 3; insertChar(value); } } } else { i += 2; insertChar(value); } } } else { i += 1; result += QChar(0); } } } else if (nextChar == 'x' && i + 3 < text.size() && isHexDigit(i + 2) && isHexDigit(i + 3)) { // hex const int value = text.midRef(i + 2, 2).toInt(nullptr, 16); i += 3; insertChar(value); } else if (nextChar == 'u' && i + 5 < text.size() && isHexDigit(i + 2) && isHexDigit(i + 3) && isHexDigit(i + 4) && isHexDigit(i + 5)) { // unicode const int value = text.midRef(i + 2, 4).toInt(nullptr, 16); i += 5; insertChar(value); } else if (nextChar == '\\') { if (forRegexReplacementText) { result += '\\'; } else { result += "\\\\"; } i += 1; } else if (nextChar == 'a') { result += QChar(0x07); i += 1; } else if (nextChar == 'b') { result += QChar(0x08); i += 1; } else if (nextChar == 'f') { result += QChar(0x0C); i += 1; } else if (nextChar == 'n') { result += '\n'; i += 1; } else if (nextChar == 'r') { result += '\r'; i += 1; } else if (nextChar == 't') { result += '\t'; i += 1; } else if (nextChar == 'v') { result += QChar(0x0b); i += 1; } else if (forRegexReplacementText && nextChar >= '1' && nextChar <= '9') { result += text.midRef(i, 2); i += 1; } else { result += nextChar; i += 1; } } else { result += startChar; } } return result; } void SearchDialog::open() { setVisible(true); raise(); placeFocus()->setFocus(); } editor-0.1.80/src/searchdialog.h000066400000000000000000000036571477475121100164770ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef SEARCHDIALOG_H #define SEARCHDIALOG_H #include #include #include #include #include class SearchDialog : public Tui::ZDialog { Q_OBJECT public: SearchDialog(Tui::ZWidget *parent, bool replace = false); void setSearchText(QString text); void setReplace(bool replace); private: void emitAllConditions(); void emitLiveSearch(); QString translateSearch(const QString &in); QString translateReplace(const QString &in); QString applyEscapeSequences(const QString &text, bool forRegexReplacementText); signals: void searchCaseSensitiveChanged(bool value); void searchRegexChanged(bool searchRegexChanged); void searchDirectionChanged(bool value); void searchWrapChanged(bool value); void liveSearch(QString text, bool forward); void searchFindNext(QString text, bool forward); void searchReplace(QString text, QString replacement, bool forward); void searchReplaceAll(QString text, QString replacement); void searchCanceled(); public slots: void open(); private: bool _replace = false; Tui::ZInputBox *_searchText = nullptr; Tui::ZInputBox *_replaceText = nullptr; Tui::ZCheckBox *_caseMatchBox = nullptr; Tui::ZRadioButton *_plainTextRadio = nullptr; Tui::ZRadioButton *_wordMatchRadio = nullptr; Tui::ZRadioButton *_regexMatchRadio = nullptr; Tui::ZRadioButton *_escapeSequenceRadio = nullptr; Tui::ZCheckBox *_liveSearchBox = nullptr; Tui::ZCheckBox *_wrapBox = nullptr; Tui::ZRadioButton *_forwardRadio = nullptr; Tui::ZRadioButton *_backwardRadio = nullptr; Tui::ZButton *_findNextBtn = nullptr; Tui::ZButton *_findPreviousBtn = nullptr; Tui::ZButton *_replaceBtn = nullptr; Tui::ZButton *_replaceAllBtn = nullptr; Tui::ZButton *_cancelBtn = nullptr; }; #endif // SEARCHDIALOG_H editor-0.1.80/src/statemux.cpp000066400000000000000000000012431477475121100162440ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "statemux.h" void StateMux::selectInput(void *id) { if (_active != id) { auto inputIt = _inputs.find(id); if (inputIt != _inputs.end()) { _active = id; auto &input = inputIt->second; for (auto &item: input.items) { item->push(); } } } } void StateMux::removeInput(void *id) { auto inputIt = _inputs.find(id); if (inputIt != _inputs.end()) { auto &input = inputIt->second; for (auto connection: input.connections) { QObject::disconnect(connection); } _inputs.erase(id); } } editor-0.1.80/src/statemux.h000066400000000000000000000036651477475121100157230ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef STATEMUX_H #define STATEMUX_H #include #include #include #include class StateMux { public: template void connect(void *id, SENDER *sender, SIGNAL signal, RECEIVER *receiver, void (RECEIVER::*slot)(ARGS...), ARGS... initial) { auto itemUniquePtr = std::make_unique>(receiver, slot, std::make_tuple(initial...)); auto item = itemUniquePtr.get(); _inputs[id].items.push_back(move(itemUniquePtr)); auto connection = QObject::connect(sender, signal, [this, item, receiver, slot, id] (ARGS... args) { item->data = std::make_tuple(args...); if (_active == id) { (receiver->*slot)(args...); } }); _inputs[id].connections.push_back(connection); } void selectInput(void *id); void removeInput(void *id); private: struct ItemBase { virtual ~ItemBase() {}; virtual void push() = 0; }; template struct Item : public ItemBase { Item(RECEIVER *receiver, SLOT slot, std::tuple initial) : receiver(receiver), slot(slot), data(initial) {}; template void pushImpl(std::index_sequence) { (receiver->*slot)(std::get(data)...); }; void push() override { pushImpl(std::index_sequence_for{}); }; RECEIVER *receiver = nullptr; SLOT slot = nullptr; std::tuple data; }; struct Input { std::vector connections; std::vector> items; }; void *_active = nullptr; std::map _inputs; }; #endif // STATEMUX_H editor-0.1.80/src/statusbar.cpp000066400000000000000000000152221477475121100164040ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "statusbar.h" #include #include #include #include #include #include #include bool StatusBar::_qtMessage = false; class GlobalKeyPressListener : public QObject { public: GlobalKeyPressListener(StatusBar *statusbar) : statusbar(statusbar) { } StatusBar *statusbar = nullptr; bool eventFilter(QObject *watched, QEvent *event) { (void)watched; if (event->type() == Tui::ZEventType::rawSequence()) { statusbar->switchToNormalDisplay(); } return false; } }; StatusBar::StatusBar(Tui::ZWidget *parent) : Tui::ZWidget(parent) { setMaximumSize(Tui::tuiMaxSize, 1); setSizePolicyH(Tui::SizePolicy::Expanding); setSizePolicyV(Tui::SizePolicy::Fixed); _keyPressListener = new GlobalKeyPressListener(this); terminal()->installEventFilter(_keyPressListener); } QSize StatusBar::sizeHint() const { return { 20, 1 }; } void StatusBar::cursorPosition(int x, int utf16CodeUnit, int utf8CodeUnit, int line) { (void)utf16CodeUnit; _cursorPositionX = x; _utf8PositionX = utf8CodeUnit; _cursorPositionY = line; update(); switchToNormalDisplay(); } QString StatusBar::viewCursorPosition() { QString text; text += QString::number(_cursorPositionY + 1) + ":" + QString::number(_utf8PositionX + 1); if (_utf8PositionX != _cursorPositionX) { text += "-" + QString::number(_cursorPositionX + 1); } text += " | " + QString::number(_scrollPositionY + 1) + ":" + QString::number(_scrollPositionX + 1); return text; } void StatusBar::scrollPosition(int x, int y) { _scrollPositionX = x; _scrollPositionY = y; update(); } void StatusBar::setModified(bool mf) { _modifiedFile = mf; update(); } QString StatusBar::viewModifiedFile() { QString text; if (_modifiedFile) { text += "🖫"; } return text; } void StatusBar::readFromStandardInput(bool activ) { _stdin = activ; update(); } void StatusBar::followStandardInput(bool follow) { _follow = follow; update(); } QString StatusBar::viewStandardInput() { QString text; if (_stdin) { text += "STDIN"; if (_follow) { text += " FOLLOW"; } } return text; } void StatusBar::setWritable(bool rw) { _readwrite = rw; update(); } QString StatusBar::viewReadWrite() { QString text; if (_readwrite) { text += "RW"; } else { text += "RO"; _bg = {0xff, 0, 0}; } return text; } void StatusBar::searchCount(int sc) { _searchCount = sc; update(); } void StatusBar::searchText(QString searchText) { _searchText = searchText; update(); } void StatusBar::searchVisible(bool visible) { _searchVisible = visible; } void StatusBar::crlfMode(bool crlf) { _crlfMode = crlf; update(); } QString StatusBar::viewMode() { QString text; text += "UTF-8"; if (_crlfMode) { text += " CRLF"; } return text; } void StatusBar::modifiedSelectMode(bool event) { _selectMode = event; update(); } QString StatusBar::viewSelectMode() { QString text; if (_selectMode) { text += "SELECT MODE (F4)"; } return text; } void StatusBar::setSelectCharLines(int selectChar, int selectLines) { _selectChars = selectChar; _selectLines = selectLines; } QString StatusBar::viewSelectCharsLines() { if (_selectChars > 0 || _selectLines > 0) { return QString::number(_selectChars) +"C "+ QString::number(_selectLines) +"L"; } return ""; } void StatusBar::fileHasBeenChangedExternally(bool fileChanged) { _fileChanged = fileChanged; update(); } QString StatusBar::viewFileChanged() { QString text; if (_fileChanged) { text += "FILE CHANGED"; _bg = {0xFF, 0xDD, 0}; } return text; } void StatusBar::overwrite(bool overwrite) { _overwrite = overwrite; update(); } QString StatusBar::viewOverwrite() { QString text; if (_overwrite) { text += "OVR"; } else { text += "INS"; } return text; } QString StatusBar::viewLanguage() { if (!_syntaxHighlightingEnabled || _language == "None") { return ""; } else { return _language; } } void StatusBar::switchToNormalDisplay() { if (_showHelp) { if (_helpHoldOff < QDateTime::currentDateTimeUtc()) { _showHelp = false; update(); _keyPressListener->deleteLater(); } } } void StatusBar::language(QString language) { _language = language; update(); } void StatusBar::notifyQtLog() { _qtMessage = true; } void StatusBar::syntaxHighlightingEnabled(bool enable) { _syntaxHighlightingEnabled = enable; update(); } QString slash(QString text) { if (text != "") { return " | " + text; } return text; } static const QChar escapedNewLine = (QChar)(0xdc00 + (unsigned char)'\n'); static const QChar escapedTab = (QChar)(0xdc00 + (unsigned char)'\t'); void StatusBar::paintEvent(Tui::ZPaintEvent *event) { _bg = getColor("chr.statusbarBg"); auto *painter = event->painter(); QString search; int cutColums = terminal()->textMetrics().splitByColumns(_searchText, 25).codeUnits; search = _searchText.left(cutColums).replace(u'\n', escapedNewLine).replace(u'\t', escapedTab) + ": "+ QString::number(_searchCount); QString text; text += slash(viewSelectCharsLines()); text += slash(viewLanguage()); text += slash(viewFileChanged()); text += slash(viewSelectMode()); text += slash(viewModifiedFile()); if (_stdin) { text += slash(viewStandardInput()); } else { text += slash(viewReadWrite()); } text += slash(viewOverwrite()); text += slash(viewMode()); text += slash(viewCursorPosition()); painter->clear({0, 0, 0}, _bg); painter->writeWithColors(terminal()->width() - text.size() - 2, 0, text.toUtf8(), {0, 0, 0}, _bg); if (_searchVisible && _searchText != "" && _searchCount != -1) { Tui::ZTextLayout searchLayout(terminal()->textMetrics(), search); searchLayout.doLayout(25); searchLayout.draw(*painter, {0, 0}, Tui::ZTextStyle({0, 0, 0}, {0xff,0xdd,00})); } if (_showHelp) { QString text = "F1: Help, F10/Alt+O: Menu, Ctrl+Q: quit"; painter->writeWithColors(0, 0, text.toUtf8(), {0, 0, 0}, _bg); } if (_qtMessage) { painter->writeWithAttributes(terminal()->width() - 2, 0, "!!", _bg, {0xff, 0, 0}, Tui::ZTextAttribute::Bold | Tui::ZTextAttribute::Blink); } } editor-0.1.80/src/statusbar.h000066400000000000000000000044241477475121100160530ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef STATUSBAR_H #define STATUSBAR_H #include #include #include class GlobalKeyPressListener; class StatusBar : public Tui::ZWidget { Q_OBJECT public: StatusBar(Tui::ZWidget *parent); QString viewCursorPosition(); QString viewFileChanged(); QString viewMode(); QString viewModifiedFile(); QString viewOverwrite(); QString viewReadWrite(); QString viewSelectMode(); QString viewSelectCharsLines(); QString viewStandardInput(); QString viewLanguage(); void switchToNormalDisplay(); public: QSize sizeHint() const override; public slots: void cursorPosition(int x, int utf16CodeUnit, int utf8CodeUnit, int line); void scrollPosition(int x, int y); void setModified(bool modifiedFile); void readFromStandardInput(bool activ); void followStandardInput(bool follow); void setWritable(bool rw); void searchCount(int sc); void searchText(QString searchText); void searchVisible(bool visible); void crlfMode(bool msdos); void modifiedSelectMode(bool f4); void setSelectCharLines(int selectChar, int selectLines); void fileHasBeenChangedExternally(bool fileChanged = true); void overwrite(bool overwrite); void syntaxHighlightingEnabled(bool enable); void language(QString language); public: static void notifyQtLog(); protected: void paintEvent(Tui::ZPaintEvent *event); private: bool _showHelp = true; QDateTime _helpHoldOff = QDateTime::currentDateTimeUtc().addMSecs(100); GlobalKeyPressListener *_keyPressListener = nullptr; bool _modifiedFile = false; int _cursorPositionX = 0; int _utf8PositionX = 0; int _cursorPositionY = 0; int _scrollPositionX = 0; int _scrollPositionY = 0; bool _stdin = false; bool _follow = false; bool _readwrite = true; int _searchCount = -1; QString _searchText = ""; bool _searchVisible = false; bool _crlfMode = false; bool _selectMode = false; int _selectLines = 0; int _selectChars = 0; bool _fileChanged = false; bool _overwrite = false; QString _language = "None"; bool _syntaxHighlightingEnabled = false; Tui::ZColor _bg; static bool _qtMessage; }; #endif // STATUSBAR_H editor-0.1.80/src/syntaxhighlightdialog.cpp000066400000000000000000000062331477475121100207740ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "syntaxhighlightdialog.h" #include #include #include #include #include #include #ifdef SYNTAX_HIGHLIGHTING #include #include #endif static QStringList getAvailableLanguages () { QStringList availableLanguages; #ifdef SYNTAX_HIGHLIGHTING KSyntaxHighlighting::Repository repo; for (const auto &def : repo.definitions()) { availableLanguages.append(def.name()); } #endif return availableLanguages; } SyntaxHighlightDialog::SyntaxHighlightDialog(Tui::ZWidget *root) : Tui::ZDialog(root) { setOptions(Tui::ZWindow::CloseOption | Tui::ZWindow::DeleteOnClose | Tui::ZWindow::MoveOption | Tui::ZWindow::AutomaticOption | Tui::ZWindow::ResizeOption); setWindowTitle("Syntax Highlighting"); setContentsMargins({1, 1, 1, 1}); Tui::ZVBoxLayout *vbox = new Tui::ZVBoxLayout(); setLayout(vbox); vbox->setSpacing(1); Tui::ZHBoxLayout *hbox1 = new Tui::ZHBoxLayout(); Tui::ZLabel *labelSyntaxHighighting = new Tui::ZLabel(this); labelSyntaxHighighting->setText("Syntax Highlighting: "); hbox1->addWidget(labelSyntaxHighighting); _checkBoxOnOff = new Tui::ZCheckBox(this); _checkBoxOnOff->setFocus(); hbox1->addWidget(_checkBoxOnOff); vbox->add(hbox1); Tui::ZVBoxLayout *vbox1 = new Tui::ZVBoxLayout(); Tui::ZLabel *labelLanguage = new Tui::ZLabel(this); _lvLanguage = new Tui::ZListView(this); labelLanguage->setMarkup("Language: "); labelLanguage->setBuddy(_lvLanguage); vbox1->addWidget(labelLanguage); _lvLanguage->setMinimumSize({30, 11}); _availableLanguages = getAvailableLanguages(); _availableLanguages.sort(); _lvLanguage->setItems(_availableLanguages); vbox1->addWidget(_lvLanguage); vbox->add(vbox1); Tui::ZHBoxLayout *hbox4 = new Tui::ZHBoxLayout(); hbox4->addStretch(); Tui::ZButton *buttonClose = new Tui::ZButton(this); buttonClose->setText("Close"); buttonClose->setDefault(true); hbox4->addWidget(buttonClose); vbox->add(hbox4); QObject::connect(_checkBoxOnOff, &Tui::ZCheckBox::stateChanged, this, [this] { _enabled = (_checkBoxOnOff->checkState() == Tui::Checked); _lvLanguage->setEnabled(_enabled); settingsChanged(_enabled, _lang); }); QObject::connect(_lvLanguage, &Tui::ZListView::enterPressed, this, [this] { _lang = _lvLanguage->currentItem(); settingsChanged(_enabled, _lang); }); QObject::connect(buttonClose, &Tui::ZButton::clicked, this, [this] { deleteLater(); }); } void SyntaxHighlightDialog::updateSettings(bool enable, QString lang) { _enabled = enable; _lang = lang; if (_enabled) { _checkBoxOnOff->setCheckState(Tui::Checked); _lvLanguage->setEnabled(true); } else { _checkBoxOnOff->setCheckState(Tui::Unchecked); _lvLanguage->setEnabled(false); } int i = _availableLanguages.indexOf(_lang); _lvLanguage->setCurrentIndex(_lvLanguage->model()->index(i, 0)); } editor-0.1.80/src/syntaxhighlightdialog.h000066400000000000000000000012171477475121100204360ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef SYNTAXHIGHLIGHTDIALOG_H #define SYNTAXHIGHLIGHTDIALOG_H #include #include #include class SyntaxHighlightDialog : public Tui::ZDialog { public: Q_OBJECT public: explicit SyntaxHighlightDialog(Tui::ZWidget *root); void updateSettings(bool enable, QString lang); signals: void settingsChanged(bool enable, QString lang); private: Tui::ZCheckBox *_checkBoxOnOff = nullptr; Tui::ZListView *_lvLanguage = nullptr; bool _enabled = false; QString _lang; QStringList _availableLanguages; }; #endif // SYNTAXHIGHLIGHTDIALOG_H editor-0.1.80/src/tabdialog.cpp000066400000000000000000000074651477475121100163340ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "tabdialog.h" #include #include #include #include TabDialog::TabDialog(Tui::ZWidget *parent) : Tui::ZDialog(parent) { setOptions(Tui::ZWindow::CloseOption | Tui::ZWindow::MoveOption | Tui::ZWindow::AutomaticOption | Tui::ZWindow::DeleteOnClose); setFocus(); setWindowTitle("Tab settings"); setContentsMargins({1, 1, 1, 1}); Tui::ZVBoxLayout *vbox = new Tui::ZVBoxLayout(); setLayout(vbox); vbox->setSpacing(1); Tui::ZHBoxLayout *hbox1 = new Tui::ZHBoxLayout(); _tabRadioButton = new Tui::ZRadioButton(this); _tabRadioButton->setMarkup("Tab"); hbox1->addWidget(_tabRadioButton); _blankRadioButton = new Tui::ZRadioButton(this); _blankRadioButton->setMarkup("Spaces"); hbox1->addWidget(_blankRadioButton); vbox->add(hbox1); vbox->addStretch(); Tui::ZHBoxLayout *hbox2 = new Tui::ZHBoxLayout(); Tui::ZLabel *tabstopLable = new Tui::ZLabel(this); tabstopLable->setText("Tab Width: "); hbox2->addWidget(tabstopLable); _tabsizeInputBox = new Tui::ZInputBox(this); _tabsizeInputBox->setFocus(); _tabsizeInputBox->setEnabled(true); hbox2->addWidget(_tabsizeInputBox); vbox->add(hbox2); vbox->addStretch(); Tui::ZHBoxLayout *hbox3 = new Tui::ZHBoxLayout(); Tui::ZLabel *tabtospaceLabel = new Tui::ZLabel(this); tabtospaceLabel->setText("Tab to Space: "); hbox3->addWidget(tabtospaceLabel); Tui::ZButton *convertButton = new Tui::ZButton(this); convertButton->setText("Convert"); hbox3->addWidget(convertButton); vbox->add(hbox3); vbox->addStretch(); Tui::ZHBoxLayout *hbox4 = new Tui::ZHBoxLayout(); _eatSpaceBeforeTabBox = new Tui::ZCheckBox(this); _eatSpaceBeforeTabBox->setMarkup("eat space before tab"); hbox4->addWidget(_eatSpaceBeforeTabBox); vbox->add(hbox4); vbox->addStretch(); Tui::ZHBoxLayout *hbox5 = new Tui::ZHBoxLayout(); hbox5->addStretch(); Tui::ZButton *cancelButton = new Tui::ZButton(this); cancelButton->setText("Cancel"); hbox5->addWidget(cancelButton); Tui::ZButton *saveButton = new Tui::ZButton(this); saveButton->setText("Ok"); saveButton->setDefault(true); hbox5->addWidget(saveButton); vbox->add(hbox5); QObject::connect(convertButton, &Tui::ZButton::clicked, [this] { convert(!_blankRadioButton->checked(), _tabsizeInputBox->text().toInt()); deleteLater(); }); QObject::connect(saveButton, &Tui::ZButton::clicked, [this] { bool eat; if (_eatSpaceBeforeTabBox->checkState() == Qt::CheckState::Checked) { eat = true; } else { eat = false; } Q_EMIT settingsChanged(!_blankRadioButton->checked(), _tabsizeInputBox->text().toInt(), eat); deleteLater(); }); QObject::connect(_tabsizeInputBox, &Tui::ZInputBox::textChanged, [this, convertButton, saveButton] { if(_tabsizeInputBox->text().toInt() > 0 && _tabsizeInputBox->text().toInt() < 100) { convertButton->setEnabled(true); saveButton->setEnabled(true); } else { convertButton->setEnabled(false); saveButton->setEnabled(false); } }); QObject::connect(cancelButton, &Tui::ZButton::clicked, [this] { deleteLater(); }); } void TabDialog::updateSettings(bool useTabs, int indentSize, bool eatSpaceBeforeTabs) { _tabRadioButton->setChecked(useTabs); _blankRadioButton->setChecked(!useTabs); _tabsizeInputBox->setText(QString::number(indentSize)); if(eatSpaceBeforeTabs) { _eatSpaceBeforeTabBox->setCheckState(Qt::CheckState::Checked); } else { _eatSpaceBeforeTabBox->setCheckState(Qt::CheckState::Unchecked); } } editor-0.1.80/src/tabdialog.h000066400000000000000000000013431477475121100157660ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef TABDIALOG_H #define TABDIALOG_H #include #include #include #include class TabDialog : public Tui::ZDialog { Q_OBJECT public: TabDialog(Tui::ZWidget *parent); public: void updateSettings(bool useTabs, int indentSize, bool eatSpaceBeforeTabs); signals: void convert(bool useTabs, int indentSize); void settingsChanged(bool useTabs, int indentSize, bool eat); private: Tui::ZRadioButton *_tabRadioButton = nullptr; Tui::ZRadioButton *_blankRadioButton = nullptr; Tui::ZCheckBox *_eatSpaceBeforeTabBox = nullptr; Tui::ZInputBox *_tabsizeInputBox = nullptr; }; #endif // TABDIALOG_H editor-0.1.80/src/tests/000077500000000000000000000000001477475121100150305ustar00rootroot00000000000000editor-0.1.80/src/tests/attributes.cpp000066400000000000000000000044511477475121100177260ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "catchwrapper.h" #include "../attributes.h" #include #include #include #include #include const QString attributesFileName = "attributes.file"; void cleanAttributesFileName() { QFile *file = new QFile(attributesFileName); file->remove(); } TEST_CASE("attributes") { SECTION("empty") { Attributes *a = new Attributes(""); CHECK(a->attributesFile() == ""); } SECTION("attributes-in-out") { QTemporaryDir dir; QString file; QList list; list.append(0); list.append(1); list.append(65535); struct TestCase { QString filename; int cursorPositionX; int cursorPositionY; int scrollPositionColumn; int scrollPositionLine; int scrollPositionFineLine; }; auto testCase = GENERATE( TestCase{"/file1", 0, 0, 0, 0, 0}, TestCase{"/file2", 1, 2, 3, 4, 5}, TestCase{"/file3", 65535, 65534, 65533, 65532, 65531}, TestCase{"/file1", 65535, 65535, 65535, 65535, 65535} ); file = dir.path() + testCase.filename; QFile datei(file); if (datei.open(QIODevice::WriteOnly)) { datei.close(); } Attributes *a = new Attributes(attributesFileName); a->writeAttributes(file, {testCase.cursorPositionX, testCase.cursorPositionY}, testCase.scrollPositionColumn, testCase.scrollPositionLine, testCase.scrollPositionFineLine, list); CHECK(a->getAttributesCursorPosition(file).codeUnit == testCase.cursorPositionX); CHECK(a->getAttributesCursorPosition(file).line == testCase.cursorPositionY); CHECK(a->getAttributesScrollCol(file) == testCase.scrollPositionColumn); CHECK(a->getAttributesScrollLine(file) == testCase.scrollPositionLine); CHECK(a->getAttributesScrollFine(file) == testCase.scrollPositionFineLine); CHECK(a->getAttributesLineMarker(file) == list); cleanAttributesFileName(); } } editor-0.1.80/src/tests/catchwrapper.h000066400000000000000000000072421477475121100176710ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef CATCHWRAPPER_H #define CATCHWRAPPER_H #include #include #include #include #ifdef CATCH3 #include using Catch::Approx; #else #include #endif #include "../filecategorize.h" namespace Catch { template<> struct StringMaker { static std::string convert(QString const& value) { return "\"" + value.toStdString() + "\""; } }; template<> struct StringMaker { static std::string convert(QRect const& value) { return QStringLiteral("(x: %0, y: %1, w: %2, h: %3)").arg( QString::number(value.x()), QString::number(value.y()), QString::number(value.width()), QString::number(value.height())).toStdString(); } }; template<> struct StringMaker { static std::string convert(QPoint const& value) { return QStringLiteral("(x: %0, y: %1)").arg( QString::number(value.x()), QString::number(value.y())).toStdString(); } }; template<> struct StringMaker { static std::string convert(Tui::ZDocumentCursor::Position const& value) { return QStringLiteral("(codeUnit: %0, line: %1)").arg( QString::number(value.codeUnit), QString::number(value.line)).toStdString(); } }; template<> struct StringMaker { static std::string convert(Tui::ZColor const& value) { if (value.colorType() == Tui::ZColor::RGB) { return QStringLiteral("(color: rgb(%0, %1, %2))").arg(QString::number(value.red()), QString::number(value.green()), QString::number(value.blue())).toStdString(); } else if (value.colorType() == Tui::ZColor::Default) { return "(color: default)"; } else if (value.colorType() == Tui::ZColor::Terminal) { return QStringLiteral("(color: %0)").arg(QString::number(static_cast(value.terminalColor()))).toStdString(); } else if (value.colorType() == Tui::ZColor::TerminalIndexed) { return QStringLiteral("(color indexed: %0)").arg(QString::number(value.terminalColorIndexed())).toStdString(); } else { return "(invalid color)"; } } }; template<> struct StringMaker { static std::string convert(FileCategory const& value) { if (value == FileCategory::dir) { return "dir"; } else if (value == FileCategory::stdin_file) { return "stdin_file"; } else if (value == FileCategory::new_file) { return "new_file"; } else if (value == FileCategory::open_file) { return "open_file"; } else if (value == FileCategory::invalid_error) { return "invalid_error"; } else if (value == FileCategory::invalid_filetype) { return "invalid_filetype"; } else if (value == FileCategory::invalid_dir_not_exist) { return "invalid_dir_not_exist"; } else if (value == FileCategory::invalid_dir_not_writable) { return "invalid_dir_not_writable"; } return "not exist"; } }; } #endif editor-0.1.80/src/tests/eventrecorder.cpp000066400000000000000000000200021477475121100203750ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "eventrecorder.h" #include #include #include RecorderEvent EventRecorder::watchEvent(QObject *o, std::string name, std::function, const QEvent *)> translator) { auto event = std::make_shared(); event->name = name; if (o->objectName().size()) { event->name += " on " + o->objectName().toStdString(); } auto it = registeredQObjects.find(o); if (it == registeredQObjects.end()) { o->installEventFilter(this); registeredQObjects[o]; } registeredQObjects[o].emplace_back(translator, event); QObject::connect(o, &QObject::destroyed, this, [this, o] { o->removeEventFilter(this); registeredQObjects.erase(o); }); return event; } RecorderEvent EventRecorder::watchCloseEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *ev) { if (ev->type() == Tui::ZEventType::close()) { auto &event = dynamic_cast(*ev); recordEvent(eventRef, event.skipChecks()); } }); } RecorderEvent EventRecorder::watchEnabledChangeEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *event) { if (event->type() == QEvent::EnabledChange) { recordEvent(eventRef); } }); } RecorderEvent EventRecorder::watchFocusInEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *ev) { if (ev->type() == Tui::ZEventType::focusIn()) { auto &event = dynamic_cast(*ev); recordEvent(eventRef, event.reason()); } }); } RecorderEvent EventRecorder::watchFocusOutEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *ev) { if (ev->type() == Tui::ZEventType::focusOut()) { auto &event = dynamic_cast(*ev); recordEvent(eventRef, event.reason()); } }); } RecorderEvent EventRecorder::watchHideEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *event) { if (event->type() == Tui::ZEventType::hide()) { recordEvent(eventRef); } }); } RecorderEvent EventRecorder::watchHideToParentEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *event) { if (event->type() == QEvent::HideToParent) { recordEvent(eventRef); } }); } RecorderEvent EventRecorder::watchKeyEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *ev) { if (ev->type() == Tui::ZEventType::key()) { auto &event = dynamic_cast(*ev); recordEvent(eventRef, event.key(), event.text(), event.modifiers()); } }); } RecorderEvent EventRecorder::watchLayoutRequestEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *event) { if (event->type() == QEvent::LayoutRequest) { recordEvent(eventRef); } }); } RecorderEvent EventRecorder::watchMoveEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *ev) { if (ev->type() == Tui::ZEventType::move()) { auto &event = dynamic_cast(*ev); recordEvent(eventRef, event.pos(), event.oldPos()); } }); } RecorderEvent EventRecorder::watchPasteEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *ev) { if (ev->type() == Tui::ZEventType::paste()) { auto &event = dynamic_cast(*ev); recordEvent(eventRef, event.text()); } }); } RecorderEvent EventRecorder::watchPendingRawSequenceEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *ev) { if (ev->type() == Tui::ZEventType::pendingRawSequence()) { auto &event = dynamic_cast(*ev); recordEvent(eventRef, event.sequence()); } }); } RecorderEvent EventRecorder::watchQueryAcceptsEnterEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *event) { if (event->type() == Tui::ZEventType::queryAcceptsEnter()) { recordEvent(eventRef); } }); } RecorderEvent EventRecorder::watchRawSequenceEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *ev) { if (ev->type() == Tui::ZEventType::rawSequence()) { auto &event = dynamic_cast(*ev); recordEvent(eventRef, event.sequence()); } }); } RecorderEvent EventRecorder::watchResizeEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *ev) { if (ev->type() == Tui::ZEventType::resize()) { auto &event = dynamic_cast(*ev); recordEvent(eventRef, event.size(), event.oldSize()); } }); } RecorderEvent EventRecorder::watchShowEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *event) { if (event->type() == Tui::ZEventType::show()) { recordEvent(eventRef); } }); } RecorderEvent EventRecorder::watchShowToParentEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *event) { if (event->type() == QEvent::ShowToParent) { recordEvent(eventRef); } }); } RecorderEvent EventRecorder::watchTerminalChangeEvent(QObject *o, std::string name) { return watchEvent(o, name, [this](std::shared_ptr eventRef, const QEvent *ev) { if (ev->type() == Tui::ZEventType::terminalChange()) { recordEvent(eventRef); } }); } RecorderEvent EventRecorder::createEvent(const std::string &name) { auto event = std::make_shared(); event->name = name; return event; } bool EventRecorder::noMoreEvents() { if (!records.empty()) { UNSCOPED_INFO("Next event would be " << records.front().event->name); } return records.empty(); } bool EventRecorder::eventFilter(QObject *watched, QEvent *event) { auto it = registeredQObjects.find(watched); if (it != registeredQObjects.end()) { auto &vec = it->second; for (auto &item: vec) { std::get<0>(item)(std::get<1>(item), event); } } return false; } void EventRecorder::waitForEvent(std::shared_ptr event) { for (const auto &record: records) { if (record.event == event) { return; } } _waitingEvent = event; QElapsedTimer timer; timer.start(); while (_waitingEvent != nullptr) { if (timer.hasExpired(10000)) { FAIL_CHECK("EventRecorder::waitForEvent: Timeout"); return; } QCoreApplication::processEvents(QEventLoop::AllEvents); } } void EventRecorder::clearEvents() { records.clear(); } editor-0.1.80/src/tests/eventrecorder.h000066400000000000000000000132631477475121100200550ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef EVENTRECORDER_H #define EVENTRECORDER_H #include #include #include #include #include #include #include "catchwrapper.h" #include #define RECORDER_SIGNAL(signal) signal, std::string(#signal).substr(1) class EventRecorder : public QObject { public: class RecorderEvent { public: std::string name; }; public: template std::shared_ptr watchSignal(const typename QtPrivate::FunctionPointer::Object *sender, SIGNAL signal, std::string name) { auto event = std::make_shared(); event->name = "Signal: " + name; if (sender->objectName().size()) { event->name += " on " + sender->objectName().toStdString(); } QObject::connect(sender, signal, this, [this, event](auto... arguments) { std::vector args; (args.emplace_back(arguments), ...); records.push_back(Record{event, std::move(args)}); if (event == _waitingEvent) { _waitingEvent = nullptr; } }); return event; } std::shared_ptr watchEvent(QObject *o, std::string name, std::function, const QEvent*)> translator); std::shared_ptr watchCloseEvent(QObject *o, std::string name); std::shared_ptr watchEnabledChangeEvent(QObject *o, std::string name); std::shared_ptr watchFocusInEvent(QObject *o, std::string name); std::shared_ptr watchFocusOutEvent(QObject *o, std::string name); std::shared_ptr watchHideEvent(QObject *o, std::string name); std::shared_ptr watchHideToParentEvent(QObject *o, std::string name); std::shared_ptr watchKeyEvent(QObject *o, std::string name); std::shared_ptr watchLayoutRequestEvent(QObject *o, std::string name); std::shared_ptr watchMoveEvent(QObject *o, std::string name); std::shared_ptr watchPasteEvent(QObject *o, std::string name); std::shared_ptr watchPendingRawSequenceEvent(QObject *o, std::string name); std::shared_ptr watchQueryAcceptsEnterEvent(QObject *o, std::string name); std::shared_ptr watchRawSequenceEvent(QObject *o, std::string name); std::shared_ptr watchResizeEvent(QObject *o, std::string name); std::shared_ptr watchShowEvent(QObject *o, std::string name); std::shared_ptr watchShowToParentEvent(QObject *o, std::string name); std::shared_ptr watchTerminalChangeEvent(QObject *o, std::string name); std::shared_ptr createEvent(const std::string &name); template void recordEvent(std::shared_ptr event, ARGUMENTS... arguments) { std::vector args; (args.emplace_back(arguments), ...); records.push_back(Record{ event, std::move(args)}); if (event == _waitingEvent) { _waitingEvent = nullptr; } } template [[nodiscard]] bool consumeFirst(std::shared_ptr event, ARGS... args) { if (!records.size()) { UNSCOPED_INFO("No more events recorded"); return false; } auto actualEvent = records[0].event; auto actualArgs = records[0].args; records.erase(records.begin()); if (event != actualEvent) { std::string expectedName = event->name; UNSCOPED_INFO("Event does not match. Called was " << actualEvent->name << " expected was " << expectedName); return false; } return checkArgs(0, actualArgs, args...); } [[nodiscard]] bool noMoreEvents(); void waitForEvent(std::shared_ptr event); void clearEvents(); bool eventFilter(QObject *watched, QEvent *event) override; protected: bool checkArgs(size_t idx, std::vector actual) { (void)idx; (void)actual; // end of recursion return true; } template bool checkArgs(size_t idx, const std::vector &actualArgs, T expected, ARGS... expectedRest) { if (idx >= actualArgs.size()) { UNSCOPED_INFO("More arguments specified than available"); return false; } bool ok = true; if (actualArgs[idx].type() == typeid(expected)) { if (std::any_cast(actualArgs[idx]) != expected) { ok = false; UNSCOPED_INFO("argument " << idx + 1 << " does not match expected value"); auto actual = std::any_cast(actualArgs[idx]); CHECK(actual == expected); } } else { UNSCOPED_INFO("Argument type mismatch on argument " << idx + 1 << " actual type " << actualArgs[idx].type().name()); ok = false; } return ok && checkArgs(idx + 1, actualArgs, expectedRest...); } struct Record { std::shared_ptr event; std::vector args; }; std::vector records; std::map, const QEvent*)>, std::shared_ptr>>> registeredQObjects; std::shared_ptr _waitingEvent; }; using RecorderEvent = std::shared_ptr; #endif // EVENTRECORDER_H editor-0.1.80/src/tests/filelistparsertests.cpp000066400000000000000000000142761477475121100216610ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "catchwrapper.h" #include #include "../filelistparser.h" TEST_CASE("parseFileList") { SECTION("empty") { QStringList args = {""}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 1); CHECK(fle[0].fileName == ""); CHECK(fle[0].pos == ""); } SECTION("datei") { QStringList args = {"datei"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 1); CHECK(fle[0].fileName == "datei"); CHECK(fle[0].pos == ""); } SECTION("+pos datei") { QStringList args = {"+0","dateiA"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 1); CHECK(fle[0].fileName == "dateiA"); CHECK(fle[0].pos == "+0"); } SECTION("dateiA dateiB") { QStringList args = {"dateiA","dateiB"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 2); CHECK(fle[0].fileName == "dateiA"); CHECK(fle[0].pos == ""); CHECK(fle[1].fileName == "dateiB"); CHECK(fle[1].pos == ""); } SECTION("+pos dateiA +pos dateiB") { QStringList args = {"+0","dateiA","+55","dateiB"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 2); CHECK(fle[0].fileName == "dateiA"); CHECK(fle[0].pos == "+0"); CHECK(fle[1].fileName == "dateiB"); CHECK(fle[1].pos == "+55"); } SECTION("+pos dateiA dateiB") { QStringList args = {"+0","dateiA","dateiB"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 2); CHECK(fle[0].fileName == "dateiA"); CHECK(fle[0].pos == "+0"); CHECK(fle[1].fileName == "dateiB"); CHECK(fle[1].pos == ""); } SECTION("dateiA +pos dateiB") { QStringList args = {"dateiA","+0","dateiB"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 2); CHECK(fle[0].fileName == "dateiA"); CHECK(fle[0].pos == ""); CHECK(fle[1].fileName == "dateiB"); CHECK(fle[1].pos == "+0"); } SECTION("dateiA +pos") { QStringList args = {"dateiA","+0",}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 1); CHECK(fle[0].fileName == "dateiA"); CHECK(fle[0].pos == "+0"); } SECTION("dateiA dateiB +pos") { QStringList args = {"dateiA","dateiB","+0"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 2); CHECK(fle[0].fileName == "dateiA"); CHECK(fle[0].pos == ""); CHECK(fle[1].fileName == "dateiB"); CHECK(fle[1].pos == "+0"); } SECTION("+/search") { QStringList args = {"+/test"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 0); } SECTION("+/search dateiA") { QStringList args = {"+/test", "dateiA"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 1); CHECK(fle[0].fileName == "dateiA"); CHECK(fle[0].pos == ""); CHECK(fle[0].search == "test"); } SECTION("+/search dateiA +pos") { QStringList args = {"+/test", "dateiA", "+1,1"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 1); CHECK(fle[0].fileName == "dateiA"); CHECK(fle[0].pos == "+1,1"); CHECK(fle[0].search == "test"); } SECTION("+/searchA dateiA +/searchB dateiB") { QStringList args = {"+/testA", "dateiA", "+/testB", "dateiB"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 2); CHECK(fle[0].fileName == "dateiA"); CHECK(fle[0].pos == ""); CHECK(fle[0].search == "testA"); CHECK(fle[1].fileName == "dateiB"); CHECK(fle[1].pos == ""); CHECK(fle[1].search == "testB"); } SECTION("+/searchA +posA dateiA +posB +/searchB dateiB") { QStringList args = {"+/testA", "+1,1", "dateiA", "+2,2", "+/testB", "dateiB"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 2); CHECK(fle[0].fileName == "dateiA"); CHECK(fle[0].pos == "+1,1"); CHECK(fle[0].search == "testA"); CHECK(fle[1].fileName == "dateiB"); CHECK(fle[1].pos == "+2,2"); CHECK(fle[1].search == "testB"); } // ERROR SECTION("+pos") { QStringList args = {"+0"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 0); } SECTION("datei +pos datei +pos") { QStringList args = {"dateiA","+0","dateiB","+0"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 0); } SECTION("+pos +pos datei datei") { QStringList args = {"+0","+0","dateiA","dateiB"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 0); } SECTION("datei datei +pos +pos") { QStringList args = {"dateiA","dateiB","+0","+0"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 0); } SECTION("dateiA +pos +pos dateiB") { QStringList args = {"dateiA", "+0", "+0", "dateiB"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 0); } SECTION("dateiA dateiB +pos +pos") { QStringList args = {"dateiA", "dateiB", "+0", "+0"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 0); } SECTION("dateiA +search +search dateiB") { QStringList args = {"dateiA", "+/testA", "+/testB", "dateiB"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 0); } SECTION("dateiA dateiB +search +search") { QStringList args = {"dateiA", "dateiB", "+/testA", "+/testB"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 0); } SECTION("+search dateiA +search") { QStringList args = {"+/testA", "dateiA", "+/testB"}; QVector fle = parseFileList(args); REQUIRE(fle.size() == 0); } } editor-0.1.80/src/tests/fileopentests.cpp000066400000000000000000000123311477475121100204200ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "catchwrapper.h" #include "../filecategorize.h" #include #include #include #include #include #include TEST_CASE("FileOpen") { auto slash = GENERATE("", "/"); //TEMP DIR QTemporaryDir dir; QDir tmpd(dir.path()); //RW QString dirRW = dir.path() + "/rw"; tmpd.mkdir("rw"); QFile tmpf(dirRW + "/filerw"); QString fileRW = tmpf.fileName(); tmpf.open(QIODevice::WriteOnly); tmpf.close(); QString fileRWNE = dirRW + "/filenotexist"; QString dirRWNE = dirRW + "/dirnotexist"; //RO tmpd.mkdir("ro"); QString dirRO = dir.path() + "/ro"; QString fileRO = dirRO + "/filero"; tmpf.setFileName(fileRO); tmpf.open(QIODevice::WriteOnly); tmpf.close(); //TODO: root with 0600 //QString fileNotR = dirRO + "/fileNotR"; //tmpf.setFileName(fileNotR); //tmpf.open(QIODevice::NotOpen); //tmpf.close(); QString fileRONE = dirRO + "/filenotexist"; QString dirRONE = dirRO + "/dirnotexist"; chmod(fileRO.toUtf8().data(), 0400); chmod(dirRO.toUtf8().data(), 0500); // Datei //RW CHECK(fileCategorize(fileRW) == FileCategory::open_file); //RO CHECK(fileCategorize(fileRO) == FileCategory::open_file); //NOT R //CHECK(fileCategorize(fileNotR) == FileCategory::invalid_file_not_readable); //not exist RW dir CHECK(fileCategorize(fileRWNE) == FileCategory::new_file); //not exist RO dir /chr if ( geteuid() ) { CHECK(fileCategorize(fileRONE) == FileCategory::invalid_dir_not_writable); } //if not a regular file CHECK(fileCategorize("/dev/null") == FileCategory::invalid_filetype); //file open with slash: test/ //CHECK(fileCategorize(fileRW + "/") == FileCategory::invalid_dir_not_exist); // Dir //dir CHECK(fileCategorize(dirRW + slash) == FileCategory::dir); CHECK(fileCategorize(dirRO + slash) == FileCategory::dir); //not exist /chr/chr /chr/chr/ CHECK(fileCategorize(dirRWNE + "/") == FileCategory::invalid_dir_not_exist); CHECK(fileCategorize(dirRONE + "/") == FileCategory::invalid_dir_not_exist); // Symlink { auto inrow = GENERATE("/ro", "/rw"); chmod(dirRO.toUtf8().data(), 0700); QString fileLinkRW = dir.path() + inrow + "/linkRW"; QFile::link(fileRW, fileLinkRW); QString fileLinkRO = dir.path() + inrow + "/linkRO"; QFile::link(fileRO, fileLinkRO); QString fileLinkRWNE = dir.path() + inrow + "/linkRWNE"; QFile::link(fileRWNE, fileLinkRWNE); QString fileLinkRONE = dir.path() + inrow + "/linkRONE"; QFile::link(fileRONE, fileLinkRONE); QString dirLinkRW = dir.path() + inrow + "/dirlinkRW"; QFile::link(dirRW, dirLinkRW); QString dirLinkRO = dir.path() + inrow + "/dirlinkRO"; QFile::link(dirRO, dirLinkRO); QString dirLinkRWNE = dir.path() + inrow + "/dirlinkRWNE"; QFile::link(dirRWNE, dirLinkRWNE); QString dirLinkRONE = dir.path() + inrow + "/dirlinkRONE"; QFile::link(dirRONE, dirLinkRONE); QString dirLinkNE = dir.path() + inrow + "/dirlinkNE"; QFile::link(dir.path() + inrow +"/NE/file", dirLinkNE); QString self = dir.path() + inrow + "/self"; QFile::link(self, self); chmod(dirRO.toUtf8().data(), 0500); //to file RW CHECK(fileCategorize(fileLinkRW) == FileCategory::open_file); //to file RO CHECK(fileCategorize(fileLinkRO) == FileCategory::open_file); //to dir //CAPTURE((dirLinkRW + slash).toUtf8().data()); CHECK(fileCategorize(dirLinkRW + slash) == FileCategory::dir); CHECK(fileCategorize(dirLinkRO + slash) == FileCategory::dir); CHECK(fileCategorize(dirLinkRWNE + "/") == FileCategory::invalid_dir_not_exist); CHECK(fileCategorize(dirLinkRONE + "/") == FileCategory::invalid_dir_not_exist); //to not exist file CHECK(fileCategorize(fileLinkRWNE) == FileCategory::new_file); if ( geteuid() ) { CHECK(fileCategorize(fileLinkRONE) == FileCategory::invalid_dir_not_writable); } //to not exist dir CHECK(fileCategorize(dirLinkNE) == FileCategory::invalid_dir_not_exist); //to self CHECK(fileCategorize(self) == FileCategory::invalid_error); QFileInfo testSelf(self + "/"); if (testSelf.isSymLink()) { // MacOS implements lstat in a way that considers this path to be a symlink (instead of an error with ELOOP) // It's ok that fileCategorize reflects this os specific difference. CHECK(fileCategorize(self + "/") == FileCategory::invalid_error); } else { CHECK(fileCategorize(self + "/") == FileCategory::invalid_dir_not_exist); } CHECK(fileCategorize(self + "/notexist") == FileCategory::invalid_dir_not_exist); } // From STDIN CHECK(fileCategorize("-") == FileCategory::stdin_file); // empty CHECK(fileCategorize("") == FileCategory::new_file); // cleanup chmod(dirRO.toUtf8().data(), 0700); chmod(fileRO.toUtf8().data(), 0600); } TEST_CASE("FileChange") { // RO // RW // change // dir delete } editor-0.1.80/src/tests/filesavetests.cpp000066400000000000000000000000771477475121100204210ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "catchwrapper.h" editor-0.1.80/src/tests/filetests.cpp000066400000000000000000001702161477475121100175450ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "catchwrapper.h" #include #include #include #include #include #include #include #include "../file.h" #include "eventrecorder.h" class DocumentTestHelper { public: Tui::ZDocument &getDoc(File *f) { return *f->document(); } void f3(bool backward, Tui::ZTerminal *terminal, File *f) { bool t = GENERATE(true, false); CAPTURE(t); if (t) { if (backward) { Tui::ZTest::sendKey(terminal, Qt::Key_F3, Qt::KeyboardModifier::ShiftModifier); } else { Tui::ZTest::sendKey(terminal, Qt::Key_F3, Qt::KeyboardModifier::NoModifier); } } else { f->runSearch(backward); } } }; TEST_CASE("file") { Tui::ZTerminal::OffScreen of(80, 24); Tui::ZTerminal terminal(of); Tui::ZRoot root; Tui::ZWindow *w = new Tui::ZWindow(&root); terminal.setMainWidget(&root); w->setGeometry({0, 0, 80, 24}); File *f = new File(terminal.textMetrics(), w); f->setFocus(); f->setGeometry({0, 0, 80, 24}); DocumentTestHelper t; Tui::ZDocument &doc = t.getDoc(f); Tui::ZDocumentCursor cursor{&doc, [&terminal,&doc](int line, bool wrappingAllowed) { (void)wrappingAllowed; Tui::ZTextLayout lay(terminal.textMetrics(), doc.line(line)); lay.doLayout(65000); return lay; } }; //OHNE TEXT CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); auto testCase = GENERATE(as{}, Qt::KeyboardModifier::NoModifier, Qt::KeyboardModifier::AltModifier, Qt::KeyboardModifier::MetaModifier, Qt::KeyboardModifier::ShiftModifier, Qt::KeyboardModifier::KeypadModifier, Qt::KeyboardModifier::ControlModifier, Qt::KeyboardModifier::GroupSwitchModifier, Qt::KeyboardModifier::KeyboardModifierMask, Qt::KeyboardModifier::ShiftModifier | Qt::KeyboardModifier::ControlModifier //Qt::KeyboardModifier::ShiftModifier | Qt::KeyboardModifier::AltModifier //TODO: multi select //Qt::KeyboardModifier::ShiftModifier | Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ControlModifier ); CAPTURE(testCase); SECTION("key-left") { Tui::ZTest::sendKey(&terminal, Qt::Key_Left, testCase); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); } SECTION("key-right") { Tui::ZTest::sendKey(&terminal, Qt::Key_Right, testCase); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); } SECTION("key-up") { Tui::ZTest::sendKey(&terminal, Qt::Key_Up, testCase); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); } SECTION("key-down") { Tui::ZTest::sendKey(&terminal, Qt::Key_Down, testCase); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); } SECTION("key-home") { Tui::ZTest::sendKey(&terminal, Qt::Key_Home, testCase); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); Tui::ZTest::sendKey(&terminal, Qt::Key_Home, testCase); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); } SECTION("key-end") { Tui::ZTest::sendKey(&terminal, Qt::Key_End, testCase); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); } SECTION("key-page-up") { Tui::ZTest::sendKey(&terminal, Qt::Key_PageUp, testCase); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); } SECTION("key-page-down") { Tui::ZTest::sendKey(&terminal, Qt::Key_PageDown, testCase); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); } SECTION("key-enter") { Tui::ZTest::sendKey(&terminal, Qt::Key_Enter, testCase); if (Qt::KeyboardModifier::NoModifier == testCase || Qt::KeyboardModifier::KeypadModifier == testCase) { CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,1}); } else { CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); } } SECTION("key-tab-space") { f->setUseTabChar(false); Tui::ZTest::sendKey(&terminal, Qt::Key_Tab, testCase); if (Qt::KeyboardModifier::NoModifier == testCase) { CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,0}); CHECK(doc.line(0) == " "); } else { CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); } } SECTION("key-tab") { f->setUseTabChar(true); Tui::ZTest::sendKey(&terminal, Qt::Key_Tab, testCase); if (Qt::KeyboardModifier::NoModifier == testCase) { CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,0}); CHECK(doc.line(0) == "\t"); } else { CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); } } SECTION("key-insert") { Tui::ZTest::sendKey(&terminal, Qt::Key_Insert, testCase); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); } SECTION("key-delete") { Tui::ZTest::sendKey(&terminal, Qt::Key_Delete, testCase); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); } SECTION("key-backspace") { Tui::ZTest::sendKey(&terminal, Qt::Key_Backspace, testCase); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); } SECTION("key-escape") { Tui::ZTest::sendKey(&terminal, Qt::Key_Escape, testCase); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); } } TEST_CASE("file-getseter") { Tui::ZTerminal::OffScreen of(80, 24); Tui::ZTerminal terminal(of); Tui::ZRoot root; Tui::ZWindow *w = new Tui::ZWindow(&root); terminal.setMainWidget(&root); w->setGeometry({0, 0, 80, 24}); File *f = new File(terminal.textMetrics(), w); DocumentTestHelper t; Tui::ZDocument &doc = t.getDoc(f); Tui::ZDocumentCursor cursor{&doc, [&terminal,&doc](int line, bool wrappingAllowed) { (void)wrappingAllowed; Tui::ZTextLayout lay(terminal.textMetrics(), doc.line(line)); lay.doLayout(65000); return lay; } }; CHECK(f->getFilename() == ""); CHECK(f->tabStopDistance() == 8); //default? CHECK(f->useTabChar() == false); CHECK(f->eatSpaceBeforeTabs() == true); CHECK(f->formattingCharacters() == true); CHECK(f->colorTabs() == true); //default? CHECK(f->colorTrailingSpaces() == true); //default? CHECK(f->selectedText() == ""); CHECK(f->hasSelection() == false); CHECK(f->overwriteMode() == false); CHECK(f->isModified() == false); CHECK(f->highlightBracket() == false); CHECK(f->attributesFile() == ""); CHECK(f->document()->crLfMode() == false); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->scrollPositionLine() == 0); CHECK(f->scrollPositionColumn() == 0); CHECK(f->rightMarginHint() == 0); CHECK(f->isNewFile() == false); //default? SECTION("getFilename") { f->setFilename("test"); //TODO: set path //CHECK(f->getFilename() == "test"); } SECTION("gotoline") { QString str = GENERATE("+", ""); f->insertText("123\n123\n123"); f->gotoLine(str + "0,0"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); f->gotoLine(str + "0,-1"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); f->gotoLine(str + "-1,0"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); f->gotoLine(str + "1,1"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); f->gotoLine(str + "2,2"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,1}); f->gotoLine(str + "3,3"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,2}); f->gotoLine(str + "4,4"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{3,2}); f->gotoLine(str + "6500000,6500000"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{3,2}); f->gotoLine(str + "1"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); f->gotoLine(str + "2"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,1}); f->gotoLine(str + "3"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,2}); f->gotoLine(str + "4"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,2}); } SECTION("tabsize") { for(int i = -1; i <= 10; i++) { CAPTURE(i); f->setTabStopDistance(i); CHECK(f->tabStopDistance() == std::max(1,i)); } } SECTION("setTabOption") { f->setUseTabChar(true); CHECK(f->useTabChar() == true); f->setUseTabChar(false); CHECK(f->useTabChar() == false); } SECTION("setEatSpaceBeforeTabs") { f->setEatSpaceBeforeTabs(true); CHECK(f->eatSpaceBeforeTabs() == true); f->setEatSpaceBeforeTabs(false); CHECK(f->eatSpaceBeforeTabs() == false); } SECTION("setFormattingCharacters") { f->setFormattingCharacters(true); CHECK(f->formattingCharacters() == true); f->setFormattingCharacters(false); CHECK(f->formattingCharacters() == false); } SECTION("colorTabs") { f->setColorTabs(true); CHECK(f->colorTabs() == true); f->setColorTabs(false); CHECK(f->colorTabs() == false); } SECTION("colorSpaceEnd") { f->setColorTrailingSpaces(true); CHECK(f->colorTrailingSpaces() == true); f->setColorTrailingSpaces(false); CHECK(f->colorTrailingSpaces() == false); } SECTION("colorSpaceEnd") { Tui::ZTextOption::WrapMode wrap = GENERATE(Tui::ZTextOption::WrapMode::NoWrap, Tui::ZTextOption::WrapMode::WordWrap, Tui::ZTextOption::WrapMode::WrapAnywhere); f->setWordWrapMode(wrap); CHECK(f->wordWrapMode() == wrap); } SECTION("overwrite") { f->toggleOverwriteMode(); CHECK(f->overwriteMode() == true); f->toggleOverwriteMode(); CHECK(f->overwriteMode() == false); } SECTION("crLfMode") { f->document()->setCrLfMode(true); CHECK(f->document()->crLfMode() == true); f->document()->setCrLfMode(false); CHECK(f->document()->crLfMode() == false); //TODO check output file } SECTION("rightMarginHint") { for(int i = -1; i <= 10; i++) { CAPTURE(i); f->setRightMarginHint(i); CHECK(f->rightMarginHint() == std::max(0,i)); } } } TEST_CASE("actions") { Tui::ZTerminal::OffScreen of(80, 24); Tui::ZTerminal terminal(of); Tui::ZRoot root; Tui::ZWindow *w = new Tui::ZWindow(&root); terminal.setMainWidget(&root); w->setGeometry({0, 0, 80, 24}); File *f = new File(terminal.textMetrics(), w); DocumentTestHelper t; Tui::ZDocument &doc = t.getDoc(f); Tui::ZDocumentCursor cursor{&doc, [&terminal,&doc](int line, bool wrappingAllowed) { (void)wrappingAllowed; Tui::ZTextLayout lay(terminal.textMetrics(), doc.line(line)); lay.doLayout(65000); return lay; } }; f->setFocus(); f->setGeometry({0, 0, 80, 24}); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); Tui::ZTest::sendText(&terminal, " text", Qt::KeyboardModifier::NoModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Enter, Qt::KeyboardModifier::NoModifier); Tui::ZTest::sendText(&terminal, " new1", Qt::KeyboardModifier::NoModifier); CHECK(doc.lineCount() == 2); SECTION("acv") { Tui::ZTest::sendText(&terminal, "a", Qt::KeyboardModifier::ControlModifier); Tui::ZTest::sendText(&terminal, "c", Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); CHECK(doc.lineCount() == 2); Tui::ZTest::sendText(&terminal, "v", Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); CHECK(doc.lineCount() == 2); } SECTION("acv") { Tui::ZTest::sendText(&terminal, "a", Qt::KeyboardModifier::ControlModifier); Tui::ZTest::sendText(&terminal, "c", Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); CHECK(doc.lineCount() == 2); Tui::ZTest::sendText(&terminal, "v", Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); CHECK(doc.lineCount() == 2); } SECTION("ac-right-v") { Tui::ZTest::sendText(&terminal, "a", Qt::KeyboardModifier::ControlModifier); Tui::ZTest::sendText(&terminal, "c", Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); CHECK(doc.lineCount() == 2); CHECK(root.findFacet()->contents() == " text\n new1"); Tui::ZTest::sendKey(&terminal, Tui::Key_Right, Qt::KeyboardModifier::NoModifier); Tui::ZTest::sendText(&terminal, "v", Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,2}); CHECK(doc.lineCount() == 3); } SECTION("axv") { Tui::ZTest::sendText(&terminal, "a", Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); CHECK(f->hasSelection() == true); Tui::ZTest::sendText(&terminal, "x", Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); Tui::ZTest::sendText(&terminal, "v", Qt::KeyboardModifier::ControlModifier); CHECK(f->hasSelection() == false); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); } SECTION("cv-newline") { CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); Tui::ZTest::sendKey(&terminal, Tui::Key_Up, Qt::KeyboardModifier::NoModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,0}); Tui::ZTest::sendKey(&terminal, Tui::Key_Right, Qt::KeyboardModifier::ShiftModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,1}); Tui::ZTest::sendText(&terminal, "c", Qt::KeyboardModifier::ControlModifier); CHECK(f->hasSelection() == true); CHECK(root.findFacet()->contents() == "\n"); CHECK(doc.lineCount() == 2); Tui::ZTest::sendText(&terminal, "v", Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,1}); CHECK(doc.lineCount() == 2); CHECK(f->hasSelection() == false); Tui::ZTest::sendText(&terminal, "v", Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,2}); CHECK(doc.lineCount() == 3); CHECK(doc.line(1) == ""); } SECTION("sort") { // Alt + Shift + s sort selected lines //CAPTURE(doc.line(1)); CHECK(doc.lineCount() == 2); CHECK(doc.line(1) == " new1"); Tui::ZTest::sendText(&terminal, "a", Qt::KeyboardModifier::ControlModifier); CHECK(f->hasSelection() == true); Tui::ZTest::sendText(&terminal, "S", Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier); CHECK(doc.line(0) == " new1"); CHECK(doc.lineCount() == 2); CHECK(f->hasSelection() == true); } SECTION("sort-231") { f->newText("123"); f->insertText("3\n2\n1"); CHECK(doc.lineCount() == 3); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,2}); Tui::ZTest::sendKey(&terminal, Qt::Key_PageUp, Qt::KeyboardModifier::NoModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); Tui::ZTest::sendKey(&terminal, Qt::Key_Home, Qt::KeyboardModifier::NoModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); Tui::ZTest::sendKey(&terminal, Qt::Key_Down, Qt::KeyboardModifier::ShiftModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,1}); CHECK(f->hasSelection() == true); Tui::ZTest::sendKey(&terminal, Qt::Key_Down, Qt::KeyboardModifier::ShiftModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,2}); CHECK(f->hasSelection() == true); CHECK(f->selectedText() == "3\n2\n"); Tui::ZTest::sendText(&terminal, "S", Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier); CHECK(doc.line(0) == "2"); CHECK(doc.line(1) == "3"); CHECK(doc.line(2) == "1"); } SECTION("sort-312") { f->newText("123"); f->insertText("3\n2\n1"); CHECK(doc.lineCount() == 3); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,2}); Tui::ZTest::sendKey(&terminal, Qt::Key_PageUp, Qt::KeyboardModifier::ShiftModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == true); CHECK(f->selectedText() == "3\n2\n1"); Tui::ZTest::sendText(&terminal, "S", Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier); CHECK(doc.line(0) == "1"); CHECK(doc.line(1) == "2"); CHECK(doc.line(2) == "3"); } SECTION("sort-3") { f->newText("123"); f->insertText("3\n2\n1"); CHECK(doc.lineCount() == 3); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,2}); Tui::ZTest::sendKey(&terminal, Qt::Key_PageUp, Qt::KeyboardModifier::NoModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == false); Tui::ZTest::sendKey(&terminal, Qt::Key_Right, Qt::KeyboardModifier::ShiftModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,0}); CHECK(f->hasSelection() == true); CHECK(f->selectedText() == "3"); Tui::ZTest::sendText(&terminal, "S", Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier); CHECK(doc.line(0) == "3"); CHECK(doc.line(1) == "2"); CHECK(doc.line(2) == "1"); } //delete SECTION("key-delete") { Tui::ZTest::sendKey(&terminal, Qt::Key_Home, Qt::KeyboardModifier::NoModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Delete, Qt::KeyboardModifier::NoModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Delete, Qt::KeyboardModifier::NoModifier); CHECK(doc.line(1) == " new1"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,1}); } SECTION("key-delete-remove-line") { Tui::ZTest::sendKey(&terminal, Qt::Key_Up, Qt::KeyboardModifier::NoModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Delete, Qt::KeyboardModifier::NoModifier); CHECK(doc.line(0) == " text new1"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,0}); } SECTION("ctrl+key-delete") { Tui::ZTest::sendKey(&terminal, Qt::Key_Home, Qt::KeyboardModifier::NoModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Delete, Qt::KeyboardModifier::ControlModifier); CHECK(doc.line(1) == ""); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,1}); } SECTION("shift+key-delete") { Tui::ZTest::sendKey(&terminal, Qt::Key_Home, Qt::KeyboardModifier::NoModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Delete, Qt::KeyboardModifier::ShiftModifier); CHECK(doc.line(1) == " new1"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,1}); } SECTION("alt+key-delete") { Tui::ZTest::sendKey(&terminal, Qt::Key_Home, Qt::KeyboardModifier::NoModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Delete, Qt::KeyboardModifier::AltModifier); CHECK(doc.line(1) == " new1"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,1}); } //backspace SECTION("key-backspace") { Tui::ZTest::sendKey(&terminal, Qt::Key_Backspace, Qt::KeyboardModifier::NoModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{7,1}); CHECK(doc.line(1) == " new"); } SECTION("ctrl+key-backspace") { Tui::ZTest::sendKey(&terminal, Qt::Key_Backspace, Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4,1}); CHECK(doc.line(1) == " "); } SECTION("alt+key-backspace") { Tui::ZTest::sendKey(&terminal, Qt::Key_Backspace, Qt::KeyboardModifier::AltModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); CHECK(doc.line(1) == " new1"); } SECTION("key-left") { for(int i = 0; i <= 8; i++) { CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8 - i, 1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::NoModifier); } CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8, 0}); } //left SECTION("crl+key-left") { Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4, 1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0, 1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8, 0}); } SECTION("crl+shift+key-left") { Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ControlModifier | Qt::KeyboardModifier::ShiftModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4, 1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ControlModifier | Qt::KeyboardModifier::ShiftModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0, 1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ControlModifier | Qt::KeyboardModifier::ShiftModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8, 0}); } //right SECTION("crl+key-right") { Tui::ZTest::sendKey(&terminal, Qt::Key_Home, Qt::KeyboardModifier::ControlModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Right, Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8, 0}); Tui::ZTest::sendKey(&terminal, Qt::Key_Right, Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0, 1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Right, Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8, 1}); } SECTION("crl+key-right") { Tui::ZTest::sendKey(&terminal, Qt::Key_Home, Qt::KeyboardModifier::ControlModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Right, Qt::KeyboardModifier::ControlModifier | Qt::KeyboardModifier::ShiftModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8, 0}); Tui::ZTest::sendKey(&terminal, Qt::Key_Right, Qt::KeyboardModifier::ControlModifier | Qt::KeyboardModifier::ShiftModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0, 1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Right, Qt::KeyboardModifier::ControlModifier | Qt::KeyboardModifier::ShiftModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8, 1}); } SECTION("scroll") { auto testCase = GENERATE(Tui::ZTextOption::WrapMode::NoWrap, Tui::ZTextOption::WrapMode::WordWrap, Tui::ZTextOption::WrapMode::WrapAnywhere); f->setWordWrapMode(testCase); CHECK(f->scrollPositionColumn() == 0); CHECK(f->scrollPositionLine() == 0); //TODO: #193 scrollup with Crl+Up and wraped lines. //Tui::ZTest::sendText(&terminal, QString("a").repeated(100), Qt::KeyboardModifier::NoModifier); for (int i = 0; i < 48; i++) { Tui::ZTest::sendKey(&terminal, Qt::Key_Enter, Qt::KeyboardModifier::NoModifier); } CHECK(f->scrollPositionColumn() == 0); CHECK(f->scrollPositionLine() == 27); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,49}); for (int i = 0; i <= 26; i++) { Tui::ZTest::sendKey(&terminal, Qt::Key_Up, Qt::KeyboardModifier::ControlModifier); CHECK(f->scrollPositionColumn() == 0); CHECK(f->scrollPositionLine() == 26 - i); } } SECTION("move-lines-up") { CHECK(doc.lineCount() == 2); CHECK(f->scrollPositionColumn() == 0); CHECK(f->scrollPositionLine() == 0); Tui::ZTest::sendKey(&terminal, Qt::Key_Up, (Qt::KeyboardModifier::ShiftModifier | Qt::KeyboardModifier::ControlModifier)); CHECK(doc.line(0) == " new1"); } SECTION("move-lines-down") { Tui::ZTest::sendKey(&terminal, Qt::Key_Up, Qt::KeyboardModifier::NoModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Down, (Qt::KeyboardModifier::ShiftModifier | Qt::KeyboardModifier::ControlModifier)); CHECK(doc.line(0) == " new1"); } SECTION("select-tab") { Tui::ZTest::sendText(&terminal, "a", Qt::KeyboardModifier::ControlModifier); CHECK(f->hasSelection() == true); Tui::ZTest::sendKey(&terminal, Qt::Key_Tab, Qt::KeyboardModifier::ShiftModifier); CHECK(doc.line(0) == "text"); CHECK(doc.line(1) == "new1"); Tui::ZTest::sendKey(&terminal, Qt::Key_Tab, Qt::KeyboardModifier::NoModifier); CHECK(doc.line(0) == " text"); CHECK(doc.line(1) == " new1"); } SECTION("tab") { Tui::ZTest::sendKey(&terminal, Qt::Key_Tab, Qt::KeyboardModifier::ShiftModifier); CHECK(doc.line(0) == " text"); CHECK(doc.line(1) == "new1"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4, 1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Tab, Qt::KeyboardModifier::NoModifier); CHECK(doc.line(1) == "new1 "); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8, 1}); } //home end SECTION("home-home") { Qt::KeyboardModifier shift = GENERATE(Qt::KeyboardModifier::NoModifier, Qt::KeyboardModifier::ShiftModifier); CAPTURE(shift); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Home, shift); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Home, shift); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4,1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Home, shift); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,1}); } SECTION("home-end") { CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Home, Qt::KeyboardModifier::NoModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,1}); Tui::ZTest::sendKey(&terminal, Qt::Key_End, Qt::KeyboardModifier::NoModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); } SECTION("shift+home-end") { CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Home, Qt::KeyboardModifier::ShiftModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,1}); Tui::ZTest::sendKey(&terminal, Qt::Key_End, Qt::KeyboardModifier::ShiftModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); } SECTION("crl+home-end") { CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Home, Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); Tui::ZTest::sendKey(&terminal, Qt::Key_End, Qt::KeyboardModifier::ControlModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); } SECTION("crl+shift+home-end") { CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Home, (Qt::KeyboardModifier::ShiftModifier | Qt::KeyboardModifier::ControlModifier)); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); Tui::ZTest::sendKey(&terminal, Qt::Key_End, (Qt::KeyboardModifier::ShiftModifier | Qt::KeyboardModifier::ControlModifier)); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); } SECTION("up-down-page-up-page-down") { Qt::KeyboardModifier shift = GENERATE(Qt::KeyboardModifier::NoModifier, Qt::KeyboardModifier::ShiftModifier); CAPTURE(shift); Tui::ZTest::sendKey(&terminal, Qt::Key_Enter, Qt::KeyboardModifier::NoModifier); Tui::ZTest::sendText(&terminal, " new2", Qt::KeyboardModifier::NoModifier); CHECK(doc.lineCount() == 3); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,2}); Tui::ZTest::sendKey(&terminal, Qt::Key_PageUp, shift); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); Tui::ZTest::sendKey(&terminal, Qt::Key_PageDown, shift); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,2}); } //esc SECTION("esc") { Tui::ZTest::sendKey(&terminal, Qt::Key_Home, (Qt::KeyboardModifier::ShiftModifier | Qt::KeyboardModifier::ControlModifier)); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == true); Tui::ZTest::sendKey(&terminal, Qt::Key_Escape, Qt::KeyboardModifier::NoModifier); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); CHECK(f->hasSelection() == true); } //eat space SECTION("eat space") { f->setTabStopDistance(4); Tui::ZTest::sendText(&terminal, "a", Qt::KeyboardModifier::ControlModifier); //prepare enviromend for(int i = 1; i <= 6; i++) { for(int space = 1; space < i; space++) { Tui::ZTest::sendKey(&terminal, Qt::Key_Space, Qt::KeyboardModifier::NoModifier); } Tui::ZTest::sendText(&terminal, "a", Qt::KeyboardModifier::NoModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Enter, Qt::KeyboardModifier::NoModifier); } CHECK(doc.line(0) == "a"); CHECK(doc.line(1) == " a"); CHECK(doc.line(2) == " a"); CHECK(doc.line(3) == " a"); CHECK(doc.line(4) == " a"); CHECK(doc.line(5) == " a"); SECTION("tab") { Tui::ZTest::sendKey(&terminal, Qt::Key_Home, Qt::KeyboardModifier::ControlModifier); for(int i = 1; i <= 6; i++) { Tui::ZTest::sendKey(&terminal, Qt::Key_Tab, Qt::KeyboardModifier::NoModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Down, Qt::KeyboardModifier::NoModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Home, Qt::KeyboardModifier::NoModifier); } CHECK(doc.line(0) == " a"); CHECK(doc.line(1) == " a"); CHECK(doc.line(2) == " a"); CHECK(doc.line(3) == " a"); CHECK(doc.line(4) == " a"); CHECK(doc.line(5) == " a"); } SECTION("shift+tab") { Tui::ZTest::sendKey(&terminal, Qt::Key_Home, Qt::KeyboardModifier::ControlModifier); for(int i = 1; i <= 6; i++) { Tui::ZTest::sendKey(&terminal, Qt::Key_Tab, Qt::KeyboardModifier::ShiftModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Down, Qt::KeyboardModifier::NoModifier); } CHECK(doc.line(0) == "a"); CHECK(doc.line(1) == "a"); CHECK(doc.line(2) == "a"); CHECK(doc.line(3) == "a"); CHECK(doc.line(4) == "a"); CHECK(doc.line(5) == " a"); } } //page up down SECTION("page") { //80x24 terminal //f->setGeometry({0, 0, 80, 24}); Tui::ZTest::sendText(&terminal, "a", Qt::KeyboardModifier::ControlModifier); Qt::KeyboardModifier shift = GENERATE(Qt::KeyboardModifier::NoModifier, Qt::KeyboardModifier::ShiftModifier); CAPTURE(shift); //prepare enviromend for(int i = 1; i <= 50; i++) { Tui::ZTest::sendKey(&terminal, Qt::Key_Enter, Qt::KeyboardModifier::NoModifier); } Tui::ZTest::sendKey(&terminal, Qt::Key_Home, Qt::KeyboardModifier::ControlModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_PageDown, shift); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,23}); Tui::ZTest::sendKey(&terminal, Qt::Key_PageDown, shift); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,46}); Tui::ZTest::sendKey(&terminal, Qt::Key_PageDown, shift); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,50}); Tui::ZTest::sendKey(&terminal, Qt::Key_PageUp, shift); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,27}); Tui::ZTest::sendKey(&terminal, Qt::Key_PageUp, shift); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,4}); Tui::ZTest::sendKey(&terminal, Qt::Key_PageUp, shift); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{0,0}); } SECTION("search") { EventRecorder recorder; auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged)); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); f->setSearchText("t"); recorder.waitForEvent(cursorSignal); recorder.clearEvents(); t.f3(false, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{5,0}); recorder.clearEvents(); t.f3(false, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,0}); recorder.clearEvents(); t.f3(false, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{5,0}); recorder.clearEvents(); } SECTION("search-t") { EventRecorder recorder; auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged)); bool backward = GENERATE(true, false); CAPTURE(backward); bool reg = GENERATE(true, false); CAPTURE(reg); f->setRegex(reg); CAPTURE(backward); f->selectAll(); f->insertText("t"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,0}); CHECK(f->hasSelection() == false); recorder.clearEvents(); f->setSearchText("t"); recorder.waitForEvent(cursorSignal); recorder.clearEvents(); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); /* t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); */ } SECTION("search-t-t") { EventRecorder recorder; auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged)); bool backward = GENERATE(true, false); CAPTURE(backward); bool reg = GENERATE(true, false); CAPTURE(reg); f->setRegex(reg); f->selectAll(); f->insertText("t t"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{3,0}); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ShiftModifier); CHECK(f->hasSelection() == true); recorder.clearEvents(); f->setSearchText("t"); recorder.waitForEvent(cursorSignal); recorder.clearEvents(); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{3,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); } SECTION("search-space-t-t") { EventRecorder recorder; auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged)); bool reg = GENERATE(true, false); CAPTURE(reg); f->setRegex(reg); f->selectAll(); f->insertText(" t t "); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{5,0}); CHECK(f->hasSelection() == false); recorder.clearEvents(); f->setSearchText("t"); recorder.waitForEvent(cursorSignal); recorder.clearEvents(); t.f3(false, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); t.f3(false, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); t.f3(false, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); } SECTION("searchBackword-space-t-t") { EventRecorder recorder; auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged)); bool reg = GENERATE(true, false); CAPTURE(reg); f->setRegex(reg); f->selectAll(); f->insertText(" t t "); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{5,0}); CHECK(f->hasSelection() == false); recorder.clearEvents(); f->setSearchText("t"); recorder.waitForEvent(cursorSignal); recorder.clearEvents(); t.f3(true, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); t.f3(true, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); t.f3(true, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); } SECTION("search-t-newline-t") { EventRecorder recorder; auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged)); bool backward = GENERATE(true, false); CAPTURE(backward); bool reg = GENERATE(true, false); CAPTURE(reg); f->setRegex(reg); f->selectAll(); f->insertText("t\nt"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ShiftModifier); CHECK(f->hasSelection() == true); recorder.clearEvents(); f->setSearchText("t"); recorder.waitForEvent(cursorSignal); recorder.clearEvents(); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,1}); recorder.clearEvents(); CHECK(f->hasSelection() == true); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); } SECTION("search-t-newline-space-t") { EventRecorder recorder; auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged)); bool backward = GENERATE(true, false); CAPTURE(backward); bool reg = GENERATE(true, false); CAPTURE(reg); f->setRegex(reg); f->selectAll(); f->insertText(" t\n t"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ShiftModifier); CHECK(f->hasSelection() == true); recorder.clearEvents(); f->setSearchText("t"); recorder.waitForEvent(cursorSignal); recorder.clearEvents(); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,1}); recorder.clearEvents(); CHECK(f->hasSelection() == true); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); } SECTION("search-aa") { EventRecorder recorder; auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged)); bool backward = GENERATE(true, false); CAPTURE(backward); bool reg = GENERATE(true, false); CAPTURE(reg); f->setRegex(reg); f->selectAll(); f->insertText(" aa\naa"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ShiftModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ShiftModifier); CHECK(f->hasSelection() == true); recorder.clearEvents(); f->setSearchText("aa"); recorder.waitForEvent(cursorSignal); recorder.clearEvents(); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{3,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,1}); recorder.clearEvents(); CHECK(f->hasSelection() == true); } SECTION("search-aa-aa") { EventRecorder recorder; auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged)); bool backward = GENERATE(true, false); CAPTURE(backward); bool reg = GENERATE(true, false); CAPTURE(reg); f->setRegex(reg); f->selectAll(); f->insertText(" aa aa\naa"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ShiftModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ShiftModifier); CHECK(f->hasSelection() == true); recorder.clearEvents(); f->setSearchText("aa"); QList positions; if (backward) { positions << Tui::ZDocumentCursor::Position{6,0} << Tui::ZDocumentCursor::Position{3,0} << Tui::ZDocumentCursor::Position{2,1}; } else { positions << Tui::ZDocumentCursor::Position{3,0} << Tui::ZDocumentCursor::Position{6,0} << Tui::ZDocumentCursor::Position{2,1}; } recorder.waitForEvent(cursorSignal); recorder.clearEvents(); for (Tui::ZDocumentCursor::Position point : positions) { t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == point); recorder.clearEvents(); CHECK(f->hasSelection() == true); } } SECTION("search-asd") { EventRecorder recorder; auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged)); bool reg = GENERATE(true, false); CAPTURE(reg); f->setRegex(reg); f->selectAll(); f->insertText("asd \n\nasd"); f->setCursorPosition(Tui::ZDocumentCursor::Position{4,0}); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4,0}); CHECK(f->hasSelection() == false); recorder.clearEvents(); f->setSearchText("asd"); recorder.waitForEvent(cursorSignal); recorder.clearEvents(); t.f3(true, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{3,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); } SECTION("search-forward-backward") { EventRecorder recorder; auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged)); bool reg = GENERATE(true, false); CAPTURE(reg); f->setRegex(reg); f->selectAll(); f->insertText("bb\nbb\nbb"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,2}); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ShiftModifier); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ShiftModifier); CHECK(f->hasSelection() == true); recorder.clearEvents(); f->setSearchText("bb"); recorder.waitForEvent(cursorSignal); recorder.clearEvents(); SECTION("toggel-wraparound") { for(int i = 0; i < 3; i++) { t.f3(false, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); t.f3(true, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,2}); recorder.clearEvents(); CHECK(f->hasSelection() == true); } } SECTION("without-wraparound") { QList positions; positions << Tui::ZDocumentCursor::Position{2,1} << Tui::ZDocumentCursor::Position{2,0} << Tui::ZDocumentCursor::Position{2,1} << Tui::ZDocumentCursor::Position{2,2} << Tui::ZDocumentCursor::Position{2,0} << Tui::ZDocumentCursor::Position{2,2} << Tui::ZDocumentCursor::Position{2,1}; bool backward = true; for (Tui::ZDocumentCursor::Position point : positions) { t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == point); recorder.clearEvents(); CHECK(f->hasSelection() == true); if (point.line == 0) backward = !backward; } } } SECTION("search-smiley") { EventRecorder recorder; auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged)); bool backward = GENERATE(true, false); CAPTURE(backward); bool reg = GENERATE(true, false); CAPTURE(reg); f->setRegex(reg); f->selectAll(); f->insertText("😎😎"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4,0}); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ShiftModifier); CHECK(f->hasSelection() == true); recorder.clearEvents(); f->setSearchText("😎"); recorder.waitForEvent(cursorSignal); recorder.clearEvents(); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); } SECTION("search-smiley-multiline") { EventRecorder recorder; auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged)); bool backward = GENERATE(true, false); CAPTURE(backward); bool reg = GENERATE(true, false); CAPTURE(reg); f->setRegex(reg); f->selectAll(); f->insertText("😎\n😎"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,1}); Tui::ZTest::sendKey(&terminal, Qt::Key_Left, Qt::KeyboardModifier::ShiftModifier); CHECK(f->hasSelection() == true); recorder.clearEvents(); f->setSearchText("😎"); recorder.waitForEvent(cursorSignal); recorder.clearEvents(); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0}); recorder.clearEvents(); CHECK(f->hasSelection() == true); t.f3(backward, &terminal, f); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,1}); recorder.clearEvents(); CHECK(f->hasSelection() == true); } SECTION("replace") { EventRecorder recorder; auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged)); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); f->replaceAll("1","2"); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1}); CHECK(doc.line(1) == " new2"); recorder.clearEvents(); f->replaceAll("e","E"); recorder.waitForEvent(cursorSignal); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{6,1}); recorder.clearEvents(); f->replaceAll(" "," "); recorder.waitForEvent(cursorSignal); CHECK(doc.line(0) == " tExt"); CHECK(doc.line(1) == " nEw2"); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{12,1}); recorder.clearEvents(); } } TEST_CASE("multiline") { Tui::ZTerminal::OffScreen of(80, 24); Tui::ZTerminal terminal(of); Tui::ZRoot root; Tui::ZWindow *w = new Tui::ZWindow(&root); terminal.setMainWidget(&root); w->setGeometry({0, 0, 80, 24}); File *f = new File(terminal.textMetrics(), w); f->setFocus(); f->setGeometry({0, 0, 80, 24}); DocumentTestHelper t; Tui::ZDocument &doc = t.getDoc(f); Tui::ZDocumentCursor cursor{&doc, [&terminal,&doc](int line, bool wrappingAllowed) { (void)wrappingAllowed; Tui::ZTextLayout lay(terminal.textMetrics(), doc.line(line)); lay.doLayout(65000); return lay; } }; f->insertText("abc\ndef\nghi\njkl"); f->setCursorPosition({2,1}); SECTION("select-up") { Tui::ZTest::sendKey(&terminal, Qt::Key_Up, (Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier)); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == true); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,1}); //TODO: position 2,0 CHECK(f->anchorPosition() == Tui::ZDocumentCursor::Position{2,1}); } SECTION("select-down") { Tui::ZTest::sendKey(&terminal, Qt::Key_Down, (Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier)); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == true); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,1}); //TODO: position 2,2 CHECK(f->anchorPosition() == Tui::ZDocumentCursor::Position{2,1}); } SECTION("select-up-left") { Tui::ZTest::sendKey(&terminal, Qt::Key_Up, (Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier)); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == true); //left Tui::ZTest::sendKey(&terminal, Qt::Key_Left, (Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier)); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == true); CHECK(f->hasMultiInsert() == false); } SECTION("select-down-rgiht") { Tui::ZTest::sendKey(&terminal, Qt::Key_Down, (Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier)); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == true); //right Tui::ZTest::sendKey(&terminal, Qt::Key_Right, (Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier)); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == true); CHECK(f->hasMultiInsert() == false); } SECTION("select-down-rgiht-key") { Tui::ZTest::sendKey(&terminal, Qt::Key_Down, (Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier)); Tui::ZTest::sendKey(&terminal, Qt::Key_Right, (Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier)); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == true); CHECK(f->hasMultiInsert() == false); SECTION("add-a") { Tui::ZTest::sendText(&terminal, "A", Qt::KeyboardModifier::NoModifier); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == true); CHECK(doc.line(0) == "abc"); CHECK(doc.line(1) == "deA"); CHECK(doc.line(2) == "ghA"); CHECK(doc.line(3) == "jkl"); } SECTION("delete") { Tui::ZTest::sendKey(&terminal, Qt::Key_Delete, Qt::KeyboardModifier::NoModifier); CHECK(f->hasSelection() == false); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == false); CHECK(doc.line(0) == "abc"); CHECK(doc.line(1) == "de"); CHECK(doc.line(2) == "gh"); CHECK(doc.line(3) == "jkl"); } SECTION("backspace") { Tui::ZTest::sendKey(&terminal, Qt::Key_Backspace, Qt::KeyboardModifier::NoModifier); CHECK(f->hasSelection() == false); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == false); CHECK(doc.line(0) == "abc"); CHECK(doc.line(1) == "de"); CHECK(doc.line(2) == "gh"); CHECK(doc.line(3) == "jkl"); } } SECTION("select-up-left") { Tui::ZTest::sendKey(&terminal, Qt::Key_Up, (Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier)); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == true); SECTION("add-a") { Tui::ZTest::sendText(&terminal, "A", Qt::KeyboardModifier::NoModifier); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == true); CHECK(doc.line(0) == "abAc"); CHECK(doc.line(1) == "deAf"); CHECK(doc.line(2) == "ghi"); CHECK(doc.line(3) == "jkl"); } SECTION("delete") { Tui::ZTest::sendKey(&terminal, Qt::Key_Delete, Qt::KeyboardModifier::NoModifier); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == true); CHECK(doc.line(0) == "ab"); CHECK(doc.line(1) == "de"); CHECK(doc.line(2) == "ghi"); CHECK(doc.line(3) == "jkl"); } SECTION("backspace") { Tui::ZTest::sendKey(&terminal, Qt::Key_Backspace, Qt::KeyboardModifier::NoModifier); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == true); CHECK(doc.line(0) == "ac"); CHECK(doc.line(1) == "df"); CHECK(doc.line(2) == "ghi"); CHECK(doc.line(3) == "jkl"); } } SECTION("select-up-down") { Tui::ZTest::sendKey(&terminal, Qt::Key_Up, (Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier)); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == true); Tui::ZTest::sendKey(&terminal, Qt::Key_Down, (Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier)); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == true); } SECTION("select-and-esc") { Tui::ZTest::sendKey(&terminal, Qt::Key_Up, (Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier)); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == true); Tui::ZTest::sendKey(&terminal, Qt::Key_Escape, Qt::KeyboardModifier::NoModifier); CHECK(f->hasSelection() == false); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == false); CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0}); } SECTION("select-left-right") { Tui::ZTest::sendKey(&terminal, Qt::Key_Left, (Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier)); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == true); CHECK(f->hasMultiInsert() == false); Tui::ZTest::sendKey(&terminal, Qt::Key_Right, (Qt::KeyboardModifier::AltModifier | Qt::KeyboardModifier::ShiftModifier)); CHECK(f->hasSelection() == true); CHECK(f->hasBlockSelection() == false); CHECK(f->hasMultiInsert() == true); } } editor-0.1.80/src/tests/materials/000077500000000000000000000000001477475121100170115ustar00rootroot00000000000000editor-0.1.80/src/tests/materials/generate-following-input.sh000077500000000000000000000011721477475121100242760ustar00rootroot00000000000000#!/bin/bash # Here we generate random text to test stdin. # ./generate-following-input.sh | chr - -f # Function to generate a random string of the specified length generate_random_string() { local length=$1 tr -c -d 'a-z0-9 ' =3.0') catch2_dep = [catch2_dep, declare_dependency(compile_args: ['-DCATCH3'])] endif endif #ide:editable-filelist tests = [ 'attributes.cpp', 'eventrecorder.cpp', 'filelistparsertests.cpp', 'fileopentests.cpp', 'filesavetests.cpp', 'filetests.cpp', 'tests.cpp', ] #ide:editable-filelist tests_headers = [ 'eventrecorder.h', 'catchwrapper.h', ] tests_bin = executable('tests', tests, qt5.preprocess(moc_headers: tests_headers, moc_sources: tests, include_directories: include_directories('.')), link_with: editor_lib, dependencies : [qt5_dep, tuiwidgets_dep, posixsignalmanager_dep, syntax_dep, catch2_dep] ) test('tests', tests_bin, workdir: meson.current_build_dir(), timeout: 180, kwargs: verbose_kwargs ) editor-0.1.80/src/tests/tests.cpp000066400000000000000000000064521477475121100167050ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #define CATCH_CONFIG_RUNNER #include "catchwrapper.h" #include "../file.h" #include #include #include #include int main( int argc, char* argv[] ) { QCoreApplication app(argc, argv); int result = Catch::Session().run( argc, argv ); return result; } // Returns empty QByteArray() on failure. QByteArray fileChecksum(const QString &fileName, QCryptographicHash::Algorithm hashAlgorithm=QCryptographicHash::Sha256) { QFile f(fileName); if (f.open(QFile::ReadOnly)) { QCryptographicHash hash(hashAlgorithm); if (hash.addData(&f)) { return hash.result(); } } return QByteArray(); } void readWrite(QString in) { CAPTURE(in); Tui::ZTerminal::OffScreen of(80, 24); Tui::ZTerminal terminal(of); QString out = in + "_dub"; File *f = new File(terminal.textMetrics(), nullptr); //f->setFilename(in); CHECK(f->openText(in)); f->setFilename(out); CHECK(f->saveText()); CHECK(fileChecksum(in) == fileChecksum(out)); QFile fp(out); fp.remove(); } TEST_CASE("read write zero byte") { QFile fp("zero"); CHECK(fp.open(QFile::WriteOnly)); fp.close(); readWrite("zero"); fp.remove(); } TEST_CASE("read write 16k byte") { QFile fp("sixteen"); CHECK(fp.open(QFile::WriteOnly)); fp.write(QByteArray(16382,'0')); fp.close(); readWrite("sixteen"); fp.remove(); } TEST_CASE("read write 64k byte") { QFile fp("64k"); CHECK(fp.open(QFile::WriteOnly)); fp.write(QByteArray(16382*4,'\xFF')); fp.close(); readWrite("64k"); fp.remove(); } TEST_CASE("read write text") { QFile fp("text"); CHECK(fp.open(QFile::WriteOnly)); fp.write(QByteArray("Hier verewigen sich die Entwickler des Editors\nChristoph Hüffelmann und Martin Hostettler\n")); fp.close(); readWrite("text"); fp.remove(); } TEST_CASE("read write text one newline") { QFile fp("text"); CHECK(fp.open(QFile::WriteOnly)); fp.write(QByteArray("Hier verewigen sich die Entwickler des Editors\nChristoph Hüffelmann und Martin Hostettler")); fp.close(); readWrite("text"); fp.remove(); } TEST_CASE("read write dos text") { QFile fp("text"); CHECK(fp.open(QFile::WriteOnly)); fp.write(QByteArray("test\r\nblub\r\n")); fp.close(); readWrite("text"); fp.remove(); } TEST_CASE("read write dos text one newline") { QFile fp("text"); CHECK(fp.open(QFile::WriteOnly)); fp.write(QByteArray("Hier verewigen sich \r\ndie Entwickler des Editors\r\nChristoph Hüffelmann und\r\nMartin Hostettler")); fp.close(); readWrite("text"); fp.remove(); } TEST_CASE("all chars") { QFile fp("text"); CHECK(fp.open(QFile::WriteOnly)); for (int i = 1; i <= 0xFFFF; i++) { fp.write(QString(1,QChar(i)).toUtf8()); if(i % 80 == 0) { fp.write("\n"); } } for (int i = 0xFFFF; i <= 0x10FFFF; i++) { fp.write((QString(1,QChar::highSurrogate(i))+QString(1,QChar::lowSurrogate(i))).toUtf8()); if(i % 80 == 0) { fp.write("\n"); } } fp.close(); readWrite("text"); fp.remove(); } TEST_CASE("read write") { readWrite("../chr"); } editor-0.1.80/src/themedialog.cpp000066400000000000000000000040721477475121100166570ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "themedialog.h" #include #include #include "edit.h" ThemeDialog::ThemeDialog(Editor *edit) : Tui::ZDialog(edit) { setOptions(Tui::ZWindow::CloseOption | Tui::ZWindow::DeleteOnClose | Tui::ZWindow::MoveOption | Tui::ZWindow::AutomaticOption); setDefaultPlacement(Qt::AlignBottom | Qt::AlignHCenter, {0, -2}); Tui::ZWindowLayout *wl = new Tui::ZWindowLayout(); this->setWindowTitle("Theme Color"); setLayout(wl); setContentsMargins({1, 1, 1, 1}); Tui::ZVBoxLayout *vbox = new Tui::ZVBoxLayout(); { Tui::ZHBoxLayout* hbox = new Tui::ZHBoxLayout(); _lv = new Tui::ZListView(this); QStringList qsl = {"Classic", "Dark"}; _lv->setItems(qsl); _lv->setFocus(); hbox->addWidget(_lv); vbox->add(hbox); } vbox->setSpacing(1); { Tui::ZHBoxLayout* hbox = new Tui::ZHBoxLayout(); hbox->addStretch(); _cancelButton = new Tui::ZButton(Tui::withMarkup, "Cancel", this); hbox->addWidget(_cancelButton); _okButton = new Tui::ZButton(Tui::withMarkup, "OK", this); _okButton->setDefault(true); hbox->addWidget(_okButton); vbox->add(hbox); } wl->setCentral(vbox); setGeometry({6, 5, 30, 8}); QObject::connect(_cancelButton, &Tui::ZButton::clicked, [this] { close(); }); QObject::connect(_lv, &Tui::ZListView::enterPressed, [this,edit] { if(_lv->currentItem() == "Dark") { edit->setTheme(Editor::Theme::dark); } else { edit->setTheme(Editor::Theme::classic); } close(); }); QObject::connect(_okButton, &Tui::ZButton::clicked, [this,edit] { if(_lv->currentItem() == "Dark") { edit->setTheme(Editor::Theme::dark); } else { edit->setTheme(Editor::Theme::classic); } close(); }); open(); } void ThemeDialog::close() { deleteLater(); } void ThemeDialog::open() { raise(); } editor-0.1.80/src/themedialog.h000066400000000000000000000007341477475121100163250ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef THEMEDIALOG_H #define THEMEDIALOG_H #include #include #include class Editor; class ThemeDialog : public Tui::ZDialog { Q_OBJECT public: ThemeDialog(Editor *edit); public slots: void open(); void close(); private: Tui::ZListView *_lv = nullptr; Tui::ZButton *_cancelButton = nullptr; Tui::ZButton *_okButton = nullptr; }; #endif // THEMEDIALOG_H editor-0.1.80/src/version.h.in000066400000000000000000000000501477475121100161240ustar00rootroot00000000000000#define VERSION_NUMBER @VERSION_NUMBER@ editor-0.1.80/src/version_git.h.in000066400000000000000000000000471477475121100167750ustar00rootroot00000000000000#define GIT_VERSION_ID "%GIT_VERSION%" editor-0.1.80/src/wrapdialog.cpp000066400000000000000000000060061477475121100165250ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "wrapdialog.h" #include #include #include #include WrapDialog::WrapDialog(Tui::ZWidget *parent, File *file) : Tui::ZDialog(parent) { setOptions(Tui::ZWindow::CloseOption | Tui::ZWindow::MoveOption | Tui::ZWindow::ResizeOption | Tui::ZWindow::AutomaticOption | Tui::ZWindow::DeleteOnClose); setWindowTitle("Wrap long lines"); setContentsMargins({1, 1, 1, 1}); Tui::ZVBoxLayout *vbox = new Tui::ZVBoxLayout(); setLayout(vbox); vbox->setSpacing(1); _noWrapRadioButton = new Tui::ZRadioButton(this); _noWrapRadioButton->setMarkup("No Wrap"); if (file->wordWrapMode() == Tui::ZTextOption::NoWrap) { _noWrapRadioButton->setChecked(true); } vbox->addWidget(_noWrapRadioButton); _wordWrapRadioButton = new Tui::ZRadioButton(this); _wordWrapRadioButton->setMarkup("Word Wrap"); if(file->wordWrapMode() == Tui::ZTextOption::WordWrap) { _wordWrapRadioButton->setChecked(true); } vbox->addWidget(_wordWrapRadioButton); _wrapAnywhereRadioButton = new Tui::ZRadioButton(this); _wrapAnywhereRadioButton->setMarkup("Wrap Anywhere"); if(file->wordWrapMode() == Tui::ZTextOption::WrapAnywhere) { _wrapAnywhereRadioButton->setChecked(true); } vbox->addWidget(_wrapAnywhereRadioButton); Tui::ZHBoxLayout *hintLayout = new Tui::ZHBoxLayout(); Tui::ZLabel *hintLabel = new Tui::ZLabel(Tui::withMarkup, "Display Right Margin at Column: ", this); hintLayout->addWidget(hintLabel); _rightMarginHintInput = new Tui::ZInputBox(QString::number(file->rightMarginHint()), this); _rightMarginHintInput->setMaximumSize(6, 1); hintLabel->setBuddy(_rightMarginHintInput); hintLayout->addWidget(_rightMarginHintInput); vbox->add(hintLayout); _noWrapRadioButton->setFocus(); Tui::ZHBoxLayout *hbox5 = new Tui::ZHBoxLayout(); hbox5->addStretch(); Tui::ZButton *cancelButton = new Tui::ZButton(this); cancelButton->setText("Cancel"); hbox5->addWidget(cancelButton); Tui::ZButton *saveButton = new Tui::ZButton(this); saveButton->setText("Ok"); saveButton->setDefault(true); hbox5->addWidget(saveButton); vbox->add(hbox5); QObject::connect(cancelButton, &Tui::ZButton::clicked, this, [this] { deleteLater(); }); QObject::connect(saveButton, &Tui::ZButton::clicked, this, [this, file] { if (_noWrapRadioButton->checked()) { file->setWordWrapMode(Tui::ZTextOption::NoWrap); } else if (_wordWrapRadioButton->checked()) { file->setWordWrapMode(Tui::ZTextOption::WordWrap); } else if (_wrapAnywhereRadioButton->checked()) { file->setWordWrapMode(Tui::ZTextOption::WrapAnywhere); } bool ok = false; int marginHint = _rightMarginHintInput->text().toInt(&ok); if (ok) { file->setRightMarginHint(marginHint); } deleteLater(); }); } editor-0.1.80/src/wrapdialog.h000066400000000000000000000010621477475121100161670ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef WRAPDIALOG_H #define WRAPDIALOG_H #include #include #include #include class WrapDialog : public Tui::ZDialog { Q_OBJECT public: WrapDialog(Tui::ZWidget *parent, File *file = nullptr); private: Tui::ZRadioButton *_noWrapRadioButton = nullptr; Tui::ZRadioButton *_wordWrapRadioButton = nullptr; Tui::ZRadioButton *_wrapAnywhereRadioButton = nullptr; Tui::ZInputBox *_rightMarginHintInput = nullptr; }; #endif // WRAPDIALOG_H editor-0.1.80/syntax.qrc000066400000000000000000000011421477475121100151320ustar00rootroot00000000000000 third-party/chr-bluebg.theme third-party/chr-blackbg.theme third-party/chr-bluebg.theme third-party/chr-blackbg.theme editor-0.1.80/third-party/000077500000000000000000000000001477475121100153465ustar00rootroot00000000000000editor-0.1.80/third-party/catch2/000077500000000000000000000000001477475121100165125ustar00rootroot00000000000000editor-0.1.80/third-party/catch2/catch.hpp000066400000000000000000024047431477475121100203230ustar00rootroot00000000000000/* * Catch v2.13.7 * Generated: 2021-07-28 20:29:27.753164 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2021 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED // start catch.hpp #define CATCH_VERSION_MAJOR 2 #define CATCH_VERSION_MINOR 13 #define CATCH_VERSION_PATCH 7 #ifdef __clang__ # pragma clang system_header #elif defined __GNUC__ # pragma GCC system_header #endif // start catch_suppress_warnings.h #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(push) # pragma warning(disable: 161 1682) # else // __ICC # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" # pragma clang diagnostic ignored "-Wswitch-enum" # pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ // Because REQUIREs trigger GCC's -Wparentheses, and because still // supported version of g++ have only buggy support for _Pragmas, // Wparentheses have to be suppressed globally. # pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunused-variable" # pragma GCC diagnostic ignored "-Wpadded" #endif // end catch_suppress_warnings.h #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL # define CATCH_CONFIG_ALL_PARTS #endif // In the impl file, we want to have access to all parts of the headers // Can also be used to sanely support PCHs #if defined(CATCH_CONFIG_ALL_PARTS) # define CATCH_CONFIG_EXTERNAL_INTERFACES # if defined(CATCH_CONFIG_DISABLE_MATCHERS) # undef CATCH_CONFIG_DISABLE_MATCHERS # endif # if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) # define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER # endif #endif #if !defined(CATCH_CONFIG_IMPL_ONLY) // start catch_platform.h // See e.g.: // https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html #ifdef __APPLE__ # include # if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) # define CATCH_PLATFORM_MAC # elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) # define CATCH_PLATFORM_IPHONE # endif #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) # define CATCH_PLATFORM_WINDOWS #endif // end catch_platform.h #ifdef CATCH_IMPL # ifndef CLARA_CONFIG_MAIN # define CLARA_CONFIG_MAIN_NOT_DEFINED # define CLARA_CONFIG_MAIN # endif #endif // start catch_user_interfaces.h namespace Catch { unsigned int rngSeed(); } // end catch_user_interfaces.h // start catch_tag_alias_autoregistrar.h // start catch_common.h // start catch_compiler_capabilities.h // Detect a number of compiler features - by compiler // The following features are defined: // // CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? // CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? // CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? // CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? // **************** // Note to maintainers: if new toggles are added please document them // in configuration.md, too // **************** // In general each macro has a _NO_ form // (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they // can be combined, en-mass, with the _NO_ forms later. #ifdef __cplusplus # if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) # define CATCH_CPP14_OR_GREATER # endif # if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # define CATCH_CPP17_OR_GREATER # endif #endif // Only GCC compiler should be used in this block, so other compilers trying to // mask themselves as GCC should be ignored. #if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) # define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) #endif #if defined(__clang__) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) // As of this writing, IBM XL's implementation of __builtin_constant_p has a bug // which results in calls to destructors being emitted for each temporary, // without a matching initialization. In practice, this can result in something // like `std::string::~string` being called on an uninitialized value. // // For example, this code will likely segfault under IBM XL: // ``` // REQUIRE(std::string("12") + "34" == "1234") // ``` // // Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. # if !defined(__ibmxl__) && !defined(__CUDACC__) # define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ # endif # define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) # define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) # define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) # define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// // Assume that non-Windows platforms support posix signals by default #if !defined(CATCH_PLATFORM_WINDOWS) #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS #endif //////////////////////////////////////////////////////////////////////////////// // We know some environments not to support full POSIX signals #if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS #endif #ifdef __OS400__ # define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS # define CATCH_CONFIG_COLOUR_NONE #endif //////////////////////////////////////////////////////////////////////////////// // Android somehow still does not support std::to_string #if defined(__ANDROID__) # define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING # define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE #endif //////////////////////////////////////////////////////////////////////////////// // Not all Windows environments support SEH properly #if defined(__MINGW32__) # define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH #endif //////////////////////////////////////////////////////////////////////////////// // PS4 #if defined(__ORBIS__) # define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE #endif //////////////////////////////////////////////////////////////////////////////// // Cygwin #ifdef __CYGWIN__ // Required for some versions of Cygwin to declare gettimeofday // see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin # define _BSD_SOURCE // some versions of cygwin (most) do not support std::to_string. Use the libstd check. // https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 # if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) # define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING # endif #endif // __CYGWIN__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ #if defined(_MSC_VER) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) // Universal Windows platform does not support SEH // Or console colours (or console at all...) # if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) # define CATCH_CONFIG_COLOUR_NONE # else # define CATCH_INTERNAL_CONFIG_WINDOWS_SEH # endif // MSVC traditional preprocessor needs some workaround for __VA_ARGS__ // _MSVC_TRADITIONAL == 0 means new conformant preprocessor // _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor # if !defined(__clang__) // Handle Clang masquerading for msvc # if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) # define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR # endif // MSVC_TRADITIONAL # endif // __clang__ #endif // _MSC_VER #if defined(_REENTRANT) || defined(_MSC_VER) // Enable async processing, as -pthread is specified or no additional linking is required # define CATCH_INTERNAL_CONFIG_USE_ASYNC #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// // Check if we are compiled with -fno-exceptions or equivalent #if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) # define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED #endif //////////////////////////////////////////////////////////////////////////////// // DJGPP #ifdef __DJGPP__ # define CATCH_INTERNAL_CONFIG_NO_WCHAR #endif // __DJGPP__ //////////////////////////////////////////////////////////////////////////////// // Embarcadero C++Build #if defined(__BORLANDC__) #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN #endif //////////////////////////////////////////////////////////////////////////////// // Use of __COUNTER__ is suppressed during code analysis in // CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly // handled by it. // Otherwise all supported compilers support COUNTER macro, // but user still might want to turn it off #if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) #define CATCH_INTERNAL_CONFIG_COUNTER #endif //////////////////////////////////////////////////////////////////////////////// // RTX is a special version of Windows that is real time. // This means that it is detected as Windows, but does not provide // the same set of capabilities as real Windows does. #if defined(UNDER_RTSS) || defined(RTX64_BUILD) #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH #define CATCH_INTERNAL_CONFIG_NO_ASYNC #define CATCH_CONFIG_COLOUR_NONE #endif #if !defined(_GLIBCXX_USE_C99_MATH_TR1) #define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER #endif // Various stdlib support checks that require __has_include #if defined(__has_include) // Check if string_view is available and usable #if __has_include() && defined(CATCH_CPP17_OR_GREATER) # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW #endif // Check if optional is available and usable # if __has_include() && defined(CATCH_CPP17_OR_GREATER) # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) // Check if byte is available and usable # if __has_include() && defined(CATCH_CPP17_OR_GREATER) # include # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) # define CATCH_INTERNAL_CONFIG_CPP17_BYTE # endif # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) // Check if variant is available and usable # if __has_include() && defined(CATCH_CPP17_OR_GREATER) # if defined(__clang__) && (__clang_major__ < 8) // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 // fix should be in clang 8, workaround in libstdc++ 8.2 # include # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) # define CATCH_CONFIG_NO_CPP17_VARIANT # else # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) # else # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT # endif // defined(__clang__) && (__clang_major__ < 8) # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) #endif // defined(__has_include) #if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) # define CATCH_CONFIG_COUNTER #endif #if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) # define CATCH_CONFIG_WINDOWS_SEH #endif // This is set by default, because we assume that unix compilers are posix-signal-compatible by default. #if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) # define CATCH_CONFIG_POSIX_SIGNALS #endif // This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. #if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) # define CATCH_CONFIG_WCHAR #endif #if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) # define CATCH_CONFIG_CPP11_TO_STRING #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) # define CATCH_CONFIG_CPP17_OPTIONAL #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) # define CATCH_CONFIG_CPP17_STRING_VIEW #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) # define CATCH_CONFIG_CPP17_VARIANT #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) # define CATCH_CONFIG_CPP17_BYTE #endif #if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) # define CATCH_INTERNAL_CONFIG_NEW_CAPTURE #endif #if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) # define CATCH_CONFIG_NEW_CAPTURE #endif #if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) # define CATCH_CONFIG_DISABLE_EXCEPTIONS #endif #if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) # define CATCH_CONFIG_POLYFILL_ISNAN #endif #if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) # define CATCH_CONFIG_USE_ASYNC #endif #if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) # define CATCH_CONFIG_ANDROID_LOGWRITE #endif #if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) # define CATCH_CONFIG_GLOBAL_NEXTAFTER #endif // Even if we do not think the compiler has that warning, we still have // to provide a macro that can be used by the code. #if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION #endif #if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #endif #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS #endif // The goal of this macro is to avoid evaluation of the arguments, but // still have the compiler warn on problems inside... #if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) # define CATCH_INTERNAL_IGNORE_BUT_WARN(...) #endif #if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) # undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #elif defined(__clang__) && (__clang_major__ < 5) # undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #endif #if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) #define CATCH_TRY if ((true)) #define CATCH_CATCH_ALL if ((false)) #define CATCH_CATCH_ANON(type) if ((false)) #else #define CATCH_TRY try #define CATCH_CATCH_ALL catch (...) #define CATCH_CATCH_ANON(type) catch (type) #endif #if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) #define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #endif // end catch_compiler_capabilities.h #define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line #define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) #ifdef CATCH_CONFIG_COUNTER # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) #else # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) #endif #include #include #include // We need a dummy global operator<< so we can bring it into Catch namespace later struct Catch_global_namespace_dummy {}; std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); namespace Catch { struct CaseSensitive { enum Choice { Yes, No }; }; class NonCopyable { NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable && ) = delete; NonCopyable& operator = ( NonCopyable const& ) = delete; NonCopyable& operator = ( NonCopyable && ) = delete; protected: NonCopyable(); virtual ~NonCopyable(); }; struct SourceLineInfo { SourceLineInfo() = delete; SourceLineInfo( char const* _file, std::size_t _line ) noexcept : file( _file ), line( _line ) {} SourceLineInfo( SourceLineInfo const& other ) = default; SourceLineInfo& operator = ( SourceLineInfo const& ) = default; SourceLineInfo( SourceLineInfo&& ) noexcept = default; SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; bool empty() const noexcept { return file[0] == '\0'; } bool operator == ( SourceLineInfo const& other ) const noexcept; bool operator < ( SourceLineInfo const& other ) const noexcept; char const* file; std::size_t line; }; std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); // Bring in operator<< from global namespace into Catch namespace // This is necessary because the overload of operator<< above makes // lookup stop at namespace Catch using ::operator<<; // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as // >> stuff +StreamEndStop struct StreamEndStop { std::string operator+() const; }; template T const& operator + ( T const& value, StreamEndStop ) { return value; } } #define CATCH_INTERNAL_LINEINFO \ ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) // end catch_common.h namespace Catch { struct RegistrarForTagAliases { RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); }; } // end namespace Catch #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION // end catch_tag_alias_autoregistrar.h // start catch_test_registry.h // start catch_interfaces_testcase.h #include namespace Catch { class TestSpec; struct ITestInvoker { virtual void invoke () const = 0; virtual ~ITestInvoker(); }; class TestCase; struct IConfig; struct ITestCaseRegistry { virtual ~ITestCaseRegistry(); virtual std::vector const& getAllTests() const = 0; virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; }; bool isThrowSafe( TestCase const& testCase, IConfig const& config ); bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); std::vector const& getAllTestCasesSorted( IConfig const& config ); } // end catch_interfaces_testcase.h // start catch_stringref.h #include #include #include #include namespace Catch { /// A non-owning string class (similar to the forthcoming std::string_view) /// Note that, because a StringRef may be a substring of another string, /// it may not be null terminated. class StringRef { public: using size_type = std::size_t; using const_iterator = const char*; private: static constexpr char const* const s_empty = ""; char const* m_start = s_empty; size_type m_size = 0; public: // construction constexpr StringRef() noexcept = default; StringRef( char const* rawChars ) noexcept; constexpr StringRef( char const* rawChars, size_type size ) noexcept : m_start( rawChars ), m_size( size ) {} StringRef( std::string const& stdString ) noexcept : m_start( stdString.c_str() ), m_size( stdString.size() ) {} explicit operator std::string() const { return std::string(m_start, m_size); } public: // operators auto operator == ( StringRef const& other ) const noexcept -> bool; auto operator != (StringRef const& other) const noexcept -> bool { return !(*this == other); } auto operator[] ( size_type index ) const noexcept -> char { assert(index < m_size); return m_start[index]; } public: // named queries constexpr auto empty() const noexcept -> bool { return m_size == 0; } constexpr auto size() const noexcept -> size_type { return m_size; } // Returns the current start pointer. If the StringRef is not // null-terminated, throws std::domain_exception auto c_str() const -> char const*; public: // substrings and searches // Returns a substring of [start, start + length). // If start + length > size(), then the substring is [start, size()). // If start > size(), then the substring is empty. auto substr( size_type start, size_type length ) const noexcept -> StringRef; // Returns the current start pointer. May not be null-terminated. auto data() const noexcept -> char const*; constexpr auto isNullTerminated() const noexcept -> bool { return m_start[m_size] == '\0'; } public: // iterators constexpr const_iterator begin() const { return m_start; } constexpr const_iterator end() const { return m_start + m_size; } }; auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { return StringRef( rawChars, size ); } } // namespace Catch constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { return Catch::StringRef( rawChars, size ); } // end catch_stringref.h // start catch_preprocessor.hpp #define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ #define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) #ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ // MSVC needs more evaluations #define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) #define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) #else #define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) #endif #define CATCH_REC_END(...) #define CATCH_REC_OUT #define CATCH_EMPTY() #define CATCH_DEFER(id) id CATCH_EMPTY() #define CATCH_REC_GET_END2() 0, CATCH_REC_END #define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 #define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 #define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT #define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) #define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) #define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) #define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) #define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) #define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) #define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) #define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) // Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, // and passes userdata as the first parameter to each invocation, // e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) #define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) #define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) #define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) #define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ #define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ #define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF #define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ #define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) #else // MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF #define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) #define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ #define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) #endif #define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ #define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) #define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) #else #define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) #endif #define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) #define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) #define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) #define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) #define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) #define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) #define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) #define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) #define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) #define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) #define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) #define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) #define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N #define INTERNAL_CATCH_TYPE_GEN\ template struct TypeList {};\ template\ constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ template class...> struct TemplateTypeList{};\ template class...Cs>\ constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ template\ struct append;\ template\ struct rewrap;\ template class, typename...>\ struct create;\ template class, typename>\ struct convert;\ \ template \ struct append { using type = T; };\ template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ template< template class L1, typename...E1, typename...Rest>\ struct append, TypeList, Rest...> { using type = L1; };\ \ template< template class Container, template class List, typename...elems>\ struct rewrap, List> { using type = TypeList>; };\ template< template class Container, template class List, class...Elems, typename...Elements>\ struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ \ template