diff --git a/irony-xref.el b/irony-xref.el new file mode 100644 index 00000000..bd63f902 --- /dev/null +++ b/irony-xref.el @@ -0,0 +1,140 @@ +;;; irony-xref.el --- Irony xref interface +;; +;;; Commentary: +;; +;; To enable, run +;; +;; (add-hook 'irony-mode-hook (lambda () (add-hook 'xref-backend-functions #'irony--xref-backend nil t))) +;; +;; Features: +;; - will jump into system headers +;; - with overloaded functions, will jump to the right function definition +;; (it's not just string matching on the function name) +;; +;; Missing commands: +;; - ‘xref-find-apropos’ +;; +;;; Code: + +(require 'irony) +(require 'irony-completion) + +(require 'xref) +(require 'dash) + + +;; +;; IO tasks +;; + +(irony-iotask-define-task irony--t-xref-definitions + "`xref-definitions' server command." + :start (lambda (line col) + (irony--server-send-command "xref-definitions" line col)) + :update irony--server-query-update) + +(irony-iotask-define-task irony--t-xref-references + "`xref-references' server command." + :start (lambda (line col) + (irony--server-send-command "xref-references" line col)) + :update irony--server-query-update) + + +;; +;; Functions +;; + +;;;###autoload +(defun irony-find-definitions (&optional pos) + (interactive) + (let* ((line-column (irony--completion-line-column pos)) + (line (car line-column)) + (column (cdr line-column))) + (irony--run-task + (irony-iotask-chain + (irony--parse-task) ; FIXME Is parsing even necessary here? + (irony-iotask-package-task irony--t-xref-definitions line column))))) + +;;;###autoload +(defun irony-find-references (&optional pos) + (interactive) + (let* ((line-column (irony--completion-line-column pos)) + (result (irony--run-task + (irony-iotask-chain + (irony--parse-task) ; FIXME Is parsing necessary here? + (irony-iotask-package-task irony--t-xref-references + (car line-column) (cdr line-column)))))) + (cl-loop for item in result + do (message "%S" item)))) + + +;; +;; Xref backend +;; + +;;;###autoload +(defun irony--xref-backend () 'irony) + +(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql irony))) + "A dummy completion table." + nil) + +(cl-defmethod xref-backend-identifier-at-point ((_backend (eql irony))) + ;; FIXME These propertized strings are not suitable for completion + ;; FIXME which xref asks for + (let* ((bounds (irony-completion-symbol-bounds)) + (start (car bounds)) + (end (cdr bounds)) + ;; FIXME Is (buffer-file-name) the right thing here? + (file (buffer-file-name)) + (buffer (current-buffer)) + (line-column (irony--completion-line-column start)) + (thing (buffer-substring-no-properties start end))) + (put-text-property 0 (length thing) 'irony-xref (list file buffer start end) thing) + thing)) + +(cl-defmethod xref-backend-definitions ((_backend (eql irony)) identifier) + (-when-let* + ((thing (get-text-property 0 'irony-xref identifier)) + (buffer (nth 1 thing)) + (line-column (irony--completion-line-column (nth 2 thing))) + (result + ;; FIXME Must this be synchronous? + (irony--run-task + (irony-iotask-chain + (irony--parse-task buffer) + (irony-iotask-package-task irony--t-xref-definitions + (car line-column) (cdr line-column)))))) + (cl-loop + for (kind name filename line column start end) in result + collect + (xref-make (concat name "(" (symbol-name kind) ")") (xref-make-file-location filename line column))))) + +(cl-defmethod xref-backend-references ((_backend (eql irony)) identifier) + (-when-let* + ((thing (get-text-property 0 'irony-xref identifier)) + (buffer (nth 1 thing)) + (line-column (irony--completion-line-column (nth 2 thing))) + (result + ;; FIXME Must this be synchronous? + (irony--run-task + (irony-iotask-chain + (irony--parse-task buffer) + (irony-iotask-package-task irony--t-xref-references + (car line-column) (cdr line-column)))))) + (cl-loop + for (kind name filename line column start end) in result + collect + (xref-make name (xref-make-file-location filename line column))))) + + +;; Setting up xref + +(defun irony-xref--enter () + (add-hook 'xref-backend-functions #'irony--xref-backend nil t)) + +(defun irony-xref--exit () + (remove-hook 'xref-backend-functions #'irony--xref-backend t)) + +(provide 'irony-xref) +;;; irony-xref.el ends here diff --git a/irony.el b/irony.el index ec4ff8bd..1aec9a3e 100644 --- a/irony.el +++ b/irony.el @@ -50,6 +50,8 @@ (autoload 'irony-completion--enter "irony-completion") (autoload 'irony-completion--exit "irony-completion") +(autoload 'irony-xref--enter "irony-xref") +(autoload 'irony-xref--exit "irony-xref") (require 'cl-lib) @@ -426,6 +428,7 @@ If no such file exists on the filesystem the special file '-' is (display-warning 'irony "Performance will be bad because a\ pipe delay is set for this platform (see variable\ `w32-pipe-read-delay').")))) + (irony-xref--enter) (irony-completion--enter)) (defun irony--mode-exit () diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index a1838d8e..e24a8060 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -14,6 +14,8 @@ include(CTest) check_for_in_source_build() release_as_default_build_type() +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0 -fsanitize=address -fsanitize=undefined -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded -Wno-missing-prototypes -Wno-shadow-field-in-constructor") + # Disable exception, we aren't using them and right now clang-cl needs them # disabled to parse Windows headers. # diff --git a/server/src/Command.cpp b/server/src/Command.cpp index a3c35c87..9d96ddf5 100644 --- a/server/src/Command.cpp +++ b/server/src/Command.cpp @@ -188,6 +188,16 @@ Command *CommandParser::parse(const std::vector &argv) { std::vector> positionalArgs; switch (command_.action) { + case Command::XrefDefinitions: + positionalArgs.push_back(UnsignedIntConverter(&command_.line)); + positionalArgs.push_back(UnsignedIntConverter(&command_.column)); + break; + + case Command::XrefReferences: + positionalArgs.push_back(UnsignedIntConverter(&command_.line)); + positionalArgs.push_back(UnsignedIntConverter(&command_.column)); + break; + case Command::SetDebug: positionalArgs.push_back(OptionConverter(&command_.opt)); break; diff --git a/server/src/Commands.def b/server/src/Commands.def index f99d8af1..704e352d 100644 --- a/server/src/Commands.def +++ b/server/src/Commands.def @@ -15,6 +15,8 @@ X(Candidates, "candidates", "PREFIX STYLE - print completion candidates (require previous complete). " "STYLE is \"exact\", \"case-insensitive\" or \"smart-case\"") +X(XrefDefinitions, "xref-definitions", "LINE COL - print definition/reference") +X(XrefReferences, "xref-references", "LINE COL - print all uses") X(Complete, "complete", "FILE LINE COL [-- [COMPILE_OPTIONS...]] - perform code completion at a given location") X(CompletionDiagnostics, "completion-diagnostics", diff --git a/server/src/Irony.cpp b/server/src/Irony.cpp index fcb877c2..114f2457 100644 --- a/server/src/Irony.cpp +++ b/server/src/Irony.cpp @@ -115,6 +115,32 @@ bool readFileContent(const std::string &filename, return true; } +void prettyPrintCursor(std::string label, CXCursor cursor) { + if (clang_Cursor_isNull(cursor)) { + // std::cout << "(" << label << ")\n"; + return; + } + CXString name = clang_getCursorDisplayName(cursor); + CXSourceRange loc = clang_getCursorExtent(cursor); + CXSourceLocation start = clang_getRangeStart(loc), + end = clang_getRangeEnd(loc); + CXFile file; + unsigned startLine, startCol, startOffset, endOffset; + clang_getSpellingLocation(start, &file, &startLine, &startCol, + &startOffset); + clang_getSpellingLocation(end, nullptr, nullptr, nullptr, &endOffset); + CXString filename = clang_getFileName(file); + // FIXME It would be nice to print cursor’s enclosing statement, or similar + // FIXME But there doesn’t seem to be a clear way to do it without going + // FIXME throught the whole AST. + std::cout << "(" << label << " " << support::quoted(clang_getCString(name)) + << " " << support::quoted(clang_getCString(filename)) << " " + << startLine << " " << startCol << " " << startOffset << " " + << endOffset << ")\n"; + clang_disposeString(name); + clang_disposeString(filename); +} + } // unnamed namespace Irony::Irony() @@ -649,3 +675,67 @@ void Irony::getCompileOptions(const std::string &buildDir, clang_CompilationDatabase_dispose(db); #endif } + +void Irony::xrefDefinitions(unsigned line, unsigned col) const { + if (activeTu_ == nullptr) { + std::clog << "W: xref-definitions - parse wasn't called\n"; + std::cout << "nil" << std::endl; + return; + } + CXFile cxFile = clang_getFile(activeTu_, file_.c_str()); + CXCursor c = clang_getCursor(activeTu_, + clang_getLocation(activeTu_, cxFile, line, col)); + + CXCursor def = clang_getCursorDefinition(c), + ref = clang_getCursorReferenced(c); + // FIXME If the cursors are in system headers, should we print nil? + // if (clang_Location_isInSystemHeader(clang_getCursorLocation(ref))) { + // std::cout << "nil" << std::endl; + // return; + // } + std::cout << "("; + prettyPrintCursor("reference", ref); + if (!clang_Cursor_isNull(def) && !clang_equalCursors(def, ref)) + prettyPrintCursor("definition", def); + std::cout << ")" << std::endl; +} + +void Irony::xrefReferences(unsigned line, unsigned col) const { + if (activeTu_ == nullptr) { + std::clog << "W: xref-references - parse wasn't called\n"; + std::cout << "nil\n"; + return; + } + CXFile cxFile = clang_getFile(activeTu_, file_.c_str()); + CXCursor what = clang_getCursor( + activeTu_, clang_getLocation(activeTu_, cxFile, line, col)); + if (!clang_Cursor_isNull(clang_getCursorReferenced(what))) + what = clang_getCursorReferenced(what); + what = clang_getCanonicalCursor(what); + + std::cout << "("; + + for (auto fileTu : tuManager_.allAvailableTranslationUnits()) { + CXCursor tuCursor = clang_getTranslationUnitCursor(fileTu.second); + + typedef std::function Visitor; + Visitor visit = [&](CXCursor cursor, CXCursor) { + // Skip all system headers, because they are generally unreadable. + if (clang_Location_isInSystemHeader(clang_getCursorLocation(cursor))) + return CXChildVisit_Continue; + CXCursor ref = clang_getCursorReferenced(cursor); + if (clang_equalCursors(what, clang_getCanonicalCursor(ref))) { + prettyPrintCursor("xref", cursor); + return CXChildVisit_Continue; + } + return CXChildVisit_Recurse; + }; + clang_visitChildren(tuCursor, + [](CXCursor c, CXCursor p, void *f) { + return (*static_cast(f))(c, p); + }, + &visit); + } + + std::cout << ")" << std::endl; +} diff --git a/server/src/Irony.h b/server/src/Irony.h index 1a23bd05..83286e65 100644 --- a/server/src/Irony.h +++ b/server/src/Irony.h @@ -109,6 +109,16 @@ class Irony { /// \pre complete() was called. void completionDiagnostics() const; + /// Lookup definition/declaration of the given symbol. + /// + /// \pre parse() was called. + void xrefDefinitions(unsigned line, unsigned col) const; + + /// Get all references to the given symbol. + /// + /// \pre parse() was called + void xrefReferences(unsigned line, unsigned col) const; + /// \brief Get compile options from JSON database. /// /// \param buildDir Directory containing compile_commands.json diff --git a/server/src/TUManager.h b/server/src/TUManager.h index bd7730dd..ed7fac72 100644 --- a/server/src/TUManager.h +++ b/server/src/TUManager.h @@ -82,6 +82,11 @@ class TUManager : public util::NonCopyable { */ void invalidateAllCachedTUs(); + const std::map + allAvailableTranslationUnits() const { + return translationUnits_; + } + private: /** * \brief Get a reference to the translation unit that matches \p filename diff --git a/server/src/main.cpp b/server/src/main.cpp index 77975286..2a14ead4 100644 --- a/server/src/main.cpp +++ b/server/src/main.cpp @@ -176,6 +176,14 @@ int main(int ac, const char *av[]) { } switch (c->action) { + case Command::XrefDefinitions: + irony.xrefDefinitions(c->line, c->column); + break; + + case Command::XrefReferences: + irony.xrefReferences(c->line, c->column); + break; + case Command::Help: printHelp(); break;