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 (¶ms, content, line)) break;
351 if (parse_emacs_modeline (¶ms, 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 (¶ms, content, line)) break;
364 if (parse_emacs_modeline (¶ms, content, line)) break;
365 }
366 g_free (content);
367
368 /* Set indentation settings */
369 return set_indentation (editor, ¶ms);
370 }
371