Class: Ibex::LSP::PositionCodec

Inherits:
Object
  • Object
show all
Defined in:
lib/ibex/lsp/position_codec.rb,
sig/ibex/lsp/position_codec.rbs

Overview

Converts between frontend byte offsets and zero-based LSP UTF-16 positions.

Instance Method Summary collapse

Constructor Details

#initialize(source) ⇒ PositionCodec

Returns a new instance of PositionCodec.

RBS:

  • (String source) -> void

Parameters:

  • source (String)


8
9
10
11
# File 'lib/ibex/lsp/position_codec.rb', line 8

def initialize(source)
  @source = Frontend::SourceEncoding.validated_utf8(source, "(lsp)")
  @line_starts = build_line_starts.freeze #: Array[Integer]
end

Instance Method Details

#build_line_startsArray[Integer]

RBS:

  • () -> Array[Integer]

Returns:

  • (Array[Integer])


49
50
51
52
53
54
55
56
57
# File 'lib/ibex/lsp/position_codec.rb', line 49

def build_line_starts
  starts = [0]
  offset = 0
  @source.each_char do |character|
    offset += character.bytesize
    starts << offset if character == "\n"
  end
  starts
end

#byte_length_at_utf16(text, units) ⇒ Integer

RBS:

  • (String text, Integer units) -> Integer

Parameters:

  • text (String)
  • units (Integer)

Returns:

  • (Integer)


74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/ibex/lsp/position_codec.rb', line 74

def byte_length_at_utf16(text, units)
  consumed_units = 0
  consumed_bytes = 0
  text.each_char do |character|
    return consumed_bytes if consumed_units == units

    width = character.ord > 0xFFFF ? 2 : 1
    if consumed_units + width > units
      raise ArgumentError, "character points into the middle of a UTF-16 surrogate pair"
    end

    consumed_units += width
    consumed_bytes += character.bytesize
  end
  return consumed_bytes if consumed_units == units

  raise ArgumentError, "character is outside the document line"
end

#byte_offset(position) ⇒ Integer

RBS:

  • (Hash[String, untyped] position) -> Integer

Parameters:

  • position (Hash[String, untyped])

Returns:

  • (Integer)


29
30
31
32
33
34
35
36
37
38
39
# File 'lib/ibex/lsp/position_codec.rb', line 29

def byte_offset(position)
  line = integer_member(position, "line")
  character = integer_member(position, "character")
  raise ArgumentError, "line and character must be non-negative" if line.negative? || character.negative?

  line_start = @line_starts[line]
  raise ArgumentError, "line is outside the document" unless line_start

  text = @source.byteslice(line_start, content_end(line) - line_start) || ""
  line_start + byte_length_at_utf16(text, character)
end

#content_end(line) ⇒ Integer

RBS:

  • (Integer line) -> Integer

Parameters:

  • line (Integer)

Returns:

  • (Integer)


60
61
62
63
64
65
66
# File 'lib/ibex/lsp/position_codec.rb', line 60

def content_end(line)
  boundary = @line_starts[line + 1] || @source.bytesize
  return boundary unless @line_starts[line + 1]

  before_newline = boundary - 1
  @source.getbyte(before_newline - 1) == 13 ? before_newline - 1 : before_newline
end

#integer_member(position, name) ⇒ Integer

RBS:

  • (Hash[String, untyped] position, String name) -> Integer

Parameters:

  • position (Hash[String, untyped])
  • name (String)

Returns:

  • (Integer)


94
95
96
97
98
99
# File 'lib/ibex/lsp/position_codec.rb', line 94

def integer_member(position, name)
  value = position[name]
  raise ArgumentError, "#{name} must be an integer" unless value.is_a?(Integer)

  value
end

#position(byte_offset) ⇒ Hash[String, Integer]

RBS:

  • (Integer byte_offset) -> Hash[String, Integer]

Parameters:

  • byte_offset (Integer)

Returns:

  • (Hash[String, Integer])


14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/ibex/lsp/position_codec.rb', line 14

def position(byte_offset)
  validate_byte_offset(byte_offset)
  prefix = @source.byteslice(0, byte_offset) || ""
  raise ArgumentError, "byte offset is not on a UTF-8 character boundary" unless prefix.valid_encoding?

  line = (@line_starts.bsearch_index { |start| start > byte_offset } || @line_starts.length) - 1
  line_start = @line_starts.fetch(line)
  line_end = content_end(line)
  raise ArgumentError, "byte offset points inside a line ending" if byte_offset > line_end

  text = @source.byteslice(line_start, byte_offset - line_start) || ""
  { "line" => line, "character" => utf16_length(text) }
end

#range(span) ⇒ Hash[String, Hash[String, Integer]]

RBS:

  • (Frontend::SourceSpan span) -> Hash[String, Hash[String, Integer]]

Parameters:

Returns:

  • (Hash[String, Hash[String, Integer]])


42
43
44
# File 'lib/ibex/lsp/position_codec.rb', line 42

def range(span)
  { "start" => position(span.start_byte), "end" => position(span.end_byte) }
end

#utf16_length(text) ⇒ Integer

RBS:

  • (String text) -> Integer

Parameters:

  • text (String)

Returns:

  • (Integer)


69
70
71
# File 'lib/ibex/lsp/position_codec.rb', line 69

def utf16_length(text)
  text.each_codepoint.sum { |codepoint| codepoint > 0xFFFF ? 2 : 1 }
end

#validate_byte_offset(byte_offset) ⇒ void

This method returns an undefined value.

RBS:

  • (Integer byte_offset) -> void

Parameters:

  • byte_offset (Integer)


102
103
104
105
106
# File 'lib/ibex/lsp/position_codec.rb', line 102

def validate_byte_offset(byte_offset)
  return if byte_offset.is_a?(Integer) && byte_offset.between?(0, @source.bytesize)

  raise ArgumentError, "byte offset is outside the document"
end