| 1 | // Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module fmt |
| 5 | |
| 6 | import v.ast |
| 7 | |
| 8 | pub enum CommentsLevel { |
| 9 | keep |
| 10 | indent |
| 11 | } |
| 12 | |
| 13 | // CommentsOptions defines the way comments are going to be written |
| 14 | // - has_nl: adds an newline at the end of a list of comments |
| 15 | // - same_line: line comments will be on the same line as the last statement |
| 16 | // - level: either .keep (don't indent), or .indent (increment indentation) |
| 17 | // - prev_line: the line number of the previous token to save linebreaks |
| 18 | @[minify; params] |
| 19 | pub struct CommentsOptions { |
| 20 | has_nl bool = true |
| 21 | same_line bool |
| 22 | level CommentsLevel |
| 23 | prev_line int = -1 |
| 24 | } |
| 25 | |
| 26 | pub fn (mut f Fmt) comment(node ast.Comment, options CommentsOptions) { |
| 27 | if node.text.starts_with('\x01 vfmt on') { |
| 28 | f.vfmt_on(node.pos.line_nr) |
| 29 | } |
| 30 | defer { |
| 31 | // ensure that the `vfmt off` comment itself was sent to the output, |
| 32 | // by deferring the check for that state transition: |
| 33 | if node.text.starts_with('\x01 vfmt off') { |
| 34 | f.vfmt_off(node.pos.line_nr) |
| 35 | } |
| 36 | } |
| 37 | // Shebang in .vsh files |
| 38 | if node.text.starts_with('#!') { |
| 39 | f.writeln(node.text) |
| 40 | return |
| 41 | } |
| 42 | if options.level == .indent { |
| 43 | f.indent++ |
| 44 | } |
| 45 | if !node.text.contains('\n') { |
| 46 | is_separate_line := !options.same_line || node.text.starts_with('\x01') |
| 47 | mut s := node.text.trim_left('\x01').trim_right(' ') |
| 48 | mut out_s := '//' |
| 49 | if s != '' { |
| 50 | if is_char_alphanumeric(s[0]) { |
| 51 | out_s += ' ' |
| 52 | } |
| 53 | out_s += s |
| 54 | } |
| 55 | if !is_separate_line && f.indent > 0 { |
| 56 | f.remove_new_line() // delete the generated \n |
| 57 | f.write(' ') |
| 58 | } |
| 59 | f.write(out_s) |
| 60 | } else { |
| 61 | lines := node.text.split_into_lines() |
| 62 | f.write('/*') |
| 63 | for i, line in lines { |
| 64 | f.empty_line = false |
| 65 | if i == lines.len - 1 { |
| 66 | f.write(line) |
| 67 | if node.text[node.text.len - 1] == `\n` { |
| 68 | f.writeln('') |
| 69 | } |
| 70 | f.write('*/') |
| 71 | } else { |
| 72 | f.writeln(line.trim_right(' ')) |
| 73 | } |
| 74 | } |
| 75 | } |
| 76 | if options.level == .indent { |
| 77 | f.indent-- |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | pub fn (mut f Fmt) comments(comments []ast.Comment, options CommentsOptions) { |
| 82 | mut prev_line := options.prev_line |
| 83 | for i, c in comments { |
| 84 | if options.prev_line > -1 |
| 85 | && ((c.pos.line_nr > prev_line && f.out.len > 1 && f.out.last_n(1) != '\n') |
| 86 | || (c.pos.line_nr > prev_line + 1 && f.out.len > 2 && f.out.last_n(2) != '\n\n')) { |
| 87 | f.writeln('') |
| 88 | } |
| 89 | if i == 0 && f.out.len > 1 && !f.out.last_n(1)[0].is_space() { |
| 90 | f.write(' ') |
| 91 | } |
| 92 | f.comment(c, options) |
| 93 | if i < comments.len - 1 || options.has_nl { |
| 94 | f.writeln('') |
| 95 | } |
| 96 | prev_line = c.pos.last_line |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | pub fn (mut f Fmt) comments_before_field(comments []ast.Comment) { |
| 101 | // They behave the same as comments after the last field. This alias is just for clarity. |
| 102 | f.comments_after_last_field(comments) |
| 103 | } |
| 104 | |
| 105 | pub fn (mut f Fmt) comments_after_last_field(comments []ast.Comment) { |
| 106 | for comment in comments { |
| 107 | f.indent++ |
| 108 | f.empty_line = true |
| 109 | f.comment(comment, same_line: true) |
| 110 | f.writeln('') |
| 111 | f.indent-- |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | pub fn (mut f Fmt) import_comments(comments []ast.Comment, options CommentsOptions) { |
| 116 | if comments.len == 0 { |
| 117 | return |
| 118 | } |
| 119 | if options.same_line { |
| 120 | f.remove_new_line() |
| 121 | } |
| 122 | for c in comments { |
| 123 | ctext := c.text.trim_left('\x01') |
| 124 | if ctext == '' { |
| 125 | continue |
| 126 | } |
| 127 | mut out_s := if options.same_line { ' ' } else { '' } + '//' |
| 128 | if is_char_alphanumeric(ctext[0]) { |
| 129 | out_s += ' ' |
| 130 | } |
| 131 | out_s += ctext |
| 132 | f.writeln(out_s) |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | fn is_char_alphanumeric(c u8) bool { |
| 137 | return c.is_letter() || c.is_digit() |
| 138 | } |
| 139 | |