Skip to content

Commit a2ef68a

Browse files
committed
Add prompt-toolkit theme integration for autocomplete and matching brackets
1 parent 9e2e36b commit a2ef68a

9 files changed

Lines changed: 157 additions & 103 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pyrepl.setup({
2424
split_horizontal = false,
2525
split_ratio = 0.5,
2626
style = "default",
27-
style_treesitter = true,
27+
style_integration = true,
2828
image_max_history = 10,
2929
image_width_ratio = 0.5,
3030
image_height_ratio = 0.5,
@@ -150,11 +150,11 @@ python -m ipykernel install --user --name {kernel_name}
150150

151151
### Use a built-in Pygments style
152152

153-
If you do not like the treesitter-based REPL colors, pick a built-in Pygments theme:
153+
If you do not want pyrepl.nvim to derive REPL colors from your Neovim theme, pick a built-in Pygments style instead:
154154

155155
```lua
156156
require("pyrepl").setup({
157-
style_treesitter = false,
157+
style_integration = false,
158158
style = "default", -- or another Pygments style, e.g. "gruvbox-dark"
159159
})
160160
```

doc/pyrepl.txt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Minimal `vim.pack` setup with the default config and example keymaps:
3636
split_horizontal = false,
3737
split_ratio = 0.5,
3838
style = "default",
39-
style_treesitter = true,
39+
style_integration = true,
4040
image_max_history = 10,
4141
image_width_ratio = 0.5,
4242
image_height_ratio = 0.5,
@@ -206,7 +206,7 @@ Default configuration:
206206
split_horizontal = false,
207207
split_ratio = 0.5,
208208
style = "default",
209-
style_treesitter = true,
209+
style_integration = true,
210210
image_max_history = 10,
211211
image_width_ratio = 0.5,
212212
image_height_ratio = 0.5,
@@ -225,10 +225,10 @@ Options:
225225
Fraction of screen used by the REPL split. Default: 0.5.
226226
style (string)
227227
Pygments style name used by jupyter-console. Default: "default".
228-
Used directly when `style_treesitter` is false.
229-
style_treesitter (boolean)
230-
Derive Pygments style overrides from current Neovim/Tree-sitter
231-
highlight groups for REPL output. Default: true.
228+
Used directly when `style_integration` is false.
229+
style_integration (boolean)
230+
Derive Pygments and prompt-toolkit style overrides from current
231+
Neovim highlight groups for the REPL. Default: true.
232232
Set to false to use built-in Pygments styles from `style`.
233233
image_max_history (integer)
234234
Maximum number of images kept in history. Default: 10. Minimum: 2.
@@ -325,11 +325,11 @@ To use an arbitrary kernel in that case, you need to install it globally:
325325

326326
Use a built-in Pygments style ~
327327

328-
If you do not like the treesitter-based REPL colors, disable it and pick a
329-
built-in Pygments theme:
328+
If you do not want pyrepl.nvim to derive REPL colors from your Neovim
329+
highlights, disable the integration and pick a built-in Pygments theme:
330330
>
331331
require("pyrepl").setup({
332-
style_treesitter = false,
332+
style_integration = false,
333333
style = "default", -- or another Pygments style, e.g. "gruvbox-dark"
334334
})
335335
<

lua/pyrepl/config.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ local defaults = {
55
split_horizontal = false,
66
split_ratio = 0.5,
77
style = "default",
8-
style_treesitter = true,
8+
style_integration = true,
99
image_max_history = 10,
1010
image_width_ratio = 0.5,
1111
image_height_ratio = 0.5,

lua/pyrepl/core.lua

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ local function open_new_repl(kernel)
9696
local console_path = python.get_console_path()
9797
local nvim_socket = vim.v.servername
9898
local style = config.get_state().style
99-
local style_treesitter = config.get_state().style_treesitter
99+
local style_integration = config.get_state().style_integration
100100

101101
local buf = vim.api.nvim_create_buf(false, true)
102102
local win = open_scratch_win(buf)
@@ -113,11 +113,18 @@ local function open_new_repl(kernel)
113113
vim.o.termguicolors and "True" or "False",
114114
}
115115

116-
if style_treesitter then
117-
local overrides = theme.build_pygments_theme()
118-
if overrides then
116+
if style_integration then
117+
local pygments_overrides = theme.build_pygments_theme()
118+
local prompt_toolkit_overrides = theme.build_prompt_toolkit_theme()
119+
120+
if pygments_overrides then
119121
cmd[#cmd + 1] = "--ZMQTerminalInteractiveShell.highlighting_style_overrides"
120-
cmd[#cmd + 1] = overrides
122+
cmd[#cmd + 1] = pygments_overrides
123+
end
124+
125+
if prompt_toolkit_overrides then
126+
cmd[#cmd + 1] = "--prompt-toolkit-overrides"
127+
cmd[#cmd + 1] = prompt_toolkit_overrides
121128
end
122129
end
123130

lua/pyrepl/theme.lua

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,31 +56,54 @@ local pygments_hl_map = {
5656
["('OutPromptNum',)"] = { "@number", "Number" },
5757
}
5858

59-
---@param hl_name string
59+
local prompt_toolkit_hl_map = {
60+
["readline-like-completions"] = "Pmenu",
61+
["matching-bracket.other"] = "MatchParen",
62+
["matching-bracket.cursor"] = "Cursor",
63+
64+
["completion-menu"] = "Pmenu",
65+
["completion-menu.completion"] = "Pmenu",
66+
["completion-menu.completion.current"] = "PmenuSel",
67+
["completion-menu.meta.completion"] = "PmenuExtra",
68+
["completion-menu.meta.completion.current"] = "PmenuExtraSel",
69+
["completion-menu.multi-column-meta"] = "PmenuExtra",
70+
71+
["completion-toolbar"] = "Pmenu",
72+
["completion-toolbar.completion.current"] = "PmenuSel",
73+
}
74+
75+
---@param hl string
6076
---@return string|nil
61-
local function style_from_hl(hl_name)
62-
local hl = vim.api.nvim_get_hl(0, {
63-
name = hl_name,
77+
local function pygments_style_from_hl(hl)
78+
local parts = {}
79+
local style = vim.api.nvim_get_hl(0, {
80+
name = hl,
6481
link = false,
6582
})
6683

67-
if type(hl.fg) ~= "number" then
68-
return
84+
if style.fg then
85+
parts[#parts + 1] = string.format("fg:#%06x", style.fg)
86+
end
87+
88+
if style.bg then
89+
parts[#parts + 1] = string.format("bg:#%06x", style.bg)
6990
end
7091

71-
return string.format("'#%06x'", hl.fg)
92+
if #parts > 0 then
93+
return table.concat(parts, " ")
94+
end
7295
end
7396

7497
---@return string|nil
7598
function M.build_pygments_theme()
7699
local theme = {}
77100

78-
for pygments, hls in pairs(pygments_hl_map) do
101+
for pygments_hls, hls in pairs(pygments_hl_map) do
79102
-- obtain style from candidates
80103
for _, hl in ipairs(hls) do
81-
local color = style_from_hl(hl)
82-
if color then
83-
theme[#theme + 1] = string.format("%s: %s", pygments, color)
104+
local style = pygments_style_from_hl(hl)
105+
if style then
106+
theme[#theme + 1] = string.format("%s: '%s'", pygments_hls, style)
84107
break
85108
end
86109
end
@@ -94,4 +117,21 @@ function M.build_pygments_theme()
94117
return "{" .. table.concat(theme, ", ") .. "}"
95118
end
96119

120+
function M.build_prompt_toolkit_theme()
121+
local theme = {}
122+
123+
for prompt_toolkit_hl, hl in pairs(prompt_toolkit_hl_map) do
124+
local style = pygments_style_from_hl(hl)
125+
if style then
126+
theme[prompt_toolkit_hl] = style
127+
end
128+
end
129+
130+
if vim.tbl_isempty(theme) then
131+
return nil
132+
end
133+
134+
return vim.json.encode(theme)
135+
end
136+
97137
return M

lua/pyrepl/types.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
---@field split_horizontal boolean
66
---@field split_ratio number
77
---@field style string
8-
---@field style_treesitter boolean
8+
---@field style_integration boolean
99
---@field image_max_history integer
1010
---@field image_width_ratio number
1111
---@field image_height_ratio number
@@ -20,7 +20,7 @@
2020
---@field split_horizontal? boolean
2121
---@field split_ratio? number
2222
---@field style? string
23-
---@field style_treesitter? boolean
23+
---@field style_integration? boolean
2424
---@field image_max_history? integer
2525
---@field image_width_ratio? number
2626
---@field image_height_ratio? number

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "pyrepl"
3-
version = "0.6.5"
3+
version = "0.7.1"
44
description = "REPL experience for Python in Neovim."
55
readme = "README.md"
66
authors = [

src/pyrepl/console.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import base64
22
import io
3+
import json
34
import os
4-
import sys
5+
from argparse import ArgumentParser
56
from enum import StrEnum
67
from queue import Queue
78
from threading import Event, Thread
89
from typing import Any
910

1011
import pynvim
1112
from jupyter_console.app import ZMQTerminalIPythonApp
13+
from prompt_toolkit.styles import defaults as ptk_defaults
1214
from traitlets.config import Config
1315

1416

@@ -64,7 +66,6 @@ def convert_image_to_png_base64(
6466
raw = image_data.encode("utf-8")
6567
png_bytes = cairosvg.svg2png(bytestring=raw)
6668
return base64.b64encode(png_bytes).decode("utf-8")
67-
6869
except Exception:
6970
return None
7071

@@ -153,9 +154,17 @@ def image_handler(data):
153154
config = Config()
154155
config.ZMQTerminalInteractiveShell.image_handler = "callable"
155156
config.ZMQTerminalInteractiveShell.callable_image_handler = image_handler
156-
157157
app = ZMQTerminalIPythonApp.instance(config=config)
158-
app.initialize(sys.argv[1:])
158+
159+
parser = ArgumentParser("Pyrepl console.")
160+
parser.add_argument("--prompt-toolkit-overrides", type=str, default=None)
161+
known, args = parser.parse_known_args()
162+
app.initialize(args)
163+
164+
if known.prompt_toolkit_overrides is not None:
165+
overrides = dict(ptk_defaults.PROMPT_TOOLKIT_STYLE)
166+
overrides.update(json.loads(known.prompt_toolkit_overrides))
167+
ptk_defaults.PROMPT_TOOLKIT_STYLE[:] = list(overrides.items())
159168

160169
thread.start()
161170
app.start() # type: ignore

0 commit comments

Comments
 (0)