@@ -283,20 +283,67 @@ function M.get_mentioned_subagents()
283283 return M .context .mentioned_subagents or {}
284284end
285285
286+ --- @param current_file table | nil
287+ --- @return boolean , boolean -- should_update , is_different_file
288+ function M .should_update_current_file (current_file )
289+ if not M .context .current_file then
290+ return current_file ~= nil , false
291+ end
292+
293+ if not current_file then
294+ return false , false
295+ end
296+
297+ -- Different file name means update needed
298+ if M .context .current_file .name ~= current_file .name then
299+ return true , true
300+ end
301+
302+ local file_path = current_file .path
303+ if not file_path or vim .fn .filereadable (file_path ) ~= 1 then
304+ return false , false
305+ end
306+
307+ local stat = vim .uv .fs_stat (file_path )
308+ if not (stat and stat .mtime and stat .mtime .sec ) then
309+ return false , false
310+ end
311+
312+ local file_mtime_sec = stat .mtime .sec --[[ @as number]]
313+ local last_sent_mtime = M .context .current_file .sent_at_mtime or 0
314+ return file_mtime_sec > last_sent_mtime , false
315+ end
316+
286317-- Load function that populates the global context state
287318-- This is the core loading logic that was originally in the main context module
288319function M .load ()
289320 local buf , win = base_context .get_current_buf ()
290321
291- if buf then
292- local current_file = base_context .get_current_file (buf )
293- local cursor_data = base_context .get_current_cursor_data (buf , win )
322+ if not buf or not win then
323+ return
324+ end
325+
326+ local current_file = base_context .get_current_file (buf )
327+ local cursor_data = base_context .get_current_cursor_data (buf , win )
328+
329+ local should_update_file , is_different_file = M .should_update_current_file (current_file )
330+
331+ if should_update_file then
332+ if is_different_file then
333+ M .context .selections = {}
334+ end
294335
295336 M .context .current_file = current_file
296- M .context .cursor_data = cursor_data
297- M .context .linter_errors = base_context .get_diagnostics (buf , nil , nil )
337+ if M .context .current_file then
338+ M .context .current_file .sent_at = nil
339+ M .context .current_file .sent_at_mtime = nil
340+ end
298341 end
299342
343+ M .context .cursor_data = cursor_data
344+ M .context .linter_errors = base_context .get_diagnostics (buf , nil , nil )
345+
346+ -- Handle current selection
300347 local current_selection = base_context .get_current_selection ()
301348 if current_selection and M .context .current_file then
302349 local selection =
@@ -305,6 +352,18 @@ function M.load()
305352 end
306353end
307354
355+ --- @param current_file table
356+ local function set_file_sent_timestamps (current_file )
357+ if not current_file then
358+ return
359+ end
360+ current_file .sent_at = vim .uv .now ()
361+ local stat = vim .uv .fs_stat (current_file .path )
362+ if stat and stat .mtime and stat .mtime .sec then
363+ current_file .sent_at_mtime = stat .mtime .sec
364+ end
365+ end
366+
308367-- This function creates a context snapshot with delta logic against the last sent context
309368function M .delta_context (opts )
310369 local config = require (' opencode.config' )
@@ -322,37 +381,31 @@ function M.delta_context(opts)
322381 end
323382
324383 local buf , win = base_context .get_current_buf ()
325- if not buf or not win then
384+ if not buf then
326385 return {}
327386 end
328387
329- local ctx = {
330- current_file = base_context .get_current_file (buf , opts ),
331- cursor_data = base_context .get_current_cursor_data (buf , win , opts ),
332- mentioned_files = M .context .mentioned_files or {},
333- selections = M .context .selections or {},
334- linter_errors = base_context .get_diagnostics (buf , opts , nil ),
335- mentioned_subagents = M .context .mentioned_subagents or {},
336- }
388+ local ctx = vim .deepcopy (M .context )
337389
338- -- Delta logic against last sent context
390+ if ctx .current_file and M .context .current_file then
391+ set_file_sent_timestamps (M .context .current_file )
392+ set_file_sent_timestamps (ctx .current_file )
393+ end
394+
395+ -- no need to send subagents again
339396 local last_context = state .last_sent_context
340397 if last_context then
341- -- no need to send file context again
342- if ctx .current_file and last_context .current_file and ctx .current_file .name == last_context .current_file .name then
343- ctx .current_file = nil
344- end
345-
346- -- no need to send subagents again
347398 if
348399 ctx .mentioned_subagents
349400 and last_context .mentioned_subagents
350401 and vim .deep_equal (ctx .mentioned_subagents , last_context .mentioned_subagents )
351402 then
352403 ctx .mentioned_subagents = nil
404+ M .context .mentioned_subagents = nil
353405 end
354406 end
355407
408+ state .context_updated_at = vim .uv .now ()
356409 return ctx
357410end
358411
@@ -368,40 +421,42 @@ M.format_message = Promise.async(function(prompt, opts)
368421 local range = opts .range
369422 local parts = {}
370423
371- -- Add mentioned files from global state (always process, even without buffer)
372424 for _ , file_path in ipairs (M .context .mentioned_files or {}) do
373425 table.insert (parts , format_file_part (file_path , prompt ))
374426 end
375427
376- -- Add mentioned subagents from global state (always process, even without buffer)
377428 for _ , agent in ipairs (M .context .mentioned_subagents or {}) do
378429 table.insert (parts , format_subagents_part (agent , prompt ))
379430 end
380431
381- if not buf or not win then
382- -- Add the main prompt
432+ if not buf then
383433 table.insert (parts , { type = ' text' , text = prompt })
384434 return { parts = parts }
385435 end
386436
387- -- Add selections (both from range and global state)
437+ if M .context .current_file and not M .context .current_file .sent_at then
438+ table.insert (parts , format_file_part (M .context .current_file .path ))
439+ set_file_sent_timestamps (M .context .current_file )
440+ end
441+
388442 if base_context .is_context_enabled (' selection' , context_config ) then
389443 local selections = {}
390444
391- -- Add range selection if specified
392445 if range and range .start and range .stop then
393446 local file = base_context .get_current_file (buf , context_config )
394447 if file then
395448 local selection = base_context .new_selection (
396449 file ,
397- table.concat (vim .api .nvim_buf_get_lines (buf , range .start - 1 , range .stop , false ), ' \n ' ),
398- string.format (' %d-%d' , range .start , range .stop )
450+ table.concat (
451+ vim .api .nvim_buf_get_lines (buf , math.floor (range .start ) - 1 , math.floor (range .stop ), false ),
452+ ' \n '
453+ ),
454+ string.format (' %d-%d' , math.floor (range .start ), math.floor (range .stop ))
399455 )
400456 table.insert (selections , selection )
401457 end
402458 end
403459
404- -- Add current visual selection if available
405460 local current_selection = base_context .get_current_selection (context_config )
406461 if current_selection then
407462 local file = base_context .get_current_file (buf , context_config )
@@ -411,7 +466,6 @@ M.format_message = Promise.async(function(prompt, opts)
411466 end
412467 end
413468
414- -- Add selections from global state
415469 for _ , sel in ipairs (M .context .selections or {}) do
416470 table.insert (selections , sel )
417471 end
@@ -421,28 +475,19 @@ M.format_message = Promise.async(function(prompt, opts)
421475 end
422476 end
423477
424- -- Add current file if enabled and not already mentioned
425- local current_file = base_context .get_current_file (buf , context_config )
426- if current_file and not vim .tbl_contains (M .context .mentioned_files or {}, current_file .path ) then
427- table.insert (parts , format_file_part (current_file .path ))
428- end
429-
430- -- Add buffer content if enabled
431478 if base_context .is_context_enabled (' buffer' , context_config ) then
432479 table.insert (parts , format_buffer_part (buf ))
433480 end
434481
435- -- Add diagnostics
436482 local diag_range = nil
437483 if range then
438- diag_range = { start_line = range .start - 1 , end_line = range .stop - 1 }
484+ diag_range = { start_line = math.floor ( range .start ) - 1 , end_line = math.floor ( range .stop ) - 1 }
439485 end
440486 local diagnostics = base_context .get_diagnostics (buf , context_config , diag_range )
441487 if diagnostics and # diagnostics > 0 then
442- table.insert (parts , format_diagnostics_part (diagnostics , nil )) -- No need to filter again
488+ table.insert (parts , format_diagnostics_part (diagnostics , diag_range ))
443489 end
444490
445- -- Add cursor data
446491 if base_context .is_context_enabled (' cursor_data' , context_config ) then
447492 local cursor_data = base_context .get_current_cursor_data (buf , win , context_config )
448493 if cursor_data then
@@ -455,15 +500,13 @@ M.format_message = Promise.async(function(prompt, opts)
455500 end
456501 end
457502
458- -- Add git diff
459503 if base_context .is_context_enabled (' git_diff' , context_config ) then
460504 local diff_text = base_context .get_git_diff (context_config ):await ()
461505 if diff_text and diff_text ~= ' ' then
462506 table.insert (parts , format_git_diff_part (diff_text ))
463507 end
464508 end
465509
466- -- Add the main prompt
467510 table.insert (parts , { type = ' text' , text = prompt })
468511
469512 return { parts = parts }
0 commit comments