Skip to content

Commit 22c6cd3

Browse files
committed
Add LSP Work-Done Progress And Unified Logging
Update the LSP stack to understand and acknowledge work-done progress, and to surface server messages through a shared logging sink instead of the older event-based path. The client now advertises window progress support during initialization, handles progress create requests with a direct JSON-RPC ack, and parses $/progress notifications into readable log output. This change also centralizes JSON-RPC serialization, expands PHP keyword highlighting to match newer language features, and adds a null JSON property helper used by the new ack path. * LSP protocol * Logging * JSON utilities * PHP syntax highlighting **Generated by CodeLite** Signed-off-by: Eran Ifrah <eran@codelite.org>
1 parent 9994b35 commit 22c6cd3

16 files changed

+300
-55
lines changed

CodeLite/JSON.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,14 @@ JSONItem& JSONItem::addProperty(const wxString& name, const wxColour& colour)
562562
}
563563
#endif
564564

565+
JSONItem& JSONItem::addNull(const wxString& name)
566+
{
567+
if (m_json) {
568+
cJSON_AddNullToObject(m_json, name.ToStdString(wxConvUTF8).data());
569+
}
570+
return *this;
571+
}
572+
565573
JSONItem& JSONItem::addProperty(const wxString& name, const char* value, const wxMBConv& conv)
566574
{
567575
return addProperty(name, wxString(value, conv));

CodeLite/JSON.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ class WXDLLIMPEXP_CL JSONItem
191191
JSONItem& addProperty(const wxString& name, const wxStringMap_t& stringMap);
192192
JSONItem& addProperty(const wxString& name, const JSONItem& element);
193193
JSONItem& addProperty(const wxString& name, const char* value, const wxMBConv& conv = wxConvUTF8);
194+
JSONItem& addNull(const wxString& name);
194195

195196
/**
196197
* @brief delete property by name

CodeLite/LSP/InitializeRequest.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ JSONItem LSP::InitializeRequest::ToJSON() const
2727
}
2828
}
2929

30-
auto textDocumentCapabilities = params.AddObject("capabilities").AddObject("textDocument");
30+
auto capabilities = params.AddObject("capabilities");
31+
auto windowCapabilities = capabilities.AddObject("window");
32+
windowCapabilities.addProperty("workDoneProgress", true);
33+
34+
auto textDocumentCapabilities = capabilities.AddObject("textDocument");
3135
auto docFormat =
3236
textDocumentCapabilities.AddObject("completion").AddObject("completionItem").AddArray("documentationFormat");
3337
docFormat.arrayAppend("plaintext");

CodeLite/LSP/JSONRpcMessage.hpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#pragma once
2+
3+
#include "JSON.h"
4+
5+
#include <memory>
6+
#include <sstream>
7+
8+
namespace LSP
9+
{
10+
class JSONRpcMessage
11+
{
12+
public:
13+
JSONRpcMessage(JSONItem json)
14+
: m_json(std::move(json))
15+
{
16+
}
17+
~JSONRpcMessage() = default;
18+
19+
/**
20+
* @brief Serializes the request into an HTTP message string.
21+
*
22+
* @details This method formats the underlying JSON payload into its raw string
23+
* representation, computes its byte length, and prepends the HTTP headers
24+
* required to describe the body. It returns the complete request text with a
25+
* Content-Length header, a JSON Content-Type header, and the serialized body
26+
* appended after a blank line.
27+
*
28+
* @return std::string The full HTTP request message, including headers and
29+
* body.
30+
*/
31+
inline std::string ToString() const
32+
{
33+
using cstr_ptr = std::unique_ptr<char, decltype(&std::free)>;
34+
cstr_ptr data(m_json.FormatRawString(false), &std::free);
35+
36+
std::string s;
37+
size_t len = strlen(data.get());
38+
39+
// Build the request header
40+
std::stringstream ss;
41+
ss << "Content-Length: " << len << "\r\n";
42+
ss << "Content-Type: application/json; charset=utf-8" << "\r\n";
43+
ss << "\r\n";
44+
s = ss.str();
45+
46+
// append the data
47+
s.append(data.get(), len);
48+
return s;
49+
}
50+
51+
private:
52+
JSONItem m_json;
53+
};
54+
55+
} // namespace LSP

CodeLite/LSP/MessageWithParams.cpp

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "LSP/MessageWithParams.h"
22

33
#include "JSON.h"
4+
#include "LSP/JSONRpcMessage.hpp"
45

56
#include <sstream>
67
#include <wx/string.h>
@@ -25,24 +26,8 @@ std::string LSP::MessageWithParams::ToString() const
2526
{
2627
// Serialize the object and construct a JSON-RPC message
2728
JSONItem json = ToJSON();
28-
char* data = json.FormatRawString(false);
29-
30-
std::string s;
31-
size_t len = strlen(data);
32-
33-
// Build the request header
34-
std::stringstream ss;
35-
ss << "Content-Length: " << len << "\r\n";
36-
ss << "Content-Type: application/json; charset=utf-8" << "\r\n";
37-
ss << "\r\n";
38-
s = ss.str();
39-
40-
// append the data
41-
s.append(data, len);
42-
43-
// release the buffer
44-
free(data);
45-
return s;
29+
JSONRpcMessage message{std::move(json)};
30+
return message.ToString();
4631
}
4732

4833
LSP::MessageWithParams::Ptr_t LSP::MessageWithParams::MakeRequest(LSP::MessageWithParams* message_ptr)

CodeLite/LSP/basic_types.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,4 +460,33 @@ std::unordered_map<wxString, std::vector<LSP::TextEdit>> ParseWorkspaceEdit(cons
460460
}
461461
return modifications;
462462
}
463+
464+
// =====---------------
465+
// Progress
466+
// =====---------------
467+
468+
std::optional<Progress> Progress::FromJSON(const JSONItem& json)
469+
{
470+
if (!json.hasNamedObject("params")) {
471+
return std::nullopt;
472+
}
473+
474+
if (!json["params"].hasNamedObject("value")) {
475+
return std::nullopt;
476+
}
477+
478+
static const std::unordered_map<wxString, ProgressKind> ProgressKindMap{
479+
{"begin", ProgressKind::begin}, {"report", ProgressKind::report}, {"end", ProgressKind::end}};
480+
auto kind = json["params"]["value"]["kind"].toString();
481+
if (!ProgressKindMap.contains(kind)) {
482+
return std::nullopt;
483+
}
484+
485+
Progress result;
486+
result.m_message = json["params"]["value"]["message"].toString();
487+
result.m_percentage = json["params"]["value"]["percentage"].toDouble(0.0);
488+
result.m_token = json["params"]["token"].toString();
489+
result.m_kind = ProgressKindMap.find(kind)->second;
490+
return result;
491+
}
463492
} // namespace LSP

CodeLite/LSP/basic_types.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "clModuleLogger.hpp"
77
#include "codelite_exports.h"
88

9+
#include <optional>
910
#include <vector>
1011

1112
// Helper macros to be used outside of this library
@@ -657,6 +658,53 @@ class WXDLLIMPEXP_CL SymbolInformation : public Serializable
657658
const wxString& container_name = wxEmptyString);
658659
};
659660

661+
enum class ProgressKind {
662+
begin,
663+
report,
664+
end,
665+
};
666+
667+
struct WXDLLIMPEXP_CL Progress {
668+
wxString m_token;
669+
wxString m_message;
670+
ProgressKind m_kind{ProgressKind::begin};
671+
double m_percentage{0.0};
672+
/**
673+
* @brief Creates a Progress object from a JSON representation.
674+
*
675+
* @details Parses the expected "params.value" structure from the given JSON object and
676+
* returns a populated Progress instance when the required fields are present and the
677+
* "kind" value is one of the supported progress kinds. If any required field is missing
678+
* or the kind is unrecognized, the function returns std::nullopt.
679+
*
680+
* @param json const JSONItem& The JSON object to parse, expected to contain "params",
681+
* "params.value", "params.value.kind", "params.value.message",
682+
* "params.value.percentage", and "params.token" fields.
683+
*
684+
* @return std::optional<Progress> A populated Progress object on success, or std::nullopt
685+
* if the JSON does not match the expected format.
686+
*/
687+
static std::optional<Progress> FromJSON(const JSONItem& json);
688+
/**
689+
* @brief Formats the progress state into a human-readable message string.
690+
*
691+
* Builds a message from the current token and message text, and appends the
692+
* percentage when this progress entry represents a report. This method does not
693+
* modify the object state.
694+
*
695+
* @return wxString The formatted message string.
696+
*/
697+
inline wxString GetMessage() const
698+
{
699+
wxString message;
700+
message << "(" << m_token << ") " << m_message;
701+
if (m_kind == LSP::ProgressKind::report) {
702+
message << ". Progress: " << m_percentage << "%";
703+
}
704+
return message;
705+
}
706+
};
707+
660708
/// Initialise the library
661709
WXDLLIMPEXP_CL void Initialise();
662710

LanguageServer/languageserver.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "CustomControls/TextGenerationPreviewFrame.hpp"
55
#include "Keyboard/clKeyboardManager.h"
66
#include "LSP/LSPDetectorManager.hpp"
7+
#include "LSP/LSPManager.hpp"
78
#include "LSP/LanguageServerConfig.h"
89
#include "LSP/LanguageServerSettingsDlg.h"
910
#include "StringUtils.h"
@@ -80,6 +81,20 @@ LanguageServerPlugin::LanguageServerPlugin(IManager* manager)
8081
/// initialise the LSP library
8182
LSP::Initialise();
8283

84+
LSP::Manager::GetInstance().SetLogSink(
85+
[this](const wxString& server, const wxString& message, LSP::Manager::LogLevel log_level) {
86+
switch (log_level) {
87+
case LSP::Manager::LogLevel::Info:
88+
CallAfter(&LanguageServerPlugin::LogMessageInfo, server, message);
89+
break;
90+
case LSP::Manager::LogLevel::Warning:
91+
CallAfter(&LanguageServerPlugin::LogMessageWarn, server, message);
92+
break;
93+
case LSP::Manager::LogLevel::Error:
94+
CallAfter(&LanguageServerPlugin::LogMessageError, server, message);
95+
break;
96+
}
97+
});
8398
CallAfter(&LanguageServerPlugin::CheckServers);
8499
}
85100

LanguageServer/languageserver.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ class LanguageServerPlugin : public IPlugin
3333
* @brief log message to the output tab
3434
*/
3535
void LogMessage(const wxString& server_name, const wxString& message, int log_leve);
36+
void LogMessageInfo(const wxString& server_name, const wxString& message) { LogMessage(server_name, message, 3); }
37+
void LogMessageError(const wxString& server_name, const wxString& message) { LogMessage(server_name, message, 1); }
38+
void LogMessageWarn(const wxString& server_name, const wxString& message) { LogMessage(server_name, message, 2); }
3639

3740
protected:
3841
void OnSettings(wxCommandEvent& e);

LiteEditor/generic_context.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ class ContextGeneric : public ContextBase
6464
//---------------------------------------
6565
// Operations
6666
//---------------------------------------
67-
virtual void ApplySettings();
68-
69-
void ProcessIdleActions();
67+
void ApplySettings() override;
68+
69+
void ProcessIdleActions() override;
7070
};
7171
#endif // CONTEXT_GENERIC_H

0 commit comments

Comments
 (0)