1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * anjuta-modeline.c
4  * Copyright (C) Sébastien Granjoux 2013 <seb.sfo@free.fr>
5  *
6  * This program is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14  * See the GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /**
21  * SECTION:anjuta-modeline
22  * @short_description: Parse editor mode line
23  * @see_also:
24  * @stability: Unstable
25  * @include: libanjuta/anjuta-modeline.h
26  *
27  */
28 
29 #include "anjuta-modeline.h"
30 
31 #include "anjuta-debug.h"
32 
33 #include <glib-object.h>
34 
35 #include <stdlib.h>
36 #include <string.h>
37 
38 /* Types declarations
39  *---------------------------------------------------------------------------*/
40 
41 enum {
42 	SET_USE_SPACES = 1 << 0,
43 	SET_STATEMENT_INDENTATION = 1 << 1,
44 	SET_TAB_SIZE = 1 << 2,
45 	CHECK_NEXT = 1 << 4
46 };
47 
48 typedef struct {
49 	gint settings;
50 
51 	gint use_spaces;
52 	gint statement_indentation;
53 	gint tab_size;
54 } IndentationParams;
55 
56 
57 /* Helpers functions
58  *---------------------------------------------------------------------------*/
59 
60 /* Private functions
61  *---------------------------------------------------------------------------*/
62 
63 static gchar *
get_editor_line(IAnjutaEditor * editor,gint line)64 get_editor_line (IAnjutaEditor *editor, gint line)
65 {
66 	IAnjutaIterable *start;
67 	IAnjutaIterable *end;
68 	gchar *content = NULL;
69 
70 	if (line < 0)
71 	{
72 		gint last;
73 
74 		end = ianjuta_editor_get_end_position(editor, NULL);
75 		last = ianjuta_editor_get_line_from_position (editor, end, NULL);
76 		line = last + line;
77 		g_object_unref (end);
78 	}
79 	if (line > 0)
80 	{
81 		start = ianjuta_editor_get_line_begin_position (editor, line, NULL);
82 		end = ianjuta_editor_get_line_end_position (editor, line, NULL);
83 		content = ianjuta_editor_get_text (editor, start, end, NULL);
84 		g_object_unref (start);
85 		g_object_unref (end);
86 	}
87 
88 	return content;
89 }
90 
91 static void
set_vim_params(IndentationParams * params,const gchar * key,const gchar * value)92 set_vim_params (IndentationParams *params, const gchar *key, const gchar *value)
93 {
94 	//DEBUG_PRINT ("Setting indent param: %s = %s", key, value);
95 	if ((strcmp (key, "expandtab") == 0) ||
96 	    (strcmp (key, "et") == 0))
97 	{
98 		params->use_spaces = 1;
99 		params->settings |= SET_USE_SPACES;
100 	}
101 	else if ((strcmp (key, "noexpandtab") == 0) ||
102 	         (strcmp (key, "noet") == 0))
103 	{
104 		params->use_spaces = 0;
105 		params->settings |= SET_USE_SPACES;
106 	}
107 	else if ((strcmp (key, "shiftwidth") == 0) ||
108 	         (strcmp (key, "sw") == 0))
109 	{
110 		params->statement_indentation = atoi (value);
111 		params->settings |= SET_STATEMENT_INDENTATION;
112 	}
113 	else if ((strcmp (key, "softtabstop") == 0) ||
114 	         (strcmp (key, "sts") == 0) ||
115 	         (strcmp (key, "tabstop") == 0) ||
116 	         (strcmp (key, "ts") == 0))
117 	{
118 		params->tab_size = atoi (value);
119 		params->settings |= SET_TAB_SIZE;
120 	}
121 }
122 
123 static gboolean
parse_vim_modeline(IndentationParams * params,const gchar * line,gint linenum)124 parse_vim_modeline (IndentationParams *params, const gchar *line, gint linenum)
125 {
126 	gchar *ptr;
127 	const gchar *end;
128 	gchar *key;
129 	gchar *value;
130 
131 	/* Check the first 5 and last 5 lines */
132 	if ((linenum < -5) || (linenum == 0) || (linenum > 5))
133 	{
134 		return FALSE;
135 	}
136 
137 	ptr = strstr (line, "vim:");
138 	if (ptr == NULL)
139 	{
140 		if ((linenum != -5) && (linenum != 5)) params->settings = CHECK_NEXT;
141 		return FALSE;
142 	}
143 	ptr += 4;
144 	while (g_ascii_isspace (*ptr)) ptr++;
145 	if (strncmp (ptr, "set", 3) != 0)
146 	{
147 		if ((linenum != -5) && (linenum != 5)) params->settings = CHECK_NEXT;
148 		return FALSE;
149 	}
150 	ptr += 3;
151 
152 	for (end = ptr; *end != '\0'; end++)
153 	{
154 		if ((*end == ':') && (*(end-1) != '\\')) break;
155 	}
156 
157 	while (ptr != end)
158 	{
159 		gchar sep;
160 
161 		while (g_ascii_isspace (*ptr)) ptr++;
162 		if (ptr == end) break;
163 
164 		/* Get key */
165 		key = ptr++;
166 		value = NULL;
167 		while ((ptr != end) && (*ptr != '=') && !g_ascii_isspace(*ptr)) ptr++;
168 		sep = *ptr;
169 		*ptr = '\0';
170 
171 		if (sep == '=')
172 		{
173 			/* Get value */
174 			value = ++ptr;
175 			while ((ptr != end) && !g_ascii_isspace(*ptr)) ptr++;
176 			sep = *ptr;
177 			*ptr = '\0';
178 
179 			if (sep != '\0') ptr++;
180 		}
181 
182 		set_vim_params (params, key, value);
183 	}
184 
185 	return TRUE;
186 }
187 
188 static void
set_emacs_params(IndentationParams * params,const gchar * key,const gchar * value)189 set_emacs_params (IndentationParams *params, const gchar *key, const gchar *value)
190 {
191 	//DEBUG_PRINT ("Setting indent param: %s = %s", key, value);
192 	if (strcmp (key, "indent-tabs-mode") == 0)
193 	{
194 		if (strcmp (value, "t") == 0)
195 		{
196 			params->use_spaces = 0;
197 			params->settings |= SET_USE_SPACES;
198 		}
199 		else if (strcmp (value, "nil") == 0)
200 		{
201 			params->use_spaces = 1;
202 			params->settings |= SET_USE_SPACES;
203 		}
204 	}
205 	else if ((strcmp (key, "c-basic-offset") == 0) ||
206 		(strcmp (key, "indent-offset") == 0))
207 	{
208 		params->statement_indentation = atoi (value);
209 		params->settings |= SET_STATEMENT_INDENTATION;
210 	}
211 	else if (strcasecmp (key, "tab-width") == 0)
212 	{
213 		params->tab_size = atoi (value);
214 		params->settings |= SET_TAB_SIZE;
215 	}
216 }
217 
218 static gboolean
parse_emacs_modeline(IndentationParams * params,gchar * line,gint linenum)219 parse_emacs_modeline (IndentationParams *params, gchar *line, gint linenum)
220 {
221 	gchar *ptr;
222 	gchar *end;
223 	gchar *key;
224 	gchar *value;
225 
226 	if (linenum == 1)
227 	{
228 		/* If first line is a shebang, check second line */
229 		if ((line[0] == '#') && (line[1] =='!'))
230 		{
231 			params->settings |= CHECK_NEXT;
232 			return FALSE;
233 		}
234 	}
235 	else if (linenum != 2)
236 	{
237 		/* Check only the 2 first lines */
238 		return FALSE;
239 	}
240 
241 	ptr = strstr (line, "-*-");
242 	if (ptr == NULL) return FALSE;
243 	ptr += 3;
244 	end = strstr (ptr, "-*-");
245 	if (end == NULL) return FALSE;
246 	*end = '\0';
247 
248 	while (*ptr != '\0')
249 	{
250 		gchar sep;
251 
252 		while (g_ascii_isspace (*ptr)) ptr++;
253 		if (*ptr == '\0') break;
254 
255 		/* Get key */
256 		key = ptr++;
257 		value = NULL;
258 		while ((*ptr != '\0') && (*ptr != ':') && (*ptr != ';')) ptr++;
259 		sep = *ptr;
260 
261 		end = ptr - 1;
262 		while (g_ascii_isspace (*end)) end--;
263 		*(end + 1) = '\0';
264 
265 		if (sep == ':')
266 		{
267 			/* Get value */
268 			ptr++;
269 			while (g_ascii_isspace (*ptr)) ptr++;
270 			if (*ptr != '\0')
271 			{
272 				value = ptr;
273 				while ((*ptr != '\0') && (*ptr != ';')) ptr++;
274 				sep = *ptr;
275 
276 				end = ptr - 1;
277 				while (g_ascii_isspace (*end)) end--;
278 				*(end + 1) = '\0';
279 
280 				if (sep == ';') ptr++;
281 			}
282 		}
283 
284 		set_emacs_params (params, key, value);
285 	}
286 
287 	return TRUE;
288 }
289 
290 
291 static gboolean
set_indentation(IAnjutaEditor * editor,IndentationParams * params)292 set_indentation (IAnjutaEditor *editor, IndentationParams *params)
293 {
294 	if (params->settings == 0) return FALSE;
295 
296 	if (params->settings & SET_USE_SPACES)
297 		ianjuta_editor_set_use_spaces (editor, params->use_spaces, NULL);
298 
299 	if (params->settings & SET_STATEMENT_INDENTATION)
300 		ianjuta_editor_set_indentsize (editor, params->statement_indentation, NULL);
301 
302 	if (params->settings & SET_TAB_SIZE)
303 		ianjuta_editor_set_tabsize (editor, params->tab_size, NULL);
304 
305 	return TRUE;
306 }
307 
308 
309 /* Public functions
310  *---------------------------------------------------------------------------*/
311 
312 
313 /**
314  * anjuta_apply_modeline:
315  * @editor: #IAnjutaEditor object
316  *
317  * Check the editor buffer to find a mode line and update the indentation
318  * settings if found.
319  *
320  * The mode line is special line used by the text editor to define settings for
321  * the current file, typically indentation. Anjuta currently recognize two kinds
322  * of mode line:
323  *
324  * Emacs mode line, on the first or the second line if the first one is a
325  * shebang (#!) with the following format:
326  * -*- key1: value1; key2: value2 -*-
327  *
328  * Vim mode line, one the first 5 or the last 5 lines with the following format:
329  * vim:set key1=value1 key2=value2
330  *
331  * Returns: %TRUE if a mode line has been found and applied.
332  */
333 gboolean
anjuta_apply_modeline(IAnjutaEditor * editor)334 anjuta_apply_modeline (IAnjutaEditor *editor)
335 {
336 	IndentationParams params = {CHECK_NEXT,0,0,0};
337 	gint line;
338 	gchar *content = NULL;
339 
340 	g_return_val_if_fail (IANJUTA_IS_EDITOR (editor), FALSE);
341 
342 	/* Check the first lines */
343 	for (line = 1; params.settings == CHECK_NEXT; line++)
344 	{
345 		g_free (content);
346 		content = get_editor_line (editor, line);
347 		if (content == NULL) return FALSE;
348 
349 		params.settings = 0;
350 		if (parse_vim_modeline (&params, content, line)) break;
351 		if (parse_emacs_modeline (&params, content, line)) break;
352 	}
353 
354 	/* Check the last lines */
355 	if (params.settings == 0) params.settings = CHECK_NEXT;
356 	for (line = -1;params.settings == CHECK_NEXT; line--)
357 	{
358 		g_free (content);
359 		content = get_editor_line (editor, line);
360 		if (content == NULL) return FALSE;
361 
362 		params.settings = 0;
363 		if (parse_vim_modeline (&params, content, line)) break;
364 		if (parse_emacs_modeline (&params, content, line)) break;
365 	}
366 	g_free (content);
367 
368 	/* Set indentation settings */
369 	return set_indentation (editor, &params);
370 }
371