| ... | ... | @@ -1395,31 +1395,169 @@ fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void { |
| 1395 | 1395 | // TODO fstat and make sure that the file has the correct size |
| 1396 | 1396 | |
| 1397 | 1397 | var buf: [mem.page_size]u8 = undefined; |
| 1398 | | var line: usize = 1; |
| 1399 | | var column: usize = 1; |
| 1400 | | while (true) { |
| 1401 | | const amt_read = try f.read(buf[0..]); |
| 1402 | | const slice = buf[0..amt_read]; |
| 1403 | | |
| 1404 | | for (slice) |byte| { |
| 1405 | | if (line == line_info.line) { |
| 1406 | | switch (byte) { |
| 1407 | | '\t' => try out_stream.writeByte(' '), |
| 1408 | | else => try out_stream.writeByte(byte), |
| 1409 | | } |
| 1410 | | if (byte == '\n') { |
| 1411 | | return; |
| 1412 | | } |
| 1398 | var amt_read = try f.read(buf[0..]); |
| 1399 | const line_start = seek: { |
| 1400 | var current_line_start: usize = 0; |
| 1401 | var next_line: usize = 1; |
| 1402 | while (next_line != line_info.line) { |
| 1403 | const slice = buf[current_line_start..amt_read]; |
| 1404 | if (mem.indexOfScalar(u8, slice, '\n')) |pos| { |
| 1405 | next_line += 1; |
| 1406 | if (pos == slice.len - 1) { |
| 1407 | amt_read = try f.read(buf[0..]); |
| 1408 | current_line_start = 0; |
| 1409 | } else current_line_start += pos + 1; |
| 1410 | } else if (amt_read < buf.len) { |
| 1411 | return error.EndOfFile; |
| 1412 | } else { |
| 1413 | amt_read = try f.read(buf[0..]); |
| 1414 | current_line_start = 0; |
| 1413 | 1415 | } |
| 1414 | | if (byte == '\n') { |
| 1415 | | line += 1; |
| 1416 | | column = 1; |
| 1416 | } |
| 1417 | break :seek current_line_start; |
| 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 | 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 | |