
    .cj                    z   d Z ddlmZ ddlZddlZddlZddlZddlmZm	Z	 ddl
mZ ddlmZmZmZmZmZmZmZ ddlmZmZ ddlmZ dd	lmZmZmZmZmZmZm Z m!Z!m"Z"m#Z#m$Z$  ej%        d
          Z&dZ'dZ(dZ)dZ*dZ+dZ,dZ-dZ.d'dZ/d(dZ0d)dZ1e G d d                      Z2 G d d          Z3d*d"Z4d+d%Z5g d&Z6dS ),u  Async LSP client over stdin/stdout.

One :class:`LSPClient` corresponds to one ``(language_server, workspace_root)``
pair — exactly what OpenCode keys clients on, and the same shape Claude
Code uses.  The client owns a child process, drives the JSON-RPC
exchange, and exposes:

- :meth:`open_file` / :meth:`change_file` — text document sync
- :meth:`wait_for_diagnostics` — block until the server emits fresh
  diagnostics for a specific file (or a timeout fires)
- :meth:`diagnostics_for` — read the current per-file diagnostic store
- :meth:`shutdown` — graceful close + SIGTERM/SIGKILL fallback

The class is designed for async use from a single asyncio event loop.
The :class:`agent.lsp.manager.LSPService` runs an event loop in a
background thread so the synchronous file_operations layer can call
into it via :func:`agent.lsp.manager.LSPService.touch_file`.

Implementation notes:

- All per-document state lives in one :class:`_DocState` keyed by
  absolute path.  Freshness is tracked with **document versions**,
  not timestamps: every didChange bumps ``version``, and each stored
  push/pull result is tagged with the version it describes.  A
  result is fresh iff its tag >= the version being waited on, so a
  didChange implicitly invalidates everything older — no clearing,
  no clock comparisons, no race windows.  This is what prevents
  "ghost diagnostics": a slow server's leftovers from the previous
  edit can never masquerade as a verdict on the current content.

- Whole-document sync.  Even when the server advertises incremental
  sync, we send a single ``contentChanges`` entry replacing the
  entire document.  Pretending to be incremental while sending a
  full replacement is well-tolerated by every major server and saves
  range bookkeeping.  See OpenCode's ``client.ts:584-659`` for the
  same trick.

- The "touch-file dance": every ``open_file`` call also fires a
  ``workspace/didChangeWatchedFiles`` notification (CREATED on the
  first open, CHANGED thereafter).  Some servers (clangd, eslint)
  only re-scan when this notification fires, even though the LSP spec
  doesn't strictly require it.

- ``ContentModified`` (-32801) errors get retried with exponential
  backoff up to 3 times.  This matches Claude Code's
  ``LSPServerInstance.sendRequest``.
    )annotationsN)	dataclassfield)Path)Any	AwaitableCallableDictListOptionalSet)quoteunquote)windows_hide_flags)ERROR_CONTENT_MODIFIEDERROR_METHOD_NOT_FOUNDLSPProtocolErrorLSPRequestErrorclassify_messageencode_messagemake_error_responsemake_notificationmake_requestmake_responseread_messagezagent.lsp.clientg     F@g      @g      $@g      @g333333?g      ?         ?pathstrreturnc                    t           j                            |           }t           j        dk    r0|                    dd          }|                    d          sd|z   }dt          |d          z   S )u   Return ``file://`` URI for an absolute filesystem path.

    Mirrors Node's ``pathToFileURL`` — handles spaces, unicode, and
    Windows drive letters (``C:\foo`` → ``file:///C:/foo``).
    nt\/file://z/:)safe)osr   abspathnamereplace
startswithr   )r   abs_paths     2/home/ice/.hermes/hermes-agent/agent/lsp/client.pyfile_urir.   Z   sp     wt$$H	w$ ##D#..""3'' 	&X~HuXD11111    uric                R   |                      d          s| S | t          d          d         }t          j        dk    r>|                     d          r)t          |          dk    r|d         dk    r
|dd         }t          j                            t          |                    S )zInverse of :func:`file_uri`.r%   Nr"   r$      :   )r+   lenr'   r)   r   normpathr   )r0   raws     r-   uri_to_pathr8   j   s    >>)$$ 

c)nnoo
C	w$3>>#..3s88a<<CFcMM!""g7GCLL)))r/   textDict[str, int]c                    | sdddS |                      d          }t          |          dz
  }|rt          |d                   nd}|                     d          r|dz   ddS ||dS )zReturn the LSP Position at the end of ``text``.

    Used to construct a single-range "replace whole document" change
    for ``textDocument/didChange`` regardless of the server's declared
    sync mode.
    r   line	characterF)keependsr4   )
)
splitlinesr5   endswith)r9   lines	last_linelast_cols       r-   _end_positionrH   t   s      +***OOUO++EE

