Skip to content

Commit bb1cdb6

Browse files
committedFeb 7, 2017
Add a prototype for clangd
clangd is a language server protocol implementation based on clang. It's supposed to provide editor integration while not suffering from the confined ABI of libclang. This implementation is limited to the bare minimum functionality of doing (whole-document) formatting and rangeFormatting. The JSON parsing is based on LLVM's YAMLParser but yet most of the code of clangd is currently dealing with JSON serialization and deserialization. This was only tested with VS Code so far, mileage with other LSP clients may vary. Differential Revision: https://reviews.llvm.org/D29451 llvm-svn: 294291
1 parent b2b7097 commit bb1cdb6

12 files changed

+1169
-0
lines changed
 

Diff for: ‎clang-tools-extra/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ endif()
1010
add_subdirectory(change-namespace)
1111
add_subdirectory(clang-query)
1212
add_subdirectory(clang-move)
13+
add_subdirectory(clangd)
1314
add_subdirectory(include-fixer)
1415
add_subdirectory(pp-trace)
1516
add_subdirectory(tool-template)

Diff for: ‎clang-tools-extra/clangd/CMakeLists.txt

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
add_clang_executable(clangd
2+
ClangDMain.cpp
3+
JSONRPCDispatcher.cpp
4+
Protocol.cpp
5+
ProtocolHandlers.cpp
6+
)
7+
8+
target_link_libraries(clangd
9+
clangBasic
10+
clangFormat
11+
LLVMSupport
12+
)

Diff for: ‎clang-tools-extra/clangd/ClangDMain.cpp

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//===--- ClangDMain.cpp - clangd server loop ------------------------------===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#include "DocumentStore.h"
11+
#include "JSONRPCDispatcher.h"
12+
#include "ProtocolHandlers.h"
13+
#include "llvm/Support/FileSystem.h"
14+
#include <iostream>
15+
#include <string>
16+
using namespace clang::clangd;
17+
18+
int main(int argc, char *argv[]) {
19+
llvm::raw_ostream &Outs = llvm::outs();
20+
llvm::raw_ostream &Logs = llvm::errs();
21+
22+
// Set up a document store and intialize all the method handlers for JSONRPC
23+
// dispatching.
24+
DocumentStore Store;
25+
JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Outs, Logs));
26+
Dispatcher.registerHandler("initialize",
27+
llvm::make_unique<InitializeHandler>(Outs, Logs));
28+
Dispatcher.registerHandler("shutdown",
29+
llvm::make_unique<ShutdownHandler>(Outs, Logs));
30+
Dispatcher.registerHandler(
31+
"textDocument/didOpen",
32+
llvm::make_unique<TextDocumentDidOpenHandler>(Outs, Logs, Store));
33+
// FIXME: Implement textDocument/didClose.
34+
Dispatcher.registerHandler(
35+
"textDocument/didChange",
36+
llvm::make_unique<TextDocumentDidChangeHandler>(Outs, Logs, Store));
37+
Dispatcher.registerHandler(
38+
"textDocument/rangeFormatting",
39+
llvm::make_unique<TextDocumentRangeFormattingHandler>(Outs, Logs, Store));
40+
Dispatcher.registerHandler(
41+
"textDocument/formatting",
42+
llvm::make_unique<TextDocumentFormattingHandler>(Outs, Logs, Store));
43+
44+
while (std::cin.good()) {
45+
// A Language Server Protocol message starts with a HTTP header, delimited
46+
// by \r\n.
47+
std::string Line;
48+
std::getline(std::cin, Line);
49+
50+
// Skip empty lines.
51+
llvm::StringRef LineRef(Line);
52+
if (LineRef.trim().empty())
53+
continue;
54+
55+
unsigned long long Len = 0;
56+
// FIXME: Content-Type is a specified header, but does nothing.
57+
// Content-Length is a mandatory header. It specifies the length of the
58+
// following JSON.
59+
if (LineRef.consume_front("Content-Length: "))
60+
llvm::getAsUnsignedInteger(LineRef.trim(), 0, Len);
61+
62+
// Check if the next line only contains \r\n. If not this is another header,
63+
// which we ignore.
64+
char NewlineBuf[2];
65+
std::cin.read(NewlineBuf, 2);
66+
if (std::memcmp(NewlineBuf, "\r\n", 2) != 0)
67+
continue;
68+
69+
// Now read the JSON. Insert a trailing null byte as required by the YAML
70+
// parser.
71+
std::vector<char> JSON(Len + 1);
72+
std::cin.read(JSON.data(), Len);
73+
74+
if (Len > 0) {
75+
// Log the message.
76+
Logs << "<-- ";
77+
Logs.write(JSON.data(), JSON.size());
78+
Logs << '\n';
79+
Logs.flush();
80+
81+
// Finally, execute the action for this JSON message.
82+
if (!Dispatcher.call(llvm::StringRef(JSON.data(), JSON.size() - 1)))
83+
Logs << "JSON dispatch failed!\n";
84+
}
85+
}
86+
}

