Skip to content

Commit 4371c55

Browse files
committed
Add DocumentSymbol flattening and refactor symbol request handling
Add a new static conversion method `SymbolInformation::From()` that recursively flattens hierarchical `DocumentSymbol` objects into a flat list of `SymbolInformation` entries, annotating each symbol with its parent container name. This enables unified handling of both flat and hierarchical symbol responses from LSP servers. Refactor DocumentSymbolsRequest response handling to support both response types: extract symbol array parsing into dedicated helper methods `FromDocumentSymbolsArray()` and `FromSymbolInformationArray()`, then use the new conversion method to normalize DocumentSymbol responses into a common SymbolInformation format for downstream processing. Apply minor code style improvements throughout the codebase: normalize spacing around conditionals and improve brace formatting for consistency. * LSP/basic_types.h: Add SymbolInformation::From() declaration * LSP/basic_types.cpp: Implement recursive DocumentSymbol-to-SymbolInformation conversion * LSP/DocumentSymbolsRequest.hpp: Add helper method declarations * LSP/DocumentSymbolsRequest.cpp: Refactor response handling with new helpers Generated by CodeLite Signed-off-by: Eran Ifrah <eran@codelite.org>
1 parent aa6df4b commit 4371c55

File tree

4 files changed

+133
-58
lines changed

4 files changed

+133
-58
lines changed

