@@ -3,7 +3,6 @@ use ruff_db::parsed::parsed_module;
33use ruff_db:: source:: source_text;
44use ruff_python_ast:: visitor:: source_order:: { SourceOrderVisitor , TraversalSignal , walk_body} ;
55use ruff_python_ast:: { AnyNodeRef , Decorator , Stmt , StmtClassDef , StmtFunctionDef } ;
6- use ruff_python_trivia:: { SimpleTokenKind , SimpleTokenizer } ;
76use ruff_source_file:: { Line , UniversalNewlines } ;
87use ruff_text_size:: { Ranged , TextLen , TextRange , TextSize } ;
98
@@ -49,9 +48,12 @@ pub fn folding_ranges(db: &dyn Db, file: File) -> Vec<FoldingRange> {
4948 let parsed = parsed_module ( db, file) . load ( db) ;
5049 let source = source_text ( db, file) ;
5150
51+ let line_index = ruff_db:: source:: line_index ( db, file) ;
52+
5253 let mut visitor = FoldingRangeVisitor {
5354 source : source. as_str ( ) ,
5455 ranges : vec ! [ ] ,
56+ line_index : & line_index,
5557 } ;
5658 visitor. visit_body ( parsed. suite ( ) ) ;
5759
@@ -68,6 +70,7 @@ pub fn folding_ranges(db: &dyn Db, file: File) -> Vec<FoldingRange> {
6870struct FoldingRangeVisitor < ' a > {
6971 source : & ' a str ,
7072 ranges : Vec < FoldingRange > ,
73+ line_index : & ' a ruff_source_file:: LineIndex ,
7174}
7275
7376impl < ' a > FoldingRangeVisitor < ' a > {
@@ -233,38 +236,33 @@ impl<'a> FoldingRangeVisitor<'a> {
233236 }
234237
235238 /// Add a folding range for the function or class definition.
236- ///
237- /// - target is `async` or `def` for functions, and `class` for classes
238239 fn add_def_range (
239240 & mut self ,
240241 range : TextRange ,
241242 decorator_list : & [ Decorator ] ,
242- target : SimpleTokenKind ,
243+ name_start : TextSize ,
243244 ) {
244- if let Some ( last ) = decorator_list. last ( ) {
245- let tokenizer = SimpleTokenizer :: starts_at ( last . end ( ) , self . source ) ;
246- if let Some ( token ) = tokenizer . skip_trivia ( ) . find ( |token| token . kind == target ) {
247- let range = TextRange :: new ( token . start ( ) , range . end ( ) ) ;
248- self . add_range ( range ) ;
249- }
245+ if decorator_list. last ( ) . is_some ( ) {
246+ // get the the beginning of the line containing the function or class name
247+ let line = self . line_index . line_index ( name_start ) ;
248+ let start = self . line_index . line_start ( line , self . source ) ;
249+ let range = TextRange :: new ( start , range . end ( ) ) ;
250+ self . add_range ( range ) ;
250251 } else {
251252 self . add_range ( range) ;
252253 }
253254 }
254255
255256 /// Add a folding range for function definitions, excluding decorators.
256257 fn add_function_def_range ( & mut self , func : & StmtFunctionDef ) {
257- let target = if func. is_async {
258- SimpleTokenKind :: Async
259- } else {
260- SimpleTokenKind :: Def
261- } ;
262- self . add_def_range ( func. range ( ) , & func. decorator_list , target) ;
258+ self . add_decorator_range ( & func. decorator_list ) ;
259+ self . add_def_range ( func. range ( ) , & func. decorator_list , func. name . start ( ) ) ;
263260 }
264261
265262 /// Add a folding range for class definitions, excluding decorators.
266263 fn add_class_def_range ( & mut self , class : & StmtClassDef ) {
267- self . add_def_range ( class. range ( ) , & class. decorator_list , SimpleTokenKind :: Class ) ;
264+ self . add_decorator_range ( & class. decorator_list ) ;
265+ self . add_def_range ( class. range ( ) , & class. decorator_list , class. name . start ( ) ) ;
268266 }
269267}
270268
@@ -273,7 +271,6 @@ impl SourceOrderVisitor<'_> for FoldingRangeVisitor<'_> {
273271 match node {
274272 // Compound statements that create folding regions
275273 AnyNodeRef :: StmtFunctionDef ( func) => {
276- self . add_decorator_range ( & func. decorator_list ) ;
277274 self . add_function_def_range ( func) ;
278275 // Note that this may be duplicative with folding
279276 // ranges added for string literals. But I don't think
@@ -285,7 +282,6 @@ impl SourceOrderVisitor<'_> for FoldingRangeVisitor<'_> {
285282 self . add_docstring_range ( & func. body ) ;
286283 }
287284 AnyNodeRef :: StmtClassDef ( class) => {
288- self . add_decorator_range ( & class. decorator_list ) ;
289285 self . add_class_def_range ( class) ;
290286 // See comment above for class docstrings about this
291287 // being duplicative with adding folding ranges for
@@ -1729,7 +1725,7 @@ with open("file.txt") as f:
17291725 }
17301726
17311727 #[ test]
1732- fn test_folding_range_decorated_function1 ( ) {
1728+ fn test_folding_range_decorated_function_single ( ) {
17331729 let test = CursorTest :: builder ( )
17341730 . source (
17351731 "main.py" ,
@@ -1755,7 +1751,7 @@ def my_function():
17551751 }
17561752
17571753 #[ test]
1758- fn test_folding_range_decorated_function2 ( ) {
1754+ fn test_folding_range_decorated_function_multiple ( ) {
17591755 let test = CursorTest :: builder ( )
17601756 . source (
17611757 "main.py" ,
@@ -1795,7 +1791,7 @@ def my_function():
17951791 }
17961792
17971793 #[ test]
1798- fn test_folding_range_decorated_class1 ( ) {
1794+ fn test_folding_range_decorated_class_single ( ) {
17991795 let test = CursorTest :: builder ( )
18001796 . source (
18011797 "main.py" ,
@@ -1824,7 +1820,7 @@ class MyClass:
18241820 }
18251821
18261822 #[ test]
1827- fn test_folding_range_decorated_class2 ( ) {
1823+ fn test_folding_range_decorated_class_multiple ( ) {
18281824 let test = CursorTest :: builder ( )
18291825 . source (
18301826 "main.py" ,
@@ -1861,6 +1857,108 @@ class MyClass:
18611857 " ) ;
18621858 }
18631859
1860+ #[ test]
1861+ fn test_folding_range_decorated_async_function ( ) {
1862+ let test = CursorTest :: builder ( )
1863+ . source (
1864+ "main.py" ,
1865+ r#"
1866+ @decorator
1867+ async def my_async_function():
1868+ pass
1869+ <CURSOR>
1870+ "# ,
1871+ )
1872+ . build ( ) ;
1873+
1874+ assert_snapshot ! ( test. folding_ranges( ) , @r"
1875+ info[folding-range]: Folding Range
1876+ --> main.py:3:1
1877+ |
1878+ 2 | @decorator
1879+ 3 | / async def my_async_function():
1880+ 4 | | pass
1881+ | |________^
1882+ |
1883+ " ) ;
1884+ }
1885+
1886+ #[ test]
1887+ fn test_folding_range_decorated_nested_function ( ) {
1888+ let test = CursorTest :: builder ( )
1889+ . source (
1890+ "main.py" ,
1891+ r#"
1892+ def outer_function():
1893+ @decorator
1894+ def inner_function():
1895+ pass
1896+ <CURSOR>
1897+ "# ,
1898+ )
1899+ . build ( ) ;
1900+
1901+ assert_snapshot ! ( test. folding_ranges( ) , @"
1902+ info[folding-range]: Folding Range
1903+ --> main.py:2:1
1904+ |
1905+ 2 | / def outer_function():
1906+ 3 | | @decorator
1907+ 4 | | def inner_function():
1908+ 5 | | pass
1909+ | |____________^
1910+ |
1911+
1912+ info[folding-range]: Folding Range
1913+ --> main.py:4:1
1914+ |
1915+ 2 | def outer_function():
1916+ 3 | @decorator
1917+ 4 | / def inner_function():
1918+ 5 | | pass
1919+ | |____________^
1920+ |
1921+ " ) ;
1922+ }
1923+
1924+ #[ test]
1925+ fn test_folding_range_decorated_async_method ( ) {
1926+ let test = CursorTest :: builder ( )
1927+ . source (
1928+ "main.py" ,
1929+ r#"
1930+ class MyClass:
1931+ @decorator
1932+ async def my_async_method(self):
1933+ pass
1934+ <CURSOR>
1935+ "# ,
1936+ )
1937+ . build ( ) ;
1938+
1939+ assert_snapshot ! ( test. folding_ranges( ) , @"
1940+ info[folding-range]: Folding Range
1941+ --> main.py:2:1
1942+ |
1943+ 2 | / class MyClass:
1944+ 3 | | @decorator
1945+ 4 | | async def my_async_method(self):
1946+ 5 | | pass
1947+ | |____________^
1948+ |
1949+
1950+ info[folding-range]: Folding Range
1951+ --> main.py:4:1
1952+ |
1953+ 2 | class MyClass:
1954+ 3 | @decorator
1955+ 4 | / async def my_async_method(self):
1956+ 5 | | pass
1957+ | |____________^
1958+ |
1959+ " ) ;
1960+ }
1961+
18641962 struct FoldingRangeDiagnostic {
18651963 file : File ,
18661964 folding_range : FoldingRange ,
0 commit comments