diff --git a/docs/assets/theme-tokyonight-dark.png b/docs/assets/theme-tokyonight-dark.png new file mode 100644 index 0000000000..ebeec93548 Binary files /dev/null and b/docs/assets/theme-tokyonight-dark.png differ diff --git a/docs/cli/themes.md b/docs/cli/themes.md index 55acc75625..93912032c0 100644 --- a/docs/cli/themes.md +++ b/docs/cli/themes.md @@ -19,6 +19,7 @@ using the `/theme` command within Gemini CLI: - `Holiday` - `Shades Of Purple` - `Solarized Dark` + - `Tokyo Night` - **Light themes:** - `ANSI Light` - `Ayu Light` @@ -252,6 +253,10 @@ identify their source, for example: `shades-of-green (green-extension)`. Solarized Dark theme +### Tokyo Night + +Tokyo Night theme + ## Light themes ### ANSI Light diff --git a/packages/cli/src/ui/components/MainContent.test.tsx b/packages/cli/src/ui/components/MainContent.test.tsx index 93d77e0dfe..2bc6ee27bc 100644 --- a/packages/cli/src/ui/components/MainContent.test.tsx +++ b/packages/cli/src/ui/components/MainContent.test.tsx @@ -27,6 +27,10 @@ import { } from '../hooks/useConfirmingTool.js'; // Mock dependencies +vi.mock('ink-spinner', () => ({ + default: () => , +})); + const mockUseSettings = vi.fn().mockReturnValue({ merged: { ui: { diff --git a/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap index 07a28039d1..f0260ddc91 100644 --- a/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap @@ -4,7 +4,7 @@ exports[`MainContent > MainContent Tool Output Height Logic > 'ASB mode - Focuse "ScrollableList AppHeader(full) ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ -│ ⊶ Shell Command Running a long command... │ +│ ⠋ Shell Command Running a long command... │ │ │ │ Line 11 │ │ Line 12 │ @@ -24,7 +24,7 @@ exports[`MainContent > MainContent Tool Output Height Logic > 'ASB mode - Unfocu "ScrollableList AppHeader(full) ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ -│ ⊶ Shell Command Running a long command... │ +│ ⠋ Shell Command Running a long command... │ │ │ │ Line 11 │ │ Line 12 │ @@ -43,7 +43,7 @@ AppHeader(full) exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Constrained height' 1`] = ` "AppHeader(full) ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ -│ ⊶ Shell Command Running a long command... │ +│ ⠋ Shell Command Running a long command... │ │ │ │ ... first 11 lines hidden (Ctrl+O to show) ... │ │ Line 12 │ @@ -62,7 +62,7 @@ exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Con exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Unconstrained height' 1`] = ` "AppHeader(full) ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ -│ ⊶ Shell Command Running a long command... │ +│ ⠋ Shell Command Running a long command... │ │ │ │ Line 1 │ │ Line 2 │ diff --git a/packages/cli/src/ui/components/__snapshots__/ThemeDialog.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/ThemeDialog.test.tsx.snap index 4a5b30fc5c..2b9090e237 100644 --- a/packages/cli/src/ui/components/__snapshots__/ThemeDialog.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/ThemeDialog.test.tsx.snap @@ -14,9 +14,9 @@ exports[`Initial Theme Selection > should default to a dark theme when terminal │ 7. Holiday Dark │ 6 return a │ │ │ 8. Shades Of Purple Dark │ │ │ │ 9. Solarized Dark │ 1 - print("Hello, " + name) │ │ -│ 10. ANSI Light │ 1 + print(f"Hello, {name}!") │ │ -│ 11. Ayu Light │ │ │ -│ 12. Default Light └─────────────────────────────────────────────────┘ │ +│ 10. Tokyo Night Dark │ 1 + print(f"Hello, {name}!") │ │ +│ 11. ANSI Light │ │ │ +│ 12. Ayu Light └─────────────────────────────────────────────────┘ │ │ ▼ │ │ │ │ (Use Enter to select, Tab to configure scope, Esc to close) │ @@ -64,9 +64,9 @@ exports[`Initial Theme Selection > should use the theme from settings even if te │ 7. Holiday Dark │ 6 return a │ │ │ 8. Shades Of Purple Dark │ │ │ │ 9. Solarized Dark │ 1 - print("Hello, " + name) │ │ -│ 10. ANSI Light │ 1 + print(f"Hello, {name}!") │ │ -│ 11. Ayu Light │ │ │ -│ 12. Default Light └─────────────────────────────────────────────────┘ │ +│ 10. Tokyo Night Dark │ 1 + print(f"Hello, {name}!") │ │ +│ 11. ANSI Light │ │ │ +│ 12. Ayu Light └─────────────────────────────────────────────────┘ │ │ ▼ │ │ │ │ (Use Enter to select, Tab to configure scope, Esc to close) │ @@ -103,9 +103,9 @@ exports[`ThemeDialog Snapshots > should render correctly in theme selection mode │ 7. Holiday Dark │ 6 return a │ │ │ 8. Shades Of Purple Dark │ │ │ │ 9. Solarized Dark │ 1 - print("Hello, " + name) │ │ -│ 10. ANSI Light │ 1 + print(f"Hello, {name}!") │ │ -│ 11. Ayu Light │ │ │ -│ 12. Default Light └─────────────────────────────────────────────────┘ │ +│ 10. Tokyo Night Dark │ 1 + print(f"Hello, {name}!") │ │ +│ 11. ANSI Light │ │ │ +│ 12. Ayu Light └─────────────────────────────────────────────────┘ │ │ ▼ │ │ │ │ (Use Enter to select, Tab to configure scope, Esc to close) │ @@ -128,9 +128,9 @@ exports[`ThemeDialog Snapshots > should render correctly in theme selection mode │ 7. Holiday Dark │ 6 return a │ │ │ 8. Shades Of Purple Dark │ │ │ │ 9. Solarized Dark │ 1 - print("Hello, " + name) │ │ -│ 10. ANSI Light │ 1 + print(f"Hello, {name}!") │ │ -│ 11. Ayu Light │ │ │ -│ 12. Default Light └─────────────────────────────────────────────────┘ │ +│ 10. Tokyo Night Dark │ 1 + print(f"Hello, {name}!") │ │ +│ 11. ANSI Light │ │ │ +│ 12. Ayu Light └─────────────────────────────────────────────────┘ │ │ ▼ │ │ ╭─────────────────────────────────────────────────╮ │ │ │ DEVELOPER TOOLS (Not visible to users) │ │ diff --git a/packages/cli/src/ui/themes/builtin/dark/tokyonight-dark.ts b/packages/cli/src/ui/themes/builtin/dark/tokyonight-dark.ts new file mode 100644 index 0000000000..ca2c242eaa --- /dev/null +++ b/packages/cli/src/ui/themes/builtin/dark/tokyonight-dark.ts @@ -0,0 +1,155 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { type ColorsTheme, Theme } from '../../theme.js'; +import { interpolateColor } from '../../color-utils.js'; + +const palette = { + bg: '#1a1b26', + bg_dark: '#16161e', + bg_dark1: '#0C0E14', + bg_highlight: '#292e42', + blue: '#7aa2f7', + blue0: '#3d59a1', + blue1: '#2ac3de', + blue2: '#0db9d7', + blue5: '#89ddff', + blue6: '#b4f9f8', + blue7: '#394b70', + comment: '#565f89', + cyan: '#7dcfff', + fg: '#c0caf5', + fg_dark: '#a9b1d6', + fg_gutter: '#3b4261', + green: '#9ece6a', + green1: '#73daca', + green2: '#41a6b5', + magenta: '#bb9af7', + magenta2: '#ff007c', + orange: '#ff9e64', + purple: '#9d7cd8', + red: '#f7768e', + red1: '#db4b4b', + teal: '#1abc9c', + yellow: '#e0af68', + diff: { + add: '#243e4a', + change: '#1f2231', + delete: '#4a272f', + }, +}; + +const tokyoNightColors: ColorsTheme = { + type: 'dark', + Background: palette.bg, + Foreground: palette.fg, + LightBlue: palette.purple, + AccentBlue: palette.magenta, + AccentPurple: palette.blue, + AccentCyan: palette.cyan, + AccentGreen: palette.teal, + AccentYellow: palette.yellow, + AccentRed: palette.red1, + DiffAdded: palette.diff.add, + DiffRemoved: palette.diff.delete, + Comment: palette.comment, + Gray: palette.fg_dark, + DarkGray: palette.fg_gutter, + FocusColor: palette.blue, + GradientColors: [palette.blue, palette.magenta, palette.cyan], +}; + +export const TokyoNight: Theme = new Theme( + 'Tokyo Night', + 'dark', + { + hljs: { + display: 'block', + overflowX: 'auto', + padding: '0.5em', + background: palette.bg, + color: palette.fg, + }, + 'hljs-addition': { background: palette.diff.add }, + 'hljs-attr': { color: palette.green1 }, + 'hljs-attribute': { color: palette.green1 }, + 'hljs-brace': { color: palette.fg_dark }, + 'hljs-built_in': { color: palette.blue1 }, + 'hljs-builtin-symbol': { color: palette.blue1 }, + 'hljs-bullet': { + color: palette.orange, + fontWeight: 'bold', + }, + 'hljs-char': { color: palette.green }, + 'hljs-char-escape': { color: palette.magenta }, + 'hljs-character': { color: palette.green }, + 'hljs-class': { color: palette.blue1 }, + 'hljs-class-title': { color: palette.blue1 }, + 'hljs-code': { color: palette.green }, + 'hljs-comment': { + color: palette.comment, + fontStyle: 'italic', + }, + 'hljs-computation-expression': { color: palette.cyan }, + 'hljs-deletion': { background: palette.diff.delete }, + 'hljs-doctag': { color: palette.yellow }, + 'hljs-emphasis': { fontStyle: 'italic' }, + 'hljs-function': { color: palette.blue }, + 'hljs-function-dispatch': { color: palette.blue }, + 'hljs-keyword': { + color: palette.magenta, + fontStyle: 'italic', + }, + 'hljs-label': { color: palette.blue }, + 'hljs-link': { color: palette.teal }, + 'hljs-literal': { color: palette.orange }, + 'hljs-message-name': { color: palette.blue }, + 'hljs-meta': { color: palette.cyan }, + 'hljs-meta-prompt': { color: palette.fg_dark }, + 'hljs-name': { color: palette.magenta }, + 'hljs-named-character': { color: palette.blue1 }, + 'hljs-number': { color: palette.orange }, + 'hljs-operator': { color: palette.blue5 }, + 'hljs-params': { color: palette.yellow }, + 'hljs-property': { color: palette.green1 }, + 'hljs-punctuation': { color: palette.fg_dark }, + 'hljs-quote': { + color: palette.comment, + fontStyle: 'italic', + }, + 'hljs-regex': { color: palette.blue6 }, + 'hljs-regexp': { color: palette.blue6 }, + 'hljs-rest_arg': { + color: interpolateColor(palette.yellow, palette.fg, 0.8), + }, + 'hljs-section': { + color: palette.blue, + fontWeight: 'bold', + }, + 'hljs-selector-attr': { color: palette.cyan }, + 'hljs-selector-class': { color: palette.green1 }, + 'hljs-selector-id': { color: palette.green1 }, + 'hljs-selector-pseudo': { color: palette.cyan }, + 'hljs-selector-tag': { color: palette.magenta }, + 'hljs-string': { color: palette.green }, + 'hljs-strong': { fontWeight: 'bold' }, + 'hljs-subst': { color: palette.blue5 }, + 'hljs-symbol': { color: palette.magenta }, + 'hljs-tag': { color: palette.blue1 }, + 'hljs-template-tag': { color: palette.blue5 }, + 'hljs-template-variable': { color: palette.fg }, + 'hljs-title': { color: palette.blue }, + 'hljs-title-class': { color: palette.blue1 }, + 'hljs-title-class-inherited': { color: palette.blue1 }, + 'hljs-title-function': { color: palette.blue }, + 'hljs-title-function-invoke': { color: palette.blue }, + 'hljs-type': { color: palette.blue1 }, + 'hljs-variable': { color: palette.fg }, + 'hljs-variable-constant': { color: palette.orange }, + 'hljs-variable-language': { color: palette.red }, + }, + tokyoNightColors, +); diff --git a/packages/cli/src/ui/themes/theme-manager.ts b/packages/cli/src/ui/themes/theme-manager.ts index 96b4fea4e3..9f0a7e528a 100644 --- a/packages/cli/src/ui/themes/theme-manager.ts +++ b/packages/cli/src/ui/themes/theme-manager.ts @@ -18,6 +18,7 @@ import { ShadesOfPurple } from './builtin/dark/shades-of-purple-dark.js'; import { SolarizedDark } from './builtin/dark/solarized-dark.js'; import { SolarizedLight } from './builtin/light/solarized-light.js'; import { XCode } from './builtin/light/xcode-light.js'; +import { TokyoNight } from './builtin/dark/tokyonight-dark.js'; import * as fs from 'node:fs'; import * as path from 'node:path'; import type { Theme, ThemeType, ColorsTheme, CustomTheme } from './theme.js'; @@ -84,6 +85,7 @@ class ThemeManager { SolarizedDark, SolarizedLight, XCode, + TokyoNight, ANSI, ANSILight, ];