Class: Pikuri::Lsp::Location

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

Overview

A place a server pointed at, parsed from either wire shape a navigation request can answer with. Servers pick between Location (+uri+/+range+) and LocationLink (+targetUri+/+targetRange+/+targetSelectionRange+) based on the linkSupport the client declared, and ruby-lsp picks the second — so both go in one door and nothing downstream learns there were two:

locations = Location.list_from_wire(connection.request('textDocument/definition', params))
locations.map { |loc| "#{loc.path}:#{loc.anchor.start.line}" }
# => ["/home/m/pikuri/pikuri-workspace/lib/pikuri/workspace/filesystem.rb:218"]

A result is a walked path — the model never supplied it — so it reaches no resolve_for_read, and every consumer owes it a Filesystem#denied? pass plus root confinement (+D_walked_path_denylist+). #path returning nil is the other half of that: a jdt: URI inside a jar can be neither denylist-checked nor read, and has to be rendered from content the server hands over.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(uri:, range:, selection_range: nil) ⇒ Location

Returns a new instance of Location.



73
74
75
# File 'lib/pikuri/lsp/location.rb', line 73

def initialize(uri:, range:, selection_range: nil)
  super
end

Instance Attribute Details

#rangeRange (readonly)

Returns the whole target — a definition's entire body, or every line of a reopened namespace.

Returns:

  • (Range)

    the whole target — a definition's entire body, or every line of a reopened namespace.



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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/pikuri/lsp/location.rb', line 35

Location = Data.define(:uri, :range, :selection_range) do
  # Parse one +Location+ or +LocationLink+.
  #
  # @param hash [Hash{String => Object}] either wire shape.
  # @param text [String, nil] the target document's text, when to hand —
  #   what makes the columns exact, see {Range.from_wire}.
  # @param encoding [String] the server's negotiated +positionEncoding+.
  # @return [Location]
  # @raise [KeyError] if the hash is neither shape.
  def self.from_wire(hash, text: nil, encoding: PositionEncoding::DEFAULT)
    uri = hash['uri'] || hash.fetch('targetUri')
    range = hash['range'] || hash.fetch('targetRange')
    selection = hash['targetSelectionRange']
    new(uri: uri,
        range: Range.from_wire(range, text: text, encoding: encoding),
        selection_range: selection && Range.from_wire(selection, text: text, encoding: encoding))
  end

  # Parse whatever a navigation request answered with: LSP allows a single
  # object, an array, or +null+ for the same method, and servers use all
  # three.
  #
  # @param result [Hash, Array<Hash>, nil] the +result+ member.
  # @param text [String, nil] as in {.from_wire}; only useful for a
  #   single-document answer.
  # @param encoding [String] the server's negotiated +positionEncoding+.
  # @return [Array<Location>] empty for +null+ or +[]+ — which means *the
  #   server answered nothing*, and must never be rendered as "no
  #   definition exists".
  def self.list_from_wire(result, text: nil, encoding: PositionEncoding::DEFAULT)
    case result
    when nil then []
    when Hash then [from_wire(result, text: text, encoding: encoding)]
    when Array then result.map { |item| from_wire(item, text: text, encoding: encoding) }
    else raise ArgumentError, "not a location result: #{result.inspect}"
    end
  end

  def initialize(uri:, range:, selection_range: nil)
    super
  end

  # @return [String, nil] local path, or +nil+ for a non-+file:+ URI. See
  #   {Uris.to_path}.
  def path
    Uris.to_path(uri)
  end

  # @return [String, nil] the URI's scheme, e.g. +"file"+ or +"jdt"+.
  def scheme
    Uris.scheme(uri)
  end

  # @return [Boolean] whether this names a readable local file.
  def file?
    !path.nil?
  end

  # @return [Range] the tightest range the server gave: the identifier when
  #   it sent a link, the whole target otherwise.
  def anchor
    selection_range || range
  end
end

#selection_rangeRange? (readonly)

Returns just the identifier inside #range when the server sent a LocationLink, nil for a plain Location. The one thing the link shape buys, and what a one-line snippet points at.

Returns:

  • (Range, nil)

    just the identifier inside #range when the server sent a LocationLink, nil for a plain Location. The one thing the link shape buys, and what a one-line snippet points at.



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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/pikuri/lsp/location.rb', line 35

