#ifdef HAVE_CONFIG_H # include "config.h" #endif #if HAVE_LIBCLANG #include #include #endif #include #include #include "edi_language_provider.h" #include "edi_config.h" #include "edi_private.h" #if HAVE_LIBCLANG static void _clang_commands_fallback_get(const char ***args, unsigned int *argc) { const char *argstr; argstr = "-I/usr/include/ " EFL_CFLAGS " " CLANG_INCLUDES " -Wall -Wextra"; *args = (const char **) eina_str_split_full(argstr, " ", 0, argc); } static void _clang_commands_get(const char *path, const char ***args, unsigned int *argc) { CXCompilationDatabase_Error error; CXCompilationDatabase database = NULL; CXCompileCommands commands; CXCompileCommand command; const char** arguments; unsigned int i, numargs, ignored = 0; if (edi_project_file_exists("compile_commands.json")) { database = clang_CompilationDatabase_fromDirectory(edi_project_get(), &error); } else if (edi_project_file_exists("build/compile_commands.json")) { char *build = edi_project_file_path_get("build"); database = clang_CompilationDatabase_fromDirectory(build, &error); free(build); } if (database == NULL || error == CXCompilationDatabase_CanNotLoadDatabase) { INF("Could not load compile_commands.json in %s", edi_project_get()); _clang_commands_fallback_get(args, argc); return; } commands = clang_CompilationDatabase_getCompileCommands(database, path); command = clang_CompileCommands_getCommand(commands, 0); numargs = clang_CompileCommand_getNumArgs(command); if (numargs == 0) { INF("File %s not found in compile_commands.json", path); _clang_commands_fallback_get(args, argc); return; } arguments = malloc(sizeof(char*) * (numargs + 1)); INF("Loading clang parameters for %s", path); arguments[0] = CLANG_INCLUDES; for(i = 1; i <= numargs; i++ ) { const char *argstr; CXString argument = clang_CompileCommand_getArg(command, i + 1); argstr = clang_getCString(argument); if (argstr && strlen(argstr) > 2 && argstr[0] == '-' && (argstr[1] == 'I' || argstr[1] == 'D')) arguments[i - ignored] = strdup(argstr); else ignored++; clang_disposeString(argument); } *args = arguments; *argc = numargs + 1 - ignored; clang_CompilationDatabase_dispose(database); } static void _clang_autosuggest_setup(Edi_Editor *editor) { Elm_Code *code; const char *path; const char **args; unsigned int argc; code = elm_code_widget_code_get(editor->entry); path = elm_code_file_path_get(code->file); //Initialize Clang _clang_commands_get(path, &args, &argc); editor->clang_idx = clang_createIndex(0, 0); editor->clang_unit = clang_parseTranslationUnit(editor->clang_idx, path, args, argc, NULL, 0, clang_defaultEditingTranslationUnitOptions() | CXTranslationUnit_DetailedPreprocessingRecord); } static void _clang_autosuggest_dispose(Edi_Editor *editor) { clang_disposeTranslationUnit(editor->clang_unit); clang_disposeIndex(editor->clang_idx); } #endif void _edi_language_c_add(Edi_Editor *editor) { #if HAVE_LIBCLANG _clang_autosuggest_setup(editor); #else (void) editor; #endif } void _edi_language_c_refresh(Edi_Editor *editor) { #if HAVE_LIBCLANG _clang_autosuggest_dispose(editor); _clang_autosuggest_setup(editor); #else (void) editor; #endif } void _edi_language_c_del(Edi_Editor *editor) { #if HAVE_LIBCLANG _clang_autosuggest_dispose(editor); #else (void) editor; #endif } const char * _edi_language_c_mime_name(const char *mime) { if (!strcasecmp(mime, "text/x-chdr")) return _("C header"); if (!strcasecmp(mime, "text/x-csrc")) return _("C source"); return NULL; } const char * _edi_language_c_snippet_get(const char *key) { if (!strcmp(key, "ret")) return "return"; if (!strcmp(key, "if")) return "if ()\n" \ " {\n" \ " }"; if (!strcmp(key, "ifel")) return "if ()\n" \ " {\n" \ " }\n" \ "else\n" \ " {\n" \ " }"; return NULL; } #if HAVE_LIBCLANG char * _edi_suggest_c_detail_get(Edi_Editor *editor, const char *term_str, const char *ret_str, const char *param_str) { char *format, *display; const char *font; int font_size, displen; unsigned int row, col; Evas_Coord w; elm_code_widget_font_get(editor->entry, &font, &font_size); elm_code_widget_cursor_position_get(editor->entry, &row, &col); elm_code_widget_geometry_for_position_get(editor->entry, row, col, NULL, NULL, &w, NULL); format = "%s
%s
%s"; displen = strlen(ret_str) + strlen(param_str) + strlen(term_str) + strlen(format) + strlen(font); display = malloc(sizeof(char) * displen); snprintf(display, displen, format, w, font, font_size, ret_str, term_str, param_str); return display; } #endif Eina_List * _edi_language_c_lookup(Edi_Editor *editor, unsigned int row, unsigned int col) { Eina_List *list = NULL; #if HAVE_LIBCLANG CXCodeCompleteResults *res; struct CXUnsavedFile unsaved_file; Elm_Code *code; const char *path = NULL; if (!editor->clang_unit) return list; code = elm_code_widget_code_get(editor->entry); if (code->file->file) path = elm_code_file_path_get(code->file); unsaved_file.Filename = path; unsaved_file.Contents = elm_code_widget_text_between_positions_get( editor->entry, 1, 1, row, col); unsaved_file.Length = strlen(unsaved_file.Contents); res = clang_codeCompleteAt(editor->clang_unit, path, row, col, &unsaved_file, 1, CXCodeComplete_IncludeMacros | CXCodeComplete_IncludeCodePatterns); clang_sortCodeCompletionResults(res->Results, res->NumResults); for (unsigned int i = 0; i < res->NumResults; i++) { const CXCompletionString str = res->Results[i].CompletionString; const char *name = NULL, *ret = NULL; char *param = NULL; Edi_Language_Suggest_Item *suggest_it; Eina_Strbuf *buf = NULL; suggest_it = calloc(1, sizeof(Edi_Language_Suggest_Item)); for (unsigned int j = 0; j < clang_getNumCompletionChunks(str); j++) { enum CXCompletionChunkKind ch_kind; const CXString str_out = clang_getCompletionChunkText(str, j); ch_kind = clang_getCompletionChunkKind(str, j); switch (ch_kind) { case CXCompletionChunk_ResultType: ret = clang_getCString(str_out); break; case CXCompletionChunk_TypedText: case CXCompletionChunk_Text: name = clang_getCString(str_out); break; case CXCompletionChunk_LeftParen: // todo buf == eina_strbuf_new(); case CXCompletionChunk_Placeholder: case CXCompletionChunk_Comma: case CXCompletionChunk_CurrentParameter: if (!buf) buf = eina_strbuf_new(); eina_strbuf_append(buf, clang_getCString(str_out)); break; case CXCompletionChunk_RightParen: eina_strbuf_append(buf, clang_getCString(str_out)); param = eina_strbuf_string_steal(buf); eina_strbuf_free(buf); buf = NULL; break; default: break; } } if (name) suggest_it->summary = strdup(name); suggest_it->detail = _edi_suggest_c_detail_get(editor, name, ret?ret:"", param?param:""); if (param) free(param); list = eina_list_append(list, suggest_it); } clang_disposeCodeCompleteResults(res); #else (void) editor; (void) row; (void) col; #endif return list; } #if HAVE_LIBCLANG static void _edi_doc_init(Edi_Language_Document *doc) { doc->title = eina_strbuf_new(); doc->detail = eina_strbuf_new(); doc->param = eina_strbuf_new(); doc->ret = eina_strbuf_new(); doc->see = eina_strbuf_new(); } static Eina_Bool _edi_doc_newline_check(Eina_Strbuf *strbuf) { const char *str; str = eina_strbuf_string_get(strbuf); if (strlen(str) < 4) return EINA_TRUE; str = str + strlen(str) - 4; if (!strcmp(str, "
")) return EINA_FALSE; else return EINA_TRUE; } static void _edi_doc_trim(Eina_Strbuf *strbuf) { const char *str; int cmp_strlen, ori_strlen; str = eina_strbuf_string_get(strbuf); ori_strlen = strlen(str); if (strlen(str) < 8) return; cmp_strlen = strlen(str) - 8; str += cmp_strlen; if (!strcmp(str, "

