xref: /Universal-ctags/main/mini-geany.c (revision c2ed162e1c737a99a2eb6c23bff05ffdc10343c3)
1 /*
2 *   Copyright (c) 2019, Jiri Techet
3 *
4 *   This source code is released for free distribution under the terms of the
5 *   GNU General Public License version 2 or (at your option) any later version.
6 *
7 *   Provides a simple application using ctags as a library and using the same
8 *   set of ctags functions as the Geany editor
9 */
10 
11 #include "general.h"  /* must always come first */
12 
13 #include "types.h"
14 #include "routines.h"
15 #include "mio.h"
16 #include "error_p.h"
17 #include "writer_p.h"
18 #include "parse_p.h"
19 #include "options_p.h"
20 #include "trashbox_p.h"
21 #include "field_p.h"
22 #include "xtag_p.h"
23 #include "entry_p.h"
24 #include "param_p.h"
25 
26 #include <stdio.h>
27 #include <string.h>
28 #include <errno.h>
29 
30 static int writeEntry (tagWriter *writer, MIO * mio, const tagEntryInfo *const tag, void *clientData);
31 static void rescanFailed (tagWriter *writer, unsigned long validTagNum, void *clientData);
32 
33 /* we need to be able to provide a custom writer using which we collect the tags */
34 tagWriter customWriter = {
35 	.writeEntry = writeEntry,
36 	.writePtagEntry = NULL,
37 	.preWriteEntry = NULL,
38 	.postWriteEntry = NULL,
39 	.rescanFailedEntry = rescanFailed,
40 	.treatFieldAsFixed = NULL,
41 	.defaultFileName = "tags_file_which_should_never_appear_anywhere",
42 	.private = NULL,
43 };
44 
45 
46 /* we need to be able to provide an error printer which doesn't crash Geany on error */
nofatalErrorPrinter(const errorSelection selection,const char * const format,va_list ap,void * data CTAGS_ATTR_UNUSED)47 static bool nofatalErrorPrinter (const errorSelection selection,
48 					  const char *const format,
49 					  va_list ap, void *data CTAGS_ATTR_UNUSED)
50 {
51 	fprintf (stderr, "%s: ", (selection & WARNING) ? "Warning: " : "Error");
52 	vfprintf (stderr, format, ap);
53 	if (selection & PERROR)
54 #ifdef HAVE_STRERROR
55 		fprintf (stderr, " : %s", strerror (errno));
56 #else
57 		perror (" ");
58 #endif
59 	fputs ("\n", stderr);
60 
61 	return false;
62 }
63 
64 
enableRoles(unsigned int lang,unsigned int kind)65 static void enableRoles(unsigned int lang, unsigned int kind)
66 {
67 	unsigned int c = countLanguageRoles(lang, kind);
68 
69 	for (unsigned int i = 0; i < c; i++)
70 	{
71 		roleDefinition* rdef = getLanguageRole(lang, kind, (int)i);
72 		enableRole(rdef, true);
73 	}
74 }
75 
76 
77 /* we need to be able to enable all kinds and roles for all languages (some are disabled by default) */
enableKindsAndRoles()78 static void enableKindsAndRoles()
79 {
80 	unsigned int lang;
81 
82 	for (lang = 0; lang < countParsers(); lang++)
83 	{
84 		unsigned int kindNum = countLanguageKinds(lang);
85 		unsigned int kind;
86 
87 		for (kind = 0; kind < kindNum; kind++)
88 		{
89 			kindDefinition *def = getLanguageKind(lang, kind);
90 			enableKind(def, true);
91 			enableRoles(lang, kind);
92 		}
93 	}
94 }
95 
96 
97 /* we need to be able to initialize ctags like in main() but skipping some things */
ctagsInit(void)98 static void ctagsInit(void)
99 {
100 	initDefaultTrashBox ();
101 
102 	setErrorPrinter (nofatalErrorPrinter, NULL);
103 	setTagWriter (WRITER_CUSTOM, &customWriter);
104 
105 	checkRegex ();
106 	initFieldObjects ();
107 	initXtagObjects ();
108 
109 	initializeParsing ();
110 	initOptions ();
111 	initRegexOptscript ();
112 
113 	/* make sure all parsers are initialized */
114 	initializeParser (LANG_AUTO);
115 
116 	/* change default value which is false */
117 	enableXtag(XTAG_TAGS_GENERATED_BY_GUEST_PARSERS, true);
118 	enableXtag(XTAG_REFERENCE_TAGS, true);
119 
120 	/* some kinds and roles we are interested in are disabled by default */
121 	enableKindsAndRoles();
122 }
123 
124 
125 /* we need to be able to get a name of a given language */
ctagsGetLangName(int lang)126 static const char *ctagsGetLangName(int lang)
127 {
128 	return getLanguageName(lang);
129 }
130 
131 
132 /* we need to be able to get an int representing a given language */
ctagsGetNamedLang(const char * name)133 static int ctagsGetNamedLang(const char *name)
134 {
135 	return getNamedLanguage(name, 0);
136 }
137 
138 
139 /* we need to be able to get kind letters provided by a given language */
ctagsGetLangKinds(int lang)140 static const char *ctagsGetLangKinds(int lang)
141 {
142 	unsigned int kindNum = countLanguageKinds(lang);
143 	static char kinds[257];
144 	unsigned int i;
145 
146 	for (i = 0; i < kindNum; i++)
147 		kinds[i] = getLanguageKind(lang, i)->letter;
148 	kinds[i] = '\0';
149 
150 	return kinds;
151 }
152 
153 
154 /* we need to be able to get kind name from a kind letter for a given language */
ctagsGetKindName(char kind,int lang)155 static const char *ctagsGetKindName(char kind, int lang)
156 {
157 	kindDefinition *def = getLanguageKindForLetter (lang, kind);
158 	return def ? def->name : "unknown";
159 }
160 
161 
162 /* we need to be able to get kind letter from a kind name for a given language */
ctagsGetKindFromName(const char * name,int lang)163 static char ctagsGetKindFromName(const char *name, int lang)
164 {
165 	kindDefinition *def = getLanguageKindForName (lang, name);
166 	return def ? def->letter : '-';
167 }
168 
169 
170 /* we need to be able to get kind letter from a kind index */
ctagsGetKindFromIndex(int index,int lang)171 static char ctagsGetKindFromIndex(int index, int lang)
172 {
173 	return getLanguageKind(lang, index)->letter;
174 }
175 
176 
177 /* we need to be able to get the number of parsers */
ctagsGetLangCount(void)178 static unsigned int ctagsGetLangCount(void)
179 {
180 	return countParsers();
181 }
182 
183 
addIgnoreSymbol(const char * value)184 void addIgnoreSymbol(const char *value)
185 {
186 	langType lang = getNamedLanguage ("CPreProcessor", 0);
187 	applyParameter (lang, "ignore", value);
188 }
189 
190 /*******************************************************************************
191  * So let's just use what we have for our great client...
192  ******************************************************************************/
193 
194 /* our internal tag representation - this is all the tag information we use in Geany */
195 typedef struct {
196 	char *name;
197 	char *signature;
198 	char *scopeName;
199 	char *inheritance;
200 	char *varType;
201 	char *access;
202 	char *implementation;
203 	char kindLetter;
204 	bool isFileScope;
205 	unsigned long lineNumber;
206 	int lang;
207 	bool isAnon;
208 } Tag;
209 
210 
createTag(const tagEntryInfo * info)211 static Tag *createTag(const tagEntryInfo *info)
212 {
213 	Tag *tag = xCalloc(1, Tag);
214 	if (info->name)
215 		tag->name = eStrdup(info->name);
216 	if (info->extensionFields.signature)
217 		tag->signature = eStrdup(info->extensionFields.signature);
218 	if (info->extensionFields.scopeName)
219 		tag->scopeName = eStrdup(info->extensionFields.scopeName);
220 	if (info->extensionFields.inheritance)
221 		tag->inheritance = eStrdup(info->extensionFields.inheritance);
222 	if (info->extensionFields.typeRef[1])
223 		tag->varType = eStrdup(info->extensionFields.typeRef[1]);
224 	if (info->extensionFields.access)
225 		tag->access = eStrdup(info->extensionFields.access);
226 	if (info->extensionFields.implementation)
227 		tag->implementation = eStrdup(info->extensionFields.implementation);
228 	tag->kindLetter = ctagsGetKindFromIndex(info->kindIndex, info->langType);
229 	tag->isFileScope = info->isFileScope;
230 	tag->lineNumber = info->lineNumber;
231 	tag->lang = info->langType;
232 	tag->isAnon = isTagExtraBitMarked(info, XTAG_ANONYMOUS);
233 	return tag;
234 }
235 
236 
destroyTag(Tag * tag)237 static void destroyTag(Tag *tag)
238 {
239 	if (tag->name)
240 		eFree(tag->name);
241 	if (tag->signature)
242 		eFree(tag->signature);
243 	if (tag->scopeName)
244 		eFree(tag->scopeName);
245 	if (tag->inheritance)
246 		eFree(tag->inheritance);
247 	if (tag->varType)
248 		eFree(tag->varType);
249 	if (tag->access)
250 		eFree(tag->access);
251 	if (tag->implementation)
252 		eFree(tag->implementation);
253 	eFree(tag);
254 }
255 
256 
257 /* callback from ctags informing us about a new tag */
writeEntry(tagWriter * writer,MIO * mio,const tagEntryInfo * const tag,void * clientData)258 static int writeEntry (tagWriter *writer, MIO *mio, const tagEntryInfo *const tag, void *clientData)
259 {
260 	Tag *t;
261 
262 	/* apparently we have to call this to get the scope info - maybe we can
263 	 * specify some option during initialization so we don't have to cal this
264 	 * ?????? */
265 	getTagScopeInformation((tagEntryInfo *)tag, NULL, NULL);
266 
267 	/* convert tags into our internal representation and store them into an array */
268 	t = createTag(tag);
269 	ptrArrayAdd((ptrArray *)clientData, t);
270 
271 	/* output length - we don't write anything to the MIO */
272 	return 0;
273 }
274 
275 
276 /* scan has failed so we have invalid tags in our array - validTagNum should
277  * tell us the number of valid tags so remove all the extra tags and shrink the array */
rescanFailed(tagWriter * writer,unsigned long validTagNum,void * clientData)278 static void rescanFailed (tagWriter *writer, unsigned long validTagNum, void *clientData)
279 {
280 	ptrArray *tagArray = clientData;
281 	int num = ptrArrayCount(tagArray);
282 
283 	if (num > validTagNum)
284 	{
285 		int i;
286 		for (i = validTagNum; i < num; i++)
287 		{
288 			Tag *tag = ptrArrayLast(tagArray);
289 			destroyTag(tag);
290 			ptrArrayRemoveLast(tagArray);
291 		}
292 	}
293 }
294 
295 
296 /* do whatever we want to do with the tags */
processCollectedTags(ptrArray * tagArray)297 static void processCollectedTags(ptrArray *tagArray)
298 {
299 	int i;
300 	int num = ptrArrayCount(tagArray);
301 
302 	for (i = 0; i < num; i++)
303 	{
304 		Tag *tag = ptrArrayItem(tagArray, i);
305 		printf("%s\tline: %lu\tkind: %s\t lang: %s\n",
306 			tag->name,
307 			tag->lineNumber,
308 			ctagsGetKindName(tag->kindLetter, tag->lang),
309 			ctagsGetLangName(tag->lang));
310 	}
311 
312 	/* prepare for the next parsing by clearing the tag array */
313 	ptrArrayClear(tagArray);
314 }
315 
316 
main(int argc,char ** argv)317 extern int main (int argc, char **argv)
318 {
319 	/* called once when Geany starts */
320 	ctagsInit();
321 
322 	/* create empty tag array *
323 	 * this is where we store the collected tags
324 	 * NOTE: Geany doesn't use the ptrArray type - it is used just for the purpose of this demo */
325 	ptrArray *tagArray = ptrArrayNew((ptrArrayDeleteFunc)destroyTag);
326 
327 	printf("This parser only parses C files - provide them as arguments on the "
328 			"command line or get a hard-coded buffer parsed when no arguments "
329 			"are provided\n\n");
330 	if (argc == 1)  /* parsing contents of a buffer */
331 	{
332 		char *program = "FOO int foo() {}\n\n int bar() {}\n\n int main() {}\n";
333 		int lang = ctagsGetNamedLang("C");
334 		const char *kinds = ctagsGetLangKinds(lang);
335 		int i;
336 
337 		/* we need to be able to set and clear ignore symbols */
338 		addIgnoreSymbol("int");
339 		/* clear */
340 		addIgnoreSymbol(NULL);
341 		/* set to something else */
342 		addIgnoreSymbol("FOO");
343 
344 		printf("Number of all parsers is: %d\n", ctagsGetLangCount());
345 		printf("We are parsing %s which provides the following kinds:\n",
346 			ctagsGetLangName(lang));
347 		for (i = 0; kinds[i] != '\0'; i++)
348 		{
349 			printf("%c: %s\n",
350 				/* back and forth conversion - the same like just kinds[i] */
351 				ctagsGetKindFromName(ctagsGetKindName(kinds[i], lang), lang),
352 				ctagsGetKindName(kinds[i], lang));
353 		}
354 
355 		printf("\nParsing buffer:\n");
356 		/* we always specify the language by ourselves and don't use ctags detection */
357 		parseRawBuffer("whatever", (unsigned char *)program, strlen(program), lang, tagArray);
358 
359 		processCollectedTags(tagArray);
360 	}
361 	else  /* parsing contents of a file */
362 	{
363 		int i;
364 		for (i = 1; i < argc; i++)
365 		{
366 			printf("\nParsing %s:\n", argv[i]);
367 			/* parseRawBuffer() is called repeatadly during Geany execution */
368 			parseRawBuffer(argv[i], NULL, 0, getNamedLanguage("C", 0), tagArray);
369 
370 			processCollectedTags(tagArray);
371 		}
372 	}
373 
374 	ptrArrayDelete(tagArray);
375 
376 	return 0;
377 }
378