Class: Ibex::LSP::Workspace
- Inherits:
-
Object
- Object
- Ibex::LSP::Workspace
- Defined in:
- lib/ibex/lsp/workspace.rb,
sig/ibex/lsp/workspace.rbs
Overview
Converts file URIs to canonical paths constrained to initialized workspace roots.
Instance Attribute Summary collapse
- #roots ⇒ Array[String] readonly
Instance Method Summary collapse
- #canonical_root(uri) ⇒ String
- #decode_path(encoded) ⇒ String
-
#initialize(root_uris, loader) ⇒ Workspace
constructor
A new instance of Workspace.
- #inside?(path, root) ⇒ Boolean
-
#parse_file_uri(uri) ⇒ Object
URI is a stdlib boundary whose implementation class varies by scheme.
- #path(uri, allow_missing: true) ⇒ String
- #root_for(path) ⇒ String?
- #uri(path) ⇒ String
-
#validate_raw_authority!(uri) ⇒ void
Ruby's URI::File may discard userinfo and port, so validate the raw authority first.
Constructor Details
#initialize(root_uris, loader) ⇒ Workspace
Returns a new instance of Workspace.
10 11 12 13 14 15 |
# File 'lib/ibex/lsp/workspace.rb', line 10 def initialize(root_uris, loader) raise ProtocolError.new("initialize requires a workspace root", code: -32_602) if root_uris.empty? @loader = loader @roots = root_uris.map { |uri| canonical_root(uri) }.uniq.sort.freeze end |
Instance Attribute Details
#roots ⇒ Array[String] (readonly)
7 8 9 |
# File 'lib/ibex/lsp/workspace.rb', line 7 def roots @roots end |
Instance Method Details
#canonical_root(uri) ⇒ String
78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/ibex/lsp/workspace.rb', line 78 def canonical_root(uri) parsed = parse_file_uri(uri) decoded = decode_path(parsed.path) canonical = File.realpath(decoded) unless File.directory?(canonical) raise ProtocolError.new("workspace root is not a directory: #{uri}", code: -32_602) end canonical rescue URI::InvalidURIError, ArgumentError, SystemCallError => e raise ProtocolError.new("invalid workspace root #{uri.inspect}: #{e.}", code: -32_602) end |
#decode_path(encoded) ⇒ String
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/ibex/lsp/workspace.rb', line 92 def decode_path(encoded) raise ArgumentError, "file URI contains an invalid percent escape" if encoded.match?(/%(?![0-9A-Fa-f]{2})/) if encoded.match?(/%(?:2f|5c|00)/i) raise ArgumentError, "file URI must not percent-encode a path separator or NUL" end parser = begin URI::RFC2396_PARSER rescue NameError URI::DEFAULT_PARSER end decoded = parser.unescape(encoded) raise ArgumentError, "file URI path must be valid UTF-8" unless decoded.valid_encoding? raise ArgumentError, "file URI path must not contain NUL" if decoded.include?("\0") raise ArgumentError, "file URI path must not contain parent traversal" if decoded.split(%r{[\\/]}).include?("..") decoded end |
#inside?(path, root) ⇒ Boolean
114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/ibex/lsp/workspace.rb', line 114 def inside?(path, root) cursor = path loop do return true if cursor == root parent = File.dirname(cursor) return false if parent == cursor cursor = parent end end |
#parse_file_uri(uri) ⇒ Object
URI is a stdlib boundary whose implementation class varies by scheme.
52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/ibex/lsp/workspace.rb', line 52 def parse_file_uri(uri) (uri) parsed = URI.parse(uri) path = parsed.path local_host = parsed.host.nil? || parsed.host.empty? || parsed.host.casecmp?("localhost") unless parsed.scheme == "file" && local_host && parsed.query.nil? && parsed.fragment.nil? && path&.start_with?("/") raise ArgumentError, "only absolute local file URIs are supported" end parsed end |
#path(uri, allow_missing: true) ⇒ String
18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/ibex/lsp/workspace.rb', line 18 def path(uri, allow_missing: true) parsed = parse_file_uri(uri) decoded = decode_path(parsed.path) canonical = @loader.canonical_path(decoded, allow_missing: allow_missing) return canonical if root_for(canonical) raise ProtocolError.new("document is outside initialized workspace roots: #{uri}", code: -32_602) rescue URI::InvalidURIError, ArgumentError => e raise ProtocolError.new("invalid file URI #{uri.inspect}: #{e.}", code: -32_602) rescue SystemCallError => e raise ProtocolError.new("cannot resolve file URI #{uri.inspect}: #{e.}", code: -32_602) end |
#root_for(path) ⇒ String?
44 45 46 |
# File 'lib/ibex/lsp/workspace.rb', line 44 def root_for(path) roots.select { |root| inside?(path, root) }.max_by(&:length) end |
#uri(path) ⇒ String
33 34 35 36 37 38 39 40 41 |
# File 'lib/ibex/lsp/workspace.rb', line 33 def uri(path) canonical = @loader.canonical_path(path, allow_missing: true) parser = begin URI::RFC2396_PARSER rescue NameError URI::DEFAULT_PARSER end "file://#{parser.escape(canonical)}" end |
#validate_raw_authority!(uri) ⇒ void
This method returns an undefined value.
Ruby's URI::File may discard userinfo and port, so validate the raw authority first.
67 68 69 70 71 72 73 74 75 |
# File 'lib/ibex/lsp/workspace.rb', line 67 def (uri) match = uri.match(%r{\Afile://([^/]*)}i) return unless match = match[1] || "" return if .empty? || .casecmp?("localhost") raise ArgumentError, "only an empty authority or localhost is supported" end |