")) { eina_strbuf_remove(strbuf, cmp_strlen, ori_strlen); _edi_doc_trim(strbuf); } else return; } static void _edi_doc_title_get(CXCursor cursor, Eina_Strbuf *strbuf) { CXCompletionString str; int chunk_num; str = clang_getCursorCompletionString(cursor); chunk_num = clang_getNumCompletionChunks(str); for (int i = 0; i < chunk_num; i++) { enum CXCompletionChunkKind kind = clang_getCompletionChunkKind(str, i); switch (kind) { case CXCompletionChunk_ResultType: eina_strbuf_append_printf(strbuf, "%s
", clang_getCString(clang_getCompletionChunkText(str, i))); break; case CXCompletionChunk_Placeholder: eina_strbuf_append_printf(strbuf, "%s", clang_getCString(clang_getCompletionChunkText(str, i))); break; default: eina_strbuf_append(strbuf, clang_getCString(clang_getCompletionChunkText(str, i))); break; } } } static void _edi_doc_dump(Edi_Language_Document *doc, CXComment comment, Eina_Strbuf *strbuf) { const char *str ,*tag; enum CXCommentKind kind = clang_Comment_getKind(comment); if (kind == CXComment_Null) return; switch (kind) { case CXComment_Text: str = clang_getCString(clang_TextComment_getText(comment)); if (doc->see == strbuf) { eina_strbuf_append_printf(strbuf, " %s", str); break; } if (clang_Comment_isWhitespace(comment)) { if (_edi_doc_newline_check(strbuf)) { if (strbuf == doc->detail) eina_strbuf_append(strbuf, "

