1 /** Conversions from string/file offset to line and column. 2 See_Also: https://gcc.gnu.org/codingconventions.html#Diagnostics 3 See_Also: https://clang.llvm.org/diagnostics.html 4 */ 5 module nxt.line_column; 6 7 import nxt.path : FilePath; 8 9 public import nxt.offset : Offset; 10 11 @safe: 12 13 /++ Source Line. 14 +/ 15 alias Line = uint; 16 17 /++ Source Column. 18 +/ 19 alias Column = uint; 20 21 /** Line and column, both 0-based byte offsets. 22 * 23 * Uses 32-bit unsigned precision for line and column offet, for now, like 24 * tree-sitter does. 25 */ 26 pure nothrow @nogc struct LineColumn { 27 Line line; ///< 0-based line byte offset. 28 Column column; ///< 0-based column byte offset. 29 } 30 31 /** Convert byte offset `offset` in `txt` to (line, column) byte offsets. 32 * 33 * The returned line byte offset and column byte offsets both start at zero. 34 * 35 * TODO: extend to support UTF-8 in column offset. 36 * TODO: Move to Phobos std.txting? 37 */ 38 LineColumn scanLineColumnToOffset(in char[] txt, in Offset offset) pure nothrow @nogc { 39 // find 0-based column offset 40 size_t c = offset.sz; // cursor offset 41 while (c != 0) { 42 if (txt[c - 1] == '\n' || 43 txt[c - 1] == '\r') 44 break; 45 c -= 1; 46 } 47 // `c` is now at beginning of line 48 49 /+ TODO: count UTF-8 chars in `txt[c .. offset.sz]` +/ 50 const column = offset.sz - c; // column byte offset 51 52 // find 0-based line offset 53 size_t lineCounter = 0; 54 while (c != 0) { 55 c -= 1; 56 if (txt[c] == '\n') { 57 if (c != 0 && txt[c - 1] == '\r') // DOS-style line ending "\r\n" 58 c -= 1; 59 else {} // Unix-style line ending "\n" 60 lineCounter += 1; 61 } else if (txt[c] == '\r') // Old Mac-style line ending "\r" 62 lineCounter += 1; 63 else {} // no line ending at `c` 64 } 65 66 return typeof(return)(cast(Line)lineCounter, 67 cast(Column)column); 68 } 69 70 /// 71 pure nothrow @safe @nogc unittest { 72 auto x = "\nx\n y\rz"; 73 assert(x.length == 7); 74 assert(x.scanLineColumnToOffset(Offset(0)) == LineColumn(0, 0)); 75 assert(x.scanLineColumnToOffset(Offset(1)) == LineColumn(1, 0)); 76 assert(x.scanLineColumnToOffset(Offset(2)) == LineColumn(1, 1)); 77 assert(x.scanLineColumnToOffset(Offset(3)) == LineColumn(2, 0)); 78 assert(x.scanLineColumnToOffset(Offset(4)) == LineColumn(2, 1)); 79 assert(x.scanLineColumnToOffset(Offset(6)) == LineColumn(3, 0)); 80 assert(x.scanLineColumnToOffset(Offset(7)) == LineColumn(3, 1)); 81 } 82 83 version (unittest) { 84 import nxt.debugio; 85 }