Class: Pikuri::Lsp::Sources

Inherits:
Object
  • Object
show all
Defined in:
lib/pikuri/lsp/sources.rb

Overview

Where the bytes behind a result come from, and what a result is called. One instance per tool call, so the memo and the fetch budget are per call.

sources = Sources.new(filesystem: filesystem)
sources.label_for(location.uri)
# => "pikuri-workspace/lib/pikuri/workspace/filesystem.rb"
# => "org.slf4j.LoggerFactory (slf4j-api-2.0.18.jar)"
sources.snippet_for(location, client: client)   # => "def resolve_for_read(path)"

Three tiers, keyed on the URI's scheme

There is no standard "read this URI" request in the LSP that ships today and no way to discover a server's non-standard one, so this is policy, not a lookup:

  1. +file:+ — the only scheme pikuri reads itself. In-root results carry the workspace-relative path, and the read goes through the Workspace seam. Out-of-root results (the 45 gem hits one goToDefinition on a constant returned) always carry the line of code — "what does this library method do" is a top reason to want LSP at all — and carry a path only when this workspace would read that file, which is a per-wiring answer: AllowAll (the --yolo and OS-agent wirings) opens a gem checkout happily, a root-confined one refuses it, and quoting a path the read tool will reject is a dead-end citation. Every file: URI takes a #denied? pass first — a result is a walked path, so nothing else stops an index hit under ~/.ssh from being quoted.
  2. A server-private scheme — standard first, one hardcoded row behind it. LSP 3.18's workspace/textDocumentContent is real discovery (the server lists the schemes it will read) and wins where advertised. Nothing implements it yet, and a standard-only stance made the Java arm untestable, so SCHEME_REQUESTS carries exactly one row with a written deletion condition: delete the jdt: row the day jdtls advertises +textDocumentContent+, and the standard path above it takes over with no other edit. The bar for adding a row is a server someone wired and can test.
  3. +http(s):+ and any scheme with no row — printed, never dereferenced. The load-bearing refusal: fetching a URI the server chose is egress with the payload in the URL, and no network row may ever enter the table. A scheme with no row still gets the inline reason, never a bare count.

One reply of attached Java source measured 22,639 bytes in 10 ms — cheap to fetch, ruinous to inline — so content is sliced to the line the result points at, memoized per URI (a 45-hit answer repeats URIs) and capped at MAX_FETCHES documents per call.

Constant Summary collapse

MAX_FETCHES =

Documents one tool call may fetch over the wire. A cap rather than a budget because the failure it prevents is one call inlining a jar.

5
MAX_SNIPPET_CHARS =

Characters of a source line a snippet keeps. A row is one line — not the result's whole range, which for a reopened namespace is the whole file.

160
STANDARD_REQUEST =

LSP 3.18's client→server content request: {uri}{text}.

'workspace/textDocumentContent'
SCHEME_REQUESTS =

The scheme→request table. One row, and the deletion condition is in the class header. java/classFileContents answers with a bare String where the standard request answers {text:}; #fetch_from_server accepts either, which is the whole reason both paths can share a call site.

{ 'jdt' => 'java/classFileContents' }.freeze

Instance Method Summary collapse

Constructor Details

#initialize(filesystem:) ⇒ Sources

Returns a new instance of Sources.

Parameters:

  • filesystem (Pikuri::Workspace::Filesystem)

    the root confinement, the denied? pass, and the seam every in-root read routes through.



69
70
71
72
73
74
75
# File 'lib/pikuri/lsp/sources.rb', line 69

def initialize(filesystem:)
  @filesystem = filesystem
  @root = filesystem.project_root.to_s
  @memo = {}
  @readable = {}
  @fetches = 0
end

Instance Method Details

#actionable?(uri) ⇒ Boolean

Whether this workspace would actually open the document behind a file: URI — what decides whether a citation may carry a link, since a link to a path the read tool refuses is the dead-end citation this class exists to avoid. Includes the #denied? pass, so it is safe to ask on its own.

Parameters:

  • uri (String)

Returns:

  • (Boolean)

    false for any non-+file:+ URI: a jar entry is not a path, so nothing here can open one.



114
115
116
117
# File 'lib/pikuri/lsp/sources.rb', line 114

def actionable?(uri)
  path = Uris.to_path(uri)
  !path.nil? && !@filesystem.denied?(path) && readable?(path)
end

#denied?(uri) ⇒ Boolean

Whether this URI must be dropped rather than rendered.

A result is a path the server walked to, so it reaches no resolve_for_read and this check is the only thing standing between an index hit under a credential root and the model's context. It fails closed, never raises, and is exact — the file tools run as the real user.

Parameters:

  • uri (String)

Returns:

  • (Boolean)

    false for a non-+file:+ URI, which cannot be denylist-checked at all: a jar's contents are not a path.



129
130
131
132
# File 'lib/pikuri/lsp/sources.rb', line 129

def denied?(uri)
  path = Uris.to_path(uri)
  !path.nil? && @filesystem.denied?(path)
end

#in_root_file?(uri) ⇒ Boolean

Returns whether this names a file under the workspace root — which is what decides whether a row carries a path the model can act on.

Parameters:

  • uri (String)

Returns:

  • (Boolean)

    whether this names a file under the workspace root — which is what decides whether a row carries a path the model can act on.



101
102
103
104
# File 'lib/pikuri/lsp/sources.rb', line 101

def in_root_file?(uri)
  path = Uris.to_path(uri)
  !path.nil? && in_root?(path)
end

#label_for(uri) ⇒ String

What to call this URI in a rendered row.

Parameters:

  • uri (String)

    as the server sent it.

Returns:

  • (String)

    a workspace-relative path in-root; the absolute path for a file outside the root this workspace would still read, or a bare basename for one it would refuse; +"org.slf4j.LoggerFactory (slf4j-api-2.0.18.jar)"+ for a jdt: handle; else the URI itself, truncated — a real jdt: URI runs to ~300 characters of encoded classpath and must never be shown raw.



86
87
88
89
90
91
92
93
94
95
# File 'lib/pikuri/lsp/sources.rb', line 86

def label_for(uri)
  path = Uris.to_path(uri)
  return relative(path) if path && in_root?(path)
  return out_of_root_label(path) if path

  scheme = Uris.scheme(uri)
  return jdt_label(uri) if scheme == 'jdt'

  truncate(uri, 200)
end

#lines_for(uri, client: nil) ⇒ Array(Array<String>, nil), Array(nil, String)

The document behind a URI, memoized for this call.

Parameters:

Returns:

  • (Array(Array<String>, nil), Array(nil, String))

    its lines, or nil and the reason.



157
158
159
# File 'lib/pikuri/lsp/sources.rb', line 157

def lines_for(uri, client: nil)
  @memo[uri] ||= fetch(uri, client)
end

#snippet_for(location, client: nil) ⇒ String

One line of code for a result, or the reason there is none.

Parameters:

  • location (Location)

    what the server pointed at.

  • client (ClientWrapper, nil) (defaults to: nil)

    the server that issued the URI — the only one that can read its private schemes.

Returns:

  • (String)

    the source line, stripped and truncated, or a bracketed reason. Never nil: a row with no snippet still has to say why.



141
142
143
144
145
146
147
148
149
# File 'lib/pikuri/lsp/sources.rb', line 141

def snippet_for(location, client: nil)
  lines, reason = lines_for(location.uri, client: client)
  return "[#{reason}]" if lines.nil?

  line = lines[location.anchor.start.line - 1]
  return '[the server pointed past the end of this document]' if line.nil?

  truncate(line.strip, MAX_SNIPPET_CHARS)
end