Diff for: ‎clang-tools-extra/clangd/DocumentStore.h

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===--- DocumentStore.h - File contents container --------------*- C++ -*-===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_DOCUMENTSTORE_H
11+
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_DOCUMENTSTORE_H
12+
13+
#include "clang/Basic/LLVM.h"
14+
#include "llvm/ADT/StringMap.h"
15+
#include <string>
16+
17+
namespace clang {
18+
namespace clangd {
19+
20+
/// A container for files opened in a workspace, addressed by URI. The contents
21+
/// are owned by the DocumentStore.
22+
class DocumentStore {
23+
public:
24+
/// Add a document to the store. Overwrites existing contents.
25+
void addDocument(StringRef Uri, StringRef Text) { Docs[Uri] = Text; }
26+
/// Delete a document from the store.
27+
void removeDocument(StringRef Uri) { Docs.erase(Uri); }
28+
/// Retrieve a document from the store. Empty string if it's unknown.
29+
StringRef getDocument(StringRef Uri) const { return Docs.lookup(Uri); }
30+
31+
private:
32+
llvm::StringMap<std::string> Docs;
33+
};
34+
35+
} // namespace clangd
36+
} // namespace clang
37+
38+
#endif

Diff for: ‎clang-tools-extra/clangd/JSONRPCDispatcher.cpp

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//===--- JSONRPCDispatcher.cpp - Main JSON parser entry point -------------===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#include "JSONRPCDispatcher.h"
11+
#include "ProtocolHandlers.h"
12+
#include "llvm/ADT/SmallString.h"
13+
#include "llvm/Support/SourceMgr.h"
14+
#include "llvm/Support/YAMLParser.h"
15+
using namespace clang;
16+
using namespace clangd;
17+
18+
void Handler::writeMessage(const Twine &Message) {
19+
llvm::SmallString<128> Storage;
20+
StringRef M = Message.toStringRef(Storage);
21+
22+
// Log without headers.
23+
Logs << "--> " << M << '\n';
24+
Logs.flush();
25+
26+
// Emit message with header.
27+
Outs << "Content-Length: " << M.size() << "\r\n\r\n" << M;
28+
Outs.flush();
29+
}
30+
31+
void Handler::handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) {
32+
Logs << "Method ignored.\n";
33+
// Return that this method is unsupported.
34+
writeMessage(
35+
R"({"jsonrpc":"2.0","id":)" + ID +
36+
R"(,"error":{"code":-32601}})");
37+
}
38+
39+
void Handler::handleNotification(llvm::yaml::MappingNode *Params) {
40+
Logs << "Notification ignored.\n";
41+
}
42+
43+
void JSONRPCDispatcher::registerHandler(StringRef Method,
44+
std::unique_ptr<Handler> H) {
45+
assert(!Handlers.count(Method) && "Handler already registered!");
46+
Handlers[Method] = std::move(H);
47+
}
48+
49+
static void
50+
callHandler(const llvm::StringMap<std::unique_ptr<Handler>> &Handlers,
51+
llvm::yaml::ScalarNode *Method, llvm::yaml::ScalarNode *Id,
52+
llvm::yaml::MappingNode *Params, Handler *UnknownHandler) {
53+
llvm::SmallString<10> MethodStorage;
54+
auto I = Handlers.find(Method->getValue(MethodStorage));
55+
auto *Handler = I != Handlers.end() ? I->second.get() : UnknownHandler;
56+
if (Id)
57+
Handler->handleMethod(Params, Id->getRawValue());
58+
else
59+
Handler->handleNotification(Params);
60+
}
61+
62+
bool JSONRPCDispatcher::call(StringRef Content) const {
63+
llvm::SourceMgr SM;
64+
llvm::yaml::Stream YAMLStream(Content, SM);
65+
66+
auto Doc = YAMLStream.begin();
67+
if (Doc == YAMLStream.end())
68+
return false;
69+
70+
auto *Root = Doc->getRoot();
71+
if (!Root)
72+
return false;
73+
74+
auto *Object = dyn_cast<llvm::yaml::MappingNode>(Root);
75+
if (!Object)
76+
return false;
77+
78+
llvm::yaml::ScalarNode *Version = nullptr;
79+
llvm::yaml::ScalarNode *Method = nullptr;
80+
llvm::yaml::MappingNode *Params = nullptr;
81+
llvm::yaml::ScalarNode *Id = nullptr;
82+
for (auto &NextKeyValue : *Object) {
83+
auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
84+
if (!KeyString)
85+
return false;
86+
87+
llvm::SmallString<10> KeyStorage;
88+
StringRef KeyValue = KeyString->getValue(KeyStorage);
89+
llvm::yaml::Node *Value = NextKeyValue.getValue();
90+
if (!Value)
91+
return false;
92+
93+
if (KeyValue == "jsonrpc") {
94+
// This should be "2.0". Always.
95+
Version = dyn_cast<llvm::yaml::ScalarNode>(Value);
96+
if (!Version || Version->getRawValue() != "\"2.0\"")
97+
return false;
98+
} else if (KeyValue == "method") {
99+
Method = dyn_cast<llvm::yaml::ScalarNode>(Value);
100+
} else if (KeyValue == "id") {
101+
Id = dyn_cast<llvm::yaml::ScalarNode>(Value);
102+
} else if (KeyValue == "params") {
103+
if (!Method)
104+
return false;
105+
// We have to interleave the call of the function here, otherwise the
106+
// YAMLParser will die because it can't go backwards. This is unfortunate
107+
// because it will break clients that put the id after params. A possible
108+
// fix would be to split the parsing and execution phases.
109+
Params = dyn_cast<llvm::yaml::MappingNode>(Value);
110+
callHandler(Handlers, Method, Id, Params, UnknownHandler.get());
111+
return true;
112+
} else {
113+
return false;
114+
}
115+
}
116+
117+
// In case there was a request with no params, call the handler on the
118+
// leftovers.
119+
if (!Method)
120+
return false;
121+
callHandler(Handlers, Method, Id, nullptr, UnknownHandler.get());
122+
123+
return true;
124+
}