CodeLite/LSP/DocumentSymbolsRequest.cpp

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ void LSP::DocumentSymbolsRequest::OnResponse(const LSP::ResponseMessage& const_r
4141
LSP::ResponseMessage& response = const_cast<LSP::ResponseMessage&>(const_response);
4242
auto json = response.take();
4343
if (!json->toElement().hasNamedObject("result")) {
44-
LSP_WARNING() << "LSP::DocumentSymbolsRequest::OnResponse(): invalid 'result' object";
44+
LSP_WARNING() << "LSP::DocumentSymbolsRequest::OnResponse(): missing 'result' object";
4545
return;
4646
}
4747

@@ -54,46 +54,50 @@ void LSP::DocumentSymbolsRequest::OnResponse(const LSP::ResponseMessage& const_r
5454
int size = result.arraySize();
5555
wxString filename = m_params->As<DocumentSymbolParams>()->GetTextDocument().GetPath();
5656
auto context = m_context;
57+
std::vector<LSP::SymbolInformation> symbols;
5758
if (result[0].hasNamedObject("location")) {
58-
auto result = json->toElement().namedObject("result");
59-
std::vector<LSP::SymbolInformation> symbols;
60-
symbols.reserve(size);
61-
for (int i = 0; i < size; ++i) {
62-
SymbolInformation si;
63-
si.FromJSON(result[i]);
64-
symbols.push_back(si);
59+
symbols = FromSymbolInformationArray(std::move(result));
60+
} else {
61+
auto document_symbol_array = FromDocumentSymbolsArray(std::move(result));
62+
// For now (until we support tree view in the outline view, convert the
63+
// DocumentSymbol array -> SymbolInformation array
64+
for (const auto& document_symbol : document_symbol_array) {
65+
auto symbol_information_array = SymbolInformation::From(document_symbol);
66+
symbols.insert(symbols.end(), symbol_information_array.begin(), symbol_information_array.end());
6567
}
68+
}
6669

67-
// sort the items by line position
68-
std::sort(symbols.begin(),
69-
symbols.end(),
70-
[=](const LSP::SymbolInformation& a, const LSP::SymbolInformation& b) -> int {
71-
return a.GetLocation().GetRange().GetStart().GetLine() <
72-
b.GetLocation().GetRange().GetStart().GetLine();
73-
});
70+
if (symbols.empty()) {
71+
return;
72+
}
7473

75-
LOG_IF_TRACE { LSP_TRACE() << symbols << endl; }
74+
// sort the items by line position
75+
std::sort(
76+
symbols.begin(), symbols.end(), [=](const LSP::SymbolInformation& a, const LSP::SymbolInformation& b) -> int {
77+
return a.GetLocation().GetRange().GetStart().GetLine() < b.GetLocation().GetRange().GetStart().GetLine();
78+
});
7679

77-
// fire event per context
78-
if (context & CONTEXT_SEMANTIC_HIGHLIGHT) {
79-
QueueEvent(owner, symbols, filename, wxEVT_LSP_DOCUMENT_SYMBOLS_FOR_HIGHLIGHT);
80-
}
80+
LOG_IF_TRACE { LSP_TRACE() << symbols << endl; }
8181

82-
bool outline_event_fired_for_event_notifier{false};
83-
if (context & CONTEXT_OUTLINE_VIEW) {
84-
QueueEvent(owner, symbols, filename, wxEVT_LSP_DOCUMENT_SYMBOLS_OUTLINE_VIEW);
85-
// if "owner" is "EventNotifier::Get()" do not send the same event twice.
86-
outline_event_fired_for_event_notifier = (owner == EventNotifier::Get());
87-
}
82+
// fire event per context
83+
if (context & CONTEXT_SEMANTIC_HIGHLIGHT) {
84+
QueueEvent(owner, symbols, filename, wxEVT_LSP_DOCUMENT_SYMBOLS_FOR_HIGHLIGHT);
85+
}
8886

89-
if (!outline_event_fired_for_event_notifier) {
90-
// always fire the wxEVT_LSP_DOCUMENT_SYMBOLS_OUTLINE_VIEW for the EventNotifier
91-
// so it might be used by other plugins as well, e.g. "Outline"
92-
QueueEvent(EventNotifier::Get(), symbols, filename, wxEVT_LSP_DOCUMENT_SYMBOLS_OUTLINE_VIEW);
93-
}
87+
bool outline_event_fired_for_event_notifier{false};
88+
if (context & CONTEXT_OUTLINE_VIEW) {
89+
QueueEvent(owner, symbols, filename, wxEVT_LSP_DOCUMENT_SYMBOLS_OUTLINE_VIEW);
90+
// if "owner" is "EventNotifier::Get()" do not send the same event twice.
91+
outline_event_fired_for_event_notifier = (owner == EventNotifier::Get());
92+
}
9493

95-
InvokeResponseCallback(CreateLSPEvent(symbols, filename, wxEVT_LSP_DOCUMENT_SYMBOLS_QUICK_OUTLINE));
94+
if (!outline_event_fired_for_event_notifier) {
95+
// always fire the wxEVT_LSP_DOCUMENT_SYMBOLS_OUTLINE_VIEW for the EventNotifier
96+
// so it might be used by other plugins as well, e.g. "Outline"
97+
QueueEvent(EventNotifier::Get(), symbols, filename, wxEVT_LSP_DOCUMENT_SYMBOLS_OUTLINE_VIEW);
9698
}
99+
100+
InvokeResponseCallback(CreateLSPEvent(symbols, filename, wxEVT_LSP_DOCUMENT_SYMBOLS_QUICK_OUTLINE));
97101
}
98102

99103
LSPEvent LSP::DocumentSymbolsRequest::CreateLSPEvent(const std::vector<LSP::SymbolInformation>& symbols,
@@ -115,3 +119,39 @@ void LSP::DocumentSymbolsRequest::QueueEvent(wxEvtHandler* owner,
115119
LSPEvent event = CreateLSPEvent(symbols, filename, event_type);
116120
owner->QueueEvent(event.Clone());
117121
}
122+
123+
std::vector<LSP::DocumentSymbol> LSP::DocumentSymbolsRequest::FromDocumentSymbolsArray(JSONItem&& result)
124+
{
125+
if (!result.isArray()) {
126+
LSP_WARNING() << "FromDocumentSymbolsArray: result is not an array" << endl;
127+
return {};
128+
}
129+
130+
int size = result.arraySize();
131+
std::vector<LSP::DocumentSymbol> symbols;
132+
symbols.reserve(size);
133+
for (int i = 0; i < size; ++i) {
134+
DocumentSymbol ds;
135+
ds.FromJSON(result[i]);
136+
symbols.push_back(std::move(ds));
137+
}
138+
return symbols;
139+
}
140+
141+
std::vector<LSP::SymbolInformation> LSP::DocumentSymbolsRequest::FromSymbolInformationArray(JSONItem&& result)
142+
{
143+
if (!result.isArray()) {
144+
LSP_WARNING() << "FromSymbolInformationArray: result is not an array" << endl;
145+
return {};
146+
}
147+
148+
int size = result.arraySize();
149+
std::vector<LSP::SymbolInformation> symbols;
150+
symbols.reserve(size);
151+
for (int i = 0; i < size; ++i) {
152+
SymbolInformation si;
153+
si.FromJSON(result[i]);
154+
symbols.push_back(std::move(si));
155+
}
156+
return symbols;
157+
}

CodeLite/LSP/DocumentSymbolsRequest.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class WXDLLIMPEXP_CL DocumentSymbolsRequest : public LSP::Request
3030
LSPEvent CreateLSPEvent(const std::vector<LSP::SymbolInformation>& symbols,
3131
const wxString& filename,
3232
const wxEventType& event_type);
33+
std::vector<LSP::DocumentSymbol> FromDocumentSymbolsArray(JSONItem&& result);
34+
std::vector<LSP::SymbolInformation> FromSymbolInformationArray(JSONItem&& result);
3335
};
3436
} // namespace LSP
3537
#endif // DOCUMDENET_SYMBOLS_REQUEST_HPP

CodeLite/LSP/basic_types.cpp

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,13 @@ INITIALISE_MODULE_LOG(LSP_LOG_HANDLER, "LSP", "lsp.log");
1111
namespace LSP
1212
{
1313

14-
clModuleLogger& GetLogHandle()
15-
{
16-
return LSP_LOG_HANDLER();
17-
}
14+
clModuleLogger& GetLogHandle() { return LSP_LOG_HANDLER(); }
1815

1916
wxString FileNameToURI(const wxString& filename)
2017
{
2118
wxString uri;
2219
#ifdef __WXMSW__
23-
if(filename.StartsWith("/")) {
20+
if (filename.StartsWith("/")) {
2421
// linux format
2522
uri << "file://" << filename;
2623
} else {
@@ -109,15 +106,15 @@ JSONItem TextDocumentItem::ToJSON(const wxString& name) const
109106
void TextDocumentContentChangeEvent::FromJSON(const JSONItem& json)
110107
{
111108
m_text = json.namedObject("text").toString();
112-
if(json.hasNamedObject("range")) {
109+
if (json.hasNamedObject("range")) {
113110
m_range.FromJSON(json["range"]);
114111
}
115112
}
116113

117114
JSONItem TextDocumentContentChangeEvent::ToJSON(const wxString& name) const
118115
{
119116
JSONItem json = JSONItem::createObject(name);
120-
if(m_range.IsOk()) {
117+
if (m_range.IsOk()) {
121118
json.addProperty("range", m_range.ToJSON("range"));
122119
}
123120
json.addProperty("text", m_text);
@@ -189,12 +186,12 @@ void SignatureInformation::FromJSON(const JSONItem& json)
189186
m_label = json.namedObject("label").toString();
190187
m_documentation = json.namedObject("documentation").toString();
191188
m_parameters.clear();
192-
if(json.hasNamedObject("parameters")) {
189+
if (json.hasNamedObject("parameters")) {
193190
JSONItem parameters = json.namedObject("parameters");
194191
const int size = parameters.arraySize();
195-
if(size > 0) {
192+
if (size > 0) {
196193
m_parameters.reserve(size);
197-
for(int i = 0; i < size; ++i) {
194+
for (int i = 0; i < size; ++i) {
198195
ParameterInformation p;
199196
p.FromJSON(parameters.arrayItem(i));
200197
m_parameters.push_back(p);
@@ -208,7 +205,7 @@ JSONItem SignatureInformation::ToJSON(const wxString& name) const
208205
JSONItem json = JSONItem::createObject(name);
209206
json.addProperty("label", m_label);
210207
json.addProperty("documentation", m_documentation);
211-
if(!m_parameters.empty()) {
208+
if (!m_parameters.empty()) {
212209
JSONItem params = JSONItem::createArray();
213210
for (const auto& paramInfo : m_parameters) {
214211
params.append(paramInfo.ToJSON(""));
@@ -224,7 +221,7 @@ void SignatureHelp::FromJSON(const JSONItem& json)
224221
m_signatures.clear();
225222
JSONItem signatures = json.namedObject("signatures");
226223
const int count = signatures.arraySize();
227-
for(int i = 0; i < count; ++i) {
224+
for (int i = 0; i < count; ++i) {
228225
SignatureInformation si;
229226
si.FromJSON(signatures.arrayItem(i));
230227
m_signatures.push_back(si);
@@ -238,7 +235,7 @@ JSONItem SignatureHelp::ToJSON(const wxString& name) const
238235
{
239236
JSONItem json = JSONItem::createObject(name);
240237
JSONItem signatures = JSONItem::createArray();
241-
for(const SignatureInformation& si : m_signatures) {
238+
for (const SignatureInformation& si : m_signatures) {
242239
signatures.arrayAppend(si.ToJSON(""));
243240
}
244241
json.addProperty("signatures", signatures);
@@ -297,7 +294,7 @@ JSONItem Diagnostic::ToJSON(const wxString& name) const
297294
TextDocumentContentChangeEvent& TextDocumentContentChangeEvent::SetText(const wxString& text)
298295
{
299296
this->m_text.clear();
300-
if(!text.empty()) {
297+
if (!text.empty()) {
301298
this->m_text.reserve(text.length() + 1); // for the null
302299
this->m_text.append(text);
303300
}
@@ -319,7 +316,7 @@ void DocumentSymbol::FromJSON(const JSONItem& json)
319316
int size = jsonChildren.arraySize();
320317
children.clear();
321318
children.reserve(size);
322-
for(int i = 0; i < size; ++i) {
319+
for (int i = 0; i < size; ++i) {
323320
auto child = jsonChildren[i];
324321
DocumentSymbol ds;
325322
ds.FromJSON(child);
@@ -344,9 +341,9 @@ void SymbolInformation::FromJSON(const JSONItem& json)
344341
location.FromJSON(json["location"]);
345342

346343
// manipulate the data: if no container exists, extract it from the name
347-
if(containerName.empty() && !name.empty()) {
344+
if (containerName.empty() && !name.empty()) {
348345
int where = name.rfind("::");
349-
if(where == wxNOT_FOUND) {
346+
if (where == wxNOT_FOUND) {
350347
return;
351348
}
352349

@@ -366,6 +363,29 @@ JSONItem SymbolInformation::ToJSON(const wxString& name) const
366363
return json;
367364
}
368365

366+
std::vector<SymbolInformation> SymbolInformation::From(const DocumentSymbol& document_symbol,
367+
const wxString& container_name)
368+
{
369+
std::vector<SymbolInformation> result;
370+
SymbolInformation si;
371+
si.name = document_symbol.GetName();
372+
si.kind = document_symbol.GetKind();
373+
si.containerName = container_name;
374+
375+
Range range =
376+
document_symbol.GetSelectionRange().IsOk() ? document_symbol.GetSelectionRange() : document_symbol.GetRange();
377+
si.location.SetRange(range);
378+
result.push_back(si);
379+
if (!document_symbol.GetChildren().empty()) {
380+
wxString scope_name = si.name;
381+
for (const DocumentSymbol& child : document_symbol.GetChildren()) {
382+
auto children = From(child, scope_name);
383+
result.insert(result.end(), children.begin(), children.end());
384+
}
385+
}
386+
return result;
387+
}
388+
369389
const wxString& URI::GetPath() const { return m_path; }
370390
const wxString& URI::GetUrl() const { return m_uri; }
371391
void URI::FromString(const wxString& str, URI* uri)
@@ -391,51 +411,51 @@ JSONItem Command::ToJSON(const wxString& name) const { return {}; }
391411

392412
std::unordered_map<wxString, std::vector<LSP::TextEdit>> ParseWorkspaceEdit(const JSONItem& result)
393413
{
394-
if(!result.isOk()) {
414+
if (!result.isOk()) {
395415
return {};
396416
}
397417

398418
LOG_IF_TRACE { LSP_TRACE() << result.format(false) << endl; }
399419

400420
std::unordered_map<wxString, std::vector<LSP::TextEdit>> modifications;
401421
// some LSPs will reply with "changes" and some with "documentChanges" -> we support them both
402-
if(result.hasNamedObject("changes")) {
422+
if (result.hasNamedObject("changes")) {
403423
auto changes = result["changes"];
404424
auto M = changes.GetAsMap();
405425

406426
modifications.reserve(M.size());
407-
for(const auto& [filepath, json] : M) {
427+
for (const auto& [filepath, json] : M) {
408428
int count = json.arraySize();
409429
std::vector<LSP::TextEdit> file_changes;
410430
file_changes.reserve(count);
411-
for(int i = 0; i < count; ++i) {
431+
for (int i = 0; i < count; ++i) {
412432
auto e = json[i];
413433
LSP::TextEdit te;
414434
te.FromJSON(e);
415435
file_changes.push_back(te);
416436
}
417437
wxString path = FileUtils::FilePathFromURI(wxString(filepath.data(), filepath.length()));
418438
modifications.erase(path);
419-
modifications.insert({ path, file_changes });
439+
modifications.insert({path, file_changes});
420440
}
421-
} else if(result.hasNamedObject("documentChanges")) {
441+
} else if (result.hasNamedObject("documentChanges")) {
422442
auto documentChanges = result["documentChanges"];
423443
int files_count = documentChanges.arraySize();
424-
for(int i = 0; i < files_count; ++i) {
444+
for (int i = 0; i < files_count; ++i) {
425445
auto edits = documentChanges[i]["edits"];
426446
wxString filepath = documentChanges[i]["textDocument"]["uri"].toString();
427447
filepath = FileUtils::FilePathFromURI(filepath);
428448
std::vector<LSP::TextEdit> file_changes;
429449
int edits_count = edits.arraySize();
430450
file_changes.reserve(edits_count);
431-
for(int j = 0; j < edits_count; ++j) {
451+
for (int j = 0; j < edits_count; ++j) {
432452
auto e = edits[j];
433453
LSP::TextEdit te;
434454
te.FromJSON(e);
435455
file_changes.push_back(te);
436456
}
437457
modifications.erase(filepath);
438-
modifications.insert({ filepath, file_changes });
458+
modifications.insert({filepath, file_changes});
439459
}
440460
}
441461
return modifications;

CodeLite/LSP/basic_types.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,19 @@ class WXDLLIMPEXP_CL SymbolInformation : public Serializable
642642
const eSymbolKind& GetKind() const { return kind; }
643643
const Location& GetLocation() const { return location; }
644644
const wxString& GetName() const { return name; }
645+
/**
646+
* @brief Converts a hierarchical DocumentSymbol into a flat list of SymbolInformation objects.
647+
*
648+
* This static method recursively traverses the children of the provided document symbol,
649+
* generating a flat representation where each symbol is annotated with its parent's name.
650+
*
651+
* @param document_symbol The source DocumentSymbol to convert.
652+
* @param container_name The name of the parent scope or container for the current symbol.
653+
* @return A vector containing the SymbolInformation representation of the input symbol
654+
* and all of its recursive children.
655+
*/
656+
static std::vector<SymbolInformation> From(const DocumentSymbol& document_symbol,
657+
const wxString& container_name = wxEmptyString);
645658
};
646659

647660
/// Initialise the library

0 commit comments

Comments
 (0)