| 33 * handle dictionary changes |
33 * handle dictionary changes |
| 34 * asynchronous lookups |
34 * asynchronous lookups |
| 35 */ |
35 */ |
| 36 |
36 |
| 37 /* size of the text buffer used in various word-processing routines. */ |
37 /* size of the text buffer used in various word-processing routines. */ |
| 38 #define BUFSIZE 1024 |
38 /* #define BUFSIZE 1024 */ |
| 39 /* number of suggestions to display on each menu. */ |
39 /* number of suggestions to display on each menu. */ |
| 40 #define MENUCOUNT 10 |
40 #define MENUCOUNT 10 |
| 41 #define BUGEMAIL "gtkspell-devel@lists.sourceforge.net" |
41 #define BUGEMAIL "gtkspell-devel@lists.sourceforge.net" |
| 42 |
42 |
| 43 /* because we keep only one copy of the spell program running, |
43 /* because we keep only one copy of the spell program running, |
| 72 |
72 |
| 73 /* functions to interface with pipe */ |
73 /* functions to interface with pipe */ |
| 74 static void writetext(char *text) { |
74 static void writetext(char *text) { |
| 75 write(fd_write[1], text, strlen(text)); |
75 write(fd_write[1], text, strlen(text)); |
| 76 } |
76 } |
| 77 static int readpipe(char *buf, int bufsize) { |
77 |
| 78 int len; |
78 static char *readline() { |
| 79 len = read(fd_read[0], buf, bufsize-1); |
79 int len = 1024; |
| 80 if (len < 0) { |
80 gchar *buf = g_malloc(len); |
| 81 error_print("read: %s\n", strerror(errno)); |
81 int pos = 0; |
| 82 return -1; |
82 do { |
| 83 } else if (len == 0) { |
83 int val = read(fd_read[0], buf + pos, 1); |
| 84 error_print("pipe closed.\n"); |
84 if (val <= 0) { |
| 85 return -1; |
85 error_print("read: %s\n", strerror(errno)); |
| 86 } else if (len == bufsize-1) { |
86 g_free(buf); |
| 87 error_print("buffer overflowed?\n"); |
87 return NULL; |
| 88 } |
88 } |
| 89 |
89 pos += val; |
| 90 buf[len] = 0; |
90 if (pos == len) { |
| 91 return len; |
91 len *= 2; |
| 92 } |
92 buf = g_realloc(buf, len); |
| 93 static int readline(char *buf) { |
93 } |
| 94 return readpipe(buf, BUFSIZE); |
94 } while (buf[pos - 1] != '\n'); |
| 95 } |
95 |
| 96 |
96 buf = g_realloc(buf, pos + 1); |
| 97 static int readresponse(char *buf) { |
97 buf[pos] = 0; |
| 98 int len; |
98 return buf; |
| 99 len = readpipe(buf, BUFSIZE); |
99 } |
| 100 |
100 |
| 101 /* all ispell responses of any reasonable length should end in \n\n. |
101 static char *readresponse() { |
| 102 * depending on the speed of the spell checker, this may require more |
102 char *r1, *r2, *result; |
| 103 * reading. */ |
103 |
| 104 if (len >= 2 && (buf[len-1] != '\n' || buf[len-2] != '\n')) { |
104 r1 = readline(); |
| 105 len += readpipe(buf+len, BUFSIZE-len); |
105 if (!r1) |
| 106 } |
106 return NULL; |
| 107 |
107 if (*r1 == '\n') { |
| 108 /* now we can remove all of the the trailing newlines. */ |
108 g_strchomp(r1); |
| 109 while (len > 0 && buf[len-1] == '\n') |
109 return r1; |
| 110 buf[--len] = 0; |
110 } |
| 111 |
111 r2 = readline(); |
| 112 return len; |
112 if (!r2) { |
| |
113 g_free(r1); |
| |
114 return NULL; |
| |
115 } |
| |
116 |
| |
117 while (r2 && *r2 != '\n') { |
| |
118 char *tmp = r1; |
| |
119 r1 = g_strconcat(tmp, r2, NULL); |
| |
120 g_free(tmp); |
| |
121 g_free(r2); |
| |
122 r2 = readline(); |
| |
123 } |
| |
124 |
| |
125 if (!r2) { |
| |
126 g_free(r1); |
| |
127 return NULL; |
| |
128 } |
| |
129 |
| |
130 result = g_strconcat(r1, r2, NULL); |
| |
131 g_free(r1); |
| |
132 g_free(r2); |
| |
133 g_strchomp(result); |
| |
134 return result; |
| 113 } |
135 } |
| 114 |
136 |
| 115 |
137 |
| 116 void gtkspell_stop() { |
138 void gtkspell_stop() { |
| 117 if (gtkspell_running()) { |
139 if (gtkspell_running()) { |
| 205 /* we're done with stderr, now. */ |
227 /* we're done with stderr, now. */ |
| 206 close(fd_error[0]); |
228 close(fd_error[0]); |
| 207 close(fd_error[1]); |
229 close(fd_error[1]); |
| 208 |
230 |
| 209 /* otherwise, fd_read[0] is set. */ |
231 /* otherwise, fd_read[0] is set. */ |
| 210 readline(buf); |
232 buf = readline(); |
| 211 |
233 |
| 212 /* ispell should print something like this: |
234 /* ispell should print something like this: |
| 213 * @(#) International Ispell Version 3.1.20 10/10/95 |
235 * @(#) International Ispell Version 3.1.20 10/10/95 |
| 214 * if it doesn't, it's an error. */ |
236 * if it doesn't, it's an error. */ |
| 215 if (buf[0] != '@') { |
237 if (!buf || buf[0] != '@') { |
| |
238 if (buf) |
| |
239 g_free(buf); |
| 216 gtkspell_stop(); |
240 gtkspell_stop(); |
| 217 return -1; |
241 return -1; |
| 218 } |
242 } |
| |
243 g_free(buf); |
| 219 } |
244 } |
| 220 |
245 |
| 221 /* put ispell into terse mode. |
246 /* put ispell into terse mode. |
| 222 * this makes it not respond on correctly spelled words. */ |
247 * this makes it not respond on correctly spelled words. */ |
| 223 sprintf(buf, "!\n"); |
248 writetext("!\n"); |
| 224 writetext(buf); |
|
| 225 return 0; |
249 return 0; |
| 226 } |
250 } |
| 227 |
251 |
| 228 static GList* misspelled_suggest(char *word) { |
252 static GList* misspelled_suggest(char *word) { |
| 229 char buf[BUFSIZE]; |
253 char *buf; |
| 230 char *newword; |
254 char *newword; |
| 231 GList *l = NULL; |
255 GList *l = NULL; |
| 232 int count; |
256 int count; |
| 233 |
257 |
| 234 sprintf(buf, "^%s\n", word); /* guard against ispell control chars */ |
258 buf = g_strdup_printf("^%s\n", word); /* guard against ispell control chars */ |
| 235 writetext(buf); |
259 writetext(buf); |
| 236 readresponse(buf); |
260 g_free(buf); |
| |
261 buf = readresponse(); |
| |
262 |
| |
263 if (!buf) |
| |
264 return NULL; |
| 237 |
265 |
| 238 switch (buf[0]) { /* first char is ispell command. */ |
266 switch (buf[0]) { /* first char is ispell command. */ |
| 239 case 0: /* no response: word is ok. */ |
267 case 0: /* no response: word is ok. */ |
| |
268 g_free(buf); |
| 240 return NULL; |
269 return NULL; |
| 241 case '&': /* misspelled, with suggestions */ |
270 case '&': /* misspelled, with suggestions */ |
| 242 /* & <orig> <count> <ofs>: <miss>, <miss>, <guess>, ... */ |
271 /* & <orig> <count> <ofs>: <miss>, <miss>, <guess>, ... */ |
| 243 strtok(buf, " "); /* & */ |
272 strtok(buf, " "); /* & */ |
| 244 newword = strtok(NULL, " "); /* orig */ |
273 newword = strtok(NULL, " "); /* orig */ |
| 258 l = g_list_append(l, |
287 l = g_list_append(l, |
| 259 g_strdup(newword[0] == ' ' ? newword+1 : newword)); |
288 g_strdup(newword[0] == ' ' ? newword+1 : newword)); |
| 260 |
289 |
| 261 count--; |
290 count--; |
| 262 } |
291 } |
| |
292 g_free(buf); |
| 263 return l; |
293 return l; |
| 264 |
294 |
| 265 case '#': /* misspelled, no suggestions */ |
295 case '#': /* misspelled, no suggestions */ |
| 266 case '?': /* ispell is guessing. */ |
296 case '?': /* ispell is guessing. */ |
| 267 /* # <orig> <ofs> */ |
297 /* # <orig> <ofs> */ |
| 268 strtok(buf, " "); /* & */ |
298 strtok(buf, " "); /* & */ |
| 269 newword = strtok(NULL, " "); /* orig */ |
299 newword = strtok(NULL, " "); /* orig */ |
| 270 l = g_list_append(l, g_strdup(newword)); |
300 l = g_list_append(l, g_strdup(newword)); |
| |
301 g_free(buf); |
| 271 return l; |
302 return l; |
| 272 default: |
303 default: |
| 273 error_print("Unsupported spell command '%c'.\n" |
304 error_print("Unsupported spell command '%c'.\n" |
| 274 "This is a bug; mail " BUGEMAIL " about it.\n", buf[0]); |
305 "This is a bug; mail " BUGEMAIL " about it.\n", buf[0]); |
| 275 error_print("Input [%s]\nOutput [%s]\n", word, buf); |
306 error_print("Input [%s]\nOutput [%s]\n", word, buf); |
| 276 |
307 |
| 277 } |
308 } |
| |
309 g_free(buf); |
| 278 return NULL; |
310 return NULL; |
| 279 } |
311 } |
| 280 |
312 |
| 281 static int misspelled_test(char *word) { |
313 static int misspelled_test(char *word) { |
| 282 char buf[BUFSIZE]; |
314 char *buf; |
| 283 sprintf(buf, "^%s\n", word); /* guard against ispell control chars */ |
315 buf = g_strdup_printf("^%s\n", word); /* guard against ispell control chars */ |
| 284 writetext(buf); |
316 writetext(buf); |
| 285 readresponse(buf); |
317 g_free(buf); |
| |
318 buf = readresponse(); |
| |
319 |
| |
320 if (!buf) |
| |
321 return 0; |
| 286 |
322 |
| 287 if (buf[0] == 0) { |
323 if (buf[0] == 0) { |
| |
324 g_free(buf); |
| 288 return 0; |
325 return 0; |
| 289 } else if (buf[0] == '&' || buf[0] == '#' || buf[0] == '?') { |
326 } else if (buf[0] == '&' || buf[0] == '#' || buf[0] == '?') { |
| |
327 g_free(buf); |
| 290 return 1; |
328 return 1; |
| 291 } |
329 } |
| 292 |
330 |
| 293 error_print("Unsupported spell command '%c'.\n" |
331 error_print("Unsupported spell command '%c'.\n" |
| 294 "This is a bug; mail " BUGEMAIL " about it.\n", buf[0]); |
332 "This is a bug; mail " BUGEMAIL " about it.\n", buf[0]); |
| 295 error_print("Input [%s]\nOutput [%s]\n", word, buf); |
333 error_print("Input [%s]\nOutput [%s]\n", word, buf); |
| |
334 g_free(buf); |
| 296 return -1; |
335 return -1; |
| 297 } |
336 } |
| 298 |
337 |
| 299 static gboolean iswordsep(char c) { |
338 static gboolean iswordsep(char c) { |
| 300 return !isalpha(c) && c != '\''; |
339 return !isalpha(c) && c != '\''; |
| 301 } |
340 } |
| 302 |
341 |
| 303 static gboolean get_word_from_pos(GtkText* gtktext, int pos, char* buf, |
342 static gboolean get_word_from_pos(GtkText* gtktext, int pos, char** buf, |
| 304 int *pstart, int *pend) { |
343 int *pstart, int *pend) { |
| 305 gint start, end; |
344 gint start, end; |
| 306 |
345 |
| 307 if (iswordsep(GTK_TEXT_INDEX(gtktext, pos))) return FALSE; |
346 if (iswordsep(GTK_TEXT_INDEX(gtktext, pos))) return FALSE; |
| 308 |
347 |
| 314 for (end = pos; end <= gtk_text_get_length(gtktext); end++) { |
353 for (end = pos; end <= gtk_text_get_length(gtktext); end++) { |
| 315 if (iswordsep(GTK_TEXT_INDEX(gtktext, end))) break; |
354 if (iswordsep(GTK_TEXT_INDEX(gtktext, end))) break; |
| 316 } |
355 } |
| 317 |
356 |
| 318 if (buf) { |
357 if (buf) { |
| |
358 *buf = g_malloc(end - start + 1); |
| 319 for (pos = start; pos < end; pos++) |
359 for (pos = start; pos < end; pos++) |
| 320 buf[pos-start] = GTK_TEXT_INDEX(gtktext, pos); |
360 (*buf)[pos-start] = GTK_TEXT_INDEX(gtktext, pos); |
| 321 buf[pos-start] = 0; |
361 (*buf)[pos-start] = 0; |
| 322 } |
362 } |
| 323 |
363 |
| 324 if (pstart) *pstart = start; |
364 if (pstart) *pstart = start; |
| 325 if (pend) *pend = end; |
365 if (pend) *pend = end; |
| 326 |
366 |
| 327 return TRUE; |
367 return TRUE; |
| 328 } |
368 } |
| 329 |
369 |
| 330 static gboolean get_curword(GtkText* gtktext, char* buf, |
370 static gboolean get_curword(GtkText* gtktext, char** buf, |
| 331 int *pstart, int *pend) { |
371 int *pstart, int *pend) { |
| 332 int pos = gtk_editable_get_position(GTK_EDITABLE(gtktext)); |
372 int pos = gtk_editable_get_position(GTK_EDITABLE(gtktext)); |
| 333 return get_word_from_pos(gtktext, pos, buf, pstart, pend); |
373 return get_word_from_pos(gtktext, pos, buf, pstart, pend); |
| 334 } |
374 } |
| 335 |
375 |
| 352 g_free(newtext); |
392 g_free(newtext); |
| 353 } |
393 } |
| 354 |
394 |
| 355 static gboolean check_at(GtkText *gtktext, int from_pos) { |
395 static gboolean check_at(GtkText *gtktext, int from_pos) { |
| 356 int start, end; |
396 int start, end; |
| 357 char buf[BUFSIZE]; |
397 char *buf = NULL; |
| 358 |
398 |
| 359 if (!get_word_from_pos(gtktext, from_pos, buf, &start, &end)) { |
399 if (!get_word_from_pos(gtktext, from_pos, &buf, &start, &end)) { |
| |
400 if (buf) |
| |
401 g_free(buf); |
| 360 return FALSE; |
402 return FALSE; |
| 361 } |
403 } |
| 362 |
404 |
| 363 if (misspelled_test(buf)) { |
405 if (misspelled_test(buf)) { |
| 364 if (highlight.pixel == 0) { |
406 if (highlight.pixel == 0) { |
| 365 /* add an entry for the highlight in the color map. */ |
407 /* add an entry for the highlight in the color map. */ |
| 366 GdkColormap *gc = gtk_widget_get_colormap(GTK_WIDGET(gtktext)); |
408 GdkColormap *gc = gtk_widget_get_colormap(GTK_WIDGET(gtktext)); |
| 367 gdk_colormap_alloc_color(gc, &highlight, FALSE, TRUE);; |
409 gdk_colormap_alloc_color(gc, &highlight, FALSE, TRUE);; |
| 368 } |
410 } |
| 369 change_color(gtktext, start, end, &highlight); |
411 change_color(gtktext, start, end, &highlight); |
| |
412 if (buf) |
| |
413 g_free(buf); |
| 370 return TRUE; |
414 return TRUE; |
| 371 } else { |
415 } else { |
| 372 change_color(gtktext, start, end, |
416 change_color(gtktext, start, end, |
| 373 &(GTK_WIDGET(gtktext)->style->fg[0])); |
417 &(GTK_WIDGET(gtktext)->style->fg[0])); |
| |
418 if (buf) |
| |
419 g_free(buf); |
| 374 return FALSE; |
420 return FALSE; |
| 375 } |
421 } |
| 376 } |
422 } |
| 377 |
423 |
| 378 void gtkspell_check_all(GtkText *gtktext) { |
424 void gtkspell_check_all(GtkText *gtktext) { |
| 454 } |
500 } |
| 455 |
501 |
| 456 static void replace_word(GtkWidget *w, gpointer d) { |
502 static void replace_word(GtkWidget *w, gpointer d) { |
| 457 int start, end; |
503 int start, end; |
| 458 char *newword; |
504 char *newword; |
| 459 char buf[BUFSIZE]; |
|
| 460 |
505 |
| 461 /* we don't save their position, |
506 /* we don't save their position, |
| 462 * because the cursor is moved by the click. */ |
507 * because the cursor is moved by the click. */ |
| 463 |
508 |
| 464 gtk_text_freeze(GTK_TEXT(d)); |
509 gtk_text_freeze(GTK_TEXT(d)); |
| 465 |
510 |
| 466 gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), &newword); |
511 gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), &newword); |
| 467 get_curword(GTK_TEXT(d), buf, &start, &end); |
512 get_curword(GTK_TEXT(d), NULL, &start, &end); |
| 468 |
513 |
| 469 gtk_text_set_point(GTK_TEXT(d), end); |
514 gtk_text_set_point(GTK_TEXT(d), end); |
| 470 gtk_text_backward_delete(GTK_TEXT(d), end-start); |
515 gtk_text_backward_delete(GTK_TEXT(d), end-start); |
| 471 gtk_text_insert(GTK_TEXT(d), NULL, NULL, NULL, newword, strlen(newword)); |
516 gtk_text_insert(GTK_TEXT(d), NULL, NULL, NULL, newword, strlen(newword)); |
| 472 |
517 |
| 477 GtkWidget *menu, *item; |
522 GtkWidget *menu, *item; |
| 478 char *caption; |
523 char *caption; |
| 479 menu = gtk_menu_new(); { |
524 menu = gtk_menu_new(); { |
| 480 caption = g_strdup_printf("Not in dictionary: %s", (char*)l->data); |
525 caption = g_strdup_printf("Not in dictionary: %s", (char*)l->data); |
| 481 item = gtk_menu_item_new_with_label(caption); |
526 item = gtk_menu_item_new_with_label(caption); |
| |
527 g_free(caption); |
| 482 /* I'd like to make it so this item is never selectable, like |
528 /* I'd like to make it so this item is never selectable, like |
| 483 * the menu titles in the GNOME panel... unfortunately, the GNOME |
529 * the menu titles in the GNOME panel... unfortunately, the GNOME |
| 484 * panel creates their own custom widget to do this! */ |
530 * panel creates their own custom widget to do this! */ |
| 485 gtk_widget_show(item); |
531 gtk_widget_show(item); |
| 486 gtk_menu_append(GTK_MENU(menu), item); |
532 gtk_menu_append(GTK_MENU(menu), item); |
| 525 } |
571 } |
| 526 return GTK_MENU(menu); |
572 return GTK_MENU(menu); |
| 527 } |
573 } |
| 528 |
574 |
| 529 static void popup_menu(GtkText *gtktext, GdkEventButton *eb) { |
575 static void popup_menu(GtkText *gtktext, GdkEventButton *eb) { |
| 530 char buf[BUFSIZE]; |
576 char *buf = NULL; |
| 531 GList *list, *l; |
577 GList *list, *l; |
| 532 |
578 |
| 533 get_curword(gtktext, buf, NULL, NULL); |
579 get_curword(gtktext, &buf, NULL, NULL); |
| 534 |
580 |
| 535 list = misspelled_suggest(buf); |
581 list = misspelled_suggest(buf); |
| |
582 if (buf) |
| |
583 g_free(buf); |
| 536 if (list != NULL) { |
584 if (list != NULL) { |
| 537 gtk_menu_popup(make_menu(list, gtktext), NULL, NULL, NULL, NULL, |
585 gtk_menu_popup(make_menu(list, gtktext), NULL, NULL, NULL, NULL, |
| 538 eb->button, eb->time); |
586 eb->button, eb->time); |
| 539 for (l = list; l != NULL; l = l->next) |
587 for (l = list; l != NULL; l = l->next) |
| 540 g_free(l->data); |
588 g_free(l->data); |