@@ -12,4 +12,189 @@ vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
1212 end ,
1313})
1414
15- return {}
15+ local M = {}
16+
17+ M .state = {
18+ wins = {},
19+ bufs = {},
20+ timers = { devtools = nil , logs = nil },
21+ devtools = { job = nil , lines = {} },
22+ }
23+
24+ local function strip_ansi (str )
25+ return str :gsub (" \27 %[[0-9;]*m" , " " )
26+ end
27+
28+ local function create_buffer (opts )
29+ local buf = vim .api .nvim_create_buf (false , true )
30+ vim .api .nvim_set_current_buf (buf )
31+ vim .bo .modifiable = true
32+ vim .bo .buftype = " nofile"
33+ vim .bo .filetype = opts .filetype or " text"
34+
35+ local win = vim .api .nvim_get_current_win ()
36+ vim .wo .number = opts .number ~= nil and opts .number or false
37+ vim .wo .relativenumber = false
38+ vim .wo .list = false
39+ vim .wo .wrap = opts .wrap or false
40+
41+ return buf , win
42+ end
43+
44+ local function auto_scroll (bufnr , line_count )
45+ local wins = vim .fn .win_findbuf (bufnr )
46+ for _ , win in ipairs (wins ) do
47+ vim .api .nvim_win_set_cursor (win , { line_count , 0 })
48+ end
49+ end
50+
51+ function M .ensure_devtools_running ()
52+ if M .state .devtools .job then
53+ return
54+ end
55+
56+ M .state .devtools .lines = {}
57+ M .state .devtools .job = vim .fn .jobstart (" just dev devtools record" , {
58+ stdout_buffered = false ,
59+ on_stdout = function (_ , data )
60+ if not data then
61+ return
62+ end
63+ local cleaned = vim .tbl_map (strip_ansi , data )
64+ local filtered = vim .tbl_filter (function (line )
65+ return line ~= " "
66+ end , cleaned )
67+ for _ , line in ipairs (filtered ) do
68+ table.insert (M .state .devtools .lines , line )
69+ end
70+ end ,
71+ on_exit = function ()
72+ M .state .devtools .job = nil
73+ end ,
74+ })
75+ end
76+
77+ function M .update_devtools (bufnr , start_idx )
78+ if not vim .api .nvim_buf_is_valid (bufnr ) then
79+ return # M .state .devtools .lines
80+ end
81+
82+ local new_lines = vim .list_slice (M .state .devtools .lines , start_idx + 1 )
83+ if # new_lines > 0 then
84+ vim .api .nvim_buf_set_lines (bufnr , - 1 , - 1 , false , new_lines )
85+ auto_scroll (bufnr , vim .api .nvim_buf_line_count (bufnr ))
86+ end
87+
88+ return # M .state .devtools .lines
89+ end
90+
91+ function M .update_logs (bufnr )
92+ local log_file = vim .fn .system (" ls -t /tmp/djls.log.* 2>/dev/null | head -1" ):gsub (" %s+$" , " " )
93+
94+ if log_file == " " then
95+ vim .api .nvim_buf_set_lines (bufnr , 0 , - 1 , false , { " Waiting for server logs..." })
96+ return
97+ end
98+
99+ local lines = vim .fn .readfile (log_file )
100+ vim .api .nvim_buf_set_lines (bufnr , 0 , - 1 , false , lines )
101+ auto_scroll (bufnr , # lines )
102+ end
103+
104+ function M .close ()
105+ for _ , timer in pairs (M .state .timers ) do
106+ if timer then
107+ timer :stop ()
108+ end
109+ end
110+ M .state .timers = { devtools = nil , logs = nil }
111+
112+ for _ , win in ipairs (M .state .wins ) do
113+ if vim .api .nvim_win_is_valid (win ) then
114+ vim .api .nvim_win_close (win , true )
115+ end
116+ end
117+ for _ , buf in ipairs (M .state .bufs ) do
118+ if vim .api .nvim_buf_is_valid (buf ) then
119+ vim .api .nvim_buf_delete (buf , { force = true })
120+ end
121+ end
122+
123+ M .state .wins = {}
124+ M .state .bufs = {}
125+ end
126+
127+ function M .open ()
128+ M .ensure_devtools_running ()
129+
130+ local main_win = vim .api .nvim_get_current_win ()
131+
132+ vim .cmd (" vsplit" )
133+ vim .cmd (" wincmd l" )
134+ vim .cmd (" vertical resize 80" )
135+
136+ -- Top: lsp-devtools
137+ local devtools_buf , devtools_win = create_buffer ({ filetype = " json" })
138+ if # M .state .devtools .lines > 0 then
139+ vim .api .nvim_buf_set_lines (devtools_buf , 0 , - 1 , false , M .state .devtools .lines )
140+ end
141+
142+ local last_line_count = # M .state .devtools .lines
143+ M .state .timers .devtools = vim .uv .new_timer ()
144+ M .state .timers .devtools :start (
145+ 100 ,
146+ 100 ,
147+ vim .schedule_wrap (function ()
148+ last_line_count = M .update_devtools (devtools_buf , last_line_count )
149+ end )
150+ )
151+
152+ table.insert (M .state .wins , devtools_win )
153+ table.insert (M .state .bufs , devtools_buf )
154+
155+ -- Bottom: server logs
156+ vim .cmd (" split" )
157+ vim .cmd (" wincmd j" )
158+ local log_buf , log_win = create_buffer ({ filetype = " log" , wrap = true })
159+
160+ M .update_logs (log_buf )
161+ M .state .timers .logs = vim .uv .new_timer ()
162+ M .state .timers .logs :start (
163+ 500 ,
164+ 500 ,
165+ vim .schedule_wrap (function ()
166+ M .update_logs (log_buf )
167+ end )
168+ )
169+
170+ table.insert (M .state .wins , log_win )
171+ table.insert (M .state .bufs , log_buf )
172+
173+ vim .api .nvim_set_current_win (main_win )
174+ end
175+
176+ function M .toggle ()
177+ if # M .state .wins > 0 then
178+ M .close ()
179+ else
180+ M .open ()
181+ end
182+ end
183+
184+ vim .api .nvim_create_autocmd (" VimLeavePre" , {
185+ callback = function ()
186+ if M .state .devtools .job then
187+ vim .fn .jobstop (M .state .devtools .job )
188+ end
189+ end ,
190+ })
191+
192+ vim .api .nvim_create_user_command (" DjlsDebugToggle" , M .toggle , {})
193+ vim .keymap .set (" n" , " <leader>dd" , M .toggle , { desc = " Toggle DJLS debug windows" })
194+
195+ return {
196+ {
197+ " fei6409/log-highlight.nvim" ,
198+ opts = {},
199+ },
200+ }
0 commit comments