@@ -40,81 +40,106 @@ public PsesDocumentSymbolHandler(ILoggerFactory factory, WorkspaceService worksp
4040 DocumentSelector  =  LspUtils . PowerShellDocumentSelector 
4141 } ; 
4242
43-  // This turns a flat list of symbols into a hierarchical list. It's ugly because we're 
44-  // dealing with records and so sadly must slowly copy and replace things whenever need to do 
45-  // a modification, but it seems to work. 
46-  private  static async  Task < List < DocumentSymbol > >  SortDocumentSymbols ( List < DocumentSymbol >  symbols ,  CancellationToken  cancellationToken ) 
43+  // This turns a flat list of symbols into a hierarchical list. 
44+  private  static async  Task < List < HierarchicalSymbol > >  SortHierarchicalSymbols ( List < HierarchicalSymbol >  symbols ,  CancellationToken  cancellationToken ) 
4745 { 
48-  // Sort by the start of the symbol definition. 
46+  // Sort by the start of the symbol definition (they're probably sorted but we need to be 
47+  // certain otherwise this algorithm won't work). 
4948 symbols . Sort ( ( x1 ,  x2 )  =>  x1 . Range . Start . CompareTo ( x2 . Range . Start ) ) ; 
5049
51-  List < DocumentSymbol >  parents  =  new ( ) ; 
50+  List < HierarchicalSymbol >  parents  =  new ( ) ; 
5251
53-  foreach  ( DocumentSymbol  symbol  in  symbols ) 
52+  foreach  ( HierarchicalSymbol  symbol  in  symbols ) 
5453 { 
5554 // This async method is pretty dense with synchronous code 
5655 // so it's helpful to add some yields. 
5756 await  Task . Yield ( ) ; 
5857 if  ( cancellationToken . IsCancellationRequested ) 
5958 { 
60-  return  symbols ; 
59+  return  parents ; 
6160 } 
62- 63-  // Base case. 
61+  // Base case where we haven't found any parents yet. 
6462 if  ( parents . Count  ==  0 ) 
6563 { 
6664 parents . Add ( symbol ) ; 
6765 } 
68-  // Symbol  starts after end of last symbol parsed. 
66+  // If the symbol  starts after end of last symbol parsed then it's a new parent . 
6967 else  if  ( symbol . Range . Start  >  parents [ parents . Count  -  1 ] . Range . End ) 
7068 { 
7169 parents . Add ( symbol ) ; 
7270 } 
73-  // Find where  it fits . 
71+  // Otherwise it's a child, we just need to figure out whose child  it is . 
7472 else 
7573 { 
76-  for ( int i = 0 ; i < parents . Count ; i ++ ) 
74+  foreach ( HierarchicalSymbol parent in parents ) 
7775 { 
78-  DocumentSymbol  parent  =  parents [ i ] ; 
79-  if  ( parent . Range . Start  <=  symbol . Range . Start  &&  symbol . Range . End  <=  parent . Range . End ) 
76+  // If the symbol starts after the parent starts and ends before the parent 
77+  // ends then its a child. 
78+  if  ( symbol . Range . Start  >  parent . Range . Start  &&  symbol . Range . End  <  parent . Range . End ) 
8079 { 
81-  List < DocumentSymbol >  children  =  new ( ) ; 
82-  if  ( parent . Children  is  not null ) 
83-  { 
84-  children . AddRange ( parent . Children ) ; 
85-  } 
86-  children . Add ( symbol ) ; 
87-  parents [ i ]  =  parent  with  {  Children  =  children  } ; 
80+  parent . Children . Add ( symbol ) ; 
8881 break ; 
8982 } 
9083 } 
84+  // TODO: If we somehow exist the list of parents and didn't find a place for the 
85+  // child...we have a problem. 
9186 } 
9287 } 
9388
94-  // Recursively  sort the children. 
95-  for ( int i = 0 ; i < parents . Count ; i ++ ) 
89+  // Now recursively  sort the children into nested buckets of children too . 
90+  foreach ( HierarchicalSymbol parent in parents ) 
9691 { 
97-  DocumentSymbol  parent  =  parents [ i ] ; 
98-  if  ( parent . Children  is  not null ) 
99-  { 
100-  List < DocumentSymbol >  children  =  new ( parent . Children ) ; 
101-  children  =  await  SortDocumentSymbols ( children ,  cancellationToken ) . ConfigureAwait ( false ) ; 
102-  parents [ i ]  =  parent  with  {  Children  =  children  } ; 
103-  } 
92+  List < HierarchicalSymbol >  sortedChildren  =  await  SortHierarchicalSymbols ( parent . Children ,  cancellationToken ) . ConfigureAwait ( false ) ; 
93+  // Since this is a foreach we can't just assign to parent.Children and have to do 
94+  // this instead. 
95+  parent . Children . Clear ( ) ; 
96+  parent . Children . AddRange ( sortedChildren ) ; 
10497 } 
10598
10699 return  parents ; 
107100 } 
108101
109-  // AKA the outline feature 
102+  // This struct and the mapping function below exist to allow us to skip a *bunch* of 
103+  // unnecessary allocations when sorting the symbols since DocumentSymbol (which this is 
104+  // pretty much a mirror of) is an immutable record...but we need to constantly modify the 
105+  // list of children when sorting. 
106+  private  struct  HierarchicalSymbol 
107+  { 
108+  public  SymbolKind  Kind ; 
109+  public  Range  Range ; 
110+  public  Range  SelectionRange ; 
111+  public  string  Name ; 
112+  public  List < HierarchicalSymbol >  Children ; 
113+  } 
114+ 115+  // Recursively turn our HierarchicalSymbol struct into OmniSharp's DocumentSymbol record. 
116+  private  static List < DocumentSymbol >  GetDocumentSymbolsFromHierarchicalSymbols ( IEnumerable < HierarchicalSymbol >  hierarchicalSymbols ) 
117+  { 
118+  List < DocumentSymbol >  documentSymbols  =  new ( ) ; 
119+  foreach  ( HierarchicalSymbol  symbol  in  hierarchicalSymbols ) 
120+  { 
121+  documentSymbols . Add ( new  DocumentSymbol 
122+  { 
123+  Kind  =  symbol . Kind , 
124+  Range  =  symbol . Range , 
125+  SelectionRange  =  symbol . SelectionRange , 
126+  Name  =  symbol . Name , 
127+  Children  =  GetDocumentSymbolsFromHierarchicalSymbols ( symbol . Children ) 
128+  } ) ; 
129+  } 
130+  return  documentSymbols ; 
131+  } 
132+ 133+  // AKA the outline feature! 
110134 public  override  async  Task < SymbolInformationOrDocumentSymbolContainer >  Handle ( DocumentSymbolParams  request ,  CancellationToken  cancellationToken ) 
111135 { 
112136 _logger . LogDebug ( $ "Handling document symbols for { request . TextDocument . Uri } ") ; 
113137
114138 ScriptFile  scriptFile  =  _workspaceService . GetFile ( request . TextDocument . Uri ) ; 
115-  List < DocumentSymbol >  symbols  =  new ( ) ; 
116139
117-  foreach  ( SymbolReference  r  in  ProvideDocumentSymbols ( scriptFile ) ) 
140+  List < HierarchicalSymbol >  hierarchicalSymbols  =  new ( ) ; 
141+ 142+  foreach  ( SymbolReference  symbolReference  in  ProvideDocumentSymbols ( scriptFile ) ) 
118143 { 
119144 // This async method is pretty dense with synchronous code 
120145 // so it's helpful to add some yields. 
@@ -128,36 +153,33 @@ public override async Task<SymbolInformationOrDocumentSymbolContainer> Handle(Do
128153 // 
129154 // TODO: We should also include function invocations that are part of DSLs (like 
130155 // Invoke-Build etc.). 
131-  if  ( ! r . IsDeclaration  ||  r . Type  is  SymbolType . Parameter ) 
156+  if  ( ! symbolReference . IsDeclaration  ||  symbolReference . Type  is  SymbolType . Parameter ) 
132157 { 
133158 continue ; 
134159 } 
135160
136-  // TODO: This now needs the Children property filled out to support hierarchical 
137-  // symbols, and we don't have the information nor algorithm to do that currently. 
138-  // OmniSharp was previously doing this for us based on the range, perhaps we can 
139-  // find that logic and reuse it. 
140-  symbols . Add ( new  DocumentSymbol 
161+  hierarchicalSymbols . Add ( new  HierarchicalSymbol 
141162 { 
142-  Kind  =  SymbolTypeUtils . GetSymbolKind ( r . Type ) , 
143-  Range  =  r . ScriptRegion . ToRange ( ) , 
144-  SelectionRange  =  r . NameRegion . ToRange ( ) , 
145-  Name  =  r . Name 
163+  Kind  =  SymbolTypeUtils . GetSymbolKind ( symbolReference . Type ) , 
164+  Range  =  symbolReference . ScriptRegion . ToRange ( ) , 
165+  SelectionRange  =  symbolReference . NameRegion . ToRange ( ) , 
166+  Name  =  symbolReference . Name , 
167+  Children  =  new  List < HierarchicalSymbol > ( ) 
146168 } ) ; 
147169 } 
148170
149171 // Short-circuit if we have no symbols. 
150-  if  ( symbols . Count  ==  0 ) 
172+  if  ( hierarchicalSymbols . Count  ==  0 ) 
151173 { 
152174 return  s_emptySymbolInformationOrDocumentSymbolContainer ; 
153175 } 
154176
155177 // Otherwise slowly sort them into a hierarchy. 
156-  symbols  =  await  SortDocumentSymbols ( symbols ,  cancellationToken ) . ConfigureAwait ( false ) ; 
178+  hierarchicalSymbols  =  await  SortHierarchicalSymbols ( hierarchicalSymbols ,  cancellationToken ) . ConfigureAwait ( false ) ; 
157179
158180 // And finally convert them to the silly SymbolInformationOrDocumentSymbol wrapper. 
159181 List < SymbolInformationOrDocumentSymbol >  container  =  new ( ) ; 
160-  foreach  ( DocumentSymbol  symbol  in  symbols ) 
182+  foreach  ( DocumentSymbol  symbol  in  GetDocumentSymbolsFromHierarchicalSymbols ( hierarchicalSymbols ) ) 
161183 { 
162184 container . Add ( new  SymbolInformationOrDocumentSymbol ( symbol ) ) ; 
163185 } 
0 commit comments