Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the main part of the xref interface #504

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions irony-xref.el
Original file line number Diff line number Diff line change
@@ -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))))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debug?



;;
;; 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
3 changes: 3 additions & 0 deletions irony.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be optional?
I think some people use irony for completion only, and sometime use rtags or something else for cross reference.

(irony-completion--enter))

(defun irony--mode-exit ()
Expand Down
2 changes: 2 additions & 0 deletions server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debug?


# Disable exception, we aren't using them and right now clang-cl needs them
# disabled to parse Windows headers.
#
Expand Down
10 changes: 10 additions & 0 deletions server/src/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,16 @@ Command *CommandParser::parse(const std::vector<std::string> &argv) {
std::vector<std::function<bool(const std::string &)>> 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;
Expand Down
2 changes: 2 additions & 0 deletions server/src/Commands.def
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
90 changes: 90 additions & 0 deletions server/src/Irony.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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<CXChildVisitResult(CXCursor, CXCursor)> 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<Visitor *>(f))(c, p);
},
&visit);
}

std::cout << ")" << std::endl;
}
10 changes: 10 additions & 0 deletions server/src/Irony.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions server/src/TUManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ class TUManager : public util::NonCopyable {
*/
void invalidateAllCachedTUs();

const std::map<const std::string, CXTranslationUnit>
allAvailableTranslationUnits() const {
return translationUnits_;
}

private:
/**
* \brief Get a reference to the translation unit that matches \p filename
Expand Down
8 changes: 8 additions & 0 deletions server/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down