1 /*
2  *      automark.c
3  *
4  *      Copyright 2014 Pavel Roschin <rpg89(at)post(dot)ru>
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  *      You should have received a copy of the GNU General Public License
16  *      along with this program; if not, write to the Free Software
17  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18  *      MA 02110-1301, USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 	#include "config.h" /* for the gettext domain */
23 #endif
24 
25 #include <string.h>
26 #ifdef HAVE_LOCALE_H
27 	#include <locale.h>
28 #endif
29 
30 #include <gdk/gdkkeysyms.h>
31 
32 #include <geanyplugin.h>
33 #include <geany.h>
34 
35 #include "Scintilla.h"
36 #include "SciLexer.h"
37 
38 #define SSM(s, m, w, l) scintilla_send_message(s, m, w, l)
39 
40 GeanyPlugin	*geany_plugin;
41 GeanyData	*geany_data;
42 
43 
44 static gint source_id;
45 
46 static const gint AUTOMARK_INDICATOR = GEANY_INDICATOR_SEARCH;
47 
48 static void
search_mark_in_range(GeanyEditor * editor,gint flags,struct Sci_TextToFind * ttf)49 search_mark_in_range(
50 	GeanyEditor *editor,
51 	gint         flags,
52 	struct       Sci_TextToFind *ttf)
53 {
54 	ScintillaObject *sci = editor->sci;
55 
56 	while (SSM(sci, SCI_FINDTEXT, flags, (uptr_t)ttf) != -1)
57 	{
58 		gint start = ttf->chrgText.cpMin;
59 		gint end = ttf->chrgText.cpMax;
60 
61 		if (end > ttf->chrg.cpMax)
62 			break;
63 
64 		ttf->chrg.cpMin = ttf->chrgText.cpMax;
65 		if (end == start)
66 			continue;
67 		if(SSM(sci, SCI_INDICATORVALUEAT, AUTOMARK_INDICATOR, start))
68 			continue;
69 		SSM(sci, SCI_SETINDICATORCURRENT, AUTOMARK_INDICATOR, 0);
70 		SSM(sci, SCI_INDICATORFILLRANGE, start, end - start);
71 	}
72 }
73 
74 /* based on editor_find_current_word_sciwc from editor.c */
75 static gchar *
get_current_word(ScintillaObject * sci)76 get_current_word(ScintillaObject *sci)
77 {
78 	gint pos = sci_get_current_position(sci);
79 	gint start = SSM(sci, SCI_WORDSTARTPOSITION, pos, TRUE);
80 	gint end = SSM(sci, SCI_WORDENDPOSITION, pos, TRUE);
81 
82 	if (end == start)
83 		return NULL;
84 
85 	if ((guint)(end - start) >= GEANY_MAX_WORD_LENGTH)
86 		end = start + (GEANY_MAX_WORD_LENGTH - 1);
87 	return sci_get_contents_range(sci, start, end);
88 }
89 
90 static gboolean
automark(gpointer user_data)91 automark(gpointer user_data)
92 {
93 	GeanyDocument      *doc = (GeanyDocument *)user_data;
94 	GeanyEditor        *editor;
95 	static GeanyEditor *editor_cache = NULL;
96 	ScintillaObject    *sci;
97 	gchar              *text;
98 	static gchar        text_cache[GEANY_MAX_WORD_LENGTH] = {0};
99 	gint                match_flag = SCFIND_MATCHCASE | SCFIND_WHOLEWORD;
100 	struct              Sci_TextToFind ttf;
101 
102 	source_id = 0;
103 
104 	/* during timeout document could be destroyed so check everything again */
105 	if (!DOC_VALID(doc))
106 		return FALSE;
107 
108 	editor = doc->editor;
109 	sci = editor->sci;
110 
111 	/* Do not highlight while selecting text and allow other markers to work */
112 	if (sci_has_selection(sci))
113 		return FALSE;
114 
115 	text = get_current_word(editor->sci);
116 
117 	if (EMPTY(text))
118 	{
119 		editor_indicator_clear(editor, AUTOMARK_INDICATOR);
120 		g_free(text);
121 		return FALSE;
122 	}
123 
124 	if (editor_cache != editor || strcmp(text, text_cache) != 0)
125 	{
126 		editor_indicator_clear(editor, AUTOMARK_INDICATOR);
127 		strcpy(text_cache, text);
128 		editor_cache = editor;
129 	}
130 
131 	gint vis_first = SSM(sci, SCI_GETFIRSTVISIBLELINE, 0, 0);
132 	gint doc_first = SSM(sci, SCI_DOCLINEFROMVISIBLE, vis_first, 0);
133 	gint vis_last  = SSM(sci, SCI_LINESONSCREEN, 0, 0) + vis_first;
134 	gint doc_last  = SSM(sci, SCI_DOCLINEFROMVISIBLE, vis_last, 0);
135 	gint start     = SSM(sci, SCI_POSITIONFROMLINE,   doc_first, 0);
136 	gint end       = SSM(sci, SCI_GETLINEENDPOSITION, doc_last, 0);
137 
138 	ttf.chrg.cpMin = start;
139 	ttf.chrg.cpMax = end;
140 	ttf.lpstrText  = text;
141 
142 	search_mark_in_range(editor, match_flag, &ttf);
143 
144 	g_free(text);
145 
146 	return FALSE;
147 }
148 
149 static gboolean
on_editor_notify(GObject * obj,GeanyEditor * editor,SCNotification * nt,gpointer user_data)150 on_editor_notify(
151 	GObject        *obj,
152 	GeanyEditor    *editor,
153 	SCNotification *nt,
154 	gpointer        user_data)
155 {
156 	if (SCN_UPDATEUI == nt->nmhdr.code)
157 	{
158 		/* if events are too intensive - remove old callback */
159 		if (source_id)
160 			g_source_remove(source_id);
161 		source_id = g_idle_add(automark, editor->document);
162 	}
163 	return FALSE;
164 }
165 
166 static PluginCallback plugin_automark_callbacks[] =
167 {
168 	{ "editor-notify",  (GCallback) &on_editor_notify, FALSE, NULL },
169 	{ NULL, NULL, FALSE, NULL }
170 };
171 
172 
173 static gboolean
plugin_automark_init(GeanyPlugin * plugin,G_GNUC_UNUSED gpointer pdata)174 plugin_automark_init(GeanyPlugin *plugin, G_GNUC_UNUSED gpointer pdata)
175 {
176 	geany_plugin = plugin;
177 	geany_data = plugin->geany_data;
178 	source_id = 0;
179 	return TRUE;
180 }
181 
182 
183 static void
plugin_automark_cleanup(G_GNUC_UNUSED GeanyPlugin * plugin,G_GNUC_UNUSED gpointer pdata)184 plugin_automark_cleanup(G_GNUC_UNUSED GeanyPlugin *plugin, G_GNUC_UNUSED gpointer pdata)
185 {
186 	if (source_id)
187 		g_source_remove(source_id);
188 }
189 
190 
191 static void
plugin_automark_help(G_GNUC_UNUSED GeanyPlugin * plugin,G_GNUC_UNUSED gpointer pdata)192 plugin_automark_help (G_GNUC_UNUSED GeanyPlugin *plugin, G_GNUC_UNUSED gpointer pdata)
193 {
194 	utils_open_browser("http://plugins.geany.org/automark.html");
195 }
196 
197 
198 /* Load module */
199 G_MODULE_EXPORT
geany_load_module(GeanyPlugin * plugin)200 void geany_load_module(GeanyPlugin *plugin)
201 {
202 	/* Setup translation */
203 	main_locale_init(LOCALEDIR, GETTEXT_PACKAGE);
204 
205 	/* Set metadata */
206 	plugin->info->name = _("Auto-mark");
207 	plugin->info->description = _("Auto-mark word under cursor");
208 	plugin->info->version = "0.1";
209 	plugin->info->author = "Pavel Roschin <rpg89(at)post(dot)ru>";
210 
211 	/* Set functions */
212 	plugin->funcs->init = plugin_automark_init;
213 	plugin->funcs->cleanup = plugin_automark_cleanup;
214 	plugin->funcs->help = plugin_automark_help;
215 	plugin->funcs->callbacks = plugin_automark_callbacks;
216 
217 	/* Register! */
218 	GEANY_PLUGIN_REGISTER(plugin, 226);
219 }
220