From 9595f074255c87aaef5415e1400869399916b5fd Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Fri, 20 Mar 2026 22:56:00 +0200 Subject: [PATCH] feat(ux): sexy intro #38378 Problem: Intro is not sexy. Solution: Make it sexy. Co-authored-by: Justin M. Keyes --- src/nvim/version.c | 99 +++++++++++++++---- test/functional/ui/messages_spec.lua | 140 +++++++++++++++------------ 2 files changed, 162 insertions(+), 77 deletions(-) diff --git a/src/nvim/version.c b/src/nvim/version.c index a437bf6236..0c441facb6 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -26,6 +26,7 @@ #include "nvim/grid_defs.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" +#include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/mbyte.h" #include "nvim/memory.h" @@ -4188,20 +4189,24 @@ bool may_show_intro(void) void intro_message(bool colon) { static char *(lines[]) = { - N_(NVIM_VERSION_LONG), + "│ ╲ ││", + "││╲╲││", + "││ ╲ │", "", + N_(NVIM_VERSION_LONG), + "────────────────────────────────────────────", N_("Nvim is open source and freely distributable"), "https://neovim.io/#chat", - "", - N_("type :help nvim if you are new! "), - N_("type :checkhealth to optimize Nvim"), - N_("type :q to exit "), - N_("type :help for help "), - "", - N_("type :help news to see changes in v%s.%s"), - "", + "────────────────────────────────────────────", + N_("type :help nvim if you are new! "), + N_("type :checkhealth to optimize Nvim"), + N_("type :q to exit "), + N_("type :help for help "), + "────────────────────────────────────────────", + N_("type :help news for v%s.%s notes "), + "────────────────────────────────────────────", N_("Help poor children in Uganda!"), - N_("type :help Kuwasha for information "), + N_("type :help Kuwasha for information "), }; // blanklines = screen height - # message lines @@ -4247,7 +4252,7 @@ void intro_message(bool colon) } if (*mesg != NUL) { - do_intro_line(row, mesg, colon); + do_intro_line(row, mesg, colon, i < 3); } row++; @@ -4258,22 +4263,64 @@ void intro_message(bool colon) } } -static void do_intro_line(int row, char *mesg, bool colon) +/// Adds extra highlighting. +static void do_intro_line(int row, char *mesg, bool colon, bool is_logo) { int l; - // Center the message horizontally. int col = vim_strsize(mesg); - col = (Columns - col) / 2; - if (col < 0) { col = 0; } grid_line_start((!colon && ui_has(kUIMultigrid)) ? &firstwin->w_grid : &default_gridview, row); - // Split up in parts to highlight <> items differently. + // Compute special highlighting attributes + int id_attr = syn_id2attr(syn_name2id("Identifier")); + int nontext_attr = syn_id2attr(syn_name2id("NonText")); + int special_attr = syn_id2attr(syn_name2id("Special")); + int string_attr = syn_id2attr(syn_name2id("String")); + + // Handle logo lines + if (is_logo) { + bool seen_diagonal = false; + + for (char *p = mesg; *p != NUL;) { + int clen = utfc_ptr2len(p); + int attr = 0; + // Multi-byte (box-drawing) character. + if ((uint8_t)(*p) >= 0x80) { + // Found "╲" diagonal logo part. + seen_diagonal = seen_diagonal || (clen == 3 && utf_ptr2char(p) == 0x2572); + attr = seen_diagonal ? string_attr : special_attr; + } + col += grid_line_puts(col, p, clen, attr); + p += clen; + } + + grid_line_flush(); + return; + } + + // Try highlighting full line: + // - Version starts with "NVIM". + // - Separator line consists from ─ (UTF-8: E2 94 80). + bool is_version = mesg[0] == 'N' && mesg[1] == 'V' && mesg[2] == 'I' && mesg[3] == 'M'; + bool is_sep = utfc_ptr2len(mesg) == 3 && utf_ptr2char(mesg) == 0x2500; + if (is_version || is_sep) { + int clen = is_sep ? 3 : 1; + int attr = is_sep ? nontext_attr : string_attr; + + for (char *p = mesg; *p != NUL;) { + col += grid_line_puts(col, p, clen, attr); + p += clen; + } + grid_line_flush(); + return; + } + + // Highlight `:...` differently. for (char *p = mesg; *p != NUL; p += l) { for (l = 0; p[l] != NUL && (l == 0 || (p[l] != '<' && p[l - 1] != '>')); @@ -4281,7 +4328,25 @@ static void do_intro_line(int row, char *mesg, bool colon) l += utfc_ptr2len(p + l) - 1; } assert(row <= INT_MAX && col <= INT_MAX); - col += grid_line_puts(col, p, l, *p == '<' ? HL_ATTR(HLF_8) : 0); + + if (*p == '<') { + col += grid_line_puts(col, p, l, HL_ATTR(HLF_8)); + } else { + // Check for ":command" pattern before a segment. + char *colon_pos = memchr(p, ':', (size_t)l); + if (colon_pos != NULL && p[l] == '<') { + // No highlight for "type ". + int prefix_len = (int)(colon_pos - p); + col += grid_line_puts(col, p, prefix_len, 0); + // Highlight ":". + col += grid_line_puts(col, colon_pos, 1, HL_ATTR(HLF_8)); + // Highlight "command" (after the ":"). + int cmd_len = l - prefix_len - 1; + col += grid_line_puts(col, colon_pos + 1, cmd_len, id_attr); + } else { + col += grid_line_puts(col, p, l, 0); + } + } } grid_line_flush(); } diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 7ccd05a078..4e6dc2eff1 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -2150,22 +2150,26 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim command('set cmdheight=0') feed(':intro') screen:expect([[ - |*5 - {MATCH:.*}| + |*3 + {16:│} {26:╲} {26:││} | + {16:││}{26:╲╲││} | + {16:││} {26:╲} {26:│} | | + {MATCH:.*}| + {1:────────────────────────────────────────────} | Nvim is open source and freely distributable | https://neovim.io/#chat | - | - type :help nvim{18:} if you are new! | - type :checkhealth{18:} to optimize Nvim | - type :q{18:} to exit | - type :help{18:} for help | - | - {MATCH: +}type :help news{18:} to see changes in v{MATCH:%d+%.%d+ +}| - | + {1:────────────────────────────────────────────} | + type {18::}{25:help nvim}{18:} if you are new! | + type {18::}{25:checkhealth}{18:} to optimize Nvim | + type {18::}{25:q}{18:} to exit | + type {18::}{25:help}{18:} for help | + {1:────────────────────────────────────────────} | + type {18::}{25:help news}{18:} for v{MATCH:%d+%.%d+} notes{MATCH: *}| + {1:────────────────────────────────────────────} | Help poor children in Uganda! | - type :help Kuwasha{18:} for information | - |*4 + type {18::}{25:help Kuwasha}{18:} for information | + |*2 ^ | ]]) feed('') @@ -2208,22 +2212,26 @@ describe('ui/ext_messages', function() -- Note parts of it depends on version or is indeterministic. We ignore those parts. local introscreen = [[ ^ | - {1:~ }|*4 - {MATCH:.*}| + {1:~ }|*2 + {1:~ }{16:│} {26:╲} {26:││}{1: }| + {1:~ }{16:││}{26:╲╲││}{1: }| + {1:~ }{16:││} {26:╲} {26:│}{1: }| {1:~ }| + {1:~{MATCH: +}}{26:NVIM {MATCH:%S+}}{1:{MATCH: +}}| + {1:~ ──────────────────────────────────────────── }| {1:~ }Nvim is open source and freely distributable{1: }| {1:~ }https://neovim.io/#chat{1: }| - {1:~ }| - {1:~ }type :help nvim{18:} if you are new! {1: }| - {1:~ }type :checkhealth{18:} to optimize Nvim{1: }| - {1:~ }type :q{18:} to exit {1: }| - {1:~ }type :help{18:} for help {1: }| - {1:~ }| - {1:~{MATCH: +}}type :help news{18:} to see changes in v{MATCH:%d+%.%d+}{1:{MATCH: +}}| - {1:~ }| + {1:~ ──────────────────────────────────────────── }| + {1:~ }type {18::}{25:help nvim}{18:} if you are new! {1: }| + {1:~ }type {18::}{25:checkhealth}{18:} to optimize Nvim{1: }| + {1:~ }type {18::}{25:q}{18:} to exit {1: }| + {1:~ }type {18::}{25:help}{18:} for help {1: }| + {1:~ ──────────────────────────────────────────── }| + {1:~ }type {18::}{25:help news}{18:} for v{MATCH:%d+%.%d+} notes {1:{MATCH: +}}| + {1:~ ──────────────────────────────────────────── }| {1:~ }Help poor children in Uganda!{1: }| - {1:~ }type :help Kuwasha{18:} for information {1: }| - {1:~ }|*5 + {1:~ }type {18::}{25:help Kuwasha}{18:} for information {1: }| + {1:~ }|*3 ]] local showmode = { { '-- INSERT --', 5, 'ModeMsg' } } screen:expect(introscreen) @@ -2244,22 +2252,26 @@ describe('ui/ext_messages', function() grid = [[ ^ | {1:~ }{4: }{1: }| - {1:~ }|*3 - {MATCH:.*}| {1:~ }| + {1:~ }{16:│} {26:╲} {26:││}{1: }| + {1:~ }{16:││}{26:╲╲││}{1: }| + {1:~ }{16:││} {26:╲} {26:│}{1: }| + {1:~ }| + {1:~{MATCH: +}}{26:NVIM {MATCH:%S+}}{1:{MATCH: +}}| + {1:~ ──────────────────────────────────────────── }| {1:~ }Nvim is open source and freely distributable{1: }| {1:~ }https://neovim.io/#chat{1: }| - {1:~ }| - {1:~ }type :help nvim{18:} if you are new! {1: }| - {1:~ }type :checkhealth{18:} to optimize Nvim{1: }| - {1:~ }type :q{18:} to exit {1: }| - {1:~ }type :help{18:} for help {1: }| - {1:~ }| - {1:~{MATCH: +}}type :help news{18:} to see changes in v{MATCH:%d+%.%d+}{1:{MATCH: +}}| - {1:~ }| + {1:~ ──────────────────────────────────────────── }| + {1:~ }type {18::}{25:help nvim}{18:} if you are new! {1: }| + {1:~ }type {18::}{25:checkhealth}{18:} to optimize Nvim{1: }| + {1:~ }type {18::}{25:q}{18:} to exit {1: }| + {1:~ }type {18::}{25:help}{18:} for help {1: }| + {1:~ ──────────────────────────────────────────── }| + {1:~ }type {18::}{25:help news}{18:} for v{MATCH:%d+%.%d+} notes {1:{MATCH: +}}| + {1:~ ──────────────────────────────────────────── }| {1:~ }Help poor children in Uganda!{1: }| - {1:~ }type :help Kuwasha{18:} for information {1: }| - {1:~ }|*5 + {1:~ }type {18::}{25:help Kuwasha}{18:} for information {1: }| + {1:~ }|*3 ]], showmode = showmode, } @@ -2281,22 +2293,26 @@ describe('ui/ext_messages', function() screen:expect { grid = [[ ^ | - |*4 - {MATCH:.*}| + |*2 + {16:│} {26:╲} {26:││} | + {16:││}{26:╲╲││} | + {16:││} {26:╲} {26:│} | | + {MATCH: +}{26:NVIM {MATCH:%S+}}{MATCH: +}| + {1:────────────────────────────────────────────} | Nvim is open source and freely distributable | https://neovim.io/#chat | - | - type :help nvim{18:} if you are new! | - type :checkhealth{18:} to optimize Nvim | - type :q{18:} to exit | - type :help{18:} for help | - | - {MATCH: +}type :help news{18:} to see changes in v{MATCH:%d+%.%d+ +}| - | + {1:────────────────────────────────────────────} | + type {18::}{25:help nvim}{18:} if you are new! | + type {18::}{25:checkhealth}{18:} to optimize Nvim | + type {18::}{25:q}{18:} to exit | + type {18::}{25:help}{18:} for help | + {1:────────────────────────────────────────────} | + type {18::}{25:help news}{18:} for v{MATCH:%d+%.%d+} notes {MATCH: +}| + {1:────────────────────────────────────────────} | Help poor children in Uganda! | - type :help Kuwasha{18:} for information | - |*5 + type {18::}{25:help Kuwasha}{18:} for information | + |*3 ]], } @@ -2411,22 +2427,26 @@ it('ui/ext_multigrid supports intro screen', function() [3:--------------------------------------------------------------------------------]| ## grid 2 ^ | - {1:~ }|*4 - {MATCH:.*}| + {1:~ }|*2 + {1:~ }{16:│} {26:╲} {26:││}{1: }| + {1:~ }{16:││}{26:╲╲││}{1: }| + {1:~ }{16:││} {26:╲} {26:│}{1: }| {1:~ }| + {1:~{MATCH: +}}{26:NVIM {MATCH:%S+}}{1:{MATCH: +}}| + {1:~ ──────────────────────────────────────────── }| {1:~ }Nvim is open source and freely distributable{1: }| {1:~ }https://neovim.io/#chat{1: }| - {1:~ }| - {1:~ }type :help nvim{18:} if you are new! {1: }| - {1:~ }type :checkhealth{18:} to optimize Nvim{1: }| - {1:~ }type :q{18:} to exit {1: }| - {1:~ }type :help{18:} for help {1: }| - {1:~ }| - {1:~{MATCH: +}}type :help news{18:} to see changes in v{MATCH:%d+%.%d+}{1:{MATCH: +}}| - {1:~ }| + {1:~ ──────────────────────────────────────────── }| + {1:~ }type {18::}{25:help nvim}{18:} if you are new! {1: }| + {1:~ }type {18::}{25:checkhealth}{18:} to optimize Nvim{1: }| + {1:~ }type {18::}{25:q}{18:} to exit {1: }| + {1:~ }type {18::}{25:help}{18:} for help {1: }| + {1:~ ──────────────────────────────────────────── }| + {1:~ }type {18::}{25:help news}{18:} for v{MATCH:%d+%.%d+} notes {1:{MATCH: +}}| + {1:~ ──────────────────────────────────────────── }| {1:~ }Help poor children in Uganda!{1: }| - {1:~ }type :help Kuwasha{18:} for information {1: }| - {1:~ }|*4 + {1:~ }type {18::}{25:help Kuwasha}{18:} for information {1: }| + {1:~ }|*2 ## grid 3 | ]],