Diff for: ‎clang-tools-extra/clangd/JSONRPCDispatcher.h

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//===--- JSONRPCDispatcher.h - Main JSON parser entry point -----*- C++ -*-===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSONRPCDISPATCHER_H
11+
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSONRPCDISPATCHER_H
12+
13+
#include "clang/Basic/LLVM.h"
14+
#include "llvm/ADT/StringMap.h"
15+
#include "llvm/Support/YAMLParser.h"
16+
17+
namespace clang {
18+
namespace clangd {
19+
20+
/// Callback for messages sent to the server, called by the JSONRPCDispatcher.
21+
class Handler {
22+
public:
23+
Handler(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs)
24+
: Outs(Outs), Logs(Logs) {}
25+
virtual ~Handler() = default;
26+
27+
/// Called when the server receives a method call. This is supposed to return
28+
/// a result on Outs. The default implementation returns an "unknown method"
29+
/// error to the client and logs a warning.
30+
virtual void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID);
31+
/// Called when the server receives a notification. No result should be
32+
/// written to Outs. The default implemetation logs a warning.
33+
virtual void handleNotification(llvm::yaml::MappingNode *Params);
34+
35+
protected:
36+
llvm::raw_ostream &Outs;
37+
llvm::raw_ostream &Logs;
38+
39+
/// Helper to write a JSONRPC result to Outs.
40+
void writeMessage(const Twine &Message);
41+
};
42+
43+
/// Main JSONRPC entry point. This parses the JSONRPC "header" and calls the
44+
/// registered Handler for the method received.
45+
class JSONRPCDispatcher {
46+
public:
47+
/// Create a new JSONRPCDispatcher. UnknownHandler is called when an unknown
48+
/// method is received.
49+
JSONRPCDispatcher(std::unique_ptr<Handler> UnknownHandler)
50+
: UnknownHandler(std::move(UnknownHandler)) {}
51+
52+
/// Registers a Handler for the specified Method.
53+
void registerHandler(StringRef Method, std::unique_ptr<Handler> H);
54+
55+
/// Parses a JSONRPC message and calls the Handler for it.
56+
bool call(StringRef Content) const;
57+
58+
private:
59+
llvm::StringMap<std::unique_ptr<Handler>> Handlers;
60+
std::unique_ptr<Handler> UnknownHandler;
61+
};
62+
63+
} // namespace clangd
64+
} // namespace clang
65+
66+
#endif

