Class: Pikuri::Lsp::Position

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

Overview

One cursor position in pikuri's coordinates: 1-based line, 1-based character column — the same numbers read and grep print, so a rendered location needs no arithmetic and the model never sees an encoded offset in either direction.

The wire is 0-based and code-unit-encoded, and this pair of calls is the only place that is true:

line = File.readlines('read.rb')[164]        # "    workspace.resolve_for_read(path)"
pos  = Position.new(line: 165, column: line.index('resolve_for_read') + 1)
pos.to_wire(line_text: line, encoding: PositionEncoding::UTF8)
# => {:line=>164, :character=>14}

Position.from_wire({ 'line' => 164, 'character' => 14 },
                 line_text: line, encoding: PositionEncoding::UTF8).to_s
# => "165:15"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(line:, column:) ⇒ Position

Returns a new instance of Position.

Raises:

  • (ArgumentError)

    if either coordinate is below 1 — a 0 means an unconverted wire value leaked in, which is the bug this type exists to prevent.



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

def initialize(line:, column:)
  raise ArgumentError, "line must be >= 1, got #{line}" if line < 1
  raise ArgumentError, "column must be >= 1, got #{column}" if column < 1

  super
end

Instance Attribute Details

#columnInteger (readonly)

Returns 1-based column, counted in characters (not bytes, not UTF-16 code units).

Returns:

  • (Integer)

    1-based column, counted in characters (not bytes, not UTF-16 code units).



27
28
29
30
31
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
66
67
68
69
70
# File 'lib/pikuri/lsp/position.rb', line 27

Position = Data.define(:line, :column) do
  # Parse LSP's +Position+ object.
  #
  # @param hash [Hash{String => Integer}] +{"line" =>, "character" =>}+,
  #   0-based.
  # @param line_text [String, nil] the target line's text; +nil+ costs
  #   column exactness on a line with multi-unit characters, see
  #   {PositionEncoding.column_for}.
  # @param encoding [String] the server's negotiated +positionEncoding+.
  # @return [Position]
  # @raise [KeyError] if either wire field is missing.
  def self.from_wire(hash, line_text: nil, encoding: PositionEncoding::DEFAULT)
    new(line: Integer(hash.fetch('line')) + 1,
        column: PositionEncoding.column_for(line_text, Integer(hash.fetch('character')), encoding))
  end

  # @raise [ArgumentError] if either coordinate is below 1 — a 0 means an
  #   unconverted wire value leaked in, which is the bug this type exists
  #   to prevent.
  def initialize(line:, column:)
    raise ArgumentError, "line must be >= 1, got #{line}" if line < 1
    raise ArgumentError, "column must be >= 1, got #{column}" if column < 1

    super
  end

  # Render as LSP's +Position+ object.
  #
  # @param line_text [String] the text of {#line}, required because the
  #   column's encoding depends on the characters preceding it.
  # @param encoding [String] the server's negotiated +positionEncoding+.
  # @return [Hash{Symbol => Integer}] +{line:, character:}+, 0-based,
  #   ready for +JSON.generate+.
  # @raise [ArgumentError] if {#column} points past the end of +line_text+.
  def to_wire(line_text:, encoding: PositionEncoding::DEFAULT)
    { line: line - 1, character: PositionEncoding.offset_for(line_text, column, encoding) }
  end

  # @return [String] +"165:15"+ — the form a rendered location appends to a
  #   path.
  def to_s
    "#{line}:#{column}"
  end
end

#lineInteger (readonly)

Returns 1-based line number.

Returns:

  • (Integer)

    1-based line number.



27
28
29
30
31
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
66
67
68
69
70
# File 'lib/pikuri/lsp/position.rb', line 27

Position = Data.define(:line, :column) do
  # Parse LSP's +Position+ object.
  #
  # @param hash [Hash{String => Integer}] +{"line" =>, "character" =>}+,
  #   0-based.
  # @param line_text [String, nil] the target line's text; +nil+ costs
  #   column exactness on a line with multi-unit characters, see
  #   {PositionEncoding.column_for}.
  # @param encoding [String] the server's negotiated +positionEncoding+.
  # @return [Position]
  # @raise [KeyError] if either wire field is missing.
  def self.from_wire(hash, line_text: nil, encoding: PositionEncoding::DEFAULT)
    new(line: Integer(hash.fetch('line')) + 1,
        column: PositionEncoding.column_for(line_text, Integer(hash.fetch('character')), encoding))
  end

  # @raise [ArgumentError] if either coordinate is below 1 — a 0 means an
  #   unconverted wire value leaked in, which is the bug this type exists
  #   to prevent.
  def initialize(line:, column:)
    raise ArgumentError, "line must be >= 1, got #{line}" if line < 1
    raise ArgumentError, "column must be >= 1, got #{column}" if column < 1

    super
  end

  # Render as LSP's +Position+ object.
  #
  # @param line_text [String] the text of {#line}, required because the
  #   column's encoding depends on the characters preceding it.
  # @param encoding [String] the server's negotiated +positionEncoding+.
  # @return [Hash{Symbol => Integer}] +{line:, character:}+, 0-based,
  #   ready for +JSON.generate+.
  # @raise [ArgumentError] if {#column} points past the end of +line_text+.
  def to_wire(line_text:, encoding: PositionEncoding::DEFAULT)
    { line: line - 1, character: PositionEncoding.offset_for(line_text, column, encoding) }
  end

  # @return [String] +"165:15"+ — the form a rendered location appends to a
  #   path.
  def to_s
    "#{line}:#{column}"
  end
end

Class Method Details

.from_wire(hash, line_text: nil, encoding: PositionEncoding::DEFAULT) ⇒ Position

Parse LSP's Position object.

Parameters:

  • hash (Hash{String => Integer})

    {"line" =>, "character" =>}, 0-based.

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

    the target line's text; nil costs column exactness on a line with multi-unit characters, see Pikuri::Lsp::PositionEncoding.column_for.

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

    the server's negotiated positionEncoding.

Returns:

Raises:

  • (KeyError)

    if either wire field is missing.



38
39
40
41
# File 'lib/pikuri/lsp/position.rb', line 38

def self.from_wire(hash, line_text: nil, encoding: PositionEncoding::DEFAULT)
  new(line: Integer(hash.fetch('line')) + 1,
      column: PositionEncoding.column_for(line_text, Integer(hash.fetch('character')), encoding))
end

Instance Method Details

#to_sString

Returns "165:15" — the form a rendered location appends to a path.

Returns:

  • (String)

    "165:15" — the form a rendered location appends to a path.



67
68
69
# File 'lib/pikuri/lsp/position.rb', line 67

def to_s
  "#{line}:#{column}"
end

#to_wire(line_text:, encoding: PositionEncoding::DEFAULT) ⇒ Hash{Symbol => Integer}

Render as LSP's Position object.

Parameters:

  • line_text (String)

    the text of #line, required because the column's encoding depends on the characters preceding it.

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

    the server's negotiated positionEncoding.

Returns:

  • (Hash{Symbol => Integer})

    {line:, character:}, 0-based, ready for JSON.generate.

Raises:

  • (ArgumentError)

    if #column points past the end of line_text.



61
62
63
# File 'lib/pikuri/lsp/position.rb', line 61

def to_wire(line_text:, encoding: PositionEncoding::DEFAULT)
  { line: line - 1, character: PositionEncoding.offset_for(line_text, column, encoding) }
end