"); else eina_strbuf_append(strbuf, "
"); } break; } eina_strbuf_append(strbuf, str); break; case CXComment_InlineCommand: str = clang_getCString(clang_InlineCommandComment_getCommandName(comment)); if (str[0] == 'p') eina_strbuf_append_printf(strbuf, "%s", clang_getCString(clang_InlineCommandComment_getArgText(comment, 0))); else if (str[0] == 'c') eina_strbuf_append_printf(strbuf, "%s", clang_getCString(clang_InlineCommandComment_getArgText(comment, 0))); else eina_strbuf_append_printf(strbuf, "@%s", str); break; case CXComment_BlockCommand: tag = clang_getCString(clang_BlockCommandComment_getCommandName(comment)); if (!strcmp(tag, "return")) strbuf = doc->ret; else if (!strcmp(tag, "see")) strbuf = doc->see; break; case CXComment_ParamCommand: str = clang_getCString(clang_ParamCommandComment_getParamName(comment)); strbuf = doc->param; eina_strbuf_append_printf(strbuf, " %s", str); break; case CXComment_VerbatimBlockLine: str = clang_getCString(clang_VerbatimBlockLineComment_getText(comment)); if (str[0] == 10) { eina_strbuf_append(strbuf, "
"); break; } eina_strbuf_append_printf(strbuf, "%s
", str); break; case CXComment_VerbatimLine: str = clang_getCString(clang_VerbatimLineComment_getText(comment)); if (doc->see == strbuf) eina_strbuf_append(strbuf, str); break; default: break; } for (unsigned i = 0; i < clang_Comment_getNumChildren(comment); i++) _edi_doc_dump(doc, clang_Comment_getChild(comment, i), strbuf); } static CXCursor _edi_doc_cursor_get(Edi_Editor *editor, unsigned int row, unsigned int col) { CXFile cxfile; CXSourceLocation location; CXCursor cursor; Elm_Code *code; const char *path; code = elm_code_widget_code_get(editor->entry); path = elm_code_file_path_get(code->file); cxfile = clang_getFile(editor->clang_unit, path); location = clang_getLocation(editor->clang_unit, cxfile, row, col); cursor = clang_getCursor(editor->clang_unit, location); return clang_getCursorReferenced(cursor); } #endif static Edi_Language_Document * _edi_language_c_lookup_doc(Edi_Editor *editor, unsigned int row, unsigned int col) { Edi_Language_Document *doc = NULL; #if HAVE_LIBCLANG CXCursor cursor; CXComment comment; cursor = _edi_doc_cursor_get(editor, row, col); comment = clang_Cursor_getParsedComment(cursor); if (clang_Comment_getKind(comment) == CXComment_Null) { return NULL; } doc = malloc(sizeof(Edi_Language_Document)); _edi_doc_init(doc); _edi_doc_dump(doc, comment, doc->detail); _edi_doc_title_get(cursor, doc->title); _edi_doc_trim(doc->detail); #else (void) editor; (void) row; (void) col; #endif return doc; }