-
-
Notifications
You must be signed in to change notification settings - Fork 36
feat: add popup window mode for LLM responses #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -55,34 +55,130 @@ EOF | |||||||||||||||||||||||||||||
| endif | ||||||||||||||||||||||||||||||
| endfunction | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| " Check if popup window support is available | ||||||||||||||||||||||||||||||
| function! chatgpt#has_popup_support() abort | ||||||||||||||||||||||||||||||
| return has('popupwin') && exists('*popup_create') | ||||||||||||||||||||||||||||||
| endfunction | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| " Create popup window for LLM output | ||||||||||||||||||||||||||||||
| function! chatgpt#create_popup_window(chat_gpt_session_id) abort | ||||||||||||||||||||||||||||||
| " Calculate popup dimensions | ||||||||||||||||||||||||||||||
| let width = min([float2nr(&columns * 0.8), &columns - 4]) | ||||||||||||||||||||||||||||||
| let height = min([float2nr(&lines * 0.6), &lines - 4]) | ||||||||||||||||||||||||||||||
| " Ensure position values are at least 2 to avoid border overlap | ||||||||||||||||||||||||||||||
| let row = max([2, float2nr((&lines - height) / 2)]) | ||||||||||||||||||||||||||||||
| let col = max([2, float2nr((&columns - width) / 2)]) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| " Get or create buffer | ||||||||||||||||||||||||||||||
| let bufnr = bufnr(a:chat_gpt_session_id) | ||||||||||||||||||||||||||||||
| if bufnr == -1 | ||||||||||||||||||||||||||||||
| let bufnr = bufadd(a:chat_gpt_session_id) | ||||||||||||||||||||||||||||||
| call bufload(bufnr) | ||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&buftype', 'nofile') | ||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&bufhidden', 'hide') | ||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&swapfile', 0) | ||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&ft', 'markdown') | ||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&syntax', 'markdown') | ||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&wrap', 1) | ||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&linebreak', 1) | ||||||||||||||||||||||||||||||
|
Comment on lines
+80
to
+83
|
||||||||||||||||||||||||||||||
| endif | ||||||||||||||||||||||||||||||
|
Comment on lines
+72
to
+84
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| " Create popup window | ||||||||||||||||||||||||||||||
| let opts = { | ||||||||||||||||||||||||||||||
| \ 'line': row, | ||||||||||||||||||||||||||||||
| \ 'col': col, | ||||||||||||||||||||||||||||||
| \ 'minwidth': width, | ||||||||||||||||||||||||||||||
| \ 'maxwidth': width, | ||||||||||||||||||||||||||||||
| \ 'minheight': height, | ||||||||||||||||||||||||||||||
| \ 'maxheight': height, | ||||||||||||||||||||||||||||||
| \ 'border': [1, 1, 1, 1], | ||||||||||||||||||||||||||||||
| \ 'borderchars': ['─', '│', '─', '│', '┌', '┐', '┘', '└'], | ||||||||||||||||||||||||||||||
| \ 'title': ' LLM Agent ', | ||||||||||||||||||||||||||||||
| \ 'padding': [0, 1, 0, 1], | ||||||||||||||||||||||||||||||
| \ 'close': 'button', | ||||||||||||||||||||||||||||||
| \ 'resize': 1, | ||||||||||||||||||||||||||||||
| \ 'wrap': 1, | ||||||||||||||||||||||||||||||
| \ 'mapping': 0, | ||||||||||||||||||||||||||||||
| \ } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| let winid = popup_create(bufnr, opts) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| " Store popup window ID for later reference | ||||||||||||||||||||||||||||||
| let g:llm_agent_popup_winid = winid | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return winid | ||||||||||||||||||||||||||||||
| endfunction | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| " Display ChatGPT responses in a buffer | ||||||||||||||||||||||||||||||
| function! chatgpt#display_response(response, finish_reason, chat_gpt_session_id) | ||||||||||||||||||||||||||||||
| let response = a:response | ||||||||||||||||||||||||||||||
| let finish_reason = a:finish_reason | ||||||||||||||||||||||||||||||
| let chat_gpt_session_id = a:chat_gpt_session_id | ||||||||||||||||||||||||||||||
| if !bufexists(chat_gpt_session_id) | ||||||||||||||||||||||||||||||
| let split_dir = exists('g:llm_agent_split_direction') ? g:llm_agent_split_direction : (exists('g:chat_gpt_split_direction') ? g:chat_gpt_split_direction : 'vertical') | ||||||||||||||||||||||||||||||
| if split_dir ==# 'vertical' | ||||||||||||||||||||||||||||||
| silent execute winwidth(0)/g:split_ratio.'vnew '. chat_gpt_session_id | ||||||||||||||||||||||||||||||
| let split_dir = exists('g:llm_agent_split_direction') ? g:llm_agent_split_direction : (exists('g:chat_gpt_split_direction') ? g:chat_gpt_split_direction : 'vertical') | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| " Handle popup window mode | ||||||||||||||||||||||||||||||
| if split_dir ==# 'popup' | ||||||||||||||||||||||||||||||
| if !chatgpt#has_popup_support() | ||||||||||||||||||||||||||||||
| echohl WarningMsg | ||||||||||||||||||||||||||||||
| echo "Popup windows require Vim 8.2+ with +popupwin feature. Falling back to vertical split." | ||||||||||||||||||||||||||||||
| echohl None | ||||||||||||||||||||||||||||||
| let split_dir = 'vertical' | ||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||
| silent execute winheight(0)/g:split_ratio.'new '. chat_gpt_session_id | ||||||||||||||||||||||||||||||
| " Check if popup already exists and is valid | ||||||||||||||||||||||||||||||
| let popup_exists = exists('g:llm_agent_popup_winid') && g:llm_agent_popup_winid > 0 && popup_getpos(g:llm_agent_popup_winid) != {} | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if !popup_exists | ||||||||||||||||||||||||||||||
| call chatgpt#create_popup_window(chat_gpt_session_id) | ||||||||||||||||||||||||||||||
| endif | ||||||||||||||||||||||||||||||
|
Comment on lines
126
to
+132
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| " Get buffer number from popup or create new one | ||||||||||||||||||||||||||||||
| let bufnr = bufnr(chat_gpt_session_id) | ||||||||||||||||||||||||||||||
| if bufnr == -1 | ||||||||||||||||||||||||||||||
| let bufnr = bufadd(chat_gpt_session_id) | ||||||||||||||||||||||||||||||
| call bufload(bufnr) | ||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&buftype', 'nofile') | ||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&bufhidden', 'hide') | ||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&swapfile', 0) | ||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&ft', 'markdown') | ||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&syntax', 'markdown') | ||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&wrap', 1) | ||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&linebreak', 1) | ||||||||||||||||||||||||||||||
| endif | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| " Update popup content | ||||||||||||||||||||||||||||||
| let winid = exists('g:llm_agent_popup_winid') ? g:llm_agent_popup_winid : 0 | ||||||||||||||||||||||||||||||
| if winid > 0 | ||||||||||||||||||||||||||||||
|
Comment on lines
+144
to
+150
|
||||||||||||||||||||||||||||||
| call setbufvar(bufnr, '&wrap', 1) | |
| call setbufvar(bufnr, '&linebreak', 1) | |
| endif | |
| " Update popup content | |
| let winid = exists('g:llm_agent_popup_winid') ? g:llm_agent_popup_winid : 0 | |
| if winid > 0 | |
| endif | |
| " Update popup content | |
| let winid = exists('g:llm_agent_popup_winid') ? g:llm_agent_popup_winid : 0 | |
| if winid > 0 | |
| call win_execute(winid, 'setlocal wrap') | |
| call win_execute(winid, 'setlocal linebreak') |
Copilot
AI
Apr 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In popup mode the popup content is updated (popup_settext + scroll) before the new response is appended to the session buffer (setbufline happens later). This means the popup can display stale content (often one response behind), and it also won’t be refreshed after the buffer changes. Consider updating the popup after setbufline(), or avoid popup_settext entirely and rely on the buffer-backed popup created by popup_create(bufnr, ...) and just scroll it after writing.
| " Update popup content | |
| let winid = exists('g:llm_agent_popup_winid') ? g:llm_agent_popup_winid : 0 | |
| if winid > 0 | |
| call popup_settext(winid, getbufline(bufnr, 1, '$')) | |
| " Scroll to bottom of popup | |
| call win_execute(winid, 'normal! G') | |
| endif | |
| " Do not manually copy buffer text into the popup before the response | |
| " is written. The popup is buffer-backed, so it should reflect the | |
| " buffer contents after the later setbufline() update. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
README says the 'popup' option uses “Vim 8+” floating windows, but the code warns that it requires Vim 8.2+ with +popupwin (has('popupwin')). Update the README to match the actual minimum requirement and mention the automatic fallback behavior.