feat(ux): sexy intro #38378

Problem: Intro is not sexy.

Solution: Make it sexy.

Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
This commit is contained in:
Evgeni Chasnovski
2026-03-20 22:56:00 +02:00
committed by GitHub
parent 9986360065
commit 9595f07425
2 changed files with 162 additions and 77 deletions

View File

@@ -26,6 +26,7 @@
#include "nvim/grid_defs.h" #include "nvim/grid_defs.h"
#include "nvim/highlight.h" #include "nvim/highlight.h"
#include "nvim/highlight_defs.h" #include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h" #include "nvim/lua/executor.h"
#include "nvim/mbyte.h" #include "nvim/mbyte.h"
#include "nvim/memory.h" #include "nvim/memory.h"
@@ -4188,20 +4189,24 @@ bool may_show_intro(void)
void intro_message(bool colon) void intro_message(bool colon)
{ {
static char *(lines[]) = { static char *(lines[]) = {
N_(NVIM_VERSION_LONG), "│ ╲ ││",
"││╲╲││",
"││ ╲ │",
"", "",
N_(NVIM_VERSION_LONG),
"────────────────────────────────────────────",
N_("Nvim is open source and freely distributable"), N_("Nvim is open source and freely distributable"),
"https://neovim.io/#chat", "https://neovim.io/#chat",
"", "────────────────────────────────────────────",
N_("type :help nvim<Enter> if you are new! "), N_("type :help nvim<Enter> if you are new! "),
N_("type :checkhealth<Enter> to optimize Nvim"), N_("type :checkhealth<Enter> to optimize Nvim"),
N_("type :q<Enter> to exit "), N_("type :q<Enter> to exit "),
N_("type :help<Enter> for help "), N_("type :help<Enter> for help "),
"", "────────────────────────────────────────────",
N_("type :help news<Enter> to see changes in v%s.%s"), N_("type :help news<Enter> for v%s.%s notes "),
"", "────────────────────────────────────────────",
N_("Help poor children in Uganda!"), N_("Help poor children in Uganda!"),
N_("type :help Kuwasha<Enter> for information "), N_("type :help Kuwasha<Enter> for information "),
}; };
// blanklines = screen height - # message lines // blanklines = screen height - # message lines
@@ -4247,7 +4252,7 @@ void intro_message(bool colon)
} }
if (*mesg != NUL) { if (*mesg != NUL) {
do_intro_line(row, mesg, colon); do_intro_line(row, mesg, colon, i < 3);
} }
row++; 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; int l;
// Center the message horizontally. // Center the message horizontally.
int col = vim_strsize(mesg); int col = vim_strsize(mesg);
col = (Columns - col) / 2; col = (Columns - col) / 2;
if (col < 0) { if (col < 0) {
col = 0; col = 0;
} }
grid_line_start((!colon && ui_has(kUIMultigrid)) ? &firstwin->w_grid : &default_gridview, row); 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 `:...<Enter>` differently.
for (char *p = mesg; *p != NUL; p += l) { for (char *p = mesg; *p != NUL; p += l) {
for (l = 0; for (l = 0;
p[l] != NUL && (l == 0 || (p[l] != '<' && p[l - 1] != '>')); 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; l += utfc_ptr2len(p + l) - 1;
} }
assert(row <= INT_MAX && col <= INT_MAX); 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 <key> 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(); grid_line_flush();
} }

View File

