13
13
use ansi_width:: ansi_width;
14
14
use std:: fmt;
15
15
16
+ /// Number of spaces in one \t.
17
+ pub const SPACES_IN_TAB : usize = 8 ;
18
+
19
+ /// Default size for separator in spaces.
20
+ pub const DEFAULT_SEPARATOR_SIZE : usize = 2 ;
21
+
16
22
/// Direction cells should be written in: either across or downwards.
17
23
#[ derive( PartialEq , Eq , Debug , Copy , Clone ) ]
18
24
pub enum Direction {
@@ -37,13 +43,22 @@ pub enum Filling {
37
43
///
38
44
/// `"|"` is a common choice.
39
45
Text ( String ) ,
46
+
47
+ /// Fill spaces with `\t`
48
+ Tabs {
49
+ /// A number of spaces
50
+ spaces : usize ,
51
+ /// Size of `\t` in spaces
52
+ tab_size : usize ,
53
+ } ,
40
54
}
41
55
42
56
impl Filling {
43
57
fn width ( & self ) -> usize {
44
58
match self {
45
59
Filling :: Spaces ( w) => * w,
46
60
Filling :: Text ( t) => ansi_width ( t) ,
61
+ Filling :: Tabs { spaces, .. } => * spaces,
47
62
}
48
63
}
49
64
}
@@ -257,9 +272,15 @@ impl<T: AsRef<str>> Grid<T> {
257
272
258
273
impl < T : AsRef < str > > fmt:: Display for Grid < T > {
259
274
fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> Result < ( ) , fmt:: Error > {
260
- let separator = match & self . options . filling {
261
- Filling :: Spaces ( n) => " " . repeat ( * n) ,
262
- Filling :: Text ( s) => s. clone ( ) ,
275
+ // If cells are empty then, nothing to print, skip.
276
+ if self . cells . is_empty ( ) {
277
+ return Ok ( ( ) ) ;
278
+ }
279
+
280
+ let ( tab_size, separator) = match & self . options . filling {
281
+ Filling :: Spaces ( n) => ( 0 , " " . repeat ( * n) ) ,
282
+ Filling :: Text ( s) => ( 0 , s. clone ( ) ) ,
283
+ Filling :: Tabs { spaces, tab_size } => ( * tab_size, " " . repeat ( * spaces) ) ,
263
284
} ;
264
285
265
286
// Initialize a buffer of spaces. The idea here is that any cell
@@ -270,24 +291,33 @@ impl<T: AsRef<str>> fmt::Display for Grid<T> {
270
291
// We overestimate how many spaces we need, but this is not
271
292
// part of the loop and it's therefore not super important to
272
293
// get exactly right.
273
- let padding = " " . repeat ( self . widest_cell_width ) ;
294
+ let padding = " " . repeat ( self . widest_cell_width + self . options . filling . width ( ) ) ;
274
295
275
296
for y in 0 ..self . dimensions . num_lines {
297
+ // Current position on the line.
298
+ let mut cursor: usize = 0 ;
276
299
for x in 0 ..self . dimensions . widths . len ( ) {
277
- let num = match self . options . direction {
278
- Direction :: LeftToRight => y * self . dimensions . widths . len ( ) + x,
279
- Direction :: TopToBottom => y + self . dimensions . num_lines * x,
300
+ // Calculate position of the current element of the grid
301
+ // in cells and widths vectors and the offset to the next value.
302
+ let ( current, offset) = match self . options . direction {
303
+ Direction :: LeftToRight => ( y * self . dimensions . widths . len ( ) + x, 1 ) ,
304
+ Direction :: TopToBottom => {
305
+ ( y + self . dimensions . num_lines * x, self . dimensions . num_lines )
306
+ }
280
307
} ;
281
308
282
- // Abandon a line mid-way through if that’s where the cells end
283
- if num >= self . cells . len ( ) {
284
- continue ;
309
+ // Abandon a line mid-way through if that’s where the cells end.
310
+ if current >= self . cells . len ( ) {
311
+ break ;
285
312
}
286
313
287
- let contents = & self . cells [ num] ;
288
- let width = self . widths [ num] ;
314
+ // Last in row checks only the predefined grid width.
315
+ // It does not check if there will be more entries.
316
+ // For this purpose we define next value as well.
317
+ // This prevents printing separator after the actual last value in a row.
289
318
let last_in_row = x == self . dimensions . widths . len ( ) - 1 ;
290
-
319
+ let contents = & self . cells [ current] ;
320
+ let width = self . widths [ current] ;
291
321
let col_width = self . dimensions . widths [ x] ;
292
322
let padding_size = col_width - width;
293
323
@@ -305,11 +335,40 @@ impl<T: AsRef<str>> fmt::Display for Grid<T> {
305
335
// We also only call `write_str` when we actually need padding as
306
336
// another optimization.
307
337
f. write_str ( contents. as_ref ( ) ) ?;
308
- if !last_in_row {
309
- if padding_size > 0 {
310
- f. write_str ( & padding[ 0 ..padding_size] ) ?;
311
- }
338
+
339
+ // In case this entry was the last on the current line,
340
+ // there is no need to print the separator and padding.
341
+ if last_in_row || current + offset >= self . cells . len ( ) {
342
+ break ;
343
+ }
344
+
345
+ // Special case if tab size was not set. Fill with spaces and separator.
346
+ if tab_size == 0 {
347
+ f. write_str ( & padding[ ..padding_size] ) ?;
312
348
f. write_str ( & separator) ?;
349
+ } else {
350
+ // Move cursor to the end of the current contents.
351
+ cursor += width;
352
+ let total_spaces = padding_size + self . options . filling . width ( ) ;
353
+ // The size of \t can be inconsistent in terminal.
354
+ // Tab stops are relative to the cursor position e.g.,
355
+ // * cursor = 0, \t moves to column 8;
356
+ // * cursor = 5, \t moves to column 8 (3 spaces);
357
+ // * cursor = 9, \t moves to column 16 (7 spaces).
358
+ // Calculate the nearest \t position in relation to cursor.
359
+ let closest_tab = tab_size - ( cursor % tab_size) ;
360
+
361
+ if closest_tab > total_spaces {
362
+ f. write_str ( & padding[ ..total_spaces] ) ?;
363
+ } else {
364
+ let rest_spaces = total_spaces - closest_tab;
365
+ let tabs = 1 + ( rest_spaces / tab_size) ;
366
+ let spaces = rest_spaces % tab_size;
367
+ f. write_str ( & "\t " . repeat ( tabs) ) ?;
368
+ f. write_str ( & padding[ ..spaces] ) ?;
369
+ }
370
+
371
+ cursor += total_spaces;
313
372
}
314
373
}
315
374
f. write_str ( "\n " ) ?;
0 commit comments