diff options
74 files changed, 604 insertions, 495 deletions
@@ -6,9 +6,9 @@ *.tar* src/kak src/kak.debug -src/kak.debug.san_* +src/kak.debug.* src/kak.opt -src/kak.opt.san_* +src/kak.opt.* src/.version* src/.*.json doc/kak.1.gz @@ -9,6 +9,9 @@ gzip_man = yes # to get format compatible with GitHub archive use "gzip -S .gz" here compress_bin = bzip2 +tag-static-no= +tag-static-yes=.static + compress-suffix-bzip2 = bz2 compress-suffix-zstd = zst @@ -96,6 +99,7 @@ KAK_LIBS = \ $(LIBS) tag = $(tag-debug-$(debug))$(tag-sanitize-$(sanitize)) +tagbin = $(tag)$(tag-static-$(static)) .SUFFIXES: $(tag).o .cc .PHONY: src/kak @@ -106,10 +110,10 @@ objects = $(sources:.cc=$(tag).o) all: src/kak -src/kak: src/kak$(tag) - ln -sf kak$(tag) $@ +src/kak: src/kak$(tagbin) + ln -sf kak$(tagbin) $@ -src/kak$(tag): src/.version.o $(objects) +src/kak$(tagbin): src/.version.o $(objects) $(CXX) $(KAK_LDFLAGS) $(KAK_CXXFLAGS) $(objects) src/.version.o $(KAK_LIBS) -o $@ deps = $(shell touch src/.version$(tag).d && find src -type f -name '.*$(tag).d') # Ensure we find one deps for FreeBSD make diff --git a/doc/pages/changelog.asciidoc b/doc/pages/changelog.asciidoc index 66733443..af3c711a 100644 --- a/doc/pages/changelog.asciidoc +++ b/doc/pages/changelog.asciidoc @@ -1,5 +1,7 @@ = Changelog +To control the startup info message, see <<options#startup-info,`:doc options startup-info`>> + This changelog contains major and/or breaking changes to Kakoune between released versions. diff --git a/doc/pages/commands.asciidoc b/doc/pages/commands.asciidoc index e386ed61..3a6bef65 100644 --- a/doc/pages/commands.asciidoc +++ b/doc/pages/commands.asciidoc @@ -293,7 +293,9 @@ of the file onto the filesystem (See <<faces#,`:doc faces`>> and <<scopes#,`:doc scopes`>>) *colorscheme* <name>:: - load named colorscheme + load named colorscheme + + Colorschemes are just `.kak` scripts that set faces. Colorschemes are searched for in `%val{config}/colors` and `%val{runtime}/colors`. + + (See <<faces#, `:doc faces`>> and <<expansions#, `:doc expansions`>>) *add-highlighter* [<switches>] <highlighter_path> <highlighter_parameters> ...:: *alias* addhl + diff --git a/doc/pages/options.asciidoc b/doc/pages/options.asciidoc index e5cb4c38..4a2120ea 100644 --- a/doc/pages/options.asciidoc +++ b/doc/pages/options.asciidoc @@ -342,7 +342,7 @@ are exclusively available to built-in options. *ui_options* `str-to-str-map`:: a list of `key=value` pairs that are forwarded to the user - interface implementation. The NCurses UI supports the following options: + interface implementation. The Terminal UI supports the following options: *terminal_set_title*::: if *yes* or *true*, the terminal emulator title will @@ -385,6 +385,10 @@ are exclusively available to built-in options. set the maximum allowable width of an info box. set to zero for no limit. + *terminal_cursor_native*::: + if *yes* or *true*, use native terminal cursor visibility control + instead of Kakoune's cursor management (defaults to *false*) + [[startup-info]] *startup_info_version* `int`:: _default_ 0 + diff --git a/rc/filetype/julia.kak b/rc/filetype/julia.kak index 5f6bbaa6..6dc2af13 100644 --- a/rc/filetype/julia.kak +++ b/rc/filetype/julia.kak @@ -13,6 +13,9 @@ hook global BufCreate .*\.(jl) %{ hook global WinSetOption filetype=julia %{ require-module julia + hook window InsertChar \n -group julia-indent julia-indent-on-new-line + hook window InsertChar d -group julia-insert julia-insert-on-new-line + hook -once -always window WinSetOption filetype=.* %{ remove-hooks window julia-.+ } } hook -group julia-highlight global WinSetOption filetype=julia %{ @@ -21,7 +24,7 @@ hook -group julia-highlight global WinSetOption filetype=julia %{ } -provide-module julia %{ +provide-module julia %§ # Highlighters # ‾‾‾‾‾‾‾‾‾‾‾‾ @@ -39,4 +42,26 @@ add-highlighter shared/julia/code/ regex \b(Number|Real|BigInt|Integer|UInt|UInt add-highlighter shared/julia/code/ regex \w+!*(?=\() 0:function add-highlighter shared/julia/code/ regex @\w+!*\b 0:meta add-highlighter shared/julia/code/ regex '(?:\?|=|:=|\+=|-=|\*=|/=|//=|\.//=|\.\*=|\./=|\\=|\.\\=|\^=|\.\^=|÷=|\.÷=|%=|\.%=|\|=|&=|\$=|=>|<<=|>>=|>>>=|~|\.\+=|\.-=|--|-->|←|→|↔|↚|↛|↠|↣|↦|↮|⇎|⇏|⇒|⇔|⇴|⇶|⇷|⇸|⇹|⇺|⇻|⇼|⇽|⇾|⇿|⟵|⟶|⟷|⟷|⟹|⟺|⟻|⟼|⟽|⟾|⟿|⤀|⤁|⤂|⤃|⤄|⤅|⤆|⤇|⤌|⤍|⤎|⤏|⤐|⤑|⤔|⤕|⤖|⤗|⤘|⤝|⤞|⤟|⤠|⥄|⥅|⥆|⥇|⥈|⥊|⥋|⥎|⥐|⥒|⥓|⥖|⥗|⥚|⥛|⥞|⥟|⥢|⥤|⥦|⥧|⥨|⥩|⥪|⥫|⥬|⥭|⥰|⧴|⬱|⬰|⬲|⬳|⬴|⬵|⬶|⬷|⬸|⬹|⬺|⬻|⬼|⬽|⬾|⬿|⭀|⭁|⭂|⭃|⭄|⭇|⭈|⭉|⭊|⭋|⭌|←|→|&&|\|\||>|<|>=|≥|<=|≤|==|===|≡|!=|≠|!==|≢|\.>|\.<|\.>=|\.≥|\.<=|\.≤|\.==|\.!=|\.≠|\.=|\.!|<:|>:|∈|∉|∋|∌|⊆|⊈|⊂|⊄|⊊|∝|∊|∍|∥|∦|∷|∺|∻|∽|∾|≁|≃|≄|≅|≆|≇|≈|≉|≊|≋|≌|≍|≎|≐|≑|≒|≓|≔|≕|≖|≗|≘|≙|≚|≛|≜|≝|≞|≟|≣|≦|≧|≨|≩|≪|≫|≬|≭|≮|≯|≰|≱|≲|≳|≴|≵|≶|≷|≸|≹|≺|≻|≼|≽|≾|≿|⊀|⊁|⊃|⊅|⊇|⊉|⊋|⊏|⊐|⊑|⊒|⊜|⊩|⊬|⊮|⊰|⊱|⊲|⊳|⊴|⊵|⊶|⊷|⋍|⋐|⋑|⋕|⋖|⋗|⋘|⋙|⋚|⋛|⋜|⋝|⋞|⋟|⋠|⋡|⋢|⋣|⋤|⋥|⋦|⋧|⋨|⋩|⋪|⋫|⋬|⋭|⋲|⋳|⋴|⋵|⋶|⋷|⋸|⋹|⋺|⋻|⋼|⋽|⋾|⋿|⟈|⟉|⟒|⦷|⧀|⧁|⧡|⧣|⧤|⧥|⩦|⩧|⩪|⩫|⩬|⩭|⩮|⩯|⩰|⩱|⩲|⩳|⩴|⩵|⩶|⩷|⩸|⩹|⩺|⩻|⩼|⩽|⩾|⩿|⪀|⪁|⪂|⪃|⪄|⪅|⪆|⪇|⪈|⪉|⪊|⪋|⪌|⪍|⪎|⪏|⪐|⪑|⪒|⪓|⪔|⪕|⪖|⪗|⪘|⪙|⪚|⪛|⪜|⪝|⪞|⪟|⪠|⪡|⪢|⪣|⪤|⪥|⪦|⪧|⪨|⪩|⪪|⪫|⪬|⪭|⪮|⪯|⪰|⪱|⪲|⪳|⪴|⪵|⪶|⪷|⪸|⪹|⪺|⪻|⪼|⪽|⪾|⪿|⫀|⫁|⫂|⫃|⫄|⫅|⫆|⫇|⫈|⫉|⫊|⫋|⫌|⫍|⫎|⫏|⫐|⫑|⫒|⫓|⫔|⫕|⫖|⫗|⫘|⫙|⫷|⫸|⫹|⫺|⊢|⊣|\|>|<\||:|\.\.|\+|-|⊕|⊖|⊞|⊟|\.\+|\.-|\+\+|\||∪|∨|\$|⊔|±|∓|∔|∸|≂|≏|⊎|⊻|⊽|⋎|⋓|⧺|⧻|⨈|⨢|⨣|⨤|⨥|⨦|⨧|⨨|⨩|⨪|⨫|⨬|⨭|⨮|⨹|⨺|⩁|⩂|⩅|⩊|⩌|⩏|⩐|⩒|⩔|⩖|⩗|⩛|⩝|⩡|⩢|⩣|<<|>>|>>>|\.<<|\.>>|\.>>>|\*|/|\./|÷|\.÷|%|⋅|∘|×|\.%|\.\*|\\|\.\\|&|∩|∧|⊗|⊘|⊙|⊚|⊛|⊠|⊡|⊓|∗|∙|∤|⅋|≀|⊼|⋄|⋆|⋇|⋉|⋊|⋋|⋌|⋏|⋒|⟑|⦸|⦼|⦾|⦿|⧶|⧷|⨇|⨰|⨱|⨲|⨳|⨴|⨵|⨶|⨷|⨸|⨻|⨼|⨽|⩀|⩃|⩄|⩋|⩍|⩎|⩑|⩓|⩕|⩘|⩚|⩜|⩞|⩟|⩠|⫛|⊍|▷|⨝|⟕|⟖|⟗|//|\.//|\^|\.\^|↑|↓|⇵|⟰|⟱|⤈|⤉|⤊|⤋|⤒|⤓|⥉|⥌|⥍|⥏|⥑|⥔|⥕|⥘|⥙|⥜|⥝|⥠|⥡|⥣|⥥|⥮|⥯|↑|↓|::|\.)' 0:operator -} + +define-command -hidden julia-indent-on-new-line %< + evaluate-commands -draft -itersel %< + # preserve previous line indent + try %{ execute-keys -draft <semicolon> K <a-&> } + # preserve previous line comment + try %{ execute-keys -draft k x <a-k> ^\h*# <ret> j i <#> <space> } + # cleanup trailing whitespaces from previous line + try %{ execute-keys -draft k x s \h+$ <ret> d } + # indent after block start + try %{ execute-keys -draft , k x <a-k> ^\h*(struct|macro|function|begin|if|try|for|while|let|quote|do) <ret> <a-K> end$ <ret> j <a-gt> } + # deindent closing brace/bracket when after cursor (for arrays and dictionaries) + try %< execute-keys -draft x <a-k> ^\h*[}\]] <ret> gh / [}\]] <ret> m <a-S> 1<a-&> > + > +> + +define-command -hidden julia-insert-on-new-line %< + evaluate-commands -draft -itersel %< + # deindent on end + try %{ execute-keys -draft x <a-k> ^\h*end <ret> <lt> } + > +> +§ diff --git a/rc/tools/make.kak b/rc/tools/make.kak index 7de2051d..9bc71c2e 100644 --- a/rc/tools/make.kak +++ b/rc/tools/make.kak @@ -62,8 +62,9 @@ define-command -hidden make-jump %{ set-option buffer jump_current_line %val{cursor_line} set-register a "%reg{1}/%reg{2}" "%reg{3}" "%reg{4}" "%reg{5}" } catch %{ - set-register / %opt{make_error_pattern} - execute-keys <a-h><a-l> s<ret>l + # check if error pattern matches exactly at the start of the current line, possibly spanning more lines + set-register / "\A%opt{make_error_pattern}" + execute-keys ghGe s<ret>l set-option buffer jump_current_line %val{cursor_line} set-register a "%reg{1}" "%reg{2}" "%reg{3}" "%reg{4}" } @@ -73,11 +74,12 @@ define-command -hidden make-jump %{ } define-command -hidden make-select-next %{ set-register / %opt{make_error_pattern} - execute-keys "%opt{jump_current_line}ggl" "/<ret>" + # go to the current line end, search and go to the start of selection + execute-keys "%opt{jump_current_line}ggl" "/<ret><a-;>;" } define-command -hidden make-select-previous %{ set-register / %opt{make_error_pattern} - execute-keys "%opt{jump_current_line}g" "<a-/><ret>" + execute-keys "%opt{jump_current_line}g" "<a-/><ret><a-;>;" } define-command make-next-error -docstring %{alias for "jump-next *make*"} %{ diff --git a/rc/windowing/detection.kak b/rc/windowing/detection.kak index 2fde81cd..36b0398f 100644 --- a/rc/windowing/detection.kak +++ b/rc/windowing/detection.kak @@ -23,7 +23,7 @@ declare-option -docstring \ "Ordered list of windowing modules to try and load. An empty list disables both automatic module loading and environment detection, enabling complete manual control of the module loading." \ -str-list windowing_modules 'tmux' 'screen' 'zellij' 'kitty' 'iterm' 'appleterminal' 'sway' 'wayland' 'x11' 'wezterm' +str-list windowing_modules 'tmux' 'screen' 'zellij' 'kitty' 'iterm' 'appleterminal' 'sway' 'hyprland' 'wayland' 'x11' 'wezterm' declare-option -docstring %{ windowing module to use in the 'terminal' command diff --git a/rc/windowing/hyprland.kak b/rc/windowing/hyprland.kak new file mode 100644 index 00000000..27ad16c2 --- /dev/null +++ b/rc/windowing/hyprland.kak @@ -0,0 +1,45 @@ +# https://hypr.land +provide-module hyprland %{ + +# Ensure we're actually in Hyprland +evaluate-commands %sh{ + [ -z "${kak_opt_windowing_modules}" ] || + [ "$XDG_CURRENT_DESKTOP" == "Hyprland" ] && + [ -n "$HYPRLAND_INSTANCE_SIGNATURE" ] || + echo 'fail hyprland not detected' +} + +require-module 'wayland' + +alias global hyprland-terminal-window wayland-terminal-window + +define-command hyprland-focus-pid -hidden %{ + evaluate-commands %sh{ + pid=$kak_client_pid + while [ "$(hyprctl dispatch focuswindow pid:$pid)" != "ok" ]; do + pid=$(ps -p $pid -o ppid= | tr -d " ") + if [ -z $pid ] || [ $pid -le 1 ]; then + echo "fail Can't find PID for Sway window to focus" + break + fi + done + } +} + +define-command hyprland-focus -params ..1 -docstring ' +hyprland-focus [<kakoune_client>]: focus a given client''s window. +If no client is passed, then the current client is used' \ +%{ + evaluate-commands %sh{ + if [ $# -eq 1 ]; then + printf "evaluate-commands -client '%s' hyprland-focus-pid" "$1" + else + echo hyprland-focus-pid + fi + } +} +complete-command -menu hyprland-focus client + +alias global focus hyprland-focus + +} diff --git a/src/buffer.hh b/src/buffer.hh index 2bcd8cc3..b256b8ef 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -224,6 +224,10 @@ public: Type type; BufferCoord begin; BufferCoord end; + + #ifdef KAK_DEBUG + bool operator==(const Change&) const = default; + #endif }; ConstArrayView<Change> changes_since(size_t timestamp) const; diff --git a/src/buffer_utils.cc b/src/buffer_utils.cc index 994dbd23..dca1a372 100644 --- a/src/buffer_utils.cc +++ b/src/buffer_utils.cc @@ -290,7 +290,8 @@ Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, AutoScroll kak_assert(m_buffer.flags() & Buffer::Flags::Fifo); close_fd(); m_buffer.run_hook_in_own_context(Hook::BufCloseFifo, ""); - m_buffer.flags() &= ~(Buffer::Flags::Fifo | Buffer::Flags::NoUndo); + if (not m_buffer.values().contains(fifo_watcher_id)) + m_buffer.flags() &= ~(Buffer::Flags::Fifo | Buffer::Flags::NoUndo); } void read_fifo() diff --git a/src/client.cc b/src/client.cc index 2cb1c973..14b7b7bc 100644 --- a/src/client.cc +++ b/src/client.cc @@ -194,6 +194,9 @@ void Client::change_buffer(Buffer& buffer, Optional<FunctionRef<void()>> set_sel if (m_buffer_reload_dialog_opened) close_buffer_reload_dialog(); + if (context().buffer().flags() & Buffer::Flags::Locked) + throw runtime_error("Changing buffer is not allowed while current buffer is locked"); + buffer.flags() |= Buffer::Flags::Locked; OnScopeEnd unlock{[&] { buffer.flags() &= ~Buffer::Flags::Locked; }}; diff --git a/src/client_manager.cc b/src/client_manager.cc index 2e7d76e7..8d4f2de0 100644 --- a/src/client_manager.cc +++ b/src/client_manager.cc @@ -67,17 +67,18 @@ Client* ClientManager::create_client(std::unique_ptr<UserInterface>&& ui, int pi m_clients.emplace_back(client); auto& context = client->context(); - if (buffer->name() == "*scratch*") + if (context.buffer().name() == "*scratch*") context.print_status({"This *scratch* buffer won't be automatically saved", context.faces()["Information"]}); if (init_coord) { - auto& selections = context.selections_write_only(); - selections = SelectionList(*buffer, buffer->clamp(*init_coord)); + context.selections_write_only() = SelectionList(*buffer, context.buffer().clamp(*init_coord)); context.window().center_line(init_coord->line); } + unlock.trigger(); + try { context.hooks().run_hook(Hook::ClientCreate, context.name(), context); @@ -86,8 +87,7 @@ Client* ClientManager::create_client(std::unique_ptr<UserInterface>&& ui, int pi catch (Kakoune::runtime_error& error) { context.print_status({error.what().str(), context.faces()["Error"]}); - context.hooks().run_hook(Hook::RuntimeError, error.what(), - context); + context.hooks().run_hook(Hook::RuntimeError, error.what(), context); } // Do not return the client if it already got moved to the trash diff --git a/src/hash_map.hh b/src/hash_map.hh index eef082dd..60ebc6ab 100644 --- a/src/hash_map.hh +++ b/src/hash_map.hh @@ -274,6 +274,7 @@ struct HashMap int index = find_index(key, hash); if (index >= 0) { + [[maybe_unused]] Item keepalive = std::move(m_items[index]); m_items.erase(m_items.begin() + index); m_index.remove(hash, index); m_index.ordered_fix_entries(index); @@ -287,7 +288,8 @@ struct HashMap int index = find_index(key, hash); if (index >= 0) { - constexpr_swap(m_items[index], m_items.back()); + [[maybe_unused]] Item keepalive = std::move(m_items[index]); + m_items[index] = std::move(m_items.back()); m_items.pop_back(); m_index.remove(hash, index); if (index != m_items.size()) @@ -296,7 +298,7 @@ struct HashMap } template<typename KeyType> requires IsHashCompatible<Key, KeyType> - constexpr void erase(const KeyType& key) { unordered_remove(key); } + constexpr void erase(const KeyType& key) { return unordered_remove(key); } template<typename KeyType> requires IsHashCompatible<Key, KeyType> constexpr void remove_all(const KeyType& key) @@ -305,6 +307,7 @@ struct HashMap for (int index = find_index(key, hash); index >= 0; index = find_index(key, hash)) { + [[maybe_unused]] Item keepalive = std::move(m_items[index]); m_items.erase(m_items.begin() + index); m_index.remove(hash, index); m_index.ordered_fix_entries(index); diff --git a/src/highlighter.hh b/src/highlighter.hh index 73520c53..fbeef9e4 100644 --- a/src/highlighter.hh +++ b/src/highlighter.hh @@ -22,11 +22,12 @@ using BufferRange = Range<BufferCoord>; enum class HighlightPass { - Wrap = 1 << 0, - Move = 1 << 1, - Colorize = 1 << 2, + Replace = 1 << 0, + Wrap = 1 << 1, + Move = 1 << 2, + Colorize = 1 << 3, - All = Wrap | Move | Colorize, + All = Replace | Wrap | Move | Colorize, }; constexpr bool with_bit_ops(Meta::Type<HighlightPass>) { return true; } @@ -43,11 +44,8 @@ struct DisplaySetup LineCount line_count; ColumnCount first_column; ColumnCount widget_columns; - // Position of the cursor in the window - DisplayCoord cursor_pos; // Offset of line and columns that must remain visible around cursor DisplayCoord scroll_offset; - bool ensure_cursor_visible; }; using HighlighterIdList = ConstArrayView<StringView>; diff --git a/src/highlighters.cc b/src/highlighters.cc index 25f93245..ff46081a 100644 --- a/src/highlighters.cc +++ b/src/highlighters.cc @@ -601,7 +601,7 @@ struct WrapHighlighter : Highlighter static constexpr StringView ms_id = "wrap"; - struct SplitPos{ ByteCount byte; ColumnCount column; }; + struct SplitPos{ DisplayLine::iterator atom_it; ByteCount byte; ColumnCount column; }; void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override { @@ -613,42 +613,27 @@ struct WrapHighlighter : Highlighter return; const Buffer& buffer = context.context.buffer(); - const auto& cursor = context.context.selections().main().cursor(); const int tabstop = context.context.options()["tabstop"].get<int>(); - const LineCount win_height = context.context.window().dimensions().line; const ColumnCount marker_len = zero_if_greater(m_marker.column_length(), wrap_column); const Face face_marker = context.context.faces()["WrapMarker"]; for (auto it = display_buffer.lines().begin(); it != display_buffer.lines().end(); ++it) { - const LineCount buf_line = it->range().begin.line; - const ByteCount line_length = buffer[buf_line].length(); const ColumnCount indent = m_preserve_indent ? - zero_if_greater(line_indent(buffer, tabstop, buf_line), wrap_column) : 0_col; + zero_if_greater(line_indent(buffer, tabstop, it->range().begin.line), wrap_column) : 0_col; const ColumnCount prefix_len = std::max(marker_len, indent); - auto pos = next_split_pos(buffer, wrap_column, prefix_len, tabstop, buf_line, {0, 0}); - if (pos.byte == line_length) - continue; - - for (auto atom_it = it->begin(); - pos.byte != line_length and atom_it != it->end(); ) + SplitPos pos{it->begin(), 0, 0}; ; + while (next_split_pos(pos, it->end(), wrap_column, prefix_len)) { - const BufferCoord coord{buf_line, pos.byte}; - if (!atom_it->has_buffer_range() or - coord < atom_it->begin() or coord >= atom_it->end()) - { - ++atom_it; - continue; - } - auto& line = *it; - if (coord > atom_it->begin()) - atom_it = ++line.split(atom_it, coord); + if (pos.byte > 0 and pos.atom_it->type() == DisplayAtom::Range) + pos.atom_it = ++line.split(pos.atom_it, pos.atom_it->begin() + BufferCoord{0, pos.byte}); - DisplayLine new_line{ AtomList{ atom_it, line.end() } }; - line.erase(atom_it, line.end()); + auto coord = pos.atom_it->begin(); + DisplayLine new_line{ AtomList{ pos.atom_it, line.end() } }; + line.erase(pos.atom_it, line.end()); if (marker_len != 0) new_line.insert(new_line.begin(), {m_marker, face_marker}); @@ -658,23 +643,13 @@ struct WrapHighlighter : Highlighter it->replace(String{' ', indent - marker_len}); } - if (it+1 - display_buffer.lines().begin() == win_height) + it = display_buffer.lines().insert(it+1, new_line); + pos = SplitPos{it->begin(), 0, 0}; + if (pos.atom_it->type() != DisplayAtom::Range) // avoid infinite loop trying to split too long non-buffer ranges { - if (cursor >= new_line.range().begin) // strip first lines if cursor is not visible - { - display_buffer.lines().erase(display_buffer.lines().begin(), display_buffer.lines().begin()+1); - --it; - } - else - { - display_buffer.lines().erase(it+1, display_buffer.lines().end()); - return; - } + pos.column += pos.atom_it->content().column_length(); + ++pos.atom_it; } - it = display_buffer.lines().insert(it+1, new_line); - - pos = next_split_pos(buffer, wrap_column - prefix_len, prefix_len, tabstop, buf_line, pos); - atom_it = it->begin(); } } } @@ -688,80 +663,9 @@ struct WrapHighlighter : Highlighter if (wrap_column <= 0) return; - const Buffer& buffer = context.context.buffer(); - const auto& cursor = context.context.selections().main().cursor(); - const int tabstop = context.context.options()["tabstop"].get<int>(); - - auto line_wrap_count = [&](LineCount line, ColumnCount prefix_len) { - LineCount count = 0; - const ByteCount line_length = buffer[line].length(); - SplitPos pos{0, 0}; - while (true) - { - pos = next_split_pos(buffer, wrap_column - (pos.byte == 0 ? 0_col : prefix_len), - prefix_len, tabstop, line, pos); - if (pos.byte == line_length) - break; - ++count; - } - return count; - }; - - const auto win_height = context.context.window().dimensions().line; - // Disable horizontal scrolling when using a WrapHighlighter setup.first_column = 0; - setup.line_count = 0; setup.scroll_offset.column = 0; - - const ColumnCount marker_len = zero_if_greater(m_marker.column_length(), wrap_column); - - for (auto buf_line = setup.first_line, win_line = 0_line; - win_line < win_height or (setup.ensure_cursor_visible and buf_line <= cursor.line); - ++buf_line, ++setup.line_count) - { - if (buf_line >= buffer.line_count()) - break; - - const ColumnCount indent = m_preserve_indent ? - zero_if_greater(line_indent(buffer, tabstop, buf_line), wrap_column) : 0_col; - const ColumnCount prefix_len = std::max(marker_len, indent); - - if (buf_line == cursor.line) - { - SplitPos pos{0, 0}; - for (LineCount count = 0; true; ++count) - { - auto next_pos = next_split_pos(buffer, wrap_column - (pos.byte != 0 ? prefix_len : 0_col), - prefix_len, tabstop, buf_line, pos); - if (next_pos.byte > cursor.column) - { - setup.cursor_pos = DisplayCoord{ - win_line + count, - get_column(buffer, tabstop, cursor) - - pos.column + (pos.byte != 0 ? indent : 0_col) - }; - break; - } - pos = next_pos; - } - } - const auto wrap_count = line_wrap_count(buf_line, prefix_len); - win_line += wrap_count + 1; - - // scroll window to keep cursor visible, and update range as lines gets removed - while (setup.ensure_cursor_visible and - buf_line >= cursor.line and setup.first_line < cursor.line and - setup.cursor_pos.line + setup.scroll_offset.line >= win_height) - { - auto remove_count = 1 + line_wrap_count(setup.first_line, indent); - ++setup.first_line; - --setup.line_count; - setup.cursor_pos.line -= std::min(win_height, remove_count); - win_line -= remove_count; - kak_assert(setup.cursor_pos.line >= 0); - } - } } void fill_unique_ids(Vector<StringView>& unique_ids) const override @@ -769,79 +673,78 @@ struct WrapHighlighter : Highlighter unique_ids.push_back(ms_id); } - SplitPos next_split_pos(const Buffer& buffer, ColumnCount wrap_column, ColumnCount prefix_len, - int tabstop, LineCount line, SplitPos current) const + bool next_split_pos(SplitPos& pos, DisplayLine::iterator line_end, + ColumnCount wrap_column, ColumnCount prefix_len) const { - const ColumnCount target_column = current.column + wrap_column; - StringView content = buffer[line]; - - SplitPos pos = current; - SplitPos last_word_boundary = {0, 0}; - SplitPos last_WORD_boundary = {0, 0}; + SplitPos last_word_boundary{}; + SplitPos last_WORD_boundary{}; - auto update_boundaries = [&](Codepoint cp) { - if (not m_word_wrap) - return; - if (!is_word<Word>(cp)) + auto update_word_boundaries = [&](Codepoint cp) { + if (m_word_wrap and not is_word<Word>(cp)) last_word_boundary = pos; - if (!is_word<WORD>(cp)) + if (m_word_wrap and not is_word<WORD>(cp)) last_WORD_boundary = pos; }; - while (pos.byte < content.length() and pos.column < target_column) + while (pos.atom_it != line_end and pos.column < wrap_column) { - if (content[pos.byte] == '\t') + auto content = pos.atom_it->content(); + const char* it = &content[pos.byte]; + const Codepoint cp = utf8::read_codepoint(it, content.end()); + const ColumnCount width = codepoint_width(cp); + if (pos.column + width > wrap_column) // the target column was in the char { - const ColumnCount next_column = (pos.column / tabstop + 1) * tabstop; - if (next_column > target_column and pos.byte != current.byte) // the target column was in the tab - break; - pos.column = next_column; - ++pos.byte; - last_word_boundary = last_WORD_boundary = pos; + update_word_boundaries(cp); + break; } - else + pos.column += width; + pos.byte = (int)(it - content.begin()); + update_word_boundaries(cp); + if (it == content.end()) { - const char* it = &content[pos.byte]; - const Codepoint cp = utf8::read_codepoint(it, content.end()); - const ColumnCount width = codepoint_width(cp); - if (pos.column + width > target_column and pos.byte != current.byte) // the target column was in the char - { - update_boundaries(cp); - break; - } - pos.column += width; - pos.byte = (int)(it - content.begin()); - update_boundaries(cp); + ++pos.atom_it; + pos.byte = 0; + // Thanks to the pass ordering, atom boundary should always be reasonable word split points + last_word_boundary = pos; + last_WORD_boundary = pos; } } + if (pos.atom_it == line_end) + return false; + auto content = pos.atom_it->content(); if (m_word_wrap and pos.byte < content.length()) { - auto find_split_pos = [&](SplitPos start_pos, auto is_word) -> Optional<SplitPos> { - if (start_pos.byte == 0) - return {}; + auto find_split_pos = [&](SplitPos start_pos, auto is_word) { + if (start_pos.column == 0) + return false; const char* it = &content[pos.byte]; // split at current position if is a word boundary if (not is_word(utf8::codepoint(it, content.end()), {'_'})) - return pos; + return true; // split at last word boundary if the word is shorter than our wrapping width ColumnCount word_length = pos.column - start_pos.column; - while (it != content.end() and word_length <= (wrap_column - prefix_len)) + ColumnCount max_word_length = wrap_column - prefix_len; + while (it != content.end() and word_length <= max_word_length) { const Codepoint cp = utf8::read_codepoint(it, content.end()); if (not is_word(cp, {'_'})) - return start_pos; + break; word_length += codepoint_width(cp); } - return {}; + if (word_length <= max_word_length) + { + pos = start_pos; + return true; + } + return false; }; - if (auto split = find_split_pos(last_WORD_boundary, is_word<WORD>)) - return *split; - if (auto split = find_split_pos(last_word_boundary, is_word<Word>)) - return *split; + if (find_split_pos(last_WORD_boundary, is_word<WORD>) or + find_split_pos(last_word_boundary, is_word<Word>)) + return true; } - return pos; + return true; } static ColumnCount line_indent(const Buffer& buffer, int tabstop, LineCount line) @@ -876,7 +779,7 @@ constexpr StringView WrapHighlighter::ms_id; struct TabulationHighlighter : Highlighter { - TabulationHighlighter() : Highlighter{HighlightPass::Move} {} + TabulationHighlighter() : Highlighter{HighlightPass::Replace} {} void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override { @@ -947,7 +850,6 @@ struct TabulationHighlighter : Highlighter const ColumnCount offset = std::max(column + width - win_end, 0_col); setup.first_column += offset; - setup.cursor_pos.column -= offset; } }; @@ -968,7 +870,7 @@ const HighlighterDesc show_whitespace_desc = { struct ShowWhitespacesHighlighter : Highlighter { ShowWhitespacesHighlighter(String tab, String tabpad, String spc, String lf, String nbsp, String indent, bool only_trailing) - : Highlighter{HighlightPass::Move}, m_tab{std::move(tab)}, m_tabpad{std::move(tabpad)}, + : Highlighter{HighlightPass::Replace}, m_tab{std::move(tab)}, m_tabpad{std::move(tabpad)}, m_spc{std::move(spc)}, m_lf{std::move(lf)}, m_nbsp{std::move(nbsp)}, m_indent{std::move(indent)}, m_only_trailing{std::move(only_trailing)} {} @@ -1641,7 +1543,7 @@ const HighlighterDesc replace_ranges_desc = { "each spec is interpreted as a display line to display in place of the range", {} }; -struct ReplaceRangesHighlighter : OptionBasedHighlighter<RangeAndStringList, ReplaceRangesHighlighter, HighlightPass::Move> +struct ReplaceRangesHighlighter : OptionBasedHighlighter<RangeAndStringList, ReplaceRangesHighlighter, HighlightPass::Replace> { using ReplaceRangesHighlighter::OptionBasedHighlighter::OptionBasedHighlighter; private: @@ -1694,7 +1596,6 @@ private: auto& buffer = context.context.buffer(); auto& sels = context.context.selections(); auto& range_and_faces = get_option(context); - const int tabstop = context.context.options()["tabstop"].get<int>(); update_ranges(buffer, range_and_faces.prefix, range_and_faces.list); range_and_faces.prefix = buffer.timestamp(); @@ -1707,19 +1608,7 @@ private: if (range.first.line < setup.first_line and last.line >= setup.first_line) setup.first_line = range.first.line; - const auto& cursor = context.context.selections().main().cursor(); - if (cursor.line == last.line and cursor.column >= last.column) - { - auto first_column = get_column(buffer, tabstop, range.first); - auto last_column = get_column(buffer, tabstop, last); - auto replacement = parse_display_line(spec, context.context.faces()); - auto cursor_move = replacement.length() - ((range.first.line == last.line) ? last_column - first_column : last_column); - setup.cursor_pos.line -= last.line - range.first.line; - setup.cursor_pos.column += cursor_move; - } - - if (setup.ensure_cursor_visible and - last.line >= setup.first_line and + if (last.line >= setup.first_line and range.first.line <= setup.first_line + setup.line_count and range.first.line != last.line) { diff --git a/src/main.cc b/src/main.cc index fb78669d..60c0669e 100644 --- a/src/main.cc +++ b/src/main.cc @@ -66,110 +66,15 @@ struct { "» custom completions are no longer sorted if the typed text is empty\n" "» {+u}terminal{} now selects implementation based on windowing options\n" "» {+u}local{} scopes\n" - }, { - 20230805, - "» Fix FreeBSD/MacOS clang compilation\n" - }, { - 20230729, - "» {+b}<a-u>{} and {+b}<a-U>{} now undo/redo selection changes; " - "the previous meaning of moving in history tree has been moved to " - "{+b}<c-j>{} and {+b}<c-k>{}\n" - "» {+u}%exp\\{...}{} expansions provide flexible quoting for expanded " - "strings (as double quoted strings)\n" - "» {+u}show-matching -previous{} switch\n" - "» {+b}<c-g>{} to cancel current operation\n" - }, { - 20221031, - "» {+b}<esc>{} does not end macro recording anymore, use {+b}Q{}\n" - "» pipe commands do not append final end-of-lines anymore\n" - "» {+u}complete-command{} to configure command completion\n" - "» {+b}p{}, {+b}P{}, {+b}!{} and {+b}<a-!>{} now select the inserted text\n" - "» {+b}x{} now uses {+b}<a-x>{} behaviour\n" - "» {+b}<space>{} and {+b},{} have been swapped\n" - }, { - 20211107, - "» colored and curly underlines support (undocumented in 20210828)\n" - }, { - 20211028, - "» {+b}g{} and {+b}v{} do not auto-convert to lowercase anymore\n" - }, { - 20210828, - "» {+u}write <filename>{} will refuse to overwrite without {+u}-force{}\n" - "» {+u}$kak_command_fifo{} support\n" - "» {+u}set-option -remove{} support\n" - "» prompts auto select {+i}menu{} completions on space\n" - "» explicit completion support ({+b}<c-x>...{}) in prompts\n" - "» {+u}write -atomic{} was replaced with {+u}write -method <method>{}\n" - }, { - 20200901, - "» daemon mode does not fork anymore\n" - }, { - 20200804, - "» {+u}User{} hook support\n" - "» Removed {+i}bold{} and {+i}italic{} faces from colorschemes\n" - "» Read from stdin support for clients\n" - "» {+u}rgba:RRGGBBAA{} faces and alpha blending\n" - }, { - 20200116, - "» {+u}InsertCompletionHide{} parameter is now the list of inserted ranges\n" - }, { - 20191210, - "» {+u}ModeChange{} parameter has changed to contain push/pop\n" - "» {+ui}Mode{+u}Begin{}/{+ui}Mode{+u}End{} hooks were removed\n" - }, { - 20190701, - "» {+u}%file\\{<filename>}{} expansions to read files\n" - "» {+u}echo -to-file <filename>{} to write to file\n" - "» completions option have an on select command instead of " - "a docstring\n" - "» Function key syntax do not accept lower case f anymore\n" - "» shell quoting of list options is now opt-in with " - "{+u}$kak_quoted_...{}\n" - }, { - 20190120, - "» named capture groups in regex\n" - "» auto_complete option renamed to autocomplete\n" - }, { - 20181027, - "» {+u}define-commands{} {+i}-shell-completion{} and {+i}-shell-candidates{} " - "has been renamed\n" - "» exclusive face attributes is replaced with final " - "(fg/bg/attr)\n" - "» {+b}<a-M>{} (merge consecutive) moved to {+b}<a-_>{} to make {+b}<a-M>{} " - "backward {+b}<a-m>{}\n" - "» {+u}remove-hooks{} now takes a regex parameter\n" - }, { - 20180904, - "» Big breaking refactoring of various Kakoune features, " - "configuration might need to be updated see `:doc changelog` " - "for details\n" - "» {+u}define-command{} {+i}-allow-override{} switch has been renamed " - "{+i}-override{}\n" - }, { - 20180413, - "» {+u}ModeChange{} hook has been introduced and is expected " - "to replace the various {+ui}Mode{+u}Begin{}/{+ui}Mode{+u}End{} hooks, " - "consider those deprecated.\n" - "» {+b}*{} Does not strip whitespaces anymore, use built-in " - "{+b}_{} to strip them\n" - "» {+b}l{} on eol will go to next line, {+b}h{} on first char will " - "go to previous\n" - "» selections merging behaviour is now a bit more complex " - "again\n" - "» {+b}x{} will only jump to next line if full line is already " - "selected\n" - "» {+i}WORD{} text object moved to {+b}<a-w>{} instead of {+b}W{} for " - "consistency\n" - "» rotate main selection moved to {+b}){}, rotate content to {+b}<a-)>{}, " - "{+b}({} for backward\n" - "» faces are now scoped, {+u}set-face{} command takes an additional " - "scope parameter\n" - "» {+b}<backtab>{} key is gone, use {+b}<s-tab>{} instead\n" -} }; + } +}; + +static_assert(sizeof(version_notes) / sizeof(version_notes[0]) <= 4 - (version_notes[0].version != 0 ? 1 : 0), + "Maximum 3 versions should be displayed in version notes, not including the development version"); void show_startup_info(Client* local_client, int last_version) { - const Face version_face{Color::Default, Color::Default, Attribute::Bold}; + const Face version_face{.attributes=Attribute::Bold}; DisplayLineList info; for (auto [version, notes] : version_notes) { @@ -183,20 +88,17 @@ void show_startup_info(Client* local_client, int last_version) const auto year = version / 10000; const auto month = (version / 100) % 100; const auto day = version % 100; - info.push_back({format("• Kakoune v{}.{}{}.{}{}", - year, month < 10 ? "0" : "", month, day < 10 ? "0" : "", day), - version_face}); + info.push_back({format("• Kakoune v{}.{:02}.{:02}", year, month, day), version_face}); } for (auto&& line : notes | split<StringView>('\n')) info.push_back(parse_display_line(line, GlobalScope::instance().faces())); } if (not info.empty()) - { - info.push_back({"See the `:doc options startup-info` to control this message", - Face{Color::Default, Color::Default, Attribute::Italic}}); - local_client->info_show({format("Kakoune {}", version), version_face}, info, {}, InfoStyle::Prompt); - } + local_client->info_show({{{format("Kakoune {}", version), version_face}, + {", more info at ", {}}, + {":doc changelog", {.attributes=Attribute::Underline}}}}, + std::move(info), {}, InfoStyle::Prompt); } inline void write_stdout(StringView str) { try { write(STDOUT_FILENO, str); } catch (runtime_error&) {} } @@ -611,6 +513,7 @@ void register_options() " terminal_shift_function_key int\n" " terminal_padding_char codepoint\n" " terminal_padding_fill bool\n" + " terminal_cursor_native bool\n" " terminal_info_max_width int\n", UserInterface::Options{}); reg.declare_option("modelinefmt", "format string used to generate the modeline", diff --git a/src/normal.cc b/src/normal.cc index a2a2504b..37bbbb2a 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -1,11 +1,14 @@ #include "normal.hh" +#include "array_view.hh" +#include "assert.hh" #include "buffer.hh" #include "buffer_manager.hh" #include "buffer_utils.hh" #include "changes.hh" #include "command_manager.hh" #include "context.hh" +#include "coord.hh" #include "diff.hh" #include "enum.hh" #include "face_registry.hh" @@ -19,6 +22,7 @@ #include "selectors.hh" #include "shell_manager.hh" #include "string.hh" +#include "units.hh" #include "user_interface.hh" #include "unit_tests.hh" #include "window.hh" @@ -537,8 +541,9 @@ void command(Context& context, NormalParams params) command(context, std::move(env_vars), params.reg); } -BufferCoord apply_diff(Buffer& buffer, BufferCoord pos, ArrayView<StringView> lines_before, StringView after) +BufferRange apply_diff(Buffer& buffer, BufferCoord pos, ArrayView<StringView> lines_before, StringView after) { + BufferCoord first = pos; const auto lines_after = after | split_after<StringView>('\n') | gather<Vector<StringView>>(); auto byte_count = [](auto&& lines, int first, int count) { @@ -546,30 +551,108 @@ BufferCoord apply_diff(Buffer& buffer, BufferCoord pos, ArrayView<StringView> li [](ByteCount l, StringView s) { return l + s.length(); }); }; + bool tried_to_erase_final_newline = false; for_each_diff(lines_before.begin(), (int)lines_before.size(), lines_after.begin(), (int)lines_after.size(), [&, posA = 0, posB = 0](DiffOp op, int len) mutable { switch (op) { case DiffOp::Keep: + kak_assert(not tried_to_erase_final_newline); pos = buffer.advance(pos, byte_count(lines_before, posA, len)); posA += len; posB += len; break; case DiffOp::Add: + if (buffer.is_end(pos)) + tried_to_erase_final_newline = false; pos = buffer.insert(pos, {lines_after[posB].begin(), lines_after[posB + len - 1].end()}).end; posB += len; break; case DiffOp::Remove: - pos = buffer.erase(pos, buffer.advance(pos, byte_count(lines_before, posA, len))); + { + kak_assert(not tried_to_erase_final_newline); + BufferCoord end = buffer.advance(pos, byte_count(lines_before, posA, len)); + tried_to_erase_final_newline |= buffer.is_end(end); + pos = buffer.erase(pos, end); posA += len; break; } + } }); - return pos; + if (tried_to_erase_final_newline) + { + first = std::min(first, buffer.back_coord()); + pos = buffer.erase(buffer.back_coord(), buffer.end_coord()); + } + return {first, pos}; } +UnitTest test_apply_diff{[] { + using Change = Buffer::Change; + auto validate = [&](LineCount line, + StringView new_text, + BufferRange expected_new_range, + ConstArrayView<Change> expected_buffer_changes) + { + Buffer buffer{"", Buffer::Flags::None, { + StringData::create("line1\n"), + StringData::create("line2\n"), + StringData::create("line3\n"), + }}; + const size_t timestamp = buffer.timestamp(); + Vector<StringView> old_text; + for (auto i = line; i < buffer.line_count(); i++) + old_text.push_back(buffer[i]); + BufferRange new_text_range = apply_diff(buffer, BufferCoord{line, 0}, old_text, new_text); + kak_assert(new_text_range == expected_new_range); + kak_assert(buffer.changes_since(timestamp) == expected_buffer_changes); + }; + // When appending at end, we add any missing newline + validate( + /*line=*/3, + "added-line3-missing-eol", + BufferRange{{3, 0}, {4, 0}}, + { + Change{Change::Insert, {3, 0}, {4, 0}}, + } + ); + // Special case: erasing until buffer end also erases the final newline. + validate( + /*line=*/2, + "", + BufferRange{{1, 5}, {1, 5}}, + { + Change{Change::Erase, {2, 0}, {3, 0}}, + } + ); + // Erasing and appending at end still produces forward-only changes. + validate( + /*line=*/2, + "changed-line3\n" + "added-line4\n" + "added-line5\n", + BufferRange{{2, 0}, {5, 0}}, + { + Change{Change::Erase, {2, 0}, {3, 0}}, + Change{Change::Insert, {2, 0}, {5, 0}}, + } + ); + // Same result when append is missing newline. + validate( + /*line=*/2, + "changed-line3\n" + "added-line4\n" + "added-line5-missing-eol", + BufferRange{{2, 0}, {5, 0}}, + { + Change{Change::Erase, {2, 0}, {3, 0}}, + Change{Change::Insert, {2, 0}, {5, 0}}, + } + ); +}}; + template<bool replace> void pipe(Context& context, NormalParams params) { @@ -628,12 +711,12 @@ void pipe(Context& context, NormalParams params) if (in_lines.back().back() != '\n' and not out.empty() and out.back() == '\n') out.resize(out.length()-1, 0); - auto new_end = apply_diff(buffer, first, in_lines, out); - if (new_end != first) + auto [new_first, new_end] = apply_diff(buffer, first, in_lines, out); + if (new_first != new_end) { auto& min = sel.min(); auto& max = sel.max(); - min = first; + min = new_first; max = buffer.char_prev(new_end); } else @@ -1612,6 +1695,7 @@ void replay_macro(Context& context, NormalParams params) auto keys = parse_keys(reg_val[0]); ScopedEdition edition(context); ScopedSelectionEdition selection_edition{context}; + ScopedSetBool disable_keymaps(context.keymaps_disabled()); do { for (auto& key : keys) diff --git a/src/ranges.hh b/src/ranges.hh index af13d6f8..f1eaffa3 100644 --- a/src/ranges.hh +++ b/src/ranges.hh @@ -546,10 +546,10 @@ auto find(Range&& range, const T& value) } template<typename Range, typename T> -auto find_if(Range&& range, T op) +auto find_if(Range&& range, T&& op) { using std::begin; using std::end; - return std::find_if(begin(range), end(range), op); + return std::find_if(begin(range), end(range), std::forward<T>(op)); } template<typename Range, typename T> @@ -560,24 +560,24 @@ bool contains(Range&& range, const T& value) } template<typename Range, typename T> -bool all_of(Range&& range, T op) +bool all_of(Range&& range, T&& op) { using std::begin; using std::end; - return std::all_of(begin(range), end(range), op); + return std::all_of(begin(range), end(range), std::forward<T>(op)); } template<typename Range, typename T> -bool any_of(Range&& range, T op) +bool any_of(Range&& range, T&& op) { using std::begin; using std::end; - return std::any_of(begin(range), end(range), op); + return std::any_of(begin(range), end(range), std::forward<T>(op)); } template<typename Range, typename T> -auto remove_if(Range&& range, T op) +auto remove_if(Range&& range, T&& op) { using std::begin; using std::end; - return std::remove_if(begin(range), end(range), op); + return std::remove_if(begin(range), end(range), std::forward<T>(op)); } template<typename Range, typename U> @@ -596,7 +596,7 @@ template<typename Range, typename Init, typename BinOp> Init accumulate(Range&& c, Init&& init, BinOp&& op) { using std::begin; using std::end; - return std::accumulate(begin(c), end(c), init, op); + return std::accumulate(begin(c), end(c), init, std::forward<BinOp>(op)); } template<typename Range, typename Compare, typename Func> @@ -632,36 +632,26 @@ auto gather() }}; } -template<typename ExceptionType, bool exact_size, size_t... Indexes> -auto elements() -{ - return ViewFactory{[=] (auto&& range) { - using std::begin; using std::end; - auto it = begin(range), end_it = end(range); - size_t i = 0; - auto elem = [&](size_t index) { - for (; i < index; ++i) - if (++it == end_it) throw ExceptionType{i}; - return *it; - }; - // Note that initializer lists elements are guaranteed to be sequenced - Array<std::remove_cvref_t<decltype(*begin(range))>, sizeof...(Indexes)> res{{elem(Indexes)...}}; - if (exact_size and ++it != end_it) - throw ExceptionType{++i}; - return res; - }}; -} - -template<typename ExceptionType, bool exact_size, size_t... Indexes> -auto static_gather_impl(std::index_sequence<Indexes...>) -{ - return elements<ExceptionType, exact_size, Indexes...>(); -} - template<typename ExceptionType, size_t size, bool exact_size = true> auto static_gather() { - return static_gather_impl<ExceptionType, exact_size>(std::make_index_sequence<size>()); + return []<size_t... Indexes>(std::index_sequence<Indexes...>) { + return ViewFactory{[] (auto&& range) { + using std::begin; using std::end; + auto it = begin(range), end_it = end(range); + size_t i = 0; + auto elem = [&](size_t index) { + for (; i < index; ++i) + if (++it == end_it) throw ExceptionType{i}; + return *it; + }; + // Note that initializer lists elements are guaranteed to be sequenced + Array<std::remove_cvref_t<decltype(*begin(range))>, sizeof...(Indexes)> res{{elem(Indexes)...}}; + if (exact_size and ++it != end_it) + throw ExceptionType{++i}; + return res; + }}; + }(std::make_index_sequence<size>()); } } diff --git a/src/regex_impl.cc b/src/regex_impl.cc index dd900125..8a46d833 100644 --- a/src/regex_impl.cc +++ b/src/regex_impl.cc @@ -505,6 +505,18 @@ private: parse_error("unclosed character class"); ++m_pos; + if (not character_class.ignore_case) + { + bool could_ignore_case = true; + for (const auto& [min, max] : character_class.ranges) + { + if (not contains(character_class.ranges, CharacterClass::Range{to_lower(min), to_lower(max)}) or + not contains(character_class.ranges, CharacterClass::Range{to_upper(min), to_upper(max)})) + could_ignore_case = false; + } + character_class.ignore_case = could_ignore_case; + } + if (character_class.ignore_case) { for (auto& range : character_class.ranges) @@ -521,7 +533,7 @@ private: if (character_class.ctypes == CharacterType::None and not character_class.negative and character_class.ranges.size() == 1 and character_class.ranges.front().min == character_class.ranges.front().max) - return add_node(ParsedRegex::Literal, character_class.ranges.front().min); + return add_node(ParsedRegex::Literal, character_class.ranges.front().min, {1,1}, character_class.ignore_case); if (character_class.ctypes != CharacterType::None and not character_class.negative and character_class.ranges.empty()) @@ -585,14 +597,14 @@ private: } } - NodeIndex add_node(ParsedRegex::Op op, Codepoint value = -1, ParsedRegex::Quantifier quantifier = {1, 1}) + NodeIndex add_node(ParsedRegex::Op op, Codepoint value = -1, ParsedRegex::Quantifier quantifier = {1, 1}, bool ignore_case = false) { constexpr auto max_nodes = std::numeric_limits<int16_t>::max(); const NodeIndex res = m_parsed_regex.nodes.size(); if (res == max_nodes) parse_error(format("regex parsed to more than {} ast nodes", max_nodes)); const NodeIndex next = res+1; - m_parsed_regex.nodes.push_back({op, m_flags & Flags::IgnoreCase, next, value, quantifier}); + m_parsed_regex.nodes.push_back({op, ignore_case or (m_flags & Flags::IgnoreCase), next, value, quantifier}); return res; } @@ -703,6 +715,7 @@ struct RegexCompiler private: template<RegexMode direction> + [[gnu::noinline]] OpIndex compile_node_inner(ParsedRegex::NodeIndex index) { auto& node = get_node(index); @@ -729,7 +742,15 @@ private: push_inst(CompiledRegex::AnyCharExceptNewLine); break; case ParsedRegex::CharClass: - push_inst(CompiledRegex::CharClass, {.character_class_index=int16_t(node.value)}); + if (auto& char_class = m_parsed_regex.character_classes[node.value]; + char_class.ranges.size() == 1 and char_class.ctypes == CharacterType::None and + char_class.ranges[0].max <= std::numeric_limits<uint8_t>::max()) + push_inst(CompiledRegex::CharRange, {.range={.min=uint8_t(char_class.ranges[0].min), + .max=uint8_t(char_class.ranges[0].max), + .ignore_case=char_class.ignore_case, + .negative=char_class.negative}}); + else + push_inst(CompiledRegex::CharClass, {.character_class_index=int16_t(node.value)}); break; case ParsedRegex::CharType: push_inst(CompiledRegex::CharType, {.character_type=CharacterType{(unsigned char)node.value}}); @@ -1107,12 +1128,18 @@ String dump_regex(const CompiledRegex& program) case CompiledRegex::AnyCharExceptNewLine: res += "anything but newline\n"; break; - case CompiledRegex::CharClass: - res += format("character class {}\n", inst.param.character_class_index); + case CompiledRegex::CharRange: + res += format("character range {}[{}{}-{}]\n", + inst.param.range.ignore_case ? "(ignore case) " : "", + inst.param.range.negative ? "^" : "", + inst.param.range.min, inst.param.range.max); break; case CompiledRegex::CharType: res += format("character type {}\n", to_underlying(inst.param.character_type)); break; + case CompiledRegex::CharClass: + res += format("character class {}\n", inst.param.character_class_index); + break; case CompiledRegex::Jump: res += format("jump {} ({:03})\n", inst.param.jump_offset, index + inst.param.jump_offset); break; @@ -1259,6 +1286,12 @@ auto test_regex = UnitTest{[]{ } { + TestVM<> vm{R"([aA])"}; + kak_assert(vm.exec("a")); + kak_assert(vm.exec("A")); + } + + { TestVM<> vm{R"(a{3,5}b)"}; kak_assert(not vm.exec("aab")); kak_assert(vm.exec("aaab")); diff --git a/src/regex_impl.hh b/src/regex_impl.hh index b04d99e7..8853ced5 100644 --- a/src/regex_impl.hh +++ b/src/regex_impl.hh @@ -76,8 +76,9 @@ struct CompiledRegex : UseMemoryDomain<MemoryDomain::Regex> Literal, AnyChar, AnyCharExceptNewLine, - CharClass, + CharRange, CharType, + CharClass, Jump, Split, Save, @@ -105,8 +106,15 @@ struct CompiledRegex : UseMemoryDomain<MemoryDomain::Regex> uint32_t codepoint : 24; bool ignore_case : 1; } literal; - int16_t character_class_index; + struct CharRange + { + uint8_t min; + uint8_t max; + bool ignore_case : 1; + bool negative; + } range; CharacterType character_type; + int16_t character_class_index; int16_t jump_offset; int16_t save_index; struct Split @@ -361,23 +369,20 @@ private: void step_current_thread(const Iterator& pos, Codepoint cp, uint16_t current_step, const ExecConfig& config) { Thread thread = m_threads.pop_current(); - auto failed = [this, &thread]() { - release_saves(thread.saves); - }; - auto consumed = [this, &thread]() { - m_threads.push_next(thread); - }; + auto failed = [&] { release_saves(thread.saves); }; + auto consumed = [&] { m_threads.push_next(thread); }; while (true) { - auto& inst = *thread.inst++; + auto* inst = thread.inst++; + auto [op, last_step, param] = *inst; // if this instruction was already executed for this step in another thread, // then this thread is redundant and can be dropped - if (inst.last_step == current_step) + if (last_step == current_step) return failed(); - inst.last_step = current_step; + inst->last_step = current_step; - switch (inst.op) + switch (op) { case CompiledRegex::Match: if ((pos != config.end and not (mode & RegexMode::Search)) or @@ -394,7 +399,7 @@ private: return; case CompiledRegex::Literal: if (pos != config.end and - inst.param.literal.codepoint == (inst.param.literal.ignore_case ? to_lower(cp) : cp)) + param.literal.codepoint == (param.literal.ignore_case ? to_lower(cp) : cp)) return consumed(); return failed(); case CompiledRegex::AnyChar: @@ -403,25 +408,31 @@ private: if (pos != config.end and cp != '\n') return consumed(); return failed(); - case CompiledRegex::CharClass: - if (pos != config.end and - m_program.character_classes[inst.param.character_class_index].matches(cp)) + case CompiledRegex::CharRange: + if (auto actual_cp = (param.range.ignore_case ? to_lower(cp) : cp); + pos != config.end and + (actual_cp >= param.range.min and actual_cp <= param.range.max) != param.range.negative) return consumed(); return failed(); case CompiledRegex::CharType: - if (pos != config.end and is_ctype(inst.param.character_type, cp)) + if (pos != config.end and is_ctype(param.character_type, cp)) + return consumed(); + return failed(); + case CompiledRegex::CharClass: + if (pos != config.end and + m_program.character_classes[param.character_class_index].matches(cp)) return consumed(); return failed(); case CompiledRegex::Jump: - thread.inst = &inst + inst.param.jump_offset; + thread.inst = inst + param.jump_offset; break; case CompiledRegex::Split: - if (auto* target = &inst + inst.param.split.offset; + if (auto* target = inst + param.split.offset; target->last_step != current_step) { if (thread.saves >= 0) ++m_saves[thread.saves].refcount; - if (not inst.param.split.prioritize_parent) + if (not param.split.prioritize_parent) std::swap(thread.inst, target); m_threads.push_current({target, thread.saves}); } @@ -436,23 +447,23 @@ private: --saves.refcount; thread.saves = new_saves<true>(saves.pos, saves.valid_mask); } - m_saves[thread.saves].pos[inst.param.save_index] = pos; - m_saves[thread.saves].valid_mask |= (1 << inst.param.save_index); + m_saves[thread.saves].pos[param.save_index] = pos; + m_saves[thread.saves].valid_mask |= (1 << param.save_index); break; case CompiledRegex::LineAssertion: - if (not (inst.param.line_start ? is_line_start(pos, config) : is_line_end(pos, config))) + if (not (param.line_start ? is_line_start(pos, config) : is_line_end(pos, config))) return failed(); break; case CompiledRegex::SubjectAssertion: - if (pos != (inst.param.subject_begin ? config.subject_begin : config.subject_end)) + if (pos != (param.subject_begin ? config.subject_begin : config.subject_end)) return failed(); break; case CompiledRegex::WordBoundary: - if (is_word_boundary(pos, config) != inst.param.word_boundary_positive) + if (is_word_boundary(pos, config) != param.word_boundary_positive) return failed(); break; case CompiledRegex::LookAround: - if (lookaround(inst.param.lookaround, pos, config) != inst.param.lookaround.positive) + if (lookaround(param.lookaround, pos, config) != param.lookaround.positive) return failed(); break; } @@ -691,13 +702,13 @@ private: void ensure_initial_capacity() { - if (m_capacity != 0) + if (m_capacity_mask != 0) return; - constexpr int32_t initial_capacity = 64 / sizeof(Thread); - static_assert(initial_capacity >= 4); + constexpr uint32_t initial_capacity = 64 / sizeof(Thread); + static_assert(initial_capacity >= 4 and std::has_single_bit(initial_capacity)); m_data.reset(new Thread[initial_capacity]); - m_capacity = initial_capacity; + m_capacity_mask = initial_capacity-1; } [[gnu::always_inline]] @@ -710,41 +721,41 @@ private: [[gnu::noinline]] void grow(bool pushed_current) { - const auto new_capacity = m_capacity * 2; + auto capacity = m_capacity_mask + 1; + const auto new_capacity = capacity * 2; Thread* new_data = new Thread[new_capacity]; Thread* old_data = m_data.get(); - std::rotate_copy(old_data, old_data + m_current, old_data + m_capacity, new_data); - m_next_begin -= m_current; - if ((pushed_current and m_next_begin == 0) or m_next_begin < 0) - m_next_begin += m_capacity; - m_next_end = m_capacity; + std::rotate_copy(old_data, old_data + m_current, old_data + capacity, new_data); + m_next_begin = (m_next_begin - m_current) & m_capacity_mask; + if (pushed_current and m_next_begin == 0) + m_next_begin = capacity; + m_next_end = capacity; m_current = 0; m_data.reset(new_data); - m_capacity = new_capacity; + kak_assert(std::has_single_bit(new_capacity)); + m_capacity_mask = new_capacity-1; } private: - int32_t decrement(int32_t& index) + uint32_t decrement(uint32_t& index) { - if (index == 0) - index = m_capacity; - return --index; + index = (index - 1) & m_capacity_mask; + return index; } - int32_t post_increment(int32_t& index) + uint32_t post_increment(uint32_t& index) { auto res = index; - if (++index == m_capacity) - index = 0; + index = (index + 1) & m_capacity_mask; return res; } std::unique_ptr<Thread[]> m_data; - int32_t m_capacity = 0; // Maximum capacity should be 2*instruction count, so 65536 - int32_t m_current = 0; - int32_t m_next_begin = 0; - int32_t m_next_end = 0; + uint32_t m_capacity_mask = 0; // Maximum capacity should be 2*instruction count, so 65536 + uint32_t m_current = 0; + uint32_t m_next_begin = 0; + uint32_t m_next_end = 0; }; static constexpr bool forward = mode & RegexMode::Forward; diff --git a/src/string.cc b/src/string.cc index 353a9f4e..551add2d 100644 --- a/src/string.cc +++ b/src/string.cc @@ -118,6 +118,21 @@ void String::Data::clear() set_empty(); } +String::String(Codepoint cp, CharCount count) +{ + reserve(utf8::codepoint_size(cp) * (int)count); + while (count-- > 0) + utf8::dump(std::back_inserter(*this), cp); +} + +String::String(Codepoint cp, ColumnCount count) +{ + int cp_count = (int)(count / max(codepoint_width(cp), 1_col)); + reserve(utf8::codepoint_size(cp) * cp_count); + while (cp_count-- > 0) + utf8::dump(std::back_inserter(*this), cp); +} + void String::resize(ByteCount size, char c) { const size_t target_size = (size_t)size; diff --git a/src/string.hh b/src/string.hh index 3b8c9013..4d7b60f9 100644 --- a/src/string.hh +++ b/src/string.hh @@ -1,14 +1,14 @@ #ifndef string_hh_INCLUDED #define string_hh_INCLUDED -#include <climits> -#include <cstddef> #include "memory.hh" #include "hash.hh" #include "units.hh" #include "utf8.hh" -#include <algorithm> +#include <climits> +#include <cstddef> +#include <cstring> namespace Kakoune { @@ -109,21 +109,9 @@ public: String() {} String(const char* content) : m_data(content, (size_t)strlen(content)) {} String(const char* content, ByteCount len) : m_data(content, (size_t)len) {} - explicit String(Codepoint cp, CharCount count = 1) - { - reserve(utf8::codepoint_size(cp) * (int)count); - while (count-- > 0) - utf8::dump(std::back_inserter(*this), cp); - } - explicit String(Codepoint cp, ColumnCount count) - { - int cp_count = (int)(count / std::max(codepoint_width(cp), 1_col)); - reserve(utf8::codepoint_size(cp) * cp_count); - while (cp_count-- > 0) - utf8::dump(std::back_inserter(*this), cp); - } String(const char* begin, const char* end) : m_data(begin, end-begin) {} - + explicit String(Codepoint cp, CharCount count = 1); + explicit String(Codepoint cp, ColumnCount count); explicit String(StringView str); struct NoCopy{}; @@ -298,9 +286,10 @@ inline String String::no_copy(StringView str) { return {NoCopy{}, str}; } template<typename Type, typename CharType> inline StringView StringOps<Type, CharType>::substr(ByteCount from, ByteCount length) const { - const auto str_len = type().length(); - kak_assert(from >= 0 and from <= str_len); - return StringView{type().data() + (int)from, std::min(str_len - from, length >= 0 ? length : str_len)}; + const auto str_length = type().length(); + const auto max_length = str_length - from; + kak_assert(from >= 0 and max_length >= 0); + return StringView{type().data() + (int)from, length >= 0 and length < max_length ? length : max_length}; } template<typename Type, typename CharType> @@ -352,7 +341,7 @@ inline String operator+(StringView lhs, StringView rhs) inline bool operator==(const StringView& lhs, const StringView& rhs) { return lhs.length() == rhs.length() and - std::equal(lhs.begin(), lhs.end(), rhs.begin()); + (lhs.empty() or std::memcmp(lhs.begin(), rhs.begin(), (size_t)lhs.length()) == 0); } inline auto operator<=>(const StringView& lhs, const StringView& rhs) diff --git a/src/terminal_ui.cc b/src/terminal_ui.cc index 672626e4..c55d0763 100644 --- a/src/terminal_ui.cc +++ b/src/terminal_ui.cc @@ -1574,6 +1574,13 @@ void TerminalUI::set_ui_options(const Options& options) m_padding_char = find("terminal_padding_char").map([](StringView s) { return s.column_length() < 1 ? ' ' : s[0_char]; }).value_or(Codepoint{'~'}); m_padding_fill = find("terminal_padding_fill").map(to_bool).value_or(false); + + bool new_cursor_native = find("terminal_cursor_native").map(to_bool).value_or(false); + if (new_cursor_native != m_cursor_native) + { + m_cursor_native = new_cursor_native; + write(STDOUT_FILENO, m_cursor_native ? "\033[?25h" : "\033[?25l"); + } m_info_max_width = find("terminal_info_max_width").map(str_to_int_ifp).value_or(0); } diff --git a/src/terminal_ui.hh b/src/terminal_ui.hh index 02085532..064edafe 100644 --- a/src/terminal_ui.hh +++ b/src/terminal_ui.hh @@ -167,6 +167,7 @@ private: Codepoint m_padding_char = '~'; bool m_padding_fill = false; + bool m_cursor_native = false; bool m_dirty = false; diff --git a/src/utils.hh b/src/utils.hh index 4b84ce75..b517735f 100644 --- a/src/utils.hh +++ b/src/utils.hh @@ -73,6 +73,9 @@ public: { other.m_valid = false; } [[gnu::always_inline]] + void trigger() { if (m_valid) m_func(); m_valid = false; } + + [[gnu::always_inline]] ~OnScopeEnd() noexcept(noexcept(m_func())) { if (m_valid) m_func(); } private: diff --git a/src/window.cc b/src/window.cc index 520d77e1..c65bebe8 100644 --- a/src/window.cc +++ b/src/window.cc @@ -132,7 +132,7 @@ const DisplayBuffer& Window::update_display_buffer(const Context& context) return m_display_buffer; kak_assert(&buffer() == &context.buffer()); - const DisplaySetup setup = compute_display_setup(context); + DisplaySetup setup = compute_display_setup(context); for (LineCount line = 0; line < setup.line_count; ++line) { @@ -144,8 +144,26 @@ const DisplayBuffer& Window::update_display_buffer(const Context& context) m_display_buffer.compute_range(); const BufferRange range{{0,0}, buffer().end_coord()}; - m_builtin_highlighters.highlight({context, setup, HighlightPass::Wrap, {}}, m_display_buffer, range); - m_builtin_highlighters.highlight({context, setup, HighlightPass::Move, {}}, m_display_buffer, range); + for (auto pass : {HighlightPass::Replace, HighlightPass::Wrap, HighlightPass::Move}) + m_builtin_highlighters.highlight({context, setup, pass, {}}, m_display_buffer, range); + + if (context.ensure_cursor_visible) + { + auto cursor_pos = display_coord(context.selections().main().cursor()); + kak_assert(cursor_pos); + + if (auto line_overflow = cursor_pos->line - m_dimensions.line + setup.scroll_offset.line + 1; line_overflow > 0) + { + lines.erase(lines.begin(), lines.begin() + (size_t)line_overflow); + setup.first_line = lines.begin()->range().begin.line; + } + + auto max_first_column = cursor_pos->column - (setup.widget_columns + setup.scroll_offset.column); + setup.first_column = std::min(setup.first_column, max_first_column); + + auto min_first_column = cursor_pos->column - (m_dimensions.column - setup.scroll_offset.column) + 1; + setup.first_column = std::max(setup.first_column, min_first_column); + } for (auto& line : m_display_buffer.lines()) line.trim_from(setup.widget_columns, setup.first_column, m_dimensions.column); @@ -203,11 +221,9 @@ DisplaySetup Window::compute_display_setup(const Context& context) const offset.line = std::min(offset.line, (m_dimensions.line + 1) / 2); offset.column = std::min(offset.column, (m_dimensions.column + 1) / 2); - const int tabstop = context.options()["tabstop"].get<int>(); const auto& cursor = context.selections().main().cursor(); - bool ensure_cursor_visible = context.ensure_cursor_visible; - if (ensure_cursor_visible) + if (context.ensure_cursor_visible) { if (cursor.line - offset.line < win_pos.line) win_pos.line = std::max(0_line, cursor.line - offset.line); @@ -216,39 +232,11 @@ DisplaySetup Window::compute_display_setup(const Context& context) const } win_pos.line = std::min(win_pos.line, buffer().line_count()-1); - DisplaySetup setup{ - win_pos.line, - m_dimensions.line, - win_pos.column, - 0_col, - {cursor.line - win_pos.line, - get_column(buffer(), tabstop, cursor) - win_pos.column}, - offset, - ensure_cursor_visible - }; - for (auto pass : { HighlightPass::Move, HighlightPass::Wrap }) + DisplaySetup setup{win_pos.line, m_dimensions.line, win_pos.column, 0_col, offset}; + for (auto pass : {HighlightPass::Move, HighlightPass::Wrap, HighlightPass::Replace}) m_builtin_highlighters.compute_display_setup({context, setup, pass, {}}, setup); check_display_setup(setup, *this); - // now ensure the cursor column is visible - if (ensure_cursor_visible) - { - auto underflow = std::max(-setup.first_column, - setup.cursor_pos.column - setup.scroll_offset.column); - if (underflow < 0) - { - setup.first_column += underflow; - setup.cursor_pos.column -= underflow; - } - auto overflow = setup.cursor_pos.column + setup.scroll_offset.column - (m_dimensions.column - setup.widget_columns) + 1; - if (overflow > 0) - { - setup.first_column += overflow; - setup.cursor_pos.column -= overflow; - } - check_display_setup(setup, *this); - } - return setup; } diff --git a/test/highlight/replace-multiline-range-pulls-new-lines/rc b/test/highlight/replace-multiline-range-pulls-new-lines/rc index fd2a7b6e..33eb085c 100644 --- a/test/highlight/replace-multiline-range-pulls-new-lines/rc +++ b/test/highlight/replace-multiline-range-pulls-new-lines/rc @@ -1,3 +1,4 @@ declare-option range-specs ranges +add-highlighter window/ number-lines add-highlighter window/ replace-ranges ranges set-option buffer ranges %val{timestamp} 2.1,6.2|.. diff --git a/test/highlight/replace-multiline-range-pulls-new-lines/script b/test/highlight/replace-multiline-range-pulls-new-lines/script index 0c4161b1..042e897f 100644 --- a/test/highlight/replace-multiline-range-pulls-new-lines/script +++ b/test/highlight/replace-multiline-range-pulls-new-lines/script @@ -1,7 +1,3 @@ ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }' -ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "0" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "1\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": ".." }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "07\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "08\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "09\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "10\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "11\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "12\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "13\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "14\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "15\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "16\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "17\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "18\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "19\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "20\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "21\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "22\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "23\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "24\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "25\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "26\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "27\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "28\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }' -ui_out '{ "jsonrpc": "2.0", "method": "menu_hide", "params": [] }' -ui_out '{ "jsonrpc": "2.0", "method": "info_hide", "params": [] }' -ui_out '{ "jsonrpc": "2.0", "method": "draw_status", "params": [[], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "out 1:1 " }, { "face": { "fg": "black", "bg": "yellow", "underline": "default", "attributes": [] }, "contents": "" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": "1 sel" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " - client0@[kak-tests]" }], { "fg": "cyan", "bg": "default", "underline": "default", "attributes": [] }] }' -ui_out '{ "jsonrpc": "2.0", "method": "set_cursor", "params": ["buffer", { "line": 0, "column": 0 }] }' -ui_out '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }' +ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 1│" }, { "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "0" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "1\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 2│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": ".." }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 7│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "07\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 8│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "08\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 9│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "09\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "10│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "10\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "11│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "11\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "12│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "12\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "13│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "13\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "14│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "14\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "15│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "15\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "16│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "16\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "17│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "17\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "18│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "18\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "19│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "19\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "20│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "20\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "21│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "21\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "22│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "22\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "23│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "23\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "24│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "24\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "25│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "25\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "26│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "26\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "27│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "27\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "28│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "28\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }' +ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }' diff --git a/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/cmd b/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/cmd new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/cmd @@ -0,0 +1 @@ + diff --git a/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/in b/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/in new file mode 100644 index 00000000..e4b48b88 --- /dev/null +++ b/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/in @@ -0,0 +1 @@ +xxxxxxxx1 2 3 4 5 6 7 8 9 10%( ) diff --git a/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/script b/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/script new file mode 100644 index 00000000..73bd69b1 --- /dev/null +++ b/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/script @@ -0,0 +1,2 @@ +ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }' +ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "1" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "2" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "3" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "4" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "5" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "6" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "7" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "8" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "9" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "10" }, { "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": " " }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }' diff --git a/test/highlight/wrap/basic/cmd b/test/highlight/wrap/basic/cmd new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/highlight/wrap/basic/cmd @@ -0,0 +1 @@ + diff --git a/test/highlight/wrap/basic/in b/test/highlight/wrap/basic/in new file mode 100644 index 00000000..fb85aeed --- /dev/null +++ b/test/highlight/wrap/basic/in @@ -0,0 +1,2 @@ +--------------------------------------------------------------------------------wrap +--------------------------------------------------------------------------------wrap----------------------------------------------------------------------------wrap diff --git a/test/highlight/wrap/basic/rc b/test/highlight/wrap/basic/rc new file mode 100644 index 00000000..2cd258c4 --- /dev/null +++ b/test/highlight/wrap/basic/rc @@ -0,0 +1 @@ +add-highlighter window/ wrap diff --git a/test/highlight/wrap/basic/script b/test/highlight/wrap/basic/script new file mode 100644 index 00000000..7151f6a6 --- /dev/null +++ b/test/highlight/wrap/basic/script @@ -0,0 +1,4 @@ +ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }' +ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "-" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "-------------------------------------------------------------------------------" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "--------------------------------------------------------------------------------" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap----------------------------------------------------------------------------" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }' +ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }' + diff --git a/test/highlight/wrap/interact-with-number-lines/cmd b/test/highlight/wrap/interact-with-number-lines/cmd new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/highlight/wrap/interact-with-number-lines/cmd @@ -0,0 +1 @@ + diff --git a/test/highlight/wrap/interact-with-number-lines/in b/test/highlight/wrap/interact-with-number-lines/in new file mode 100644 index 00000000..7bba8c8e --- /dev/null +++ b/test/highlight/wrap/interact-with-number-lines/in @@ -0,0 +1,2 @@ +line 1 +line 2 diff --git a/test/highlight/wrap/interact-with-number-lines/rc b/test/highlight/wrap/interact-with-number-lines/rc new file mode 100644 index 00000000..8f447491 --- /dev/null +++ b/test/highlight/wrap/interact-with-number-lines/rc @@ -0,0 +1,2 @@ +add-highlighter window/ wrap -word -width 4 +add-highlighter window/ number-lines diff --git a/test/highlight/wrap/interact-with-number-lines/script b/test/highlight/wrap/interact-with-number-lines/script new file mode 100644 index 00000000..67e34fea --- /dev/null +++ b/test/highlight/wrap/interact-with-number-lines/script @@ -0,0 +1,3 @@ +ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }' +ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 1│" }, { "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "l" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "ine" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": ["italic"] }, "contents": " 1" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 1\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 2│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "line" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": ["italic"] }, "contents": " 2" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 2\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }' +ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }' diff --git a/test/highlight/wrap/interact-with-replace-ranges/cmd b/test/highlight/wrap/interact-with-replace-ranges/cmd new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/highlight/wrap/interact-with-replace-ranges/cmd @@ -0,0 +1 @@ + diff --git a/test/highlight/wrap/interact-with-replace-ranges/in b/test/highlight/wrap/interact-with-replace-ranges/in new file mode 100644 index 00000000..1c17e1bf --- /dev/null +++ b/test/highlight/wrap/interact-with-replace-ranges/in @@ -0,0 +1,3 @@ +------------------------------------------------------------------------- wrap +------------------------------------------------------------------------- +prefix replaced wrapped diff --git a/test/highlight/wrap/interact-with-replace-ranges/rc b/test/highlight/wrap/interact-with-replace-ranges/rc new file mode 100644 index 00000000..563fc934 --- /dev/null +++ b/test/highlight/wrap/interact-with-replace-ranges/rc @@ -0,0 +1,4 @@ +declare-option range-specs ranges %val{timestamp} '1.1+0|HINT:' '2.74+0|WRAPPED HINT' '3.8+8|OVERFLOWING HINT XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' + +add-highlighter window/ wrap -word +add-highlighter window/ replace-ranges ranges diff --git a/test/highlight/wrap/interact-with-replace-ranges/script b/test/highlight/wrap/interact-with-replace-ranges/script new file mode 100644 index 00000000..6511f11c --- /dev/null +++ b/test/highlight/wrap/interact-with-replace-ranges/script @@ -0,0 +1,3 @@ +ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }' +ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "HINT:" }, { "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "-" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "------------------------------------------------------------------------ " }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "-------------------------------------------------------------------------" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "WRAPPED HINT" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "prefix " }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "OVERFLOWING HINT XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " wrapped\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }' +ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }' diff --git a/test/highlight/wrap/interact-with-show-whitespaces/cmd b/test/highlight/wrap/interact-with-show-whitespaces/cmd new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/highlight/wrap/interact-with-show-whitespaces/cmd @@ -0,0 +1 @@ + diff --git a/test/highlight/wrap/interact-with-show-whitespaces/in b/test/highlight/wrap/interact-with-show-whitespaces/in new file mode 100644 index 00000000..d675fa44 --- /dev/null +++ b/test/highlight/wrap/interact-with-show-whitespaces/in @@ -0,0 +1 @@ +foo bar diff --git a/test/highlight/wrap/interact-with-show-whitespaces/rc b/test/highlight/wrap/interact-with-show-whitespaces/rc new file mode 100644 index 00000000..37119e08 --- /dev/null +++ b/test/highlight/wrap/interact-with-show-whitespaces/rc @@ -0,0 +1,2 @@ +add-highlighter window/ wrap -word -width 5 +add-highlighter window/ show-whitespaces diff --git a/test/highlight/wrap/interact-with-show-whitespaces/script b/test/highlight/wrap/interact-with-show-whitespaces/script new file mode 100644 index 00000000..538ab98a --- /dev/null +++ b/test/highlight/wrap/interact-with-show-whitespaces/script @@ -0,0 +1,4 @@ +ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }' +ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "f" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "oo" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": ["final_fg"] }, "contents": "·" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "bar" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": ["final_fg"] }, "contents": "¬" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }' +ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }' + diff --git a/test/highlight/wrap/interact-with-tabulation/cmd b/test/highlight/wrap/interact-with-tabulation/cmd new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/highlight/wrap/interact-with-tabulation/cmd @@ -0,0 +1 @@ + diff --git a/test/highlight/wrap/interact-with-tabulation/in b/test/highlight/wrap/interact-with-tabulation/in new file mode 100644 index 00000000..9077d5dc --- /dev/null +++ b/test/highlight/wrap/interact-with-tabulation/in @@ -0,0 +1 @@ +---------------------------------------------------------------------------- diff --git a/test/highlight/wrap/interact-with-tabulation/rc b/test/highlight/wrap/interact-with-tabulation/rc new file mode 100644 index 00000000..784f691c --- /dev/null +++ b/test/highlight/wrap/interact-with-tabulation/rc @@ -0,0 +1,2 @@ +add-highlighter window/ number-lines +add-highlighter window/ wrap diff --git a/test/highlight/wrap/interact-with-tabulation/script b/test/highlight/wrap/interact-with-tabulation/script new file mode 100644 index 00000000..4961bd6c --- /dev/null +++ b/test/highlight/wrap/interact-with-tabulation/script @@ -0,0 +1,4 @@ +ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }' +ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 1│" }, { "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "-" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "---------------------------------------------------------------------------" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": ["italic"] }, "contents": " 1" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }' +ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }' + diff --git a/test/highlight/wrap/marker-and-indent/cmd b/test/highlight/wrap/marker-and-indent/cmd new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/highlight/wrap/marker-and-indent/cmd @@ -0,0 +1 @@ + diff --git a/test/highlight/wrap/marker-and-indent/in b/test/highlight/wrap/marker-and-indent/in new file mode 100644 index 00000000..37962f9e --- /dev/null +++ b/test/highlight/wrap/marker-and-indent/in @@ -0,0 +1,4 @@ +--------------------------------------------------------------------------------wrap +--------------------------------------------------------------------------------wrap-------------------------------------------------------------------------wrap + ----------------------------------------------------------------------------wrap + ----------------------------------------------------------------------------wrap------------------------------------------------------------------------wrap diff --git a/test/highlight/wrap/marker-and-indent/rc b/test/highlight/wrap/marker-and-indent/rc new file mode 100644 index 00000000..3ac2bf1e --- /dev/null +++ b/test/highlight/wrap/marker-and-indent/rc @@ -0,0 +1 @@ +add-highlighter window/ wrap -marker '>>>' -indent diff --git a/test/highlight/wrap/marker-and-indent/script b/test/highlight/wrap/marker-and-indent/script new file mode 100644 index 00000000..72861f73 --- /dev/null +++ b/test/highlight/wrap/marker-and-indent/script @@ -0,0 +1,4 @@ +ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }' +ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "-" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "-------------------------------------------------------------------------------" }], [{ "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": ">>>" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "--------------------------------------------------------------------------------" }], [{ "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": ">>>" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap-------------------------------------------------------------------------" }], [{ "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": ">>>" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " ----------------------------------------------------------------------------" }], [{ "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": ">>>" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " ----------------------------------------------------------------------------" }], [{ "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": ">>>" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap------------------------------------------------------------------------" }], [{ "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": ">>>" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }' +ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }' + diff --git a/test/highlight/wrap/word/cmd b/test/highlight/wrap/word/cmd new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/highlight/wrap/word/cmd @@ -0,0 +1 @@ + diff --git a/test/highlight/wrap/word/in b/test/highlight/wrap/word/in new file mode 100644 index 00000000..5cdc9a1d --- /dev/null +++ b/test/highlight/wrap/word/in @@ -0,0 +1,3 @@ +123456789 wrap +123456 wrap +123456 wrap 1234 wrap 123456789012 wrap diff --git a/test/highlight/wrap/word/rc b/test/highlight/wrap/word/rc new file mode 100644 index 00000000..921daf40 --- /dev/null +++ b/test/highlight/wrap/word/rc @@ -0,0 +1 @@ +add-highlighter window/ wrap -word -width 10 diff --git a/test/highlight/wrap/word/script b/test/highlight/wrap/word/script new file mode 100644 index 00000000..eb18dadc --- /dev/null +++ b/test/highlight/wrap/word/script @@ -0,0 +1,4 @@ +ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }' +ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "1" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "23456789 " }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "123456 " }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "123456 " }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap 1234 " }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap 12345" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "6789012 " }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }' +ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }' + diff --git a/test/normal/pipe-replaces-all-lines/cmd b/test/normal/pipe-replaces-all-lines/cmd new file mode 100644 index 00000000..fb90dc1a --- /dev/null +++ b/test/normal/pipe-replaces-all-lines/cmd @@ -0,0 +1 @@ +%|printf '\n\n'<ret> diff --git a/test/normal/pipe-replaces-all-lines/in b/test/normal/pipe-replaces-all-lines/in new file mode 100644 index 00000000..422c2b7a --- /dev/null +++ b/test/normal/pipe-replaces-all-lines/in @@ -0,0 +1,2 @@ +a +b diff --git a/test/normal/pipe-replaces-all-lines/out b/test/normal/pipe-replaces-all-lines/out new file mode 100644 index 00000000..139597f9 --- /dev/null +++ b/test/normal/pipe-replaces-all-lines/out @@ -0,0 +1,2 @@ + + diff --git a/test/regression/0-slow-BufCloseFifo/cmd b/test/regression/0-slow-BufCloseFifo/cmd new file mode 100644 index 00000000..2eeea72e --- /dev/null +++ b/test/regression/0-slow-BufCloseFifo/cmd @@ -0,0 +1 @@ +:run<ret> diff --git a/test/regression/0-slow-BufCloseFifo/in b/test/regression/0-slow-BufCloseFifo/in new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/regression/0-slow-BufCloseFifo/in @@ -0,0 +1 @@ + diff --git a/test/regression/0-slow-BufCloseFifo/rc b/test/regression/0-slow-BufCloseFifo/rc new file mode 100644 index 00000000..8a953e18 --- /dev/null +++ b/test/regression/0-slow-BufCloseFifo/rc @@ -0,0 +1,16 @@ +define-command run %{ + evaluate-commands %sh{ + mkfifo fifo1 fifo2 2>/dev/null + ( : >fifo1 & ) > /dev/null 2>&1 </dev/null + } + edit! -fifo fifo1 *fifo* + add-highlighter global/myhl regex foo 0:green + hook -once global BufCloseFifo .* %{ + evaluate-commands -client client0 %{ + nop %sh{sleep 2} + } + hook -once buffer NormalIdle .* %{ + echo -to-file fifo2 still alive + } + } +} diff --git a/test/regression/0-slow-BufCloseFifo/script b/test/regression/0-slow-BufCloseFifo/script new file mode 100644 index 00000000..2147a8e8 --- /dev/null +++ b/test/regression/0-slow-BufCloseFifo/script @@ -0,0 +1,2 @@ +mkfifo fifo2 2>/dev/null +assert_eq "$(cat fifo2)" "still alive" diff --git a/test/regression/2999-buggy-wrapping/cmd b/test/regression/2999-buggy-wrapping/cmd index 4c559f78..5238dfce 100644 --- a/test/regression/2999-buggy-wrapping/cmd +++ b/test/regression/2999-buggy-wrapping/cmd @@ -1 +1 @@ -j +jvt diff --git a/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/cmd b/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/cmd new file mode 100644 index 00000000..a1a5692c --- /dev/null +++ b/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/cmd @@ -0,0 +1 @@ +:e -scratch<ret><c-o> diff --git a/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/in b/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/in new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/in @@ -0,0 +1 @@ + diff --git a/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/rc b/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/rc new file mode 100644 index 00000000..6240a36f --- /dev/null +++ b/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/rc @@ -0,0 +1 @@ +hook global WinDisplay .* %{ edit out } @@ -32,15 +32,13 @@ main() { number_tests=0 number_failures=0 - for dir in $(find "${@:-.}" -type d | sort); do + for dir in $(find "${@:-.}" -type d | sed 's|^\./||' | sort); do cd $root/$dir; - indent="$(echo "$dir/" | sed -e 's|[^/]*//*| |g')" - name=${PWD##*/} if [ ! -f cmd ]; then - echo "$indent$name" + echo "$dir" continue elif [ -x enabled ] && ! ./enabled; then - printf "${yellow}$indent%s (disabled)${none}\n" "$name" + echo "${yellow}$dir (disabled)${none}" continue fi @@ -81,8 +79,8 @@ main() { if [ ! -e error ]; then # failure not expected if [ $retval -ne 0 ]; then - printf "${red}$indent%s${none}\n" "$name" - echo "$indent Kakoune returned error $retval" + printf "${red}%s${none}\n" "$dir" + echo " Kakoune returned error $retval" failed=1 else for file in out $env_vars; do @@ -100,19 +98,19 @@ main() { if [ -f stderr ]; then sed -i -e 's/^[0-9]*:[0-9]*: //g' stderr if [ -s error ] && ! cmp -s error stderr; then - printf "${yellow}$indent%s${none}\n" "$name" + printf "${yellow}%s${none}\n" "$dir" show_diff error stderr failed=1 fi elif [ $retval -eq 0 ]; then - printf "${red}$indent%s${none}\n" "$name" - echo "$indent Expected failure, but Kakoune returned 0" + printf "${red}%s${none}\n" "$dir" + echo " Expected failure, but Kakoune returned 0" failed=1 fi fi if [ $failed -eq 0 ]; then - printf "${green}$indent%s${none}\n" "$name" + printf "${green}%s${none}\n" "$dir" else number_failures=$(($number_failures + 1)) fi @@ -131,7 +129,7 @@ main() { fail_ifn() { if [ $failed -eq 0 ]; then - printf "${red}$indent%s${none}\n" "$name" + printf "${red}%s${none}\n" "$dir" failed=1 fi } @@ -139,7 +137,13 @@ fail_ifn() { assert_eq() { if [ ! "$1" = "$2" ]; then fail_ifn - printf "${indent} ${red}- %s\n${indent} ${green}+ %s${none}\n" "$1" "$2" + if command -v git > /dev/null; then + echo "$1" > expected + echo "$2" > actual + git --no-pager diff --color-words --no-index expected actual + else + printf " ${red}- %s\n ${green}+ %s${none}\n" "$1" "$2" + fi fi } @@ -198,11 +202,11 @@ ui_out() { [ "$event" = "$expected" ] && break done ;; - -until-eval | -until-grep) + -until-grep) pattern=$1 shift while read -r event <&4; do - if printf %s "$event" | "${arg#-until-}" "$pattern" >/dev/null; then + if printf %s "$event" | grep "$pattern" >/dev/null; then if [ $# -ne 0 ]; then assert_eq "$1" "$event" shift diff --git a/test/tools/git/blame-in-diff/rc b/test/tools/git/blame-in-diff/rc index eba02270..1fc54f3f 100644 --- a/test/tools/git/blame-in-diff/rc +++ b/test/tools/git/blame-in-diff/rc @@ -11,4 +11,19 @@ define-command run %{ git commit --all --message 'changed line 2' # Show the commit, jumping to the new version of line 2. git blame-jump + hook -once buffer BufCloseFifo .* %{ + evaluate-commands -client client0 %{ + # We've jumped to the new version of line 2. Move to the old + # version so we can annotate the old version of the file. + execute-keys k + git blame + hook -once buffer BufCloseFifo .* %{ + execute-keys -client client0 x + # Wait for a redraw before reporting completion. + hook -once buffer NormalIdle .* %{ + echo -to-file fifo + } + } + } + } } diff --git a/test/tools/git/blame-in-diff/script b/test/tools/git/blame-in-diff/script index 0cee0ca7..4c371348 100644 --- a/test/tools/git/blame-in-diff/script +++ b/test/tools/git/blame-in-diff/script @@ -1,10 +1,3 @@ -ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [false] }' - -# We've jumped to the new version of line 2. Move to the old version so we -# can annotate the old file. -ui_in '{ "jsonrpc": "2.0", "method": "keys", "params": [ "k:git blame<ret>" ] }' -ui_out -until-eval 'grep "draw_status" | grep -v "\[fifo\]"' - +mkfifo fifo 2>/dev/null +cat fifo # We should have jumped to the old version of line 2, assert on kak_selection. -ui_in '{ "jsonrpc": "2.0", "method": "keys", "params": [ "x" ] }' -ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [false] }' |
