82 lines
2.6 KiB
Ruby
82 lines
2.6 KiB
Ruby
# -*- coding: utf-8; frozen_string_literal: true -*-
|
|
#
|
|
#--
|
|
# Copyright (C) 2009-2026 Thomas Leitner <t_leitner@gmx.at>
|
|
#
|
|
# This file is part of kramdown which is licensed under the MIT.
|
|
#++
|
|
#
|
|
|
|
require 'strscan'
|
|
|
|
module Kramdown
|
|
module Utils
|
|
|
|
# This patched StringScanner adds line number information for current scan position and a
|
|
# start_line_number override for nested StringScanners.
|
|
class StringScanner < ::StringScanner
|
|
|
|
# The start line number. Used for nested StringScanners that scan a sub-string of the source
|
|
# document. The kramdown parser uses this, e.g., for span level parsers.
|
|
attr_reader :start_line_number
|
|
|
|
# Takes the start line number as optional second argument.
|
|
#
|
|
# Note: The original second argument is no longer used so this should be safe.
|
|
def initialize(string, start_line_number = 1)
|
|
super(string)
|
|
@start_line_number = start_line_number || 1
|
|
@previous_pos = 0
|
|
@previous_line_number = @start_line_number
|
|
end
|
|
|
|
# Sets the byte position of the scan pointer.
|
|
#
|
|
# Note: This also resets some internal variables, so always use pos= when setting the position
|
|
# and don't use any other method for that!
|
|
def pos=(pos)
|
|
if self.pos > pos
|
|
@previous_line_number = @start_line_number
|
|
@previous_pos = 0
|
|
end
|
|
super
|
|
end
|
|
|
|
# Return information needed to revert the byte position of the string scanner in a performant
|
|
# way.
|
|
#
|
|
# The returned data can be fed to #revert_pos to revert the position to the saved one.
|
|
#
|
|
# Note: Just saving #pos won't be enough.
|
|
def save_pos
|
|
[pos, @previous_pos, @previous_line_number]
|
|
end
|
|
|
|
# Revert the position to one saved by #save_pos.
|
|
def revert_pos(data)
|
|
self.pos = data[0]
|
|
@previous_pos, @previous_line_number = data[1], data[2]
|
|
end
|
|
|
|
# Returns the line number for current charpos.
|
|
#
|
|
# NOTE: Requires that all line endings are normalized to '\n'
|
|
#
|
|
# NOTE: Normally we'd have to add one to the count of newlines to get the correct line number.
|
|
# However we add the one indirectly by using a one-based start_line_number.
|
|
def current_line_number
|
|
# Not using string[@previous_pos..best_pos].count('\n') because it is slower
|
|
strscan = ::StringScanner.new(string)
|
|
strscan.pos = @previous_pos
|
|
old_pos = pos + 1
|
|
@previous_line_number += 1 while strscan.skip_until(/\n/) && strscan.pos <= old_pos
|
|
|
|
@previous_pos = (eos? ? pos : pos + 1)
|
|
@previous_line_number
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
end
|