codeframe.ts 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. const range: number = 2
  2. export function generateCodeFrame(
  3. source: string,
  4. start = 0,
  5. end = source.length,
  6. ): string {
  7. // Split the content into individual lines but capture the newline sequence
  8. // that separated each line. This is important because the actual sequence is
  9. // needed to properly take into account the full line length for offset
  10. // comparison
  11. let lines = source.split(/(\r?\n)/)
  12. // Separate the lines and newline sequences into separate arrays for easier referencing
  13. const newlineSequences = lines.filter((_, idx) => idx % 2 === 1)
  14. lines = lines.filter((_, idx) => idx % 2 === 0)
  15. let count = 0
  16. const res: string[] = []
  17. for (let i = 0; i < lines.length; i++) {
  18. count +=
  19. lines[i].length +
  20. ((newlineSequences[i] && newlineSequences[i].length) || 0)
  21. if (count >= start) {
  22. for (let j = i - range; j <= i + range || end > count; j++) {
  23. if (j < 0 || j >= lines.length) continue
  24. const line = j + 1
  25. res.push(
  26. `${line}${' '.repeat(Math.max(3 - String(line).length, 0))}| ${
  27. lines[j]
  28. }`,
  29. )
  30. const lineLength = lines[j].length
  31. const newLineSeqLength =
  32. (newlineSequences[j] && newlineSequences[j].length) || 0
  33. if (j === i) {
  34. // push underline
  35. const pad = start - (count - (lineLength + newLineSeqLength))
  36. const length = Math.max(
  37. 1,
  38. end > count ? lineLength - pad : end - start,
  39. )
  40. res.push(` | ` + ' '.repeat(pad) + '^'.repeat(length))
  41. } else if (j > i) {
  42. if (end > count) {
  43. const length = Math.max(Math.min(end - count, lineLength), 1)
  44. res.push(` | ` + '^'.repeat(length))
  45. }
  46. count += lineLength + newLineSeqLength
  47. }
  48. }
  49. break
  50. }
  51. }
  52. return res.join('\n')
  53. }