Skip to content

Commit

Permalink
Add documentSymbol outline feature.
Browse files Browse the repository at this point in the history
We adapt the verible internals with the language server by
walking the parsed structure and emit some select DocumentSymbols.
For now, these are just higher level structural components, but more
can be added easily. This is the first baseline implementation.

Signed-off-by: Henner Zeller <[email protected]>
  • Loading branch information
hzeller committed Nov 1, 2021
1 parent 72ca8df commit 0152c49
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 1 deletion.
5 changes: 5 additions & 0 deletions common/lsp/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ jcxxgen(
namespace = "verible::lsp",
)

cc_library(
name = "lsp-protocol-enums",
hdrs = ["lsp-protocol-enums.h"]
)

cc_library(
name = "lsp-protocol-operators",
hdrs = ["lsp-protocol-operators.h"],
Expand Down
57 changes: 57 additions & 0 deletions common/lsp/lsp-protocol-enums.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2021 The Verible Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef COMMON_LSP_LSP_PROTOCOL_ENUMS_H
#define COMMON_LSP_LSP_PROTOCOL_ENUMS_H

// Enums are currently not handled yet by jcxxgen, so enumeration are
// done separately here.
namespace verible {
namespace lsp {
// These are the SymbolKinds defined by the LSP specifcation
// https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#symbolKind
// Interesting is, that not all of them are actually supported by all
// editors (playing around with Kate (https://kate-editor.org/)). So maybe
// these editors need to be made understanding.
enum SymbolKind {
File = 1,
Module = 2, // SV module. Kate does not seem to support that ?
Namespace = 3,
Package = 4, // SV package
Class = 5, // SV class
Method = 6, // SV class -> method
Property = 7,
Field = 8,
Constructor = 9,
Enum = 10, // SV enum
Interface = 11,
Function = 12, // SV function
Variable = 13,
Constant = 14,
String = 15,
Number = 16,
Boolean = 17,
Array = 18,
Object = 19,
Key = 20,
Null = 21,
EnumMember = 22,
Struct = 23,
Event = 24,
Operator = 25,
TypeParameter = 26,
};
} // namespace lsp
} // namespace verible
#endif // COMMON_LSP_LSP_PROTOCOL_ENUMS_H
13 changes: 13 additions & 0 deletions common/lsp/lsp-protocol.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,16 @@ CodeAction:
diagnostics+: Diagnostic # What diagnostic is this referring to ?
isPreferred: boolean = false
edit: WorkspaceEdit

# -- textDocument/documentSymbol
DocumentSymbolParams:
textDocument: TextDocumentIdentifier

DocumentSymbol:
name: string
kind: integer # SymbolKind enum
range: Range # The whole range this symbol (e.g. function/class) covers
selectionRange: Range # Part to be highlighted (e.g. name of class)
# children: DocumentSymbol[]. Since we can't to that recursively in jcxxgen,
# these are directly emitted as json
children?: object = nullptr
1 change: 1 addition & 0 deletions verilog/CST/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package(
"//verilog/parser:__subpackages__",
"//verilog/tools/kythe:__pkg__",
"//verilog/tools/syntax:__pkg__", # for printing
"//verilog/tools/ls:__pkg__", # DocumentSymbol
],
)