Location = Data.define(:uri, :range, :selection_range) do
  # Parse one +Location+ or +LocationLink+.
  #
  # @param hash [Hash{String => Object}] either wire shape.
  # @param text [String, nil] the target document's text, when to hand —
  #   what makes the columns exact, see {Range.from_wire}.
  # @param encoding [String] the server's negotiated +positionEncoding+.
  # @return [Location]
  # @raise [KeyError] if the hash is neither shape.
  def self.from_wire(hash, text: nil, encoding: PositionEncoding::DEFAULT)
    uri = hash['uri'] || hash.fetch('targetUri')
    range = hash['range'] || hash.fetch('targetRange')
    selection = hash['targetSelectionRange']
    new(uri: uri,
        range: Range.from_wire(range, text: text, encoding: encoding),
        selection_range: selection && Range.from_wire(selection, text: text, encoding: encoding))
  end

  # Parse whatever a navigation request answered with: LSP allows a single
  # object, an array, or +null+ for the same method, and servers use all
  # three.
  #
  # @param result [Hash, Array<Hash>, nil] the +result+ member.
  # @param text [String, nil] as in {.from_wire}; only useful for a
  #   single-document answer.
  # @param encoding [String] the server's negotiated +positionEncoding+.
  # @return [Array<Location>] empty for +null+ or +[]+ — which means *the
  #   server answered nothing*, and must never be rendered as "no
  #   definition exists".
  def self.list_from_wire(result, text: nil, encoding: PositionEncoding::DEFAULT)
    case result
    when nil then []
    when Hash then [from_wire(result, text: text, encoding: encoding)]
    when Array then result.map { |item| from_wire(item, text: text, encoding: encoding) }
    else raise ArgumentError, "not a location result: #{result.inspect}"
    end
  end

  def initialize(uri:, range:, selection_range: nil)
    super
  end

  # @return [String, nil] local path, or +nil+ for a non-+file:+ URI. See
  #   {Uris.to_path}.
  def path
    Uris.to_path(uri)
  end

  # @return [String, nil] the URI's scheme, e.g. +"file"+ or +"jdt"+.
  def scheme
    Uris.scheme(uri)
  end

  # @return [Boolean] whether this names a readable local file.
  def file?
    !path.nil?
  end

  # @return [Range] the tightest range the server gave: the identifier when
  #   it sent a link, the whole target otherwise.
  def anchor
    selection_range || range
  end
end

#uriString (readonly)

Returns the URI verbatim, e.g. "file:///home/m/pikuri/read.rb" — or a ~300-character "jdt://contents/…" whose query string is a whole classpath entry, which is why a Java target is never rendered raw.

Returns:

  • (String)

    the URI verbatim, e.g. "file:///home/m/pikuri/read.rb" — or a ~300-character "jdt://contents/…" whose query string is a whole classpath entry, which is why a Java target is never rendered raw.



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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/pikuri/lsp/location.rb', line 35

Location = Data.define(:uri, :range, :selection_range) do
  # Parse one +Location+ or +LocationLink+.
  #
  # @param hash [Hash{String => Object}] either wire shape.
  # @param text [String, nil] the target document's text, when to hand —
  #   what makes the columns exact, see {Range.from_wire}.
  # @param encoding [String] the server's negotiated +positionEncoding+.
  # @return [Location]
  # @raise [KeyError] if the hash is neither shape.
  def self.from_wire(hash, text: nil, encoding: PositionEncoding::DEFAULT)
    uri = hash['uri'] || hash.fetch('targetUri')
    range = hash['range'] || hash.fetch('targetRange')
    selection = hash['targetSelectionRange']
    new(uri: uri,
        range: Range.from_wire(range, text: text, encoding: encoding),
        selection_range: selection && Range.from_wire(selection, text: text, encoding: encoding))
  end

  # Parse whatever a navigation request answered with: LSP allows a single
  # object, an array, or +null+ for the same method, and servers use all
  # three.
  #
  # @param result [Hash, Array<Hash>, nil] the +result+ member.
  # @param text [String, nil] as in {.from_wire}; only useful for a
  #   single-document answer.
  # @param encoding [String] the server's negotiated +positionEncoding+.
  # @return [Array<Location>] empty for +null+ or +[]+ — which means *the
  #   server answered nothing*, and must never be rendered as "no
  #   definition exists".
  def self.list_from_wire(result, text: nil, encoding: PositionEncoding::DEFAULT)
    case result
    when nil then []
    when Hash then [from_wire(result, text: text, encoding: encoding)]
    when Array then result.map { |item| from_wire(item, text: text, encoding: encoding) }
    else raise ArgumentError, "not a location result: #{result.inspect}"
    end
  end

  def initialize(uri:, range:, selection_range: nil)
    super
  end

  # @return [String, nil] local path, or +nil+ for a non-+file:+ URI. See
  #   {Uris.to_path}.
  def path
    Uris.to_path(uri)
  end

  # @return [String, nil] the URI's scheme, e.g. +"file"+ or +"jdt"+.
  def scheme
    Uris.scheme(uri)
  end

  # @return [Boolean] whether this names a readable local file.
  def file?
    !path.nil?
  end

  # @return [Range] the tightest range the server gave: the identifier when
  #   it sent a link, the whole target otherwise.
  def anchor
    selection_range || range
  end