QI!&-s59~~~AH }}\"" 7!AA666H555r/   c                      e Zd ZU dZdZded<   dZded<    ee          Z	d	ed
<    ee          Z
d	ed<   dZded<   dZded<   dZded<   dddZdddZdS )	_DocStateu1  Everything the client tracks for one open document.

    ``version`` is the LSP document version we last sent (didOpen=0,
    each didChange +1).  It doubles as the freshness token: stored
    push/pull results are tagged with the version they describe
    (``push_version`` / ``pull_version``), and a result is *fresh*
    iff its tag has caught up to ``version``.  Bumping the version on
    didChange therefore invalidates all older results implicitly —
    no store-clearing, no timestamps.

    ``push_version``/``pull_version`` start at -1 = "no data yet".
    Servers that echo a document version in publishDiagnostics get
    exact tagging; those that don't are credited with the current
    version at receipt time (a push observed after we sent the
    change describes the changed content or newer).
    r   intversion r   r9   )default_factoryList[Dict[str, Any]]pushpullr@   push_versionpull_versionFbool	seed_seenNOptional[int]r    c                *    | j         || j        n|k    S N)rR   rL   selfrL   s     r-   
fresh_pushz_DocState.fresh_push        W_T\\'RRr/   c                *    | j         || j        n|k    S rX   )rS   rL   rY   s     r-   
fresh_pullz_DocState.fresh_pull   r\   r/   rX   )rL   rV   r    rT   )__name__
__module____qualname____doc__rL   __annotations__r9   r   listrP   rQ   rR   rS   rU   r[   r^    r/   r-   rJ   rJ      s          " GDNNNN!&t!<!<!<D<<<<!&t!<!<!<D<<<<LLIS S S S SS S S S S S Sr/   rJ   c                  z   e Zd ZdZddddddOdZedPd            ZedQd            ZdRdZe	dSd            Z
dRdZdRdZdRdZdRdZe	dTd!            ZdRd"ZdRd#ZdUd'ZdVd*ZdWd+ZdXd.ZdYd1ZdZd3Zd[d4Zd\d5Zd]d6Zd]d7Zd]d8Zd]d9Zd]d:Zd]d;Zd^d<Z d=d>d_dAZ!d`dBZ"d`dCZ#dDddEdadIZ$dbdJZ%ddKdcdNZ&dS )d	LSPClienta  Async LSP client tied to one server process and one workspace root.

    Lifecycle:

        c = LSPClient(server_id, workspace_root, command, args, init_options)
        await c.start()       # spawn + initialize
        ver = await c.open_file("/path/to/foo.py")
        await c.wait_for_diagnostics("/path/to/foo.py", ver)
        diags = c.diagnostics_for("/path/to/foo.py")
        await c.shutdown()
    NF)envcwdinitialization_optionsseed_diagnostics_on_first_push	server_idr   workspace_rootcommand	List[str]rh   Optional[Dict[str, str]]ri   Optional[str]rj   Optional[Dict[str, Any]]rk   rT   r    Nonec                  || _         || _        t          |          | _        || _        |p|| _        |pi | _        || _        d | _        d | _	        d | _
        d| _        i | _        | j        | j        | j        | j        | j        | j        d| _        d| j        i| _        i | _        i | _        d| _        d | _        d| _        d| _        t9          j                    | _        d| _        t9          j                    | _         d S )Nr   )zwindow/workDoneProgress/createzworkspace/configurationzclient/registerCapabilityzclient/unregisterCapabilityzworkspace/workspaceFolderszworkspace/diagnostic/refreshztextDocument/publishDiagnosticsstoppedr4   F)!rl   rm   rd   _command_env_cwd_init_options_seed_first_push_proc_stderr_task_reader_task_next_id_pending_handle_work_done_create_handle_workspace_configuration_handle_register_capability_handle_unregister_capability_handle_workspace_folders_handle_diagnostic_refresh_request_handlers_handle_publish_diagnostics_notification_handlers_docs_diagnostic_registrations_state_initialize_result
_sync_kind	_stoppingasyncioEvent_push_event_push_counter_registration_event)rZ   rl   rm   rn   rh   ri   rj   rk   s           r-   __init__zLSPClient.__init__   s    #,W	)>	39r > <@
4848 35
 /3.K'+'K)-)I+/+M*.*H,0,KN
 N
 .t/OI
# ,.
DF& %<@ $ #=??
  $+=??   r/   c                F    | j         dk    o| j        d uo| j        j        d u S )Nrunning)r   r{   
returncoderZ   s    r-   
is_runningzLSPClient.is_running   s+    {i'dDJd,BdtzG\`dGddr/   c                    | j         S rX   )r   r   s    r-   statezLSPClient.state  s
    {r/   c                  K   | j         dv rdS d| _         	 |                                  d{V  |                                  d{V  d| _         dS # t          $ r# d| _         |                                  d{V   w xY w)u   Spawn the server and complete the initialize handshake.

        Raises any exception encountered during spawn/init.  On failure
        the process is killed and the client is left in state
        ``"error"`` — re-call ``start()`` to retry.
        >   r   startingNr   r   error)r   _spawn_initialize	Exception_cleanup_processr   s    r-   startzLSPClient.start  s       ;111F 	++--""$$$$$$$$$#DKKK 	 	 	!DK'')))))))))	s   ;A -B cmdc                p    | d         }|                                                     d          rddg| S | S )z?On Windows, wrap .cmd/.bat shims so CreateProcess can run them.r   )z.cmdz.batzcmd.exez/c)lowerrD   )r   exes     r-   _win_wrap_cmdzLSPClient._win_wrap_cmd  sA     !f99;; 011 	+t*c**
r/   c                  K   t          t          j                  }| j        r|                    | j                   | j        }t          j        dk    r|                     |          }t                      }	 t          j        |d         g|dd          R t          j        j        t          j        j        t          j        j        || j        d|d d {V | _        n/# t           $ r"}t#          d|d          d| d          |d }~ww xY wt          j        |                                           | _        t          j        |                                           | _        d S )	Nwin32r   r4   T)stdinstdoutstderrrh   ri   start_new_sessioncreationflagszLSP server binary not found: z ())dictr'   environrw   updaterv   sysplatformr   r   r   create_subprocess_exec
subprocessPIPErx   r{   FileNotFoundErrorr   create_task_drain_stderrr|   _reader_loopr}   )rZ   rh   r   r   es        r-   r   zLSPClient._spawn#  s     2:9 	"JJty!!!m<7""$$S))C +,,	  '=A
 QRR
  
  (-).).I"&+
  
  
  
 
 
 
 
 
DJJ ! 	 	 	">A>>!>>> 	 $/0B0B0D0DEE#/0A0A0C0CDDs   8A&C 
D)DDc                h  K   | j         | j         j        d S 	 	 | j         j                                         d {V }|sd S |                    dd                                          }|r)t
                              d| j        |d d                    }# t          j	        t          f$ r Y d S w xY w)NTutf-8r*   )errorsz[%s] stderr: %si  )r{   r   readlinedecoderstriploggerdebugrl   r   CancelledErrorOSError)rZ   r=   r9   s      r-   r   zLSPClient._drain_stderrP  s      :!2!:F		Q!Z.7799999999 E{{79{==DDFF QLL!2DND$KPPPQ &0 	 	 	DD	s   'B AB B10B1c                  K   | j         | j         j        d S 	 	 t          | j         j                   d {V }|!t                              d| j                   nt          |          \  }}|dk    r|                     ||           nm|dk    r)t          j	        | 
                    ||                     n>|dk    r|                     ||           n!t                              d| j        |           nO# t          $ r+}t                              d| j        |           Y d }~nd }~wt          j        t          f$ r Y nw xY wt!          | j                                                  D ]8}|                                s"|                    t          d                     9| j                                         d S # t!          | j                                                  D ]8}|                                s"|                    t          d                     9| j                                         w xY w)	NTz![%s] server closed stdout cleanlyresponserequestnotificationz![%s] dropping invalid message: %rz&[%s] protocol error in reader loop: %szserver connection closed)r{   r   r   r   r   rl   r   _dispatch_responser   r   _dispatch_request_dispatch_notificationwarningr   r   r   rd   r   valuesdoneset_exceptionclear)rZ   msgkindkeyr   futs         r-   r   zLSPClient._reader_loop^  sk     :!2!:F	"]():;;;;;;;;;LL!DdnUUU,S11	c:%%++C5555Y&&'(>(>sC(H(HIIII^++//S9999NN#FX[\\\]    	X 	X 	XNNCT^UVWWWWWWWW&0 	 	 	D	 DM002233 T Txxzz T%%&67Q&R&RSSSM!!!!! DM002233 T Txxzz T%%&67Q&R&RSSSM!!!!s=   C C: 9G :
E!D*%G *EG EG A:H=c                v  K   t          | j                  | j        t          j                    dt          | j                  dg| j        ddiddddiddidddddddd	ddd
ddddgidddddddgiddii ddidddgidd}t          j        |                     d|          t                     d {V }|| _	        | 
                    |                    d          pi           | _        |                     di            d {V  | j        r%|                     dd| j        i           d {V  d S d S )N	workspacer)   r0   workDoneProgressTdynamicRegistrationrefreshSupportF)configurationworkspaceFoldersdidChangeWatchedFilesdiagnostics)r   didOpen	didChangedidSavewillSavewillSaveWaitUntil)r   relatedDocumentSupportvalueSetr4   r2   )relatedInformation
tagSupportversionSupportcodeDescriptionSupportdataSupportcontentFormatmarkdown	plaintextlinkSupport!hierarchicalDocumentSymbolSupport)synchronization
diagnosticpublishDiagnosticshover
definition
referencesdocumentSymbolpositionEncodingszutf-16)windowr   textDocumentgeneral)rootUrirootPath	processIdr   initializationOptionscapabilities
initializetimeoutr  initializedz workspace/didChangeConfigurationsettings)r.   rm   r'   getpidry   r   wait_for_send_requestINITIALIZE_TIMEOUTr   _extract_sync_kindgetr   _send_notification)rZ   paramsresults      r-   r   zLSPClient._initialize{  s      344+$Xd6I-J-JKK! &*%7-t4%)(,.CT-J$4e#<	  05#'%)#'$)-2( ( 0426# #
 /3'1Aq6&:*.26',+ + .
K/HI#0$"7"$'JD&Q/! !2 0(<C" "+
 +
Z '|V44&
 
 
 
 
 
 
 
 
 #)11&**^2L2L2RPRSS%%mR888888888 	 ))2T/0          		 	r/   r  r   rK   c                    |                      d          }t          |t                    r|S t          |t                    r,|                     d          }t          |t                    r|S dS )NtextDocumentSyncchanger4   )r  
isinstancerK   r   )r  syncr  s      r-   r  zLSPClient._extract_sync_kind  sl     233dC   	KdD!! 	XXh''F&#&& qr/   c                  K   | j         rdS d| _         	 | j        r	 t          j        |                     dd          d           d{V  n"# t          j        t          t          f$ r Y nw xY w	 |                     dd           d{V  n# t          $ r Y nw xY wd| _
        |                                  d{V  dS # d| _
        |                                  d{V  w xY w)zBest-effort graceful shutdown.

        Sends ``shutdown`` + ``exit``, then SIGTERMs/SIGKILLs the
        process if it doesn't exit cleanly.  Idempotent.
        NTshutdowng       @r  exitru   )r   r   r   r	  r
  TimeoutErrorr   r   r  r   r   r   r   s    r-   r  zLSPClient.shutdown  sP      > 	F	* !*4+=+=j$+O+OY\]]]]]]]]]]],o?OP   D11&$??????????    D $DK''))))))))))) $DK''))))))))))sR   C  0A C  A,)C  +A,,C  0B C  
BC  BC   #C#c                6  K   | j         ]| j                                         sD| j                                          	 | j          d {V  n# t          j        t
          f$ r Y nw xY w| j        ]| j                                        sD| j                                         	 | j         d {V  n# t          j        t
          f$ r Y nw xY w| j        }d | _        |d S |j        	 |	                                 	 t          j
        |                                t                     d {V  d S # t          j        $ rG 	 |                                 |                                 d {V  n# t          $ r Y nw xY wY d S Y d S w xY w# t          $ r Y d S w xY wd S )Nr  )r}   r   cancelr   r   r   r|   r{   r   	terminater	  waitSHUTDOWN_GRACEr  killProcessLookupError)rZ   procs     r-   r   zLSPClient._cleanup_process  s!     (1B1G1G1I1I($$&&&'''''''''*I6   (1B1G1G1I1I($$&&&'''''''''*I6   z
<F?"   !*499;;OOOOOOOOOOOO+   		"iikk))))))))-    *)) &    #"s}   A A$#A$!B/ /CC%F :3D/ /F?.E.-F.
E;8F:E;;F>F F FF 
FFmethodr  r   c           	     
  K   | j         *| j         j        | j         j                                        rt          d|d          t	          j                    }| j        }| xj        dz  c_        |                                }|| j        |<   	 | j         j        	                    t          t          |||                               | j         j                                         d {V  nP# t          t          t          f$ r6}| j                            |d            t          d|d|           |d }~ww xY w	 | d {V 	 | j                            |d            S # | j                            |d            w xY w)Nzcannot send z: stdin closedr4   zsend failed for : )r{   r   
is_closingr   r   get_running_loopr~   create_futurer   writer   r   drainBrokenPipeErrorConnectionResetErrorr   pop)rZ   r"  r  loopreq_idr   r   s          r-   r
  zLSPClient._send_request  s     :!1!9TZ=M=X=X=Z=Z!9"#J&#J#J#JKKK'))"0022 #f	NJ"">,vvv2V2V#W#WXXX*"((**********!5w? 	N 	N 	NMfd+++"#Ef#E#E!#E#EFFAM	N	,999999Mfd++++DMfd++++s%   AC0 0D=1D88D=E% %Fr  floatc               R  K   t          t          dz             D ]}	 t          j        |                     ||          |           d{V c S # t
          $ rK}|j        t          k    r5|t          k     r*t          j        t          d|z  z             d{V  Y d}~ d}~ww xY wdS )u   Send a request, retrying on ``ContentModified`` (-32801).

        Other errors propagate.  The retry policy matches Claude Code's
        ``LSPServerInstance.sendRequest`` — 3 attempts with delays
        0.5s, 1.0s, 2.0s.
        r4   r  Nr2   )
rangeMAX_CONTENT_MODIFIED_RETRIESr   r	  r
  r   coder   sleepRETRY_BASE_DELAY)rZ   r"  r  r  attemptr   s         r-   _send_request_with_retryz"LSPClient._send_request_with_retry  s       9A=>> 	 	G$-d.@.@.P.PZabbbbbbbbbbbb"   6333B^8^8^!-(8AL(IJJJJJJJJJHHHH		 	s   /A
B$A BBB$c                  K   | j         *| j         j        | j         j                                        rd S 	 | j         j                            t	          t          ||                               | j         j                                         d {V  d S # t          t          t          f$ r-}t                              d| j        ||           Y d }~d S d }~ww xY w)Nz[%s] notify %s failed: %s)r{   r   r%  r(  r   r   r)  r*  r+  r   r   r   rl   )rZ   r"  r  r   s       r-   r  zLSPClient._send_notification"  s      :!1!9TZ=M=X=X=Z=Z!9F	QJ"">2CFF2S2S#T#TUUU*"((***********!5w? 	Q 	Q 	QLL4dnfaPPPPPPPPP	Qs   AB C."CCr.  r  c                j  K   | j         *| j         j        | j         j                                        rd S 	 | j         j                            t	          t          ||                               | j         j                                         d {V  d S # t          t          t          f$ r Y d S w xY wrX   )
r{   r   r%  r(  r   r   r)  r*  r+  r   )rZ   r.  r  s      r-   _send_responsezLSPClient._send_response+  s      :!1!9TZ=M=X=X=Z=Z!9F	J"">-2O2O#P#PQQQ*"((***********!5w? 	 	 	DD	s   AB B21B2r3  messagec           	     l  K   | j         *| j         j        | j         j                                        rd S 	 | j         j                            t	          t          |||                               | j         j                                         d {V  d S # t          t          t          f$ r Y d S w xY wrX   )
r{   r   r%  r(  r   r   r)  r*  r+  r   )rZ   r.  r3  r;  s       r-   _send_error_responsezLSPClient._send_error_response4  s      :!1!9TZ=M=X=X=Z=Z!9F	J"">2EfdT[2\2\#]#]^^^*"((***********!5w? 	 	 	DD	s   AB B32B3r   c                   | j                             |          }||                                rd S d|v r|d         pi }|                    t	          t          |                    dd                    t          |                    dd                    |                    d                               d S |                    |                    d                     d S )	Nr   r3   r;  unknowndata)r3  r;  rA  r  )r   r  r   r   r   rK   r   
set_result)rZ   r.  r   r   errs        r-   r   zLSPClient._dispatch_response=  s    m'';#((**;Fc>>g,$"CSWWVV4455	9 = =>>       NN3778,,-----r/   c                  K   |                     dd          }|                     d          }| j                             |          }|'|                     |t          d|            d {V  d S 	  ||           d {V }nZ# t          $ rM}t
                              d| j        ||           |                     |dd|            d {V  Y d }~d S d }~ww xY w|                     ||           d {V  d S )Nr"  rM   r  zmethod not found: z"[%s] request handler %s failed: %sr?  zhandler failed: )	r  r   r=  r   r   r   r   rl   r:  )rZ   r.  r   r"  r  handlerr  r   s           r-   r   zLSPClient._dispatch_requestM  sQ     2&&""(,,V44?++F4JLiagLiLijjjjjjjjjF	"76??******FF 	 	 	NN?QWYZ[[[++FF<Rq<R<RSSSSSSSSSFFFFF	 !!&&11111111111s   2B 
CACCc                    | j                             |          }|d S 	  ||                    d                     d S # t          $ r-}t                              d| j        ||           Y d }~d S d }~ww xY w)Nr  z'[%s] notification handler %s failed: %s)r   r  r   r   r   rl   )rZ   r"  r   rE  r   s        r-   r   z LSPClient._dispatch_notification\  s    -11&99?F	_GCGGH%%&&&&& 	_ 	_ 	_LLBDNTZ\]^^^^^^^^^	_s   A   
A7
"A22A7c                
   K   d S rX   re   rZ   r  s     r-   r   z"LSPClient._handle_work_done_createi        tr/   c                  K   t          |t                    sd gS |                    d          pg }g }|D ]}t          |t                    s|                    d            -|                    d          }|r| j        s|                    | j        pd            h| j        }t          |                              d          D ]&}t          |t                    r||v r	||         }$d } |                    |           |S )Nitemssection.)r  r   r  appendry   r   split)rZ   r  rK  outitemrL  curparts           r-   r   z)LSPClient._handle_workspace_configurationm  s       &$'' 	6M

7##)r 	 	DdD)) 

4   hhy))G $"4 

4-5666)CG**3//  c4(( TS[[d)CCCJJsOOOO
r/   c                \  K   t          |t                    sd S |                    d          pg D ]z}t          |t                    s|                    d          }|                    d          }|dk    r2|r0|| j        t	          |          <   | j                                         {d S )Nregistrationsr"  idtextDocument/diagnostic)r  r   r  r   r   r   set)rZ   r  regr"  reg_ids        r-   r   z%LSPClient._handle_register_capability  s      &$'' 	4::o..4" 	/ 	/Cc4(( WWX&&FWWT]]F222v2>A.s6{{;(,,...tr/   c                  K   t          |t                    sd S |                    d          pg D ]W}t          |t                    s|                    d          }|r(| j                            t          |          d            Xd S )NunregisterationsrV  )r  r   r  r   r,  r   )rZ   r  unregrZ  s       r-   r   z'LSPClient._handle_unregister_capability  s      &$'' 	4ZZ 2339r 	F 	FEeT** YYt__F F.223v;;EEEtr/   c                6   K   dt          | j                  dgS )Nr   r   )r.   rm   rH  s     r-   r   z#LSPClient._handle_workspace_folders  s"      $Xd6I-J-JKKLLr/   c                
   K   d S rX   re   rH  s     r-   r   z$LSPClient._handle_diagnostic_refresh  rI  r/   c                r   t          |t                    sd S |                    d          }t          |t                    sd S t	          |          }|                    d          pg }t          |t
                    sg }|                    d          }| j                            |t          d                    }| j	        r|j
        sd|_
        ||_        d S d|_
        ||_        t          |t                    r|n|j        |_        | xj        dz  c_        | j                                         d S )Nr0   r   rL   r@   rL   Tr4   )r  r   r  r   r8   rd   r   
setdefaultrJ   rz   rU   rP   rK   rL   rR   r   r   rX  )rZ   r  r0   r   r   rL   docs          r-   r   z%LSPClient._handle_publish_diagnostics  s5   &$'' 	Fjj#s## 	F3jj//52+t,, 	K**Y''j##D)B*?*?*?@@  	 	
 !CM"CHF '1#&>&>O77CK 	ar/   r   )language_idr   rd  c          	     V  K   | j         st          d          t          j                            |          }	 t          |                              dd          }n(# t          $ r}t          d| d|           |d}~ww xY wt          |          }| j	        
                    |          }||j        dk    r|                     d	d
|ddgi           d{V  |j        dz   }|j        }	| j        dk    rdddt          |	          d|dg}
nd|ig}
|                     d||d|
d           d{V  ||_        ||_        |S |                     d	d
|ddgi           d{V  t!          d|          | j	        |<   |                     dd||d|di           d{V  dS )zSend didOpen (first time) or didChange (subsequent) for ``path``.

        Returns the new document version number that the agent's
        ``wait_for_diagnostics`` should match against.
        zclient not runningr   r*   )encodingr   zcannot read r$  Nr   zworkspace/didChangeWatchedFileschangesr2   )r0   typer4   r<   )r   end)r1  r9   r9   ztextDocument/didChange)r0   rL   )r   contentChanges)rL   r9   ztextDocument/didOpenr   )r0   
languageIdrL   r9   )r   r   r'   r   r(   r   	read_textr   r.   r   r  rL   r  r9   r   rH   rJ   )rZ   r   rd  r,   r9   r   r0   rc  new_versionold_textcontent_changess              r-   	open_filezLSPClient.open_file  s       	9"#78887??4((	J>>++WY+OODD 	J 	J 	J"#A(#A#Aa#A#ABBI	J x  jnnX&&?s{a//))1S!4456         +/KxH!## /0a%@%@#0#:#:" " !% # %+D>"2))(,/K$H$H&5          &CKCH %%-a0012
 
 	
 	
 	
 	
 	
 	
 	
  )>>>
8%%""-  	! !

 

 
	
 
	
 
	
 
	
 
	
 
	
 
	
 qs   $A 
B(A>>Bc                   K   | j         sdS t          j                            |          }|                     dddt          |          ii           d{V  dS )z>Send didSave for ``path``.  Some linters re-scan only on save.NztextDocument/didSaver   r0   )r   r'   r   r(   r  r.   )rZ   r   r,   s      r-   	save_filezLSPClient.save_file  s       	F7??4((%%"eXh%7%789
 
 	
 	
 	
 	
 	
 	
 	
 	
 	
r/   c                &  K   t           j                            |          }| j                            |          }|r|j        nd}	 ddt          |          ii}|                     d|t                     d{V }nK# t          t          t          j        f$ r,}t                              d| j        |           Y d}~dS d}~ww xY wt!          |t"                    sdS |                    d          }t!          |t$                    r7| j                            |t)          d	                    }||_        ||_        |                    d
          }	t!          |	t"                    r|	                                D ]\  }
}t!          |t"                    s|                    d          }t!          |t$                    rI| j                            t1          |
          t)          d	                    }||_        |j        |_        dS dS )u  Send ``textDocument/diagnostic`` for one file.

        Stores results into the doc's pull store, tagged with the
        document version captured at request send time.  If a didChange
        races past the in-flight request, the version bump makes the
        stored result stale automatically — no explicit invalidation.
        Silently no-ops on errors (server may not support the pull
        endpoint).
        r@   r   r0   rW  r  Nz([%s] document diagnostic pull failed: %srK  ra  relatedDocuments)r'   r   r(   r   r  rL   r.   r7  DIAGNOSTICS_REQUEST_TIMEOUTr   r   r   r  r   r   rl   r  r   rd   rb  rJ   rQ   rS   rK  r8   )rZ   r   r,   rc  sent_versionr  r  r   rK  relatedr0   sub	sub_itemsrels                 r-   _pull_document_diagnosticsz$LSPClient._pull_document_diagnostics+  s+      7??4((jnnX&&&)1s{{r	(:(: ;&F  88)3 9        FF
  !173GH 	 	 	LLCT^UVWWWFFFFF	 &$'' 	F

7##eT"" 	,*'')B2G2G2GHHCCH+C**/00gt$$ 
	3#MMOO 	3 	3S!#t,, GGG,,	i.. 3*//C0@0@)TVBWBWBWXXC(CH (+{C$
	3 
	3	3 	3s   6A? ?C!CCdocument)moder  rL   r}  Optional[float]c               N  K   |	|dk    r|}n|dk    rt           nt          }t          j                                                    |z   }t
          j                            |          }	 |t          j                                                    z
  }|dk    rdS t          j        | 	                    |                    }	t          j        | 
                    |||                    }
t          j        |	|
h|t          j                   d{V \  }}|D ]}|                                 |D ]'}	 | d{V  # t          j        t          f$ r Y $w xY w| j                            |          }|r|                    |          rdS |r|                    |          rdS =)u  Wait for the server to publish diagnostics for ``path`` at ``version``.

        ``mode`` is ``"document"`` (5s budget, document pulls) or
        ``"full"`` (10s budget, also workspace pulls).  ``timeout``
        overrides the mode's default budget when provided — this is
        how the user's ``lsp.wait_timeout`` config reaches the wait
        loop (slow servers like tsserver on big projects need more
        than the 5s default).

        Returns ``True`` when *fresh* diagnostics arrived (a push at
        or after our didChange, or a pull answered after it) and
        ``False`` on timeout.  Callers must treat ``False`` as "no
        data", NOT as "no errors" — the diagnostic stores may still
        hold stale entries from the previous edit at that point.
        Best-effort — never throws if the server doesn't support pull
        diagnostics; we still get the push side.
        Nr   fullTF)r  return_when)DIAGNOSTICS_FULL_WAITDIAGNOSTICS_DOCUMENT_WAITr   get_event_looptimer'   r   r(   r   r{  _wait_for_fresh_pushr  FIRST_COMPLETEDr  r   r   r   r  r[   r^   )rZ   r   rL   r}  r  budgetdeadliner,   	remaining	pull_task	push_taskr   pendingtrc  s                  r-   wait_for_diagnosticszLSPClient.wait_for_diagnosticsX  s     2 7Q;;FF.2fnn**B[F)++0022V;7??4((	 7#9#;#;#@#@#B#BBIA~~u  +D,K,KH,U,UVVI+D,E,EhPWYb,c,cddI"),I&!#3# # #      MD'
   



  GGGGGGGG.	:   D *..**C s~~g.. t  s~~g.. t;	s   4D==EEc                  K   t          j                                                    |z   }| j        }	 | j                            |          }|r|                    |          r| j        }t          j                                                    t          z   }| j        |k    r|t          j                                                    z
  }	|	dk    rnn| j        	                                 	 t          j
        | j                                        |	           d{V  n# t           j        $ r Y nw xY w| j        |k    dS |t          j                                                    z
  }	|	dk    rdS | j        |k    r	| j        }T| j        	                                 	 t          j
        | j                                        t          |	d                     d{V  n# t           j        $ r Y w xY w)zKWait until a fresh publishDiagnostics arrives for ``path`` at ``version``+.Tr   r  Nr   )r   r  r  r   r   r  r[   PUSH_DEBOUNCEr   r   r	  r  r  min)
rZ   r   rL   r  r  baselinerc  debounce_baselinedebounce_deadliner  s
             r-   r  zLSPClient._wait_for_fresh_push  s$     )++0022W<%	*..&&C s~~g.. 
 %)$6!$+$:$<$<$A$A$C$Cm$S!(,=== 1G4J4L4L4Q4Q4S4S SI A~~$**,,,%.t/?/D/D/F/FPYZZZZZZZZZZZ"/    (,===  7#9#;#;#@#@#B#BBIA~~!H,,  -""$$$&t'7'<'<'>'>IWZH[H[\\\\\\\\\\\'   =	s%   ,3D   D21D2 AG" "G54G5)
fresh_onlyr  rO   c               <   | j                             t          j                            |                    }|g S |rFt          |                                r|j        ng |                                r|j	        ng           S t          |j        |j	                  S )uX  Return current merged + deduped diagnostics for one file.

        Diagnostics from push and pull stores are concatenated and
        deduplicated by ``(severity, code, message, range)`` content
        key.  Empty list if the server hasn't published anything.

        With ``fresh_only=True``, a store only contributes when its
        version tag has caught up to the document's current version —
        stale leftovers from the previous edit cycle are excluded.
        This is what report paths should use: after an edit, "stale
        errors" and "no errors" must not be conflated.
        )
r   r  r'   r   r(   _deduper[   rP   r^   rQ   )rZ   r   r  rc  s       r-   diagnostics_forzLSPClient.diagnostics_for  s     jnnRW__T2233;I 	NN,,4"NN,,4"   sx***r/   )rl   r   rm   r   rn   ro   rh   rp   ri   rq   rj   rr   rk   rT   r    rs   )r    rT   )r    r   )r    rs   )r   ro   r    ro   )r  r   r    rK   )r"  r   r  r   r    r   )r"  r   r  r   r  r/  r    r   )r"  r   r  r   r    rs   )r.  r   r  r   r    rs   )r.  r   r3  rK   r;  r   r    rs   )r.  rK   r   r   r    rs   )r.  r   r   r   r    rs   )r"  r   r   r   r    rs   )r  r   r    r   )r  r   r    rs   )r   r   rd  r   r    rK   )r   r   r    rs   )
r   r   rL   rK   r}  r   r  r~  r    rT   )r   r   rL   rK   r  r/  r    rs   )r   r   r  rT   r    rO   )'r_   r`   ra   rb   r   propertyr   r   r   staticmethodr   r   r   r   r   r  r  r   r
  r7  r  r:  r=  r   r   r   r   r   r   r   r   r   r   rp  rr  r{  r  r  r  re   r/   r-   rg   rg      sW       
 
, )-!;?/4C3 C3 C3 C3 C3 C3J e e e Xe    X   &    \+E +E +E +EZ   " " " ":= = = =~    \* * * *.   F, , , ,&    Q Q Q Q      . . . . 2 2 2 2_ _ _ _      2   	 	 	 	M M M M   $ $ $ $T @K H H H H H HT
 
 
 
+3 +3 +3 +3d #'= = = = = =B# # # #J @E + + + + + + + +r/   rg   listsrO   c                     t                      }g }| D ][}|D ]V}t          |t                    st          |          }||v r,|                    |           |                    |           W\|S rX   )rX  r  r   _diagnostic_keyaddrN  )r  seenrP  lstdr   s         r-   r  r    s    UUD "C   	 	Aa&& !!$$Cd{{HHSMMMJJqMMMM	 Jr/   r  Dict[str, Any]c                   |                      d          pi }|                     d          pi }|                     d          pi }|                      d          }|$t          |t                    st          |          }d                    t          |                      d          pd          t          |pd	          t          |                      d
          pd	          t          |                      d          pd	                                          |                     dd           d|                     dd           d|                     dd           d|                     dd           g          S )u  Content-equality key for a diagnostic.

    Matches the structural-equality used in claude-code's
    ``areDiagnosticsEqual`` — message + severity + source + code +
    range coords.  The range is reduced to a tuple to keep the key
    stable across dict orderings.
    r1  r   ri  r3  N severityr4   rM   sourcer;  r=   r   r3   r>   -)r  r  r   joinstrip)r  rngr   ri  r3  s        r-   r  r    sV    %%..
BCGGG"E
''%..
BC55==D
4 5 54yy;;j!!&Q''
OOh%2&&i  &B''--//yy##ppeiiQ&?&?pp#''&RSBTBTppWZW^W^_jlmWnWnpp	
  r/   )rg   r.   r8   r  r  r  )r   r   r    r   )r0   r   r    r   )r9   r   r    r:   )r  rO   r    rO   )r  r  r    r   )7rb   
__future__r   r   loggingr'   r   dataclassesr   r   pathlibr   typingr   r   r	   r
   r   r   r   urllib.parser   r   hermes_cli._subprocess_compatr   agent.lsp.protocolr   r   r   r   r   r   r   r   r   r   r   	getLoggerr   r  r  r  ru  r  r  r2  r5  r.   r8   rH   rJ   rg   r  r  __all__re   r/   r-   <module>r     s  . .^ # " " " " "   				 



 ( ( ( ( ( ( ( (       F F F F F F F F F F F F F F F F F F ' ' ' ' ' ' ' ' < < < < < <                          
	-	.	.    !   !  2 2 2 2 * * * *6 6 6 6( S S S S S S S SBi+ i+ i+ i+ i+ i+ i+ i+X      2  r/   