Expand Down
14 changes: 14 additions & 0 deletions verilog/tools/ls/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ cc_library(
srcs = ["verible-lsp-adapter.cc"],
hdrs = ["verible-lsp-adapter.h"],
deps = [
":document-symbol-filler",
":lsp-parse-buffer",
"//common/lsp:lsp-protocol",
"//common/lsp:lsp-protocol-operators",
Expand All @@ -38,6 +39,19 @@ cc_library(
],
)

cc_library(
name = "document-symbol-filler",
hdrs = ["document-symbol-filler.h"],
deps = [
"//common/lsp:lsp-protocol-enums",
"//verilog/CST:module",
"//verilog/CST:class",
"//verilog/CST:package",
"//verilog/CST:functions",
"//verilog/CST:seq_block",
]
)

cc_binary(
name = "verible-verilog-ls",
srcs = ["verilog_ls.cc"],
Expand Down
158 changes: 158 additions & 0 deletions verilog/tools/ls/document-symbol-filler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright 2021 The Verible Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef VERILOG_TOOLS_LS_DOCUMENT_SYMBOL_FILLER_H
#define VERILOG_TOOLS_LS_DOCUMENT_SYMBOL_FILLER_H

#include "common/lsp/lsp-protocol-enums.h"
#include "common/lsp/lsp-protocol.h"
#include "common/strings/line_column_map.h"
#include "common/util/value_saver.h"
#include "verilog/CST/class.h"
#include "verilog/CST/functions.h"
#include "verilog/CST/module.h"
#include "verilog/CST/package.h"
#include "verilog/CST/seq_block.h"

namespace verilog {
class DocumentSymbolFiller : public verible::SymbolVisitor {
public:
// Magic value to hint that we have to fill out the start range.
static constexpr int kUninitializedStartLine = -1;

DocumentSymbolFiller(bool kate_workaround,
const verible::TextStructureView &text,
verible::lsp::DocumentSymbol *toplevel)
: kModuleSymbolKind(kate_workaround ? verible::lsp::SymbolKind::Method
: verible::lsp::SymbolKind::Module),
kBlockSymbolKind(kate_workaround ? verible::lsp::SymbolKind::Class
: verible::lsp::SymbolKind::Namespace),
text_view_(text),
current_symbol_(toplevel) {
toplevel->range.start = {.line = 0, .character = 0};
}

verible::lsp::Range RangeFromLeaf(const verible::SyntaxTreeLeaf &leaf) const {
return RangeFromToken(leaf.get());
}
verible::lsp::Range RangeFromToken(const verible::TokenInfo &token) const {
verible::LineColumn start =
text_view_.GetLineColAtOffset(token.left(text_view_.Contents()));
verible::LineColumn end =
text_view_.GetLineColAtOffset(token.right(text_view_.Contents()));
return {.start = {.line = start.line, .character = start.column},
.end = {.line = end.line, .character = end.column}};
}

void Visit(const verible::SyntaxTreeLeaf &leaf) final {
verible::lsp::Range range = RangeFromLeaf(leaf);
if (current_symbol_->range.start.line == kUninitializedStartLine) {
// We're the first concrete token with a position within our parent,
// set the start position.
current_symbol_->range.start = range.start;
}
// Update the end position with every token we see. The last one wins.
current_symbol_->range.end = range.end;
}

void Visit(const verible::SyntaxTreeNode &node) final {
verible::lsp::DocumentSymbol *parent = current_symbol_;

// These things can probably be done easier with Matchers.
verible::lsp::DocumentSymbol node_symbol;
node_symbol.range.start.line = kUninitializedStartLine;
bool is_visible_node = false;
switch (static_cast<verilog::NodeEnum>(node.Tag().tag)) {
case verilog::NodeEnum::kModuleDeclaration: {
is_visible_node = true;
node_symbol.kind = kModuleSymbolKind;
const auto &name_leaf = verilog::GetModuleName(node);
node_symbol.selectionRange = RangeFromLeaf(name_leaf);
node_symbol.name = std::string(name_leaf.get().text());
break;
}
case verilog::NodeEnum::kSeqBlock:
case verilog::NodeEnum::kGenerateBlock:
if (!node.children().empty()) {
const auto &begin = node.children().front().get();
if (begin) {
if (const auto *token = GetBeginLabelTokenInfo(*begin); token) {
is_visible_node = true;
node_symbol.kind = kBlockSymbolKind;
node_symbol.selectionRange = RangeFromToken(*token);
node_symbol.name = std::string(token->text());
}
}
}
break;
case verilog::NodeEnum::kClassDeclaration: {
const auto &class_name_leaf = verilog::GetClassName(node);
is_visible_node = true;
node_symbol.kind = verible::lsp::SymbolKind::Class;
node_symbol.selectionRange = RangeFromToken(class_name_leaf.get());
node_symbol.name = std::string(class_name_leaf.get().text());
} break;

case verilog::NodeEnum::kPackageDeclaration: {
const auto &package_name = verilog::GetPackageNameToken(node);
is_visible_node = true;
node_symbol.kind = verible::lsp::SymbolKind::Package;
node_symbol.selectionRange = RangeFromToken(package_name);
node_symbol.name = std::string(package_name.text());
} break;
case verilog::NodeEnum::kFunctionDeclaration: {
const auto &function_name = verilog::GetFunctionName(node);
if (function_name) {
is_visible_node = true;
node_symbol.kind = verible::lsp::SymbolKind::Function;
node_symbol.selectionRange = RangeFromToken(function_name->get());
node_symbol.name = std::string(function_name->get().text());
}
} break;
default:
is_visible_node = false;
break;
}

// Independent of visible or not, we always descent to our children.
if (is_visible_node) {
const verible::ValueSaver<verible::lsp::DocumentSymbol *> value_saver(
&current_symbol_, &node_symbol);
for (const auto &child : node.children()) {
if (child) child->Accept(this);
}
if (parent->children == nullptr) {
if (parent->range.start.line == kUninitializedStartLine) {
parent->range.start = node_symbol.range.start;
}
parent->children = nlohmann::json::array();
parent->has_children = true;
}
parent->children.push_back(node_symbol);
parent->range.end = node_symbol.range.end;
} else {
for (const auto &child : node.children()) {
if (child) child->Accept(this);
}
}
}

private:
const int kModuleSymbolKind; // Might be different if kate-workaround.
const int kBlockSymbolKind;
const verible::TextStructureView &text_view_;
verible::lsp::DocumentSymbol *current_symbol_;
};
} // namespace verilog
#endif // VERILOG_TOOLS_LS_DOCUMENT_SYMBOL_FILLER_H
21 changes: 21 additions & 0 deletions verilog/tools/ls/verible-lsp-adapter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@

#include "common/lsp/lsp-protocol-operators.h"
#include "common/lsp/lsp-protocol.h"
#include "common/text/text_structure.h"
#include "nlohmann/json.hpp"
#include "verilog/analysis/verilog_analyzer.h"
#include "verilog/analysis/verilog_linter.h"
#include "verilog/tools/ls/document-symbol-filler.h"
#include "verilog/tools/ls/lsp-parse-buffer.h"

namespace verilog {
Expand Down Expand Up @@ -153,4 +155,23 @@ std::vector<verible::lsp::CodeAction> GenerateLinterCodeActions(
}
return result;
}

nlohmann::json CreateDocumentSymbolOutline(
const BufferTracker *tracker, const verible::lsp::DocumentSymbolParams &p,
bool kate_compatible_tags) {
if (!tracker) return nlohmann::json::array();
// Only if the tree has been fully parsed, it makes sense to create an outline
const ParsedBuffer *const last_good = tracker->last_good();
if (!last_good) return nlohmann::json::array();

verible::lsp::DocumentSymbol toplevel;
const auto &text_structure = last_good->parser().Data();
verilog::DocumentSymbolFiller filler(kate_compatible_tags, text_structure,
&toplevel);
const auto &syntax_tree = text_structure.SyntaxTree();
syntax_tree->Accept(&filler);
// We cut down one level, not interested in toplevel file:
return toplevel.children;
}

} // namespace verilog
9 changes: 9 additions & 0 deletions verilog/tools/ls/verible-lsp-adapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,14 @@ std::vector<verible::lsp::Diagnostic> CreateDiagnostics(const BufferTracker &);
// Generate code actions from autofixes provided by the linter.
std::vector<verible::lsp::CodeAction> GenerateLinterCodeActions(
const BufferTracker *tracker, const verible::lsp::CodeActionParams &p);

// Given a parse tree, generate a document symbol outline
// textDocument/documentSymbol request
// There is a workaround for the kate editor currently. Goal is to actually
// fix this upstream in the kate editor, but for now let's have an explicit
// boolean to make it visible what is needed.
nlohmann::json CreateDocumentSymbolOutline(
const BufferTracker *tracker, const verible::lsp::DocumentSymbolParams &p,
bool kate_compatible_tags = true);
} // namespace verilog
#endif // VERILOG_TOOLS_LS_VERIBLE_LSP_ADAPTER_H
11 changes: 10 additions & 1 deletion verilog/tools/ls/verilog_ls.cc
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ static InitializeResult InitializeServer(const nlohmann::json &params) {
{"change", 2}, // Incremental updates
},
},
{"codeActionProvider", true}, // Autofixes for lint errors
{"codeActionProvider", true}, // Autofixes for lint errors
{"documentSymbolProvider", true}, // Outline of file
};

return result;
Expand Down Expand Up @@ -140,6 +141,14 @@ int main(int argc, char *argv[]) {
parsed_buffers.FindBufferTrackerOrNull(p.textDocument.uri), p);
});

// Provide outline
dispatcher.AddRequestHandler(
"textDocument/documentSymbol",
[&parsed_buffers](const verible::lsp::DocumentSymbolParams &p) {
return verilog::CreateDocumentSymbolOutline(
parsed_buffers.FindBufferTrackerOrNull(p.textDocument.uri), p);
});

// The client sends a request to shut down. Use that to exit our loop.
bool shutdown_requested = false;
dispatcher.AddRequestHandler("shutdown",
Expand Down

0 comments on commit 0152c49

Please sign in to comment.