index.js 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. /**
  2. * @import {VFile, Value} from 'vfile'
  3. * @import {Location} from 'vfile-location'
  4. */
  5. /**
  6. * Create an index of the given document to translate between line/column and
  7. * offset based positional info.
  8. *
  9. * Also implemented in Rust in [`wooorm/markdown-rs`][markdown-rs].
  10. *
  11. * [markdown-rs]: https://github.com/wooorm/markdown-rs/blob/main/src/util/location.rs
  12. *
  13. * @param {VFile | Value} file
  14. * File to index.
  15. * @returns {Location}
  16. * Accessors for index.
  17. */
  18. export function location(file) {
  19. const value = String(file)
  20. /**
  21. * List, where each index is a line number (0-based), and each value is the
  22. * byte index *after* where the line ends.
  23. *
  24. * @type {Array<number>}
  25. */
  26. const indices = []
  27. return {toOffset, toPoint}
  28. /** @type {Location['toPoint']} */
  29. function toPoint(offset) {
  30. if (typeof offset === 'number' && offset > -1 && offset <= value.length) {
  31. let index = 0
  32. while (true) {
  33. let end = indices[index]
  34. if (end === undefined) {
  35. const eol = next(value, indices[index - 1])
  36. end = eol === -1 ? value.length + 1 : eol + 1
  37. indices[index] = end
  38. }
  39. if (end > offset) {
  40. return {
  41. line: index + 1,
  42. column: offset - (index > 0 ? indices[index - 1] : 0) + 1,
  43. offset
  44. }
  45. }
  46. index++
  47. }
  48. }
  49. }
  50. /** @type {Location['toOffset']} */
  51. function toOffset(point) {
  52. if (
  53. point &&
  54. typeof point.line === 'number' &&
  55. typeof point.column === 'number' &&
  56. !Number.isNaN(point.line) &&
  57. !Number.isNaN(point.column)
  58. ) {
  59. while (indices.length < point.line) {
  60. const from = indices[indices.length - 1]
  61. const eol = next(value, from)
  62. const end = eol === -1 ? value.length + 1 : eol + 1
  63. if (from === end) break
  64. indices.push(end)
  65. }
  66. const offset =
  67. (point.line > 1 ? indices[point.line - 2] : 0) + point.column - 1
  68. // The given `column` could not exist on this line.
  69. if (offset < indices[point.line - 1]) return offset
  70. }
  71. }
  72. }
  73. /**
  74. * @param {string} value
  75. * @param {number} from
  76. */
  77. function next(value, from) {
  78. const cr = value.indexOf('\r', from)
  79. const lf = value.indexOf('\n', from)
  80. if (lf === -1) return cr
  81. if (cr === -1 || cr + 1 === lf) return lf
  82. return cr < lf ? cr : lf
  83. }