Skip to content

Commit 7e95b40

Browse files
Use fallback indenter for textDocument/onTypeFormatting
For eng/ide/ada_language_server#1529
1 parent b40306c commit 7e95b40

File tree

8 files changed

+207
-10
lines changed

8 files changed

+207
-10
lines changed

source/ada/lsp-ada_handlers-formatting.adb

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,6 @@ package body LSP.Ada_Handlers.Formatting is
3333
"GNATformat: Syntactically invalid code can't be formatted";
3434
-- Error message sent when trying to format invalid code.
3535

36-
function Reindent_Line
37-
(Filename : GNATCOLL.VFS.Virtual_File;
38-
Line : VSS.Strings.Virtual_String;
39-
Options : Gnatformat.Configuration.Format_Options_Type;
40-
Pos : LSP.Structures.Position;
41-
New_Indent : Natural) return LSP.Structures.TextEdit;
42-
-- Generate a textEdit to reindent the current line
43-
4436
-------------------
4537
-- Reindent_Line --
4638
-------------------

source/ada/lsp-ada_handlers-formatting.ads

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,18 @@ package LSP.Ada_Handlers.Formatting is
8888
-- Messages contains any informational or warning messages.
8989
-- Error is set if an error occurred.
9090

91+
function Reindent_Line
92+
(Filename : GNATCOLL.VFS.Virtual_File;
93+
Line : VSS.Strings.Virtual_String;
94+
Options : Gnatformat.Configuration.Format_Options_Type;
95+
Pos : LSP.Structures.Position;
96+
New_Indent : Natural) return LSP.Structures.TextEdit;
97+
-- Reindent a line to the given new indentation.
98+
-- Line contains the actual line in the document.
99+
-- Options are the formatting options to handle tabs/spaces.
100+
-- Pos is the LSP position of the line in the document.
101+
-- New_Indent is the new indentation level for the line.
102+
91103
function Handle_Tabs
92104
(Filename : GNATCOLL.VFS.Virtual_File;
93105
Options : Gnatformat.Configuration.Format_Options_Type;

source/gpr/lsp-gpr_handlers.adb

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ with LSP.Ada_Handlers.Formatting;
2828
with LSP.Constants;
2929
with LSP.Enumerations;
3030
with LSP.Errors;
31+
with LSP.Formatters.Fallback_Indenter;
3132
with LSP.Generic_Cancel_Check;
3233
with LSP.GPR_Completions;
3334
with LSP.GPR_Documentation;
@@ -39,6 +40,7 @@ with LSP.Text_Documents.Langkit_Documents;
3940
with LSP.Utils;
4041

4142
with Gpr_Parser.Common;
43+
with VSS.Characters.Latin;
4244
with VSS.String_Vectors;
4345
with VSS.Strings;
4446

@@ -409,7 +411,7 @@ package body LSP.GPR_Handlers is
409411
Response : LSP.Structures.InitializeResult;
410412
Capabilities : LSP.Structures.ServerCapabilities renames
411413
Response.capabilities;
412-
414+
use VSS.Strings;
413415
begin
414416
Self.File_Reader := LSP.GPR_File_Readers.Create (Self'Unchecked_Access);
415417

@@ -446,6 +448,11 @@ package body LSP.GPR_Handlers is
446448
(Is_Set => True, Value => (Is_Boolean => True, Boolean => True));
447449
Capabilities.documentFormattingProvider :=
448450
(Is_Set => True, Value => (Is_Boolean => True, Boolean => True));
451+
Capabilities.documentOnTypeFormattingProvider :=
452+
(Is_Set => True,
453+
Value =>
454+
(firstTriggerCharacter => 1 * VSS.Characters.Latin.Line_Feed,
455+
moreTriggerCharacter => <>));
449456

450457
Capabilities.textDocumentSync :=
451458
(Is_Set => True,
@@ -628,6 +635,76 @@ package body LSP.GPR_Handlers is
628635
end if;
629636
end On_RangeFormatting_Request;
630637

638+
---------------------------------
639+
-- On_OnTypeFormatting_Request --
640+
---------------------------------
641+
642+
overriding
643+
procedure On_OnTypeFormatting_Request
644+
(Self : in out Message_Handler;
645+
Id : LSP.Structures.Integer_Or_Virtual_String;
646+
Value : LSP.Structures.DocumentOnTypeFormattingParams)
647+
is
648+
use VSS.Strings;
649+
Response : LSP.Structures.TextEdit_Vector_Or_Null;
650+
Document : constant LSP.GPR_Documents.Document_Access :=
651+
Self.Get_Open_Document (Value.textDocument.uri);
652+
Options : constant Gnatformat.Configuration.Format_Options_Type :=
653+
Gnatformat.Configuration.Default_Format_Options;
654+
655+
Filename : constant GNATCOLL.VFS.Virtual_File :=
656+
Self.To_File (Value.textDocument.uri);
657+
658+
Indent_Array :
659+
constant LSP.Formatters.Fallback_Indenter.Indentation_Array :=
660+
LSP.Ada_Handlers.Formatting.Get_Indentation
661+
(Filename => Filename,
662+
Buffer =>
663+
Document.Slice (((0, 0), (Value.position.line + 1, 0))),
664+
Span =>
665+
((0, 0), (Value.position.line + 1, 0)),
666+
Options => Options);
667+
Indentation : constant VSS.Strings.Character_Count :=
668+
(if Indent_Array (Value.position.line + 1) = -1
669+
then 0
670+
else
671+
VSS.Strings.Character_Count
672+
(Indent_Array (Value.position.line + 1)));
673+
begin
674+
-- First set the indentation for the new line
675+
Response.Append
676+
(LSP.Structures.TextEdit'
677+
(a_range =>
678+
(start => (Value.position.line, 0), an_end => Value.position),
679+
newText =>
680+
LSP.Ada_Handlers.Formatting.Handle_Tabs
681+
(Filename => Filename,
682+
Options => Options,
683+
S => Indentation * ' ')));
684+
685+
-- If not in indent-only mode, re-indent the previous line too
686+
if not Self.Configuration.Indent_Only then
687+
declare
688+
Prev_Line : constant Natural :=
689+
Natural'Max (Value.position.line - 1, 0);
690+
Indentation : constant Natural :=
691+
(if Indent_Array (Prev_Line + 1) = -1
692+
then 0
693+
else Indent_Array (Prev_Line + 1));
694+
begin
695+
Response.Append
696+
(LSP.Ada_Handlers.Formatting.Reindent_Line
697+
(Filename => Filename,
698+
Line => Document.Get_Line (Prev_Line),
699+
Pos => (Prev_Line, 0),
700+
Options => Options,
701+
New_Indent => Indentation));
702+
end;
703+
end if;
704+
705+
Self.Sender.On_OnTypeFormatting_Response (Id, Response);
706+
end On_OnTypeFormatting_Request;
707+
631708
---------------------------
632709
-- On_Completion_Request --
633710
---------------------------

source/gpr/lsp-gpr_handlers.ads

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@ private
176176
Id : LSP.Structures.Integer_Or_Virtual_String;
177177
Value : LSP.Structures.DocumentRangeFormattingParams);
178178

179+
overriding
180+
procedure On_OnTypeFormatting_Request
181+
(Self : in out Message_Handler;
182+
Id : LSP.Structures.Integer_Or_Virtual_String;
183+
Value : LSP.Structures.DocumentOnTypeFormattingParams);
184+
179185
overriding procedure On_Completion_Request
180186
(Self : in out Message_Handler;
181187
Id : LSP.Structures.Integer_Or_Virtual_String;

testsuite/drivers/lsp_ada_requests.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,12 +247,16 @@ class IndentationTestCase:
247247
where the line break occurs.
248248
expected_indentation (str): The expected indentation string after the
249249
onTypeFormatting request is applied.
250+
additional_text_edits (List[TextEdit]): A list of additional TextEdit
251+
objects that represent other expected changes in the source code
252+
(e.g: a TextEdit on the previous line when indentOnly is False).
250253
"""
251254

252255
description: str
253256
source_filename: str
254257
line_break_position: Position
255258
expected_indentation: str
259+
additional_text_edits: List[TextEdit] = []
256260

257261
@property
258262
def change_range(self) -> Range:
@@ -288,7 +292,7 @@ def expected_text_edits(self) -> List[TextEdit]:
288292
),
289293
self.expected_indentation,
290294
)
291-
]
295+
] + self.additional_text_edits
292296

293297

294298
async def run_indentation_testcases(lsp, testcases, options):
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
project Test is
2+
3+
package Compiler is
4+
for Default_Switches ("ada") use ("-gnat2022", "-O0", "-g", "-fdiagnostics-color=never");
5+
end Compiler;
6+
7+
end Test;
8+
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""Test the textDocument/onTypeFormatting request on GPR files"""
2+
3+
import os
4+
from drivers.pylsp import (
5+
URI,
6+
ALSClientServerConfig,
7+
ALSLanguageClient,
8+
OnTypeFormattingSetting,
9+
test,
10+
)
11+
from drivers.lsp_ada_requests import (
12+
IndentationTestCase,
13+
run_indentation_testcases,
14+
)
15+
from lsprotocol.types import (
16+
ClientCapabilities,
17+
DocumentOnTypeFormattingOptions,
18+
FormattingOptions,
19+
InitializedParams,
20+
InitializeParams,
21+
Position,
22+
Range,
23+
TextEdit,
24+
)
25+
26+
27+
@test(
28+
config=ALSClientServerConfig(
29+
[
30+
os.environ.get("ALS", "ada_language_server"),
31+
"--language-gpr",
32+
]
33+
),
34+
)
35+
async def test_on_type_formatting_indentation(lsp: ALSLanguageClient) -> None:
36+
"""
37+
Test the onTypeFormatting feature on GPR files.
38+
39+
This test verifies that ALS correctly estimates the indentation that should
40+
be added after a line break.
41+
42+
Tests should be added to the indentation_tests list.
43+
"""
44+
45+
# Send the initialize request
46+
47+
response = await lsp.initialize_session(
48+
InitializeParams(
49+
capabilities=ClientCapabilities(),
50+
root_uri=URI(os.getcwd()),
51+
initialization_options={
52+
"ada": {"onTypeFormatting": OnTypeFormattingSetting(indentOnly=False)}
53+
},
54+
)
55+
)
56+
57+
# Verify that the right capability is advertised
58+
59+
lsp.assertEqual(
60+
response.capabilities.document_on_type_formatting_provider,
61+
DocumentOnTypeFormattingOptions(
62+
first_trigger_character="\n", more_trigger_character=None
63+
),
64+
)
65+
66+
# Send the initialized notification and the didChangeConfiguration
67+
# notification, configuring ALS to only indent when it receives an
68+
# onTypeFormattingRequest
69+
70+
lsp.initialized(InitializedParams())
71+
72+
# Test list
73+
indentation_tests = [
74+
IndentationTestCase(
75+
"Indentation after for inside package",
76+
"test.gpr",
77+
Position(3, 95),
78+
" ",
79+
[TextEdit(Range(Position(3, 0), Position(3, 6)), new_text=" ")],
80+
),
81+
IndentationTestCase(
82+
"Indentation after end of package",
83+
"test.gpr",
84+
Position(4, 18),
85+
" ",
86+
[TextEdit(Range(Position(4, 0), Position(4, 6)), new_text=" ")],
87+
),
88+
]
89+
90+
failed_tests = await run_indentation_testcases(
91+
lsp, indentation_tests, FormattingOptions(tab_size=3, insert_spaces=False)
92+
)
93+
94+
if len(failed_tests) > 0:
95+
fail_messages = "\n\n".join(failed_tests)
96+
message = f"Indentation tests failed\n\n{fail_messages}"
97+
raise Exception(message) # pylint: disable=broad-exception-raised
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
driver: pylsp

0 commit comments

Comments
 (0)