1 /*
2 * xmlsnippets.c - this file is part of XMLSnippets, a Geany plugin
3 *
4 * Copyright 2010 Eugene Arshinov <earshinov(at)gmail(dot)com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 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,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25
26 #include "xmlsnippets.h"
27 #include <ctype.h>
28 #include <string.h>
29
30
31 typedef struct Info
32 {
33 struct
34 {
35 const gchar *completion;
36 const gchar *tag_name_start; /* start of the first tag within `completion' */
37 } snippet;
38 struct
39 {
40 const gchar *tag_name_end;
41 } input;
42 } Info;
43
44
skip_xml_tag_name(const gchar * start)45 static const gchar * skip_xml_tag_name(const gchar * start)
46 {
47 const gchar *iter;
48
49 for (iter = start; strchr(":_-.", *iter) || isalnum(*iter); iter++) ;
50 return iter;
51 }
52
53
54 /* If user entered some attributes, copy them to the first tag within snippet body
55 * @return Newly allocated completion string or @c NULL to indicate
56 * that completion should be aborted
57 */
merge_attributes(const gchar * sel,gint size,const Info * info)58 static gchar * merge_attributes(const gchar *sel, gint size, const Info * info)
59 {
60 const gchar *input_iter, *input_iter_end, *iter;
61
62 /* Separately ensure there is at least one space
63 * Needed for the code below which copy attributes */
64 input_iter = info->input.tag_name_end;
65 if (!isspace(*input_iter))
66 goto normal; /* nothing to do */
67
68 input_iter++;
69 while (isspace(*input_iter))
70 input_iter++;
71 if (*input_iter == '>')
72 goto normal; /* nothing to do */
73
74 /* Find the end of the attribute string */
75 g_assert(sel[size-1] == '>');
76 input_iter_end = &sel[size-2];
77 while (isspace(*input_iter_end))
78 input_iter_end--;
79 input_iter_end++;
80
81 /* Ensure the tag within snippet body does not contain attributes */
82 iter = info->snippet.tag_name_start;
83 iter = skip_xml_tag_name(iter);
84 if (*iter != '>')
85 {
86 g_message("%s",
87 "Autocompletion aborted: both of the input string and "
88 "the first tag of the snippet body contain attributes");
89 goto abort;
90 }
91
92 /* Merge */
93 {
94 GString *completion = g_string_sized_new(20);
95 g_string_append_len(completion, info->snippet.completion, iter - info->snippet.completion);
96
97 input_iter -= 1; /* leave one space */
98 for (; input_iter != input_iter_end; ++input_iter)
99 {
100 switch (*input_iter)
101 {
102 case '{':
103 g_string_append(completion, "{ob}");
104 break;
105 case '}':
106 g_string_append(completion, "{cb}");
107 break;
108 case '%':
109 g_string_append(completion, "{pc}");
110 break;
111 default:
112 g_string_append_c(completion, *input_iter);
113 }
114 }
115
116 g_string_append(completion, iter);
117 return g_string_free(completion, FALSE);
118 }
119
120 abort:
121 return NULL;
122 normal:
123 return g_strdup(info->snippet.completion);
124 }
125
126
get_completion(GeanyEditor * editor,const gchar * sel,const gint size,CompletionInfo * c,InputInfo * i)127 gboolean get_completion(GeanyEditor *editor, const gchar *sel, const gint size,
128 CompletionInfo * c, InputInfo * i)
129 {
130 Info info;
131 const gchar *str_found, *tagname, *input_iter, *completion, *iter;
132 gchar *completion_result;
133
134 g_return_val_if_fail(sel[size-1] == '>', FALSE);
135 if (size < 3)
136 return FALSE;
137 if (sel[size - 2] == '/')
138 return FALSE; /* closing tag */
139
140 str_found = utils_find_open_xml_tag_pos(sel, size);
141 if (str_found == NULL)
142 return FALSE;
143
144 tagname = str_found + 1; /* skip the left bracket */
145 input_iter = skip_xml_tag_name(tagname);
146 if (input_iter == tagname)
147 return FALSE;
148
149 tagname = g_strndup(tagname, input_iter - tagname);
150 completion = editor_find_snippet(editor, tagname);
151 g_free((gchar *)tagname);
152 if (completion == NULL)
153 return FALSE;
154
155 /* To prevent insertion of a snippet, which user intended to use,
156 * e.g., in JavaScript, ensure the snippet body starts with a tag.
157 * We don't prevent name clashes by using special snippet names
158 * as it won't allow the plugin to automatically catch up the
159 * snippets supplied with Geany, e.g. "table". */
160 iter = completion;
161 while (TRUE)
162 {
163 if (isspace(*iter))
164 iter++;
165 else if (*iter == '\\' && (*(iter+1) == 'n' || *(iter+1) == 't'))
166 iter += 2;
167 else
168 break;
169 }
170 if (*iter != '<')
171 return FALSE;
172
173 info.snippet.completion = completion;
174 info.snippet.tag_name_start = iter+1; /* +1: skip the left bracket */
175 info.input.tag_name_end = input_iter;
176
177 completion_result = merge_attributes(sel, size, &info);
178 if (completion_result == NULL)
179 return FALSE;
180
181 c->completion = completion_result;
182 i->tag_start = str_found - sel;
183 return TRUE;
184 }
185