Class: Pikuri::Lsp::Registry
- Inherits:
-
Object
- Object
- Pikuri::Lsp::Registry
- Defined in:
- lib/pikuri/lsp/registry.rb
Overview
Pure configuration: which language servers exist, how to launch each, and which files each one claims. No I/O and no live state — a host writes this once and ClientWrapper spawns from it.
registry = Registry.new(entries: [
Registry::StdioEntry.new(id: 'ruby', command: %w[ruby-lsp],
files: %w[*.rb *.rake *.gemspec Rakefile Gemfile]),
Registry::StdioEntry.new(id: 'java', command: ['jdtls', '-data', data_dir],
files: %w[*.java],
env: { 'JAVA_HOME' => ENV.fetch('JAVA_HOME') },
init_options: { extendedClientCapabilities: {
classFileContentsSupport: true } })
])
registry.entry_for('lib/pikuri/agent.rb').id # => "ruby"
registry.entry_for('Gemfile').id # => "ruby"
registry.entry_for('README.md') # => nil
A Ruby server is two facts, command and files; the Java one exercises
every remaining field.
Defined Under Namespace
Classes: StdioEntry
Constant Summary collapse
- LANGUAGE_IDS =
Extension → LSP language identifier, for entries that don't spell out a
language_id:. Only thedidOpennotification uses it, and a server given the wrong one indexes nothing, so an unmapped extension is a config error rather than a guess. { '.rb' => 'ruby', '.rake' => 'ruby', '.gemspec' => 'ruby', '.java' => 'java', '.kt' => 'kotlin', '.kts' => 'kotlin', '.scala' => 'scala', '.groovy' => 'groovy', '.clj' => 'clojure', '.py' => 'python', '.js' => 'javascript', '.jsx' => 'javascriptreact', '.ts' => 'typescript', '.tsx' => 'typescriptreact', '.go' => 'go', '.rs' => 'rust', '.c' => 'c', '.h' => 'c', '.cpp' => 'cpp', '.cc' => 'cpp', '.hpp' => 'cpp', '.cs' => 'csharp', '.php' => 'php', '.swift' => 'swift', '.dart' => 'dart', '.ex' => 'elixir', '.exs' => 'elixir', '.erl' => 'erlang', '.hs' => 'haskell', '.lua' => 'lua', '.pl' => 'perl', '.r' => 'r', '.jl' => 'julia', '.sh' => 'shellscript', '.sql' => 'sql', '.html' => 'html', '.css' => 'css', '.scss' => 'scss', '.json' => 'json', '.yaml' => 'yaml', '.yml' => 'yaml', '.xml' => 'xml', '.md' => 'markdown', '.tex' => 'latex' }.freeze
- ENTRY_FIELDS =
The fields a StdioEntry accepts from a config file — everything but
id, which is the key it is written under. %i[command files language_id env init_options].freeze
- EMPTY =
The no-servers default, so a host that configures nothing passes nothing.
new
Instance Attribute Summary collapse
-
#entries ⇒ Array<StdioEntry>
readonly
The entries, in declaration order.
Class Method Summary collapse
-
.derive_language_id(files, id) ⇒ String
LSP language id for a
files:list. -
.from_h(config) ⇒ Registry
Builds a registry out of a plain Hash, so a host can map its own config file onto one — which is where this belongs, because which language servers you have installed is machine state, like an API key.
Instance Method Summary collapse
-
#empty? ⇒ Boolean
Whether no server is configured, in which case a host wires no
lsptool at all. -
#entry_for(path) ⇒ StdioEntry?
The server that answers for
path, ornilwhen none claims it. -
#initialize(entries: []) ⇒ Registry
constructor
A new instance of Registry.
Constructor Details
#initialize(entries: []) ⇒ Registry
Returns a new instance of Registry.
261 262 263 264 265 266 |
# File 'lib/pikuri/lsp/registry.rb', line 261 def initialize(entries: []) duplicate = entries.map(&:id).tally.find { |_, count| count > 1 } raise ArgumentError, "duplicate server id #{duplicate.first.inspect}" if duplicate @entries = entries.freeze end |
Instance Attribute Details
#entries ⇒ Array<StdioEntry> (readonly)
Returns the entries, in declaration order.
255 256 257 |
# File 'lib/pikuri/lsp/registry.rb', line 255 def entries @entries end |
Class Method Details
.derive_language_id(files, id) ⇒ String
LSP language id for a files: list.
243 244 245 246 247 248 249 250 251 252 |
# File 'lib/pikuri/lsp/registry.rb', line 243 def self.derive_language_id(files, id) files.each do |glob| extension = glob[/\.[A-Za-z0-9]+\z/]&.downcase language = extension && LANGUAGE_IDS[extension] return language if language end raise ArgumentError, "cannot derive language_id for #{id.inspect} from files: #{files.inspect} — " \ 'pass language_id: explicitly' end |
.from_h(config) ⇒ Registry
Builds a registry out of a plain Hash, so a host can map its own config file onto one — which is where this belongs, because which language servers you have installed is machine state, like an API key. The demo scripts read the YAML they already keep keys in:
# ~/.pikuri-examples-config.yaml
lsp_servers:
ruby:
command: [ruby-lsp]
files: ["*.rb", "*.rake", "*.gemspec", Rakefile, Gemfile]
java:
command: [jdtls, -data, /home/me/.cache/pikuri/jdtls/my-app]
files: ["*.java"]
env:
JAVA_HOME: /usr/lib/jvm/java-21
init_options:
extendedClientCapabilities:
classFileContentsSupport: true
Registry.from_h(YAML.safe_load_file(path)['lsp_servers'])
Nothing here knows what YAML is; a host reading TOML, JSON or its own
settings object uses the same method. Keyed by server id rather than
holding a list of {id: …} Hashes, which makes the duplicate
#initialize refuses unwritable; insertion order is dispatch order.
Two things a config file cannot do, both visible above. It cannot
compute, so jdtls's -data path is spelled out rather than derived
from Paths.cache — a host that wants it computed builds the
StdioEntry in Ruby, which is equally first-class. And it cannot be
type-checked by the person writing it, so every field is validated here
rather than coerced: a mistyped env is otherwise a jdtls that
hard-fails below Java 21, and a mistyped init_options is every Java
library symbol answering with silence.
157 158 159 |
# File 'lib/pikuri/lsp/registry.rb', line 157 def self.from_h(config) new(entries: (config || {}).map { |id, fields| entry_from_h(id.to_s, fields) }) end |
Instance Method Details
#empty? ⇒ Boolean
Returns whether no server is configured, in which case a host
wires no lsp tool at all.
278 279 280 |
# File 'lib/pikuri/lsp/registry.rb', line 278 def empty? @entries.empty? end |
#entry_for(path) ⇒ StdioEntry?
The server that answers for path, or nil when none claims it.
272 273 274 |
# File 'lib/pikuri/lsp/registry.rb', line 272 def entry_for(path) @entries.find { |entry| entry.claims?(path) } end |