Class: RubynCode::IDE::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/rubyn_code/ide/client.rb

Overview

Sends JSON-RPC requests from the CLI server to the VS Code extension and awaits responses. Enables the CLI to ask the IDE to do things like open diffs, read diagnostics, or navigate to a file.

Uses the server’s write mutex for thread-safe output. Tracks pending responses via a { id => ConditionVariable } map.

Defined Under Namespace

Classes: TimeoutError

Constant Summary collapse

DEFAULT_TIMEOUT =

seconds

30

Instance Method Summary collapse

Constructor Details

#initialize(server) ⇒ Client

Returns a new instance of Client.



16
17
18
19
20
21
# File 'lib/rubyn_code/ide/client.rb', line 16

def initialize(server)
  @server = server
  @mutex = Mutex.new
  @next_id = 1000 # Start high to avoid collisions with client IDs
  @pending = {} # { id => { cv: ConditionVariable, result: nil, error: nil } }
end

Instance Method Details

#pending?(id) ⇒ Boolean

Check if we have a pending request with this id.

Returns:

  • (Boolean)


88
89
90
# File 'lib/rubyn_code/ide/client.rb', line 88

def pending?(id)
  @mutex.synchronize { @pending.key?(id) }
end

#request(method, params = {}, timeout: DEFAULT_TIMEOUT) ⇒ Hash

Send a JSON-RPC request to the extension and block until the response arrives or the timeout expires.

Parameters:

  • method (String)

    the RPC method name (e.g. “ide/readSelection”)

  • params (Hash) (defaults to: {})

    the request params

  • timeout (Numeric) (defaults to: DEFAULT_TIMEOUT)

    seconds to wait for a response

Returns:

  • (Hash)

    the result from the extension

Raises:

  • (TimeoutError)

    if no response within timeout

  • (StandardError)

    if the extension returns an RPC error



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/rubyn_code/ide/client.rb', line 32

def request(method, params = {}, timeout: DEFAULT_TIMEOUT)
  id = allocate_id
  cv = ConditionVariable.new

  @mutex.synchronize do
    @pending[id] = { cv: cv, result: nil, error: nil }
  end

  # Write the request via the server's write path
  write_raw({
    'jsonrpc' => Protocol::JSONRPC_VERSION,
    'id' => id,
    'method' => method,
    'params' => Protocol.send(:stringify_keys_deep, params)
  })

  # Block until the extension responds or we time out
  @mutex.synchronize do
    deadline = Time.now + timeout
    while @pending[id][:result].nil? && @pending[id][:error].nil?
      remaining = deadline - Time.now
      if remaining <= 0
        @pending.delete(id)
        raise TimeoutError, "IDE RPC request '#{method}' timed out after #{timeout}s"
      end
      cv.wait(@mutex, remaining)
    end

    entry = @pending.delete(id)
    raise StandardError, entry[:error] if entry[:error]

    entry[:result]
  end
end

#resolve(id, result: nil, error: nil) ⇒ Object

Called by the server when it receives a response message (has id + result/error, no method) that matches one of our pending outbound requests.

Parameters:

  • id (Integer)

    the response id

  • result (Hash, nil) (defaults to: nil)

    the result payload

  • error (Hash, nil) (defaults to: nil)

    the error payload



73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/rubyn_code/ide/client.rb', line 73

def resolve(id, result: nil, error: nil)
  @mutex.synchronize do
    entry = @pending[id]
    return unless entry

    if error
      entry[:error] = "RPC error #{error['code']}: #{error['message']}"
    else
      entry[:result] = result || {}
    end
    entry[:cv].signal
  end
end