11local config = require (' opencode.config' )
22local Timer = require (' opencode.ui.timer' )
33
4+ --- @class Timer
5+ --- @field start function
6+ --- @field stop function
7+ --- @field is_running function
8+
49--- @class CursorSpinner
510--- @field buf integer
611--- @field row integer
@@ -11,6 +16,8 @@ local Timer = require('opencode.ui.timer')
1116--- @field timer Timer | nil
1217--- @field active boolean
1318--- @field frames string[]
19+ --- @field float_win integer | nil
20+ --- @field float_buf integer | nil
1421local CursorSpinner = {}
1522CursorSpinner .__index = CursorSpinner
1623
@@ -24,27 +31,54 @@ function CursorSpinner.new(buf, row, col)
2431 self .current_frame = 1
2532 self .timer = nil
2633 self .active = true
34+ self .float_win = nil
35+ self .float_buf = nil
2736
28- self .frames = config .values . ui .loading_animation and config . values .ui .loading_animation .frames
37+ self .frames = config .ui .loading_animation and config .ui .loading_animation .frames
2938 or { ' ⠋' , ' ⠙' , ' ⠹' , ' ⠸' , ' ⠼' , ' ⠴' , ' ⠦' , ' ⠧' , ' ⠇' , ' ⠏' }
3039
40+ self :create_float ()
3141 self :render ()
3242 self :start_timer ()
3343 return self
3444end
3545
36- function CursorSpinner :render ()
46+ function CursorSpinner :create_float ()
3747 if not self .active or not vim .api .nvim_buf_is_valid (self .buf ) then
3848 return
3949 end
4050
41- local frame = ' ' .. self .frames [self .current_frame ]
42- self .extmark_id = vim .api .nvim_buf_set_extmark (self .buf , self .ns_id , self .row , self .col , {
43- id = self .extmark_id ,
44- virt_text = { { frame .. ' ' , ' Comment' } },
45- virt_text_pos = ' overlay' ,
46- right_gravity = false ,
47- })
51+ self .float_buf = vim .api .nvim_create_buf (false , true )
52+
53+ local win_config = self :get_float_config ()
54+
55+ self .float_win = vim .api .nvim_open_win (self .float_buf , false , win_config )
56+
57+ vim .api .nvim_set_option_value (' winhl' , ' Normal:Comment' , { win = self .float_win })
58+ vim .api .nvim_set_option_value (' wrap' , false , { win = self .float_win })
59+ end
60+
61+ function CursorSpinner :get_float_config ()
62+ return {
63+ relative = ' cursor' ,
64+ width = 3 ,
65+ height = 1 ,
66+ row = 0 ,
67+ col = 2 , -- 2 columns to the right of cursor
68+ style = ' minimal' ,
69+ border = ' rounded' ,
70+ focusable = false ,
71+ zindex = 1000 ,
72+ }
73+ end
74+
75+ function CursorSpinner :render ()
76+ if not self .active or not self .float_buf or not vim .api .nvim_buf_is_valid (self .float_buf ) then
77+ return
78+ end
79+
80+ local frame = ' ' .. self .frames [self .current_frame ] .. ' '
81+ vim .api .nvim_buf_set_lines (self .float_buf , 0 , - 1 , false , { frame })
4882end
4983
5084function CursorSpinner :next_frame ()
5387
5488function CursorSpinner :start_timer ()
5589 self .timer = Timer .new ({
56- interval = 100 , -- 10 FPS like the main loading animation
90+ interval = 100 , -- 10 FPS
5791 on_tick = function ()
5892 if not self .active then
5993 return false
@@ -79,6 +113,14 @@ function CursorSpinner:stop()
79113 self .timer = nil
80114 end
81115
116+ if self .float_win and vim .api .nvim_win_is_valid (self .float_win ) then
117+ pcall (vim .api .nvim_win_close , self .float_win , true )
118+ end
119+
120+ if self .float_buf and vim .api .nvim_buf_is_valid (self .float_buf ) then
121+ pcall (vim .api .nvim_buf_delete , self .float_buf , { force = true })
122+ end
123+
82124 if self .extmark_id and vim .api .nvim_buf_is_valid (self .buf ) then
83125 pcall (vim .api .nvim_buf_del_extmark , self .buf , self .ns_id , self .extmark_id )
84126 end
0 commit comments