@@ -2150,22 +2150,26 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim
command('set cmdheight=0') command('set cmdheight=0')
feed(':intro<CR>') feed(':intro<CR>')
screen:expect([[ screen:expect([[
|*5 |*3
{MATCH:.*}| {16:│} {26:╲} {26:││} |
{16:││}{26:╲╲││} |
{16:││} {26:╲} {26:│} |
| |
{MATCH:.*}|
{1:────────────────────────────────────────────} |
Nvim is open source and freely distributable | Nvim is open source and freely distributable |
https://neovim.io/#chat | https://neovim.io/#chat |
| {1:────────────────────────────────────────────} |
type :help nvim{18:<Enter>} if you are new! | type {18::}{25:help nvim}{18:<Enter>} if you are new! |
type :checkhealth{18:<Enter>} to optimize Nvim | type {18::}{25:checkhealth}{18:<Enter>} to optimize Nvim |
type :q{18:<Enter>} to exit | type {18::}{25:q}{18:<Enter>} to exit |
type :help{18:<Enter>} for help | type {18::}{25:help}{18:<Enter>} for help |
| {1:────────────────────────────────────────────} |
{MATCH: +}type :help news{18:<Enter>} to see changes in v{MATCH:%d+%.%d+ +}| type {18::}{25:help news}{18:<Enter>} for v{MATCH:%d+%.%d+} notes{MATCH: *}|
| {1:────────────────────────────────────────────} |
Help poor children in Uganda! | Help poor children in Uganda! |
type :help Kuwasha{18:<Enter>} for information | type {18::}{25:help Kuwasha}{18:<Enter>} for information |
|*4 |*2
^ | ^ |
]]) ]])
feed('<CR>') feed('<CR>')
@@ -2208,22 +2212,26 @@ describe('ui/ext_messages', function()
-- Note parts of it depends on version or is indeterministic. We ignore those parts. -- Note parts of it depends on version or is indeterministic. We ignore those parts.
local introscreen = [[ local introscreen = [[
^ | ^ |
{1:~ }|*4 {1:~ }|*2
{MATCH:.*}| {1:~ }{16:│} {26:╲} {26:││}{1: }|
{1:~ }{16:││}{26:╲╲││}{1: }|
{1:~ }{16:││} {26:╲} {26:│}{1: }|
{1:~ }| {1:~ }|
{1:~{MATCH: +}}{26:NVIM {MATCH:%S+}}{1:{MATCH: +}}|
{1:~ ──────────────────────────────────────────── }|
{1:~ }Nvim is open source and freely distributable{1: }| {1:~ }Nvim is open source and freely distributable{1: }|
{1:~ }https://neovim.io/#chat{1: }| {1:~ }https://neovim.io/#chat{1: }|
{1:~ }| {1:~ ──────────────────────────────────────────── }|
{1:~ }type :help nvim{18:<Enter>} if you are new! {1: }| {1:~ }type {18::}{25:help nvim}{18:<Enter>} if you are new! {1: }|
{1:~ }type :checkhealth{18:<Enter>} to optimize Nvim{1: }| {1:~ }type {18::}{25:checkhealth}{18:<Enter>} to optimize Nvim{1: }|
{1:~ }type :q{18:<Enter>} to exit {1: }| {1:~ }type {18::}{25:q}{18:<Enter>} to exit {1: }|
{1:~ }type :help{18:<Enter>} for help {1: }| {1:~ }type {18::}{25:help}{18:<Enter>} for help {1: }|
{1:~ }| {1:~ ──────────────────────────────────────────── }|
{1:~{MATCH: +}}type :help news{18:<Enter>} to see changes in v{MATCH:%d+%.%d+}{1:{MATCH: +}}| {1:~ }type {18::}{25:help news}{18:<Enter>} for v{MATCH:%d+%.%d+} notes {1:{MATCH: +}}|
{1:~ }| {1:~ ──────────────────────────────────────────── }|
{1:~ }Help poor children in Uganda!{1: }| {1:~ }Help poor children in Uganda!{1: }|
{1:~ }type :help Kuwasha{18:<Enter>} for information {1: }| {1:~ }type {18::}{25:help Kuwasha}{18:<Enter>} for information {1: }|
{1:~ }|*5 {1:~ }|*3
]] ]]
local showmode = { { '-- INSERT --', 5, 'ModeMsg' } } local showmode = { { '-- INSERT --', 5, 'ModeMsg' } }
screen:expect(introscreen) screen:expect(introscreen)
@@ -2244,22 +2252,26 @@ describe('ui/ext_messages', function()
grid = [[ grid = [[
^ | ^ |
{1:~ }{4: }{1: }| {1:~ }{4: }{1: }|
{1:~ }|*3
{MATCH:.*}|
{1:~ }| {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:~ }Nvim is open source and freely distributable{1: }|
{1:~ }https://neovim.io/#chat{1: }| {1:~ }https://neovim.io/#chat{1: }|
{1:~ }| {1:~ ──────────────────────────────────────────── }|
{1:~ }type :help nvim{18:<Enter>} if you are new! {1: }| {1:~ }type {18::}{25:help nvim}{18:<Enter>} if you are new! {1: }|
{1:~ }type :checkhealth{18:<Enter>} to optimize Nvim{1: }| {1:~ }type {18::}{25:checkhealth}{18:<Enter>} to optimize Nvim{1: }|
{1:~ }type :q{18:<Enter>} to exit {1: }| {1:~ }type {18::}{25:q}{18:<Enter>} to exit {1: }|
{1:~ }type :help{18:<Enter>} for help {1: }| {1:~ }type {18::}{25:help}{18:<Enter>} for help {1: }|
{1:~ }| {1:~ ──────────────────────────────────────────── }|
{1:~{MATCH: +}}type :help news{18:<Enter>} to see changes in v{MATCH:%d+%.%d+}{1:{MATCH: +}}| {1:~ }type {18::}{25:help news}{18:<Enter>} for v{MATCH:%d+%.%d+} notes {1:{MATCH: +}}|
{1:~ }| {1:~ ──────────────────────────────────────────── }|
{1:~ }Help poor children in Uganda!{1: }| {1:~ }Help poor children in Uganda!{1: }|
{1:~ }type :help Kuwasha{18:<Enter>} for information {1: }| {1:~ }type {18::}{25:help Kuwasha}{18:<Enter>} for information {1: }|
{1:~ }|*5 {1:~ }|*3
]], ]],
showmode = showmode, showmode = showmode,
} }
@@ -2281,22 +2293,26 @@ describe('ui/ext_messages', function()
screen:expect { screen:expect {
grid = [[ grid = [[
^ | ^ |
|*4 |*2
{MATCH:.*}| {16:│} {26:╲} {26:││} |
{16:││}{26:╲╲││} |
{16:││} {26:╲} {26:│} |
| |
{MATCH: +}{26:NVIM {MATCH:%S+}}{MATCH: +}|
{1:────────────────────────────────────────────} |
Nvim is open source and freely distributable | Nvim is open source and freely distributable |
https://neovim.io/#chat | https://neovim.io/#chat |
| {1:────────────────────────────────────────────} |
type :help nvim{18:<Enter>} if you are new! | type {18::}{25:help nvim}{18:<Enter>} if you are new! |
type :checkhealth{18:<Enter>} to optimize Nvim | type {18::}{25:checkhealth}{18:<Enter>} to optimize Nvim |
type :q{18:<Enter>} to exit | type {18::}{25:q}{18:<Enter>} to exit |
type :help{18:<Enter>} for help | type {18::}{25:help}{18:<Enter>} for help |
| {1:────────────────────────────────────────────} |
{MATCH: +}type :help news{18:<Enter>} to see changes in v{MATCH:%d+%.%d+ +}| type {18::}{25:help news}{18:<Enter>} for v{MATCH:%d+%.%d+} notes {MATCH: +}|
| {1:────────────────────────────────────────────} |
Help poor children in Uganda! | Help poor children in Uganda! |
type :help Kuwasha{18:<Enter>} for information | type {18::}{25:help Kuwasha}{18:<Enter>} for information |
|*5 |*3
]], ]],
} }
@@ -2411,22 +2427,26 @@ it('ui/ext_multigrid supports intro screen', function()
[3:--------------------------------------------------------------------------------]| [3:--------------------------------------------------------------------------------]|
## grid 2 ## grid 2
^ | ^ |
{1:~ }|*4 {1:~ }|*2
{MATCH:.*}| {1:~ }{16:│} {26:╲} {26:││}{1: }|
{1:~ }{16:││}{26:╲╲││}{1: }|
{1:~ }{16:││} {26:╲} {26:│}{1: }|
{1:~ }| {1:~ }|
{1:~{MATCH: +}}{26:NVIM {MATCH:%S+}}{1:{MATCH: +}}|
{1:~ ──────────────────────────────────────────── }|
{1:~ }Nvim is open source and freely distributable{1: }| {1:~ }Nvim is open source and freely distributable{1: }|
{1:~ }https://neovim.io/#chat{1: }| {1:~ }https://neovim.io/#chat{1: }|
{1:~ }| {1:~ ──────────────────────────────────────────── }|
{1:~ }type :help nvim{18:<Enter>} if you are new! {1: }| {1:~ }type {18::}{25:help nvim}{18:<Enter>} if you are new! {1: }|
{1:~ }type :checkhealth{18:<Enter>} to optimize Nvim{1: }| {1:~ }type {18::}{25:checkhealth}{18:<Enter>} to optimize Nvim{1: }|
{1:~ }type :q{18:<Enter>} to exit {1: }| {1:~ }type {18::}{25:q}{18:<Enter>} to exit {1: }|
{1:~ }type :help{18:<Enter>} for help {1: }| {1:~ }type {18::}{25:help}{18:<Enter>} for help {1: }|
{1:~ }| {1:~ ──────────────────────────────────────────── }|
{1:~{MATCH: +}}type :help news{18:<Enter>} to see changes in v{MATCH:%d+%.%d+}{1:{MATCH: +}}| {1:~ }type {18::}{25:help news}{18:<Enter>} for v{MATCH:%d+%.%d+} notes {1:{MATCH: +}}|
{1:~ }| {1:~ ──────────────────────────────────────────── }|
{1:~ }Help poor children in Uganda!{1: }| {1:~ }Help poor children in Uganda!{1: }|
{1:~ }type :help Kuwasha{18:<Enter>} for information {1: }| {1:~ }type {18::}{25:help Kuwasha}{18:<Enter>} for information {1: }|
{1:~ }|*4 {1:~ }|*2
## grid 3 ## grid 3
| |
]], ]],