Skip to content

Commit 8531c0f

Browse files
authored
Added formatter implementation based on uncrustify
Implement `textDocument/formatting` Closes #212
1 parent a7b19ac commit 8531c0f

File tree

5 files changed

+314
-1
lines changed

5 files changed

+314
-1
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ We recommend using VSCode with the [Vala plugin](https://marketplace.visualstudi
6060
- for implementing abstract methods/properties
6161
- [x] code lenses
6262
- [ ] code actions
63-
- [ ] code formatting
63+
- [x] code formatting
6464
- [ ] workspaces
6565
- [ ] supported projects
6666
- [x] meson
@@ -79,6 +79,8 @@ We recommend using VSCode with the [Vala plugin](https://marketplace.visualstudi
7979
- `libvala >= 0.48.12`
8080
- you also need the `posix` VAPI, which should come preinstalled
8181

82+
[Uncrustify](http://uncrustify.sourceforge.net/) is required for formatting.
83+
8284
#### Install dependencies with Guix
8385

8486
If you're using Guix, to launch a shell with build dependencies satisfied:

src/codehelp/formatter.vala

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/* formatter.vala
2+
*
3+
* Copyright 2022 JCWasmx86 <[email protected]>
4+
*
5+
* This file is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU Lesser General Public License as
7+
* published by the Free Software Foundation; either version 3 of the
8+
* License, or (at your option) any later version.
9+
*
10+
* This file is distributed in the hope that it will be useful, but
11+
* WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*
18+
* SPDX-License-Identifier: LGPL-3.0-or-later
19+
*/
20+
21+
using Gee;
22+
using Lsp;
23+
24+
errordomain FormattingError {
25+
FORMAT,
26+
READ
27+
}
28+
29+
namespace Vls.Formatter {
30+
TextEdit format (FormattingOptions options, CodeStyleAnalyzer analyzed_style, Vala.SourceFile source, Cancellable? cancellable = null) throws FormattingError, Error {
31+
// SEARCH_PATH_FROM_ENVP does not seem to be available even in quite fast distros like Fedora 35,
32+
// so we have to use a SubprocessLauncher and call set_environ()
33+
var launcher = new SubprocessLauncher (SubprocessFlags.STDERR_PIPE | SubprocessFlags.STDOUT_PIPE | SubprocessFlags.STDIN_PIPE);
34+
launcher.set_environ (Environ.get ());
35+
Subprocess subprocess = launcher.spawnv (get_uncrustify_args (source, options, analyzed_style));
36+
string? stdout_buf = null, stderr_buf = null;
37+
subprocess.communicate_utf8 (source.content, cancellable, out stdout_buf, out stderr_buf);
38+
if (!subprocess.get_successful ()) {
39+
if (stderr_buf != null && stderr_buf.strip ().length > 0) {
40+
throw new FormattingError.FORMAT ("%s", stderr_buf);
41+
} else {
42+
throw new FormattingError.READ ("uncrustify failed with error code %d", subprocess.get_exit_status ());
43+
}
44+
}
45+
int last_nl_pos;
46+
uint nl_count = Util.count_chars_in_string (source.content, '\n', out last_nl_pos);
47+
return new TextEdit () {
48+
range = new Range () {
49+
start = new Position () {
50+
line = 0,
51+
character = 0
52+
},
53+
end = new Position () {
54+
line = nl_count + 1,
55+
// handle trailing newline
56+
character = last_nl_pos == source.content.length - 1 ? 1 : 0
57+
}
58+
},
59+
newText = stdout_buf
60+
};
61+
}
62+
63+
string[] get_uncrustify_args (Vala.SourceFile source, FormattingOptions options, CodeStyleAnalyzer analyzed_style) {
64+
var conf = new HashMap<string, string> ();
65+
// https://github.com/uncrustify/uncrustify/blob/master/documentation/htdocs/default.cfg
66+
conf["indent_with_tabs"] = "%d".printf (options.insertSpaces ? 0 : 1);
67+
conf["nl_end_of_file"] = options.insertFinalNewline ? "force" : "remove";
68+
conf["nl_end_of_file_min"] = "%d".printf (options.trimFinalNewlines ? 1 : 0);
69+
conf["output_tab_size"] = "%u".printf (options.tabSize);
70+
conf["indent_columns"] = "4";
71+
conf["indent_align_string"] = "true";
72+
conf["indent_xml_string"] = "4";
73+
conf["indent_namespace"] = "true";
74+
conf["indent_class"] = "true";
75+
conf["indent_var_def_cont"] = "true";
76+
conf["indent_func_def_param"] = "true";
77+
conf["indent_func_proto_param"] = "true";
78+
conf["indent_func_class_param"] = "true";
79+
conf["indent_func_ctor_var_param"] = "true";
80+
conf["indent_template_param"] = "true";
81+
conf["indent_member"] = "1";
82+
conf["indent_paren_close"] = "2";
83+
conf["indent_align_assign"] = "false";
84+
conf["indent_oc_block_msg_xcode_style"] = "true";
85+
conf["indent_oc_block_msg_from_keyword"] = "true";
86+
conf["indent_oc_block_msg_from_colon"] = "true";
87+
conf["indent_oc_block_msg_from_caret"] = "true";
88+
conf["indent_oc_block_msg_from_brace"] = "true";
89+
conf["newlines"] = "auto";
90+
conf["sp_arith"] = "force";
91+
conf["sp_assign"] = "force";
92+
conf["sp_assign_default"] = "force";
93+
conf["sp_before_assign"] = "force";
94+
conf["sp_after_assign"] = "force";
95+
conf["sp_enum_assign"] = "force";
96+
conf["sp_enum_after_assign"] = "force";
97+
conf["sp_bool"] = "force";
98+
conf["sp_compare"] = "force";
99+
conf["sp_inside_paren"] = "remove";
100+
conf["sp_paren_paren"] = "remove";
101+
conf["sp_cparen_oparen"] = "force";
102+
conf["sp_paren_brace"] = "force";
103+
conf["sp_before_ptr_star"] = "remove";
104+
conf["sp_before_unnamed_ptr_star"] = "remove";
105+
conf["sp_between_ptr_star"] = "force";
106+
conf["sp_after_ptr_star"] = "force";
107+
conf["sp_after_ptr_star_func"] = "force";
108+
conf["sp_ptr_star_paren"] = "force";
109+
conf["sp_before_ptr_star_func"] = "force";
110+
conf["sp_before_byref"] = "force";
111+
conf["sp_after_byref_func"] = "remove";
112+
conf["sp_before_byref_func"] = "force";
113+
conf["sp_before_angle"] = "remove";
114+
conf["sp_inside_angle"] = "remove";
115+
conf["sp_after_angle"] = "remove";
116+
conf["sp_angle_paren"] = "force";
117+
conf["sp_angle_word"] = "force";
118+
conf["sp_before_sparen"] = "force";
119+
conf["sp_inside_sparen"] = "remove";
120+
conf["sp_after_sparen"] = "remove";
121+
conf["sp_sparen_brace"] = "force";
122+
conf["sp_special_semi"] = "remove";
123+
conf["sp_before_semi_for"] = "remove";
124+
conf["sp_before_semi_for_empty"] = "force";
125+
conf["sp_after_semi_for_empty"] = "force";
126+
conf["sp_before_square"] = "remove";
127+
conf["sp_before_squares"] = "remove";
128+
conf["sp_inside_square"] = "remove";
129+
conf["sp_after_comma"] = "force";
130+
conf["sp_before_ellipsis"] = "remove";
131+
conf["sp_after_class_colon"] = "force";
132+
conf["sp_before_class_colon"] = "force";
133+
conf["sp_after_constr_colon"] = "ignore";
134+
conf["sp_before_constr_colon"] = "ignore";
135+
conf["sp_after_operator"] = "force";
136+
conf["sp_after_cast"] = "force";
137+
conf["sp_inside_paren_cast"] = "remove";
138+
conf["sp_sizeof_paren"] = "force";
139+
conf["sp_inside_braces_enum"] = "force";
140+
conf["sp_inside_braces_struct"] = "force";
141+
conf["sp_inside_braces"] = "force";
142+
conf["sp_inside_braces_empty"] = "remove";
143+
conf["sp_type_func"] = "remove";
144+
conf["sp_inside_fparens"] = "remove";
145+
conf["sp_inside_fparen"] = "remove";
146+
conf["sp_inside_tparen"] = "remove";
147+
conf["sp_after_tparen_close"] = "remove";
148+
conf["sp_square_fparen"] = "force";
149+
conf["sp_fparen_brace"] = "force";
150+
if (analyzed_style.average_spacing_before_parens > 0) {
151+
conf["sp_func_proto_paren"] = "force";
152+
conf["sp_func_def_paren"] = "force";
153+
conf["sp_func_class_paren"] = "force";
154+
conf["sp_func_call_paren"] = "force";
155+
} else {
156+
conf["sp_func_proto_paren"] = "remove";
157+
conf["sp_func_def_paren"] = "remove";
158+
conf["sp_func_class_paren"] = "remove";
159+
conf["sp_func_call_paren"] = "remove";
160+
}
161+
conf["sp_func_call_paren_empty"] = "ignore";
162+
// It is really "set func_call_user _"
163+
// conf["set func_call_user"] = "C_ NC_ N_ Q_ _";
164+
conf["sp_return_paren"] = "force";
165+
conf["sp_attribute_paren"] = "force";
166+
conf["sp_defined_paren"] = "force";
167+
conf["sp_throw_paren"] = "force";
168+
conf["sp_after_throw"] = "force";
169+
conf["sp_catch_paren"] = "force";
170+
conf["sp_else_brace"] = "force";
171+
conf["sp_brace_else"] = "force";
172+
conf["sp_brace_typedef"] = "force";
173+
conf["sp_catch_brace"] = "force";
174+
conf["sp_brace_catch"] = "force";
175+
conf["sp_finally_brace"] = "force";
176+
conf["sp_brace_finally"] = "force";
177+
conf["sp_try_brace"] = "force";
178+
conf["sp_getset_brace"] = "force";
179+
conf["sp_word_brace_ns"] = "force";
180+
conf["sp_before_dc"] = "remove";
181+
conf["sp_after_dc"] = "remove";
182+
conf["sp_cond_colon"] = "force";
183+
conf["sp_cond_colon_before"] = "force";
184+
conf["sp_cond_question"] = "force";
185+
conf["sp_cond_question_after"] = "force";
186+
conf["sp_cond_ternary_short"] = "force";
187+
conf["sp_case_label"] = "force";
188+
conf["sp_cmt_cpp_start"] = "force";
189+
conf["sp_endif_cmt"] = "remove";
190+
conf["sp_after_new"] = "force";
191+
conf["sp_before_tr_cmt"] = "force";
192+
conf["align_keep_extra_space"] = "true";
193+
conf["nl_assign_leave_one_liners"] = "true";
194+
conf["nl_class_leave_one_liners"] = "true";
195+
conf["nl_enum_brace"] = "remove";
196+
conf["nl_struct_brace"] = "remove";
197+
conf["nl_union_brace"] = "remove";
198+
conf["nl_if_brace"] = "remove";
199+
conf["nl_brace_else"] = "remove";
200+
conf["nl_elseif_brace"] = "remove";
201+
conf["nl_else_brace"] = "remove";
202+
conf["nl_else_if"] = "remove";
203+
conf["nl_brace_finally"] = "remove";
204+
conf["nl_finally_brace"] = "remove";
205+
conf["nl_try_brace"] = "remove";
206+
conf["nl_getset_brace"] = "remove";
207+
conf["nl_for_brace"] = "remove";
208+
conf["nl_catch_brace"] = "remove";
209+
conf["nl_brace_catch"] = "remove";
210+
conf["nl_brace_square"] = "remove";
211+
conf["nl_brace_fparen"] = "remove";
212+
conf["nl_while_brace"] = "remove";
213+
conf["nl_using_brace"] = "remove";
214+
conf["nl_do_brace"] = "remove";
215+
conf["nl_brace_while"] = "remove";
216+
conf["nl_switch_brace"] = "remove";
217+
conf["nl_before_throw"] = "remove";
218+
conf["nl_namespace_brace"] = "remove";
219+
conf["nl_class_brace"] = "remove";
220+
conf["nl_class_init_args"] = "remove";
221+
conf["nl_class_init_args"] = "remove";
222+
conf["nl_func_type_name"] = "remove";
223+
conf["nl_func_type_name_class"] = "remove";
224+
conf["nl_func_proto_type_name"] = "remove";
225+
conf["nl_func_paren"] = "remove";
226+
conf["nl_func_def_paren"] = "remove";
227+
conf["nl_func_decl_start"] = "remove";
228+
conf["nl_func_def_start"] = "remove";
229+
conf["nl_func_decl_end"] = "remove";
230+
conf["nl_func_def_end"] = "remove";
231+
conf["nl_func_decl_empty"] = "remove";
232+
conf["nl_func_def_empty"] = "remove";
233+
conf["nl_fdef_brace"] = "remove";
234+
conf["nl_return_expr"] = "remove";
235+
conf["nl_after_func_proto_group"] = "2";
236+
conf["nl_after_func_body"] = "2";
237+
conf["nl_after_func_body_class"] = "2";
238+
conf["eat_blanks_before_close_brace"] = "true";
239+
conf["pp_indent_count"] = "0";
240+
string[] args = { "uncrustify", "-c", "-", "--assume", source.filename, "-q" };
241+
foreach (var entry in conf.entries) {
242+
args += "--set";
243+
args += @"$(entry.key)=$(entry.value)";
244+
}
245+
return args;
246+
}
247+
}

src/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ vls_src = files([
88
'codehelp/completionengine.vala',
99
'codehelp/find_scope.vala',
1010
'codehelp/find_symbol.vala',
11+
'codehelp/formatter.vala',
1112
'codehelp/signaturehelpengine.vala',
1213
'codehelp/symbolextractor.vala',
1314
'codehelp/symbolreferences.vala',

src/protocol.vala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,4 +954,17 @@ namespace Lsp {
954954
*/
955955
public Command? command { get; set; }
956956
}
957+
958+
class DocumentFormattingParams : Object {
959+
public TextDocumentIdentifier textDocument { get; set; }
960+
public FormattingOptions options { get; set; }
961+
}
962+
963+
class FormattingOptions : Object {
964+
public uint tabSize { get; set; }
965+
public bool insertSpaces { get; set; }
966+
public bool trimTrailingWhitespace { get; set; }
967+
public bool insertFinalNewline { get; set; }
968+
public bool trimFinalNewlines { get; set; }
969+
}
957970
}

src/server.vala

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ class Vls.Server : Object {
173173
call_handlers["textDocument/completion"] = this.textDocumentCompletion;
174174
call_handlers["textDocument/signatureHelp"] = this.textDocumentSignatureHelp;
175175
call_handlers["textDocument/hover"] = this.textDocumentHover;
176+
call_handlers["textDocument/formatting"] = this.textDocumentFormatting;
176177
call_handlers["textDocument/references"] = this.textDocumentReferences;
177178
call_handlers["textDocument/documentHighlight"] = this.textDocumentReferences;
178179
call_handlers["textDocument/implementation"] = this.textDocumentImplementation;
@@ -246,6 +247,7 @@ class Vls.Server : Object {
246247
hoverProvider: new Variant.boolean (true),
247248
referencesProvider: new Variant.boolean (true),
248249
documentHighlightProvider: new Variant.boolean (true),
250+
documentFormattingProvider: new Variant.boolean (true),
249251
implementationProvider: new Variant.boolean (true),
250252
workspaceSymbolProvider: new Variant.boolean (true),
251253
renameProvider: buildDict (prepareProvider: new Variant.boolean (true)),
@@ -1581,6 +1583,54 @@ class Vls.Server : Object {
15811583
Vala.CodeContext.pop ();
15821584
});
15831585
}
1586+
1587+
void textDocumentFormatting (Jsonrpc.Server self, Jsonrpc.Client client, string method, Variant id, Variant @params) {
1588+
var p = Util.parse_variant<Lsp.DocumentFormattingParams>(@params);
1589+
var results = new ArrayList<Pair<Vala.SourceFile, Compilation>> ();
1590+
Project selected_project = null;
1591+
foreach (var project in projects.get_keys_as_array ()) {
1592+
results = project.lookup_compile_input_source_file (p.textDocument.uri);
1593+
if (!results.is_empty) {
1594+
selected_project = project;
1595+
break;
1596+
}
1597+
}
1598+
// fallback to default project
1599+
if (selected_project == null) {
1600+
results = default_project.lookup_compile_input_source_file (p.textDocument.uri);
1601+
selected_project = default_project;
1602+
}
1603+
if (results.is_empty) {
1604+
debug (@"file `$(Uri.unescape_string (p.textDocument.uri))' not found");
1605+
reply_null (id, client, method);
1606+
return;
1607+
}
1608+
var json_array = new Json.Array ();
1609+
foreach (var pair in results) {
1610+
TextEdit edited;
1611+
Vala.SourceFile source_file = pair.first;
1612+
Compilation compilation = pair.second;
1613+
var code_style = compilation.get_analysis_for_file<CodeStyleAnalyzer> (source_file) as CodeStyleAnalyzer;
1614+
try {
1615+
edited = Formatter.format (p.options, code_style, source_file, cancellable);
1616+
} catch (Error e) {
1617+
client.reply_error_async.begin (
1618+
id,
1619+
Jsonrpc.ClientError.INTERNAL_ERROR,
1620+
e.message,
1621+
cancellable);
1622+
warning ("Formatting failed: %s", e.message);
1623+
return;
1624+
}
1625+
json_array.add_element (Json.gobject_serialize (edited));
1626+
}
1627+
try {
1628+
Variant variant_array = Json.gvariant_deserialize (new Json.Node.alloc ().init_array (json_array), null);
1629+
client.reply (id, variant_array, cancellable);
1630+
} catch (Error e) {
1631+
debug (@"[$method] failed to reply to client: $(e.message)");
1632+
}
1633+
}
15841634

15851635
void handle_workspace_symbol_request (Jsonrpc.Server self, Jsonrpc.Client client, string method, Variant id, Variant @params) {
15861636
var query = (string) @params.lookup_value ("query", VariantType.STRING);

0 commit comments

Comments
 (0)