end

Class Method Details

.from_wire(hash, text: nil, encoding: PositionEncoding::DEFAULT) ⇒ Location

Parse one Location or LocationLink.

Parameters:

  • hash (Hash{String => Object})

    either wire shape.

  • text (String, nil) (defaults to: nil)

    the target document's text, when to hand — what makes the columns exact, see Range.from_wire.

  • encoding (String) (defaults to: PositionEncoding::DEFAULT)

    the server's negotiated positionEncoding.

Returns:

Raises:

  • (KeyError)

    if the hash is neither shape.



44
45
46
47
48
49
50
51
# File 'lib/pikuri/lsp/location.rb', line 44

def self.from_wire(hash, text: nil, encoding: PositionEncoding::DEFAULT)
  uri = hash['uri'] || hash.fetch('targetUri')
  range = hash['range'] || hash.fetch('targetRange')
  selection = hash['targetSelectionRange']
  new(uri: uri,
      range: Range.from_wire(range, text: text, encoding: encoding),
      selection_range: selection && Range.from_wire(selection, text: text, encoding: encoding))
end

.list_from_wire(result, text: nil, encoding: PositionEncoding::DEFAULT) ⇒ Array<Location>

Parse whatever a navigation request answered with: LSP allows a single object, an array, or null for the same method, and servers use all three.

Parameters:

  • result (Hash, Array<Hash>, nil)

    the result member.

  • text (String, nil) (defaults to: nil)

    as in from_wire; only useful for a single-document answer.

  • encoding (String) (defaults to: PositionEncoding::DEFAULT)

    the server's negotiated positionEncoding.

Returns:

  • (Array<Location>)

    empty for null or [] — which means the server answered nothing, and must never be rendered as "no definition exists".



64
65
66
67
68
69
70
71
# File 'lib/pikuri/lsp/location.rb', line 64

def self.list_from_wire(result, text: nil, encoding: PositionEncoding::DEFAULT)
  case result
  when nil then []
  when Hash then [from_wire(result, text: text, encoding: encoding)]
  when Array then result.map { |item| from_wire(item, text: text, encoding: encoding) }
  else raise ArgumentError, "not a location result: #{result.inspect}"
  end
end

Instance Method Details

#anchorRange

Returns the tightest range the server gave: the identifier when it sent a link, the whole target otherwise.

Returns:

  • (Range)

    the tightest range the server gave: the identifier when it sent a link, the whole target otherwise.



95
96
97
# File 'lib/pikuri/lsp/location.rb', line 95

def anchor
  selection_range || range
end

#file?Boolean

Returns whether this names a readable local file.

Returns:

  • (Boolean)

    whether this names a readable local file.



89
90
91
# File 'lib/pikuri/lsp/location.rb', line 89

def file?
  !path.nil?
end

#pathString?

Returns local path, or nil for a non-+file:+ URI. See Uris.to_path.

Returns:

  • (String, nil)

    local path, or nil for a non-+file:+ URI. See Uris.to_path.



79
80
81
# File 'lib/pikuri/lsp/location.rb', line 79

def path
  Uris.to_path(uri)
end

#schemeString?

Returns the URI's scheme, e.g. "file" or "jdt".

Returns:

  • (String, nil)

    the URI's scheme, e.g. "file" or "jdt".



84
85
86
# File 'lib/pikuri/lsp/location.rb', line 84

def scheme
  Uris.scheme(uri)
end