1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial |
3 | |
4 | /*! |
5 | Work in progress for a formatter. |
6 | Use like this to format a file: |
7 | ```sh |
8 | cargo run --bin slint-lsp -- format -i some_file.slint |
9 | ``` |
10 | |
11 | Some code in this main.rs file is duplicated with the slint-updater, i guess it could |
12 | be refactored in a separate utility crate or module or something. |
13 | |
14 | The [`writer::TokenWriter`] trait is meant to be able to support the LSP later as the |
15 | LSP wants just the edits, not the full file |
16 | */ |
17 | |
18 | use i_slint_compiler::diagnostics::BuildDiagnostics; |
19 | use i_slint_compiler::parser::{syntax_nodes, SyntaxNode}; |
20 | use std::io::{BufWriter, Write}; |
21 | use std::path::Path; |
22 | |
23 | use super::{fmt, writer}; |
24 | |
25 | pub fn run(files: Vec<std::path::PathBuf>, inplace: bool) -> std::io::Result<()> { |
26 | for path: PathBuf in files { |
27 | let source: String = std::fs::read_to_string(&path)?; |
28 | |
29 | if inplace { |
30 | let file: BufWriter = BufWriter::new(inner:std::fs::File::create(&path)?); |
31 | process_file(source, path, file)? |
32 | } else { |
33 | process_file(source, path, file:std::io::stdout())? |
34 | } |
35 | } |
36 | Ok(()) |
37 | } |
38 | |
39 | /// FIXME! this is duplicated with the updater |
40 | fn process_rust_file(source: String, mut file: impl Write) -> std::io::Result<()> { |
41 | let mut last: usize = 0; |
42 | for range: Range in i_slint_compiler::lexer::locate_slint_macro(&source) { |
43 | file.write_all(buf:source[last..=range.start].as_bytes())?; |
44 | last = range.end; |
45 | let code: &str = &source[range]; |
46 | |
47 | let mut diag: BuildDiagnostics = BuildDiagnostics::default(); |
48 | let syntax_node: SyntaxNode = i_slint_compiler::parser::parse(source:code.to_owned(), path:None, version:None, &mut diag); |
49 | let len: usize = syntax_node.text_range().end().into(); |
50 | visit_node(syntax_node, &mut file)?; |
51 | if diag.has_error() { |
52 | file.write_all(&code.as_bytes()[len..])?; |
53 | diag.print(); |
54 | } |
55 | } |
56 | file.write_all(buf:source[last..].as_bytes()) |
57 | } |
58 | |
59 | /// FIXME! this is duplicated with the updater |
60 | fn process_markdown_file(source: String, mut file: impl Write) -> std::io::Result<()> { |
61 | let mut source_slice = &source[..]; |
62 | const CODE_FENCE_START: &str = "```slint \n" ; |
63 | const CODE_FENCE_END: &str = "``` \n" ; |
64 | 'l: while let Some(code_start) = |
65 | source_slice.find(CODE_FENCE_START).map(|idx| idx + CODE_FENCE_START.len()) |
66 | { |
67 | let code_end = if let Some(code_end) = source_slice[code_start..].find(CODE_FENCE_END) { |
68 | code_end |
69 | } else { |
70 | break 'l; |
71 | }; |
72 | file.write_all(source_slice[..=code_start - 1].as_bytes())?; |
73 | source_slice = &source_slice[code_start..]; |
74 | let code = &source_slice[..code_end]; |
75 | source_slice = &source_slice[code_end..]; |
76 | |
77 | let mut diag = BuildDiagnostics::default(); |
78 | let syntax_node = i_slint_compiler::parser::parse(code.to_owned(), None, None, &mut diag); |
79 | let len = syntax_node.text_range().end().into(); |
80 | visit_node(syntax_node, &mut file)?; |
81 | if diag.has_error() { |
82 | file.write_all(&code.as_bytes()[len..])?; |
83 | diag.print(); |
84 | } |
85 | } |
86 | return file.write_all(source_slice.as_bytes()); |
87 | } |
88 | |
89 | fn process_slint_file( |
90 | source: String, |
91 | path: std::path::PathBuf, |
92 | mut file: impl Write, |
93 | ) -> std::io::Result<()> { |
94 | let mut diag: BuildDiagnostics = BuildDiagnostics::default(); |
95 | let syntax_node: SyntaxNode = i_slint_compiler::parser::parse(source.clone(), path:Some(&path), version:None, &mut diag); |
96 | let len: usize = syntax_node.node.text_range().end().into(); |
97 | visit_node(syntax_node, &mut file)?; |
98 | if diag.has_error() { |
99 | file.write_all(&source.as_bytes()[len..])?; |
100 | diag.print(); |
101 | } |
102 | Ok(()) |
103 | } |
104 | |
105 | fn process_file( |
106 | source: String, |
107 | path: std::path::PathBuf, |
108 | mut file: impl Write, |
109 | ) -> std::io::Result<()> { |
110 | match path.extension() { |
111 | Some(ext: &OsStr) if ext == "rs" => process_rust_file(source, file), |
112 | Some(ext: &OsStr) if ext == "md" => process_markdown_file(source, file), |
113 | // Formatting .60 files because of backwards compatibility (project was recently renamed) |
114 | Some(ext: &OsStr) if ext == "slint" || ext == ".60" => process_slint_file(source, path, file), |
115 | _ => { |
116 | // This allows usage like `cat x.slint | slint-lsp format /dev/stdin` |
117 | if path.as_path() == Path::new("/dev/stdin" ) { |
118 | return process_slint_file(source, path, file); |
119 | } |
120 | // With other file types, we just output them in their original form. |
121 | return file.write_all(buf:source.as_bytes()); |
122 | } |
123 | } |
124 | } |
125 | |
126 | fn visit_node(node: SyntaxNode, file: &mut impl Write) -> std::io::Result<()> { |
127 | if let Some(doc: Document) = syntax_nodes::Document::new(node) { |
128 | let mut writer: FileWriter<'_, impl Write> = writer::FileWriter { file }; |
129 | fmt::format_document(doc, &mut writer) |
130 | } else { |
131 | Err(std::io::Error::new(kind:std::io::ErrorKind::Other, error:"Not a Document" )) |
132 | } |
133 | } |
134 | |