Diff for: ‎clang-tools-extra/clangd/Protocol.cpp

+412
Large diffs are not rendered by default.

Diff for: ‎clang-tools-extra/clangd/Protocol.h

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//===--- Protocol.h - Language Server Protocol Implementation ---*- C++ -*-===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This file contains structs based on the LSP specification at
11+
// https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md
12+
//
13+
// This is not meant to be a complete implementation, new interfaces are added
14+
// when they're needed.
15+
//
16+
// Each struct has a parse and unparse function, that converts back and forth
17+
// between the struct and a JSON representation.
18+
//
19+
//===----------------------------------------------------------------------===//
20+
21+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H
22+
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H
23+
24+
#include "llvm/ADT/Optional.h"
25+
#include "llvm/Support/YAMLParser.h"
26+
#include <string>
27+
28+
namespace clang {
29+
namespace clangd {
30+
31+
struct TextDocumentIdentifier {
32+
/// The text document's URI.
33+
std::string uri;
34+
35+
static llvm::Optional<TextDocumentIdentifier>
36+
parse(llvm::yaml::MappingNode *Params);
37+
};
38+
39+
struct Position {
40+
/// Line position in a document (zero-based).
41+
int line;
42+
43+
/// Character offset on a line in a document (zero-based).
44+
int character;
45+
46+
static llvm::Optional<Position> parse(llvm::yaml::MappingNode *Params);
47+
static std::string unparse(const Position &P);
48+
};
49+
50+
struct Range {
51+
/// The range's start position.
52+
Position start;
53+
54+
/// The range's end position.
55+
Position end;
56+
57+
static llvm::Optional<Range> parse(llvm::yaml::MappingNode *Params);
58+
static std::string unparse(const Range &P);
59+
};
60+
61+
struct TextEdit {
62+
/// The range of the text document to be manipulated. To insert
63+
/// text into a document create a range where start === end.
64+
Range range;
65+
66+
/// The string to be inserted. For delete operations use an
67+
/// empty string.
68+
std::string newText;
69+
70+
static llvm::Optional<TextEdit> parse(llvm::yaml::MappingNode *Params);
71+
static std::string unparse(const TextEdit &P);
72+
};
73+
74+
struct TextDocumentItem {
75+
/// The text document's URI.
76+
std::string uri;
77+
78+
/// The text document's language identifier.
79+
std::string languageId;
80+
81+
/// The version number of this document (it will strictly increase after each
82+
int version;
83+
84+
/// The content of the opened text document.
85+
std::string text;
86+
87+
static llvm::Optional<TextDocumentItem>
88+
parse(llvm::yaml::MappingNode *Params);
89+
};
90+
91+
struct DidOpenTextDocumentParams {
92+
/// The document that was opened.
93+
TextDocumentItem textDocument;
94+
95+
static llvm::Optional<DidOpenTextDocumentParams>
96+
parse(llvm::yaml::MappingNode *Params);
97+
};
98+
99+
struct TextDocumentContentChangeEvent {
100+
/// The new text of the document.
101+
std::string text;
102+
103+
static llvm::Optional<TextDocumentContentChangeEvent>
104+
parse(llvm::yaml::MappingNode *Params);
105+
};
106+
107+
struct DidChangeTextDocumentParams {
108+
/// The document that did change. The version number points
109+
/// to the version after all provided content changes have
110+
/// been applied.
111+
TextDocumentIdentifier textDocument;
112+
113+
/// The actual content changes.
114+
std::vector<TextDocumentContentChangeEvent> contentChanges;
115+
116+
static llvm::Optional<DidChangeTextDocumentParams>
117+
parse(llvm::yaml::MappingNode *Params);
118+
};
119+
120+
struct FormattingOptions {
121+
/// Size of a tab in spaces.
122+
int tabSize;
123+
124+
/// Prefer spaces over tabs.
125+
bool insertSpaces;
126+
127+
static llvm::Optional<FormattingOptions>
128+
parse(llvm::yaml::MappingNode *Params);
129+
static std::string unparse(const FormattingOptions &P);
130+
};
131+
132+
struct DocumentRangeFormattingParams {
133+
/// The document to format.
134+
TextDocumentIdentifier textDocument;
135+
136+
/// The range to format
137+
Range range;
138+
139+
/// The format options
140+
FormattingOptions options;
141+
142+
static llvm::Optional<DocumentRangeFormattingParams>
143+
parse(llvm::yaml::MappingNode *Params);
144+
};
145+
146+
struct DocumentFormattingParams {
147+
/// The document to format.
148+
TextDocumentIdentifier textDocument;
149+
150+
/// The format options
151+
FormattingOptions options;
152+
153+
static llvm::Optional<DocumentFormattingParams>
154+
parse(llvm::yaml::MappingNode *Params);
155+
};
156+
157+
} // namespace clangd
158+
} // namespace clang
159+
160+
#endif

Diff for: ‎clang-tools-extra/clangd/ProtocolHandlers.cpp

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//===--- ProtocolHandlers.cpp - LSP callbacks -----------------------------===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#include "ProtocolHandlers.h"
11+
#include "DocumentStore.h"
12+
#include "clang/Format/Format.h"
13+
using namespace clang;
14+
using namespace clangd;
15+
16+
void TextDocumentDidOpenHandler::handleNotification(
17+
llvm::yaml::MappingNode *Params) {
18+
auto DOTDP = DidOpenTextDocumentParams::parse(Params);
19+
if (!DOTDP) {
20+
Logs << "Failed to decode DidOpenTextDocumentParams!\n";
21+
return;
22+
}
23+
Store.addDocument(DOTDP->textDocument.uri, DOTDP->textDocument.text);
24+
}
25+
26+
void TextDocumentDidChangeHandler::handleNotification(
27+
llvm::yaml::MappingNode *Params) {
28+
auto DCTDP = DidChangeTextDocumentParams::parse(Params);
29+
if (!DCTDP || DCTDP->contentChanges.size() != 1) {
30+
Logs << "Failed to decode DidChangeTextDocumentParams!\n";
31+
return;
32+
}
33+
// We only support full syncing right now.
34+
Store.addDocument(DCTDP->textDocument.uri, DCTDP->contentChanges[0].text);
35+
}
36+
37+
/// Turn a [line, column] pair into an offset in Code.
38+
static size_t positionToOffset(StringRef Code, Position P) {
39+
size_t Offset = 0;
40+
for (int I = 0; I != P.line; ++I) {
41+
// FIXME: \r\n
42+
// FIXME: UTF-8
43+
size_t F = Code.find('\n', Offset);
44+
if (F == StringRef::npos)
45+
return 0; // FIXME: Is this reasonable?
46+
Offset = F + 1;
47+
}
48+
return (Offset == 0 ? 0 : (Offset - 1)) + P.character;
49+
}
50+
51+
/// Turn an offset in Code into a [line, column] pair.
52+
static Position offsetToPosition(StringRef Code, size_t Offset) {
53+
StringRef JustBefore = Code.substr(0, Offset);
54+
// FIXME: \r\n
55+
// FIXME: UTF-8
56+
int Lines = JustBefore.count('\n');
57+
int Cols = JustBefore.size() - JustBefore.rfind('\n') - 1;
58+
return {Lines, Cols};
59+
}
60+
61+
static std::string formatCode(StringRef Code, StringRef Filename,
62+
ArrayRef<tooling::Range> Ranges, StringRef ID) {
63+
// Call clang-format.
64+
// FIXME: Don't ignore style.
65+
format::FormatStyle Style = format::getLLVMStyle();
66+
tooling::Replacements Replacements =
67+
format::reformat(Style, Code, Ranges, Filename);
68+
69+
// Now turn the replacements into the format specified by the Language Server
70+
// Protocol. Fuse them into one big JSON array.
71+
std::string Edits;
72+
for (auto &R : Replacements) {
73+
Range ReplacementRange = {
74+
offsetToPosition(Code, R.getOffset()),
75+
offsetToPosition(Code, R.getOffset() + R.getLength())};
76+
TextEdit TE = {ReplacementRange, R.getReplacementText()};
77+
Edits += TextEdit::unparse(TE);
78+
Edits += ',';
79+
}
80+
if (!Edits.empty())
81+
Edits.pop_back();
82+
83+
return R"({"jsonrpc":"2.0","id":)" + ID.str() +
84+
R"(,"result":[)" + Edits + R"(]})";
85+
}
86+
87+
void TextDocumentRangeFormattingHandler::handleMethod(
88+
llvm::yaml::MappingNode *Params, StringRef ID) {
89+
auto DRFP = DocumentRangeFormattingParams::parse(Params);
90+
if (!DRFP) {
91+
Logs << "Failed to decode DocumentRangeFormattingParams!\n";
92+
return;
93+
}
94+
95+
StringRef Code = Store.getDocument(DRFP->textDocument.uri);
96+
97+
size_t Begin = positionToOffset(Code, DRFP->range.start);
98+
size_t Len = positionToOffset(Code, DRFP->range.end) - Begin;
99+
100+
writeMessage(formatCode(Code, DRFP->textDocument.uri,
101+
{clang::tooling::Range(Begin, Len)}, ID));
102+
}
103+
104+
void TextDocumentFormattingHandler::handleMethod(
105+
llvm::yaml::MappingNode *Params, StringRef ID) {
106+
auto DFP = DocumentFormattingParams::parse(Params);
107+
if (!DFP) {
108+
Logs << "Failed to decode DocumentFormattingParams!\n";
109+
return;
110+
}
111+
112+
// Format everything.
113+
StringRef Code = Store.getDocument(DFP->textDocument.uri);
114+
writeMessage(formatCode(Code, DFP->textDocument.uri,
115+
{clang::tooling::Range(0, Code.size())}, ID));
116+
}

Diff for: ‎clang-tools-extra/clangd/ProtocolHandlers.h

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//===--- ProtocolHandlers.h - LSP callbacks ---------------------*- C++ -*-===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This file contains the actions performed when the server gets a specific
11+
// request.
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOLHANDLERS_H
16+
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOLHANDLERS_H
17+
18+
#include "JSONRPCDispatcher.h"
19+
#include "Protocol.h"
20+
#include "llvm/ADT/Twine.h"
21+
#include "llvm/Support/raw_ostream.h"
22+
23+
namespace clang {
24+
namespace clangd {
25+
class DocumentStore;
26+
27+
struct InitializeHandler : Handler {
28+
InitializeHandler(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs)
29+
: Handler(Outs, Logs) {}
30+
31+
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
32+
writeMessage(
33+
R"({"jsonrpc":"2.0","id":)" + ID +
34+
R"(,"result":{"capabilities":{
35+
"textDocumentSync": 1,
36+
"documentFormattingProvider": true,
37+
"documentRangeFormattingProvider": true
38+
}}})");
39+
}
40+
};
41+
42+
struct ShutdownHandler : Handler {
43+
ShutdownHandler(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs)
44+
: Handler(Outs, Logs) {}
45+
46+
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
47+
// FIXME: Calling exit is rude, can we communicate to main somehow?
48+
exit(0);
49+
}
50+
};
51+
52+
struct TextDocumentDidOpenHandler : Handler {
53+
TextDocumentDidOpenHandler(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs,
54+
DocumentStore &Store)
55+
: Handler(Outs, Logs), Store(Store) {}
56+
57+
void handleNotification(llvm::yaml::MappingNode *Params) override;
58+
59+
private:
60+
DocumentStore &Store;
61+
};
62+
63+
struct TextDocumentDidChangeHandler : Handler {
64+
TextDocumentDidChangeHandler(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs,
65+
DocumentStore &Store)
66+
: Handler(Outs, Logs), Store(Store) {}
67+
68+
void handleNotification(llvm::yaml::MappingNode *Params) override;
69+
70+
private:
71+
DocumentStore &Store;
72+
};
73+
74+
struct TextDocumentRangeFormattingHandler : Handler {
75+
TextDocumentRangeFormattingHandler(llvm::raw_ostream &Outs,
76+
llvm::raw_ostream &Logs,
77+
DocumentStore &Store)
78+
: Handler(Outs, Logs), Store(Store) {}
79+
80+
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
81+
82+
private:
83+
DocumentStore &Store;
84+
};
85+
86+
struct TextDocumentFormattingHandler : Handler {
87+
TextDocumentFormattingHandler(llvm::raw_ostream &Outs,
88+
llvm::raw_ostream &Logs, DocumentStore &Store)
89+
: Handler(Outs, Logs), Store(Store) {}
90+
91+
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
92+
93+
private:
94+
DocumentStore &Store;
95+
};
96+
97+
} // namespace clangd
98+
} // namespace clang
99+
100+
#endif

Diff for: ‎clang-tools-extra/test/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ set(CLANG_TOOLS_TEST_DEPS
4343
# Individual tools we test.
4444
clang-apply-replacements
4545
clang-change-namespace
46+
clangd
4647
clang-include-fixer
4748
clang-move
4849
clang-query

Diff for: ‎clang-tools-extra/test/clangd/formatting.test

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# RUN: sed -e '/^#/d' %s | clangd | FileCheck %s
2+
# It is absolutely vital that this file has CRLF line endings.
3+
#
4+
Content-Length: 125
5+
6+
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
7+
# CHECK: Content-Length: 191
8+
# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
9+
# CHECK: "textDocumentSync": 1,
10+
# CHECK: "documentFormattingProvider": true,
11+
# CHECK: "documentRangeFormattingProvider": true
12+
# CHECK: }}}
13+
#
14+
Content-Length: 193
15+
16+
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.c","languageId":"c","version":1,"text":"int foo ( int x ) {\n x = x+1;\n return x;\n }"}}}
17+
#
18+
#
19+
Content-Length: 233
20+
21+
{"jsonrpc":"2.0","id":1,"method":"textDocument/rangeFormatting","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":1,"character":4},"end":{"line":1,"character":12}},"options":{"tabSize":4,"insertSpaces":true}}}
22+
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"range": {"start": {"line": 0, "character": 19}, "end": {"line": 1, "character": 4}}, "newText": "\n "},{"range": {"start": {"line": 1, "character": 9}, "end": {"line": 1, "character": 9}}, "newText": " "},{"range": {"start": {"line": 1, "character": 10}, "end": {"line": 1, "character": 10}}, "newText": " "},{"range": {"start": {"line": 1, "character": 12}, "end": {"line": 2, "character": 4}}, "newText": "\n "}]}
23+
#
24+
#
25+
Content-Length: 197
26+
27+
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///foo.c","version":5},"contentChanges":[{"text":"int foo ( int x ) {\n x = x + 1;\n return x;\n }"}]}}
28+
#
29+
#
30+
Content-Length: 233
31+
32+
{"jsonrpc":"2.0","id":2,"method":"textDocument/rangeFormatting","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":1,"character":2},"end":{"line":1,"character":12}},"options":{"tabSize":4,"insertSpaces":true}}}
33+
# CHECK: {"jsonrpc":"2.0","id":2,"result":[]}
34+
#
35+
Content-Length: 153
36+
37+
{"jsonrpc":"2.0","id":3,"method":"textDocument/formatting","params":{"textDocument":{"uri":"file:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}}
38+
# CHECK: {"jsonrpc":"2.0","id":3,"result":[{"range": {"start": {"line": 0, "character": 7}, "end": {"line": 0, "character": 8}}, "newText": ""},{"range": {"start": {"line": 0, "character": 9}, "end": {"line": 0, "character": 10}}, "newText": ""},{"range": {"start": {"line": 0, "character": 15}, "end": {"line": 0, "character": 16}}, "newText": ""},{"range": {"start": {"line": 2, "character": 11}, "end": {"line": 3, "character": 4}}, "newText": "\n"}]}
39+
#
40+
#
41+
Content-Length: 190
42+
43+
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///foo.c","version":9},"contentChanges":[{"text":"int foo(int x) {\n x = x + 1;\n return x;\n}"}]}}
44+
#
45+
#
46+
Content-Length: 153
47+
48+
{"jsonrpc":"2.0","id":4,"method":"textDocument/formatting","params":{"textDocument":{"uri":"file:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}}
49+
# CHECK: {"jsonrpc":"2.0","id":4,"result":[]}
50+
#
51+
Content-Length: 44
52+
53+
{"jsonrpc":"2.0","id":5,"method":"shutdown"}

0 commit comments

Comments
 (0)
Please sign in to comment.