| ... | @@ -1395,31 +1395,169 @@ fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void { | ... | @@ -1395,31 +1395,169 @@ fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void { |
| 1395 | // TODO fstat and make sure that the file has the correct size | 1395 | // TODO fstat and make sure that the file has the correct size |
| 1396 | | 1396 | |
| 1397 | var buf: [mem.page_size]u8 = undefined; | 1397 | var buf: [mem.page_size]u8 = undefined; |
| 1398 | var line: usize = 1; | 1398 | var amt_read = try f.read(buf[0..]); |
| 1399 | var column: usize = 1; | 1399 | const line_start = seek: { |
| 1400 | while (true) { | 1400 | var current_line_start: usize = 0; |
| 1401 | const amt_read = try f.read(buf[0..]); | 1401 | var next_line: usize = 1; |
| 1402 | const slice = buf[0..amt_read]; | 1402 | while (next_line != line_info.line) { |
| 1403 | | 1403 | const slice = buf[current_line_start..amt_read]; |
| 1404 | for (slice) |byte| { | 1404 | if (mem.indexOfScalar(u8, slice, '\n')) |pos| { |
| 1405 | if (line == line_info.line) { | 1405 | next_line += 1; |
| 1406 | switch (byte) { | 1406 | if (pos == slice.len - 1) { |
| 1407 | '\t' => try out_stream.writeByte(' '), | 1407 | amt_read = try f.read(buf[0..]); |
| 1408 | else => try out_stream.writeByte(byte), | 1408 | current_line_start = 0; |
| 1409 | } | 1409 | } else current_line_start += pos + 1; |
| 1410 | if (byte == '\n') { | 1410 | } else if (amt_read < buf.len) { |
| 1411 | return; | 1411 | return error.EndOfFile; |
| 1412 | } | 1412 | } else { |
| | 1413 | amt_read = try f.read(buf[0..]); |
| | 1414 | current_line_start = 0; |
| 1413 | } | 1415 | } |
| 1414 | if (byte == '\n') { | 1416 | } |
| 1415 | line += 1; | 1417 | break :seek current_line_start; |
| 1416 | column = 1; | 1418 | }; |
| | 1419 | const slice = buf[line_start..amt_read]; |
| | 1420 | if (mem.indexOfScalar(u8, slice, '\n')) |pos| { |
| | 1421 | const line = slice[0 .. pos + 1]; |
| | 1422 | mem.replaceScalar(u8, line, '\t', ' '); |
| | 1423 | return out_stream.writeAll(line); |
| | 1424 | } else { // Line is the last inside the buffer, and requires another read to find delimiter. Alternatively the file ends. |
| | 1425 | mem.replaceScalar(u8, slice, '\t', ' '); |
| | 1426 | try out_stream.writeAll(slice); |
| | 1427 | while (amt_read == buf.len) { |
| | 1428 | amt_read = try f.read(buf[0..]); |
| | 1429 | if (mem.indexOfScalar(u8, buf[0..amt_read], '\n')) |pos| { |
| | 1430 | const line = buf[0 .. pos + 1]; |
| | 1431 | mem.replaceScalar(u8, line, '\t', ' '); |
| | 1432 | return out_stream.writeAll(line); |
| 1417 | } else { | 1433 | } else { |
| 1418 | column += 1; | 1434 | const line = buf[0..amt_read]; |
| | 1435 | mem.replaceScalar(u8, line, '\t', ' '); |
| | 1436 | try out_stream.writeAll(line); |
| 1419 | } | 1437 | } |
| 1420 | } | 1438 | } |
| | 1439 | // Make sure printing last line of file inserts extra newline |
| | 1440 | try out_stream.writeByte('\n'); |
| | 1441 | } |
| | 1442 | } |
| | 1443 | |
| | 1444 | test "printLineFromFileAnyOs" { |
| | 1445 | var output = std.ArrayList(u8).init(std.testing.allocator); |
| | 1446 | defer output.deinit(); |
| | 1447 | const output_stream = output.writer(); |
| | 1448 | |
| | 1449 | const allocator = std.testing.allocator; |
| | 1450 | const join = std.fs.path.join; |
| | 1451 | const expectError = std.testing.expectError; |
| | 1452 | const expectEqualStrings = std.testing.expectEqualStrings; |
| | 1453 | |
| | 1454 | var test_dir = std.testing.tmpDir(.{}); |
| | 1455 | defer test_dir.cleanup(); |
| | 1456 | // Relies on testing.tmpDir internals which is not ideal, but LineInfo requires paths. |
| | 1457 | const test_dir_path = try join(allocator, &.{ "zig-cache", "tmp", test_dir.sub_path[0..] }); |
| | 1458 | defer allocator.free(test_dir_path); |
| | 1459 | |
| | 1460 | // Cases |
| | 1461 | { |
| | 1462 | const path = try join(allocator, &.{ test_dir_path, "one_line.zig" }); |
| | 1463 | defer allocator.free(path); |
| | 1464 | try test_dir.dir.writeFile("one_line.zig", "no new lines in this file, but one is printed anyway"); |
| | 1465 | |
| | 1466 | try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); |
| | 1467 | |
| | 1468 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); |
| | 1469 | try expectEqualStrings("no new lines in this file, but one is printed anyway\n", output.items); |
| | 1470 | output.clearRetainingCapacity(); |
| | 1471 | } |
| | 1472 | { |
| | 1473 | const path = try fs.path.join(allocator, &.{ test_dir_path, "three_lines.zig" }); |
| | 1474 | defer allocator.free(path); |
| | 1475 | try test_dir.dir.writeFile("three_lines.zig", |
| | 1476 | \\1 |
| | 1477 | \\2 |
| | 1478 | \\3 |
| | 1479 | ); |
| | 1480 | |
| | 1481 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); |
| | 1482 | try expectEqualStrings("1\n", output.items); |
| | 1483 | output.clearRetainingCapacity(); |
| | 1484 | |
| | 1485 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 3, .column = 0 }); |
| | 1486 | try expectEqualStrings("3\n", output.items); |
| | 1487 | output.clearRetainingCapacity(); |
| | 1488 | } |
| | 1489 | { |
| | 1490 | const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{}); |
| | 1491 | defer file.close(); |
| | 1492 | const path = try fs.path.join(allocator, &.{ test_dir_path, "line_overlaps_page_boundary.zig" }); |
| | 1493 | defer allocator.free(path); |
| | 1494 | |
| | 1495 | const overlap = 10; |
| | 1496 | var writer = file.writer(); |
| | 1497 | try writer.writeByteNTimes('a', mem.page_size - overlap); |
| | 1498 | try writer.writeByte('\n'); |
| | 1499 | try writer.writeByteNTimes('a', overlap); |
| | 1500 | |
| | 1501 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); |
| | 1502 | try expectEqualStrings(("a" ** overlap) ++ "\n", output.items); |
| | 1503 | output.clearRetainingCapacity(); |
| | 1504 | } |
| | 1505 | { |
| | 1506 | const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{}); |
| | 1507 | defer file.close(); |
| | 1508 | const path = try fs.path.join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); |
| | 1509 | defer allocator.free(path); |
| | 1510 | |
| | 1511 | var writer = file.writer(); |
| | 1512 | try writer.writeByteNTimes('a', mem.page_size); |
| | 1513 | |
| | 1514 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); |
| | 1515 | try expectEqualStrings(("a" ** mem.page_size) ++ "\n", output.items); |
| | 1516 | output.clearRetainingCapacity(); |
| | 1517 | } |
| | 1518 | { |
| | 1519 | const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{}); |
| | 1520 | defer file.close(); |
| | 1521 | const path = try fs.path.join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); |
| | 1522 | defer allocator.free(path); |
| | 1523 | |
| | 1524 | var writer = file.writer(); |
| | 1525 | try writer.writeByteNTimes('a', 3 * mem.page_size); |
| | 1526 | |
| | 1527 | try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); |
| | 1528 | |
| | 1529 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); |
| | 1530 | try expectEqualStrings(("a" ** (3 * mem.page_size)) ++ "\n", output.items); |
| | 1531 | output.clearRetainingCapacity(); |
| | 1532 | |
| | 1533 | try writer.writeAll("a\na"); |
| | 1534 | |
| | 1535 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); |
| | 1536 | try expectEqualStrings(("a" ** (3 * mem.page_size)) ++ "a\n", output.items); |
| | 1537 | output.clearRetainingCapacity(); |
| | 1538 | |
| | 1539 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); |
| | 1540 | try expectEqualStrings("a\n", output.items); |
| | 1541 | output.clearRetainingCapacity(); |
| | 1542 | } |
| | 1543 | { |
| | 1544 | const file = try test_dir.dir.createFile("file_of_newlines.zig", .{}); |
| | 1545 | defer file.close(); |
| | 1546 | const path = try fs.path.join(allocator, &.{ test_dir_path, "file_of_newlines.zig" }); |
| | 1547 | defer allocator.free(path); |
| | 1548 | |
| | 1549 | var writer = file.writer(); |
| | 1550 | const real_file_start = 3 * mem.page_size; |
| | 1551 | try writer.writeByteNTimes('\n', real_file_start); |
| | 1552 | try writer.writeAll("abc\ndef"); |
| | 1553 | |
| | 1554 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 }); |
| | 1555 | try expectEqualStrings("abc\n", output.items); |
| | 1556 | output.clearRetainingCapacity(); |
| 1421 | | 1557 | |
| 1422 | if (amt_read < buf.len) return error.EndOfFile; | 1558 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 }); |
| | 1559 | try expectEqualStrings("def\n", output.items); |
| | 1560 | output.clearRetainingCapacity(); |
| 1423 | } | 1561 | } |
| 1424 | } | 1562 | } |
| 1425 | | 1563 | |