revert between 56095 -> 55830 in arch
[AROS.git] / workbench / classes / zune / texteditor / mcc / SpellChecker.c
blob14f00e3d0e89bf9512ac393cb3f6b63ce0a3b01e
1 /***************************************************************************
3 TextEditor.mcc - Textediting MUI Custom Class
4 Copyright (C) 1997-2000 Allan Odgaard
5 Copyright (C) 2005-2014 TextEditor.mcc Open Source Team
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 TextEditor class Support Site: http://www.sf.net/projects/texteditor-mcc
19 $Id$
21 ***************************************************************************/
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string.h>
27 #include <exec/memory.h>
28 #include <dos/dosextens.h>
29 #include <dos/dostags.h>
30 #include <dos/var.h>
31 #include <rexx/storage.h>
32 #include <workbench/workbench.h>
33 #include <clib/alib_protos.h>
34 #include <proto/rexxsyslib.h>
35 #include <proto/muimaster.h>
36 #include <proto/intuition.h>
37 #include <proto/graphics.h>
38 #include <proto/locale.h>
39 #include <proto/exec.h>
40 #include <proto/dos.h>
41 #include <proto/wb.h>
43 #include "private.h"
44 #include "Debug.h"
46 #if !defined(__amigaos4__) || (INCLUDE_VERSION < 50)
47 struct PathNode
49 BPTR pn_Next;
50 BPTR pn_Lock;
52 #endif
54 /// SelectCode()
55 HOOKPROTONH(SelectCode, void, void *lvobj, long **parms)
57 struct InstData *data = (struct InstData *)*parms;
58 struct marking block;
59 char *entry;
61 ENTER();
63 DoMethod(lvobj, MUIM_List_GetEntry, MUIV_List_GetEntry_Active, &entry);
65 if(entry != NULL)
67 int length = strlen(entry);
68 LONG oldpos;
70 if(Enabled(data))
71 Key_Clear(data);
73 block.startline = data->actualline;
74 block.startx = data->CPos_X;
75 block.stopline = data->actualline;
76 block.stopx = data->CPos_X+length;
77 AddToUndoBuffer(data, ET_PASTEBLOCK, &block);
78 oldpos = data->CPos_X;
79 data->CPos_X += length;
80 PasteChars(data, oldpos, data->actualline, length, entry, NULL);
82 set(data->SuggestWindow, MUIA_Window_Open, FALSE);
83 DoMethod(lvobj, MUIM_List_Clear);
84 set(data->object, MUIA_TextEditor_AreaMarked, FALSE);
86 LEAVE();
88 MakeStaticHook(SelectHook, SelectCode);
90 ///
91 /// SafePutMsg()
92 static BOOL SafePutMsg(CONST_STRPTR portName, struct Message *msg)
94 struct MsgPort *port;
95 BOOL success = FALSE;
97 ENTER();
99 D(DBF_SPELL, "put message 0x%08lx to port '%s'", msg, portName);
101 // forbid task switching before we look up the port
102 Forbid();
104 if((port = FindPort(portName)) != NULL)
106 // send off the message while the Forbid() is still active
107 PutMsg(port, msg);
108 success = TRUE;
110 else
111 W(DBF_SPELL, "cannot find port '%s'", portName);
113 // permit task switching again
114 Permit();
116 RETURN(success);
117 return success;
121 /// SendRexx()
122 static BOOL SendRexx(CONST_STRPTR word, CONST_STRPTR command)
124 struct MsgPort *clipport;
125 BOOL result = FALSE;
127 ENTER();
129 #if defined(__amigaos4__)
130 clipport = AllocSysObjectTags(ASOT_PORT, TAG_DONE);
131 #else
132 clipport = CreateMsgPort();
133 #endif
134 if(clipport != NULL)
136 struct RexxMsg *rxmsg;
138 if((rxmsg = CreateRexxMsg(clipport, NULL, NULL)) != NULL)
140 char buffer[512];
142 snprintf(buffer, sizeof(buffer), command, word);
143 SHOWSTRING(DBF_SPELL, buffer);
145 rxmsg->rm_Action = RXCOMM;
146 #ifdef __AROS__
147 if((rxmsg->rm_Args[0] = (IPTR)CreateArgstring(buffer, strlen(buffer))) != 0)
148 #else
149 if((rxmsg->rm_Args[0] = CreateArgstring(buffer, strlen(buffer))) != 0)
150 #endif
152 if(SafePutMsg("REXX", (struct Message *)rxmsg) == TRUE)
154 if(Wait((1 << clipport->mp_SigBit) | SIGBREAKF_CTRL_C) != SIGBREAKF_CTRL_C)
156 GetMsg(clipport);
158 D(DBF_SPELL, "ARexx result1 %ld", rxmsg->rm_Result1);
159 if(rxmsg->rm_Result1 == 0)
161 result = TRUE;
162 DeleteArgstring((APTR)rxmsg->rm_Result2);
167 DeleteArgstring((APTR)rxmsg->rm_Args[0]);
170 DeleteRexxMsg(rxmsg);
173 #if defined(__amigaos4__)
174 FreeSysObject(ASOT_PORT, clipport);
175 #else
176 DeleteMsgPort(clipport);
177 #endif
180 RETURN(result);
181 return result;
186 #if !defined(__amigaos4__) && !defined(__MORPHOS__) && !defined(__AROS__)
187 #undef WorkbenchControl
188 /// WorkbenchControl()
189 BOOL WorkbenchControl(STRPTR name, ...)
191 BOOL ret;
192 va_list args;
194 ENTER();
196 va_start(args, name);
197 ret = WorkbenchControlA(name, (struct TagItem *)args);
198 va_end(args);
200 RETURN(ret);
201 return ret;
205 #endif
207 /// CloneSearchPath()
208 /***********************************************************************
209 This returns a duplicated search path (preferable the workbench
210 searchpath) usable for NP_Path of SystemTagList().
211 ************************************************************************/
212 static BPTR CloneSearchPath(void)
214 BPTR path = 0;
216 ENTER();
218 if(WorkbenchBase != NULL && WorkbenchBase->lib_Version >= 44)
219 WorkbenchControl(NULL, WBCTRLA_DuplicateSearchPath, &path, TAG_DONE);
221 // We don't like this evil code in OS4 compile, as we should have
222 // a recent enough Workbench available
223 #if !defined(__amigaos4__)
224 if(path == 0)
226 struct Process *pr = (struct Process*)FindTask(NULL);
228 if(pr->pr_Task.tc_Node.ln_Type == NT_PROCESS)
230 struct CommandLineInterface *cli = BADDR(pr->pr_CLI);
232 if(cli != 0)
234 BPTR *p = &path;
235 BPTR dir = cli->cli_CommandDir;
237 while(dir != 0)
239 BPTR dir2;
240 struct FileLock *lock = BADDR(dir);
241 struct PathNode *node;
243 dir = lock->fl_Link;
244 dir2 = DupLock((BPTR)lock->fl_Key);
245 if(dir2 == 0)
246 break;
248 /* Use AllocVec(), because this memory is freed by FreeVec()
249 * by the system later */
250 if((node = AllocVec(sizeof(struct PathNode), MEMF_ANY)) == NULL)
252 UnLock(dir2);
253 break;
255 node->pn_Next = 0;
256 node->pn_Lock = dir2;
257 *p = MKBADDR(node);
258 p = &node->pn_Next;
263 #endif
265 RETURN(path);
266 return path;
270 /// FreeSearchPath()
271 /***********************************************************************
272 Free the memory returned by CloneSearchPath
273 ************************************************************************/
274 static void FreeSearchPath(BPTR path)
276 ENTER();
278 if(path != 0)
280 #if !defined(__MORPHOS__)
281 if(WorkbenchBase != NULL)
283 WorkbenchControl(NULL, WBCTRLA_FreeSearchPath, path, TAG_DONE);
285 else
286 #endif
288 #if !defined(__amigaos4__)
289 /* This is compatible with WorkbenchControl(NULL, WBCTRLA_FreeSearchPath, ...)
290 * in Ambient */
291 while(path != 0)
293 struct PathNode *node = BADDR(path);
295 path = node->pn_Next;
296 UnLock(node->pn_Lock);
297 FreeVec(node);
299 #endif
303 LEAVE();
307 /// SendCLI()
308 static BOOL SendCLI(CONST_STRPTR word, CONST_STRPTR command)
310 char buffer[512];
311 BOOL result;
312 BPTR path;
314 ENTER();
316 snprintf(buffer, sizeof(buffer), command, word);
318 // path maybe 0, which is allowed
319 path = CloneSearchPath();
321 if(SystemTags(buffer, NP_Path, path, TAG_DONE) == -1)
323 W(DBF_SPELL, "command '%s' failed, error code %ld", buffer, IoErr());
324 FreeSearchPath(path);
325 result = FALSE;
327 else
329 result = TRUE;
332 RETURN(result);
333 return result;
337 /// SuggestWindow()
338 Object *SuggestWindow(struct InstData *data)
340 Object *window;
341 Object *lvobj;
343 window = WindowObject,
344 MUIA_Window_Borderless, TRUE,
345 MUIA_Window_CloseGadget, FALSE,
346 MUIA_Window_DepthGadget, FALSE,
347 MUIA_Window_DragBar, FALSE,
348 MUIA_Window_SizeGadget, FALSE,
349 WindowContents, VGroup,
350 MUIA_InnerBottom, 0,
351 MUIA_InnerLeft, 0,
352 MUIA_InnerRight, 0,
353 MUIA_InnerTop, 0,
354 MUIA_Group_PageMode, TRUE,
355 Child, ListviewObject,
356 MUIA_Listview_Input, FALSE,
357 MUIA_Listview_List, FloattextObject,
358 MUIA_Floattext_Text, "Word is spelled correctly.",
359 MUIA_Frame, MUIV_Frame_ReadList,
360 MUIA_Background, MUII_ListBack,
361 End,
362 End,
363 Child, lvobj = ListviewObject,
364 MUIA_Listview_List, ListObject,
365 MUIA_Frame, MUIV_Frame_InputList,
366 MUIA_Background, MUII_ListBack,
367 MUIA_List_ConstructHook, MUIV_List_ConstructHook_String,
368 MUIA_List_DestructHook, MUIV_List_DestructHook_String,
369 MUIA_List_Pool, data->mypool,
370 End,
371 End,
372 End,
373 End;
375 DoMethod(lvobj, MUIM_Notify, MUIA_Listview_DoubleClick, TRUE,
376 MUIV_Notify_Self, 3, MUIM_CallHook, &SelectHook, data);
378 DoMethod(window, MUIM_Notify, MUIA_Window_CloseRequest, TRUE,
379 MUIV_Notify_Self, 3, MUIM_Set, MUIA_Window_Open, FALSE);
381 DoMethod(window, MUIM_Notify, MUIA_Window_Open, MUIV_EveryTime,
382 data->object, 3, MUIM_Set, MUIA_TextEditor_PopWindow_Open, MUIV_TriggerValue);
384 data->SuggestListview = lvobj;
386 RETURN(window);
387 return window;
391 /// LookupWord()
392 static BOOL LookupWord(struct InstData *data, CONST_STRPTR word)
394 BOOL res = FALSE;
396 ENTER();
398 SHOWSTRING(DBF_SPELL, data->LookupCmd);
399 SHOWSTRING(DBF_SPELL, word);
400 SHOWVALUE(DBF_SPELL, data->LookupSpawn);
402 if(data->LookupCmd[0] != '\0')
404 if(data->LookupSpawn == FALSE)
405 res = SendCLI(word, data->LookupCmd);
406 else
407 res = SendRexx(word, data->LookupCmd);
409 if(res == TRUE)
411 char buf[4];
413 buf[0] = '\0';
414 if(GetVar("Found", &buf[0], sizeof(buf), GVF_GLOBAL_ONLY) != -1)
416 if(buf[0] == '0')
417 res = FALSE;
419 else
421 D(DBF_SPELL, "cannot read ENV variable 'Found', error code %ld", IoErr());
423 // don't treat a missing "Found" variable as a failure, at least this
424 // is what previous releases did.
427 else
429 D(DBF_SPELL, "lookup of word '%s' failed", word);
432 else
433 W(DBF_SPELL, "empty lookupcmd found");
435 RETURN(res);
436 return res;
440 /// SuggestWord()
441 void SuggestWord(struct InstData *data)
443 LONG top;
444 LONG left;
445 LONG line_nr;
446 struct pos_info pos;
447 struct line_node *line = data->actualline;
449 ENTER();
451 if(IsAlpha(data->mylocale, line->line.Contents[data->CPos_X]))
453 if(Enabled(data))
455 data->blockinfo.enabled = FALSE;
456 MarkText(data, data->blockinfo.startx, data->blockinfo.startline, data->blockinfo.stopx, data->blockinfo.stopline);
458 /* else
460 SetCursor(data, data->CPos_X, line, FALSE);
464 while(data->CPos_X != 0 && (IsAlpha(data->mylocale, line->line.Contents[data->CPos_X-1]) || line->line.Contents[data->CPos_X-1] == '-' || line->line.Contents[data->CPos_X-1] == '\''))
466 GoPreviousWord(data);
469 line = data->actualline;
470 data->blockinfo.startx = data->CPos_X;
471 data->blockinfo.startline = line;
473 line_nr = LineToVisual(data, line) - 1;
474 OffsetToLines(data, data->CPos_X, line, &pos);
475 left = xget(_win(data->object), MUIA_Window_LeftEdge);
476 top = xget(_win(data->object), MUIA_Window_TopEdge);
477 left += _mleft(data->object) + FlowSpace(data, line->line.Flow, line->line.Contents+(data->CPos_X-pos.x)) + TextLength(&data->tmprp, line->line.Contents+(data->CPos_X-pos.x), pos.x);
478 top += data->ypos + (data->fontheight * (line_nr + pos.lines));
480 while(data->CPos_X < line->line.Length && (IsAlpha(data->mylocale, line->line.Contents[data->CPos_X]) || line->line.Contents[data->CPos_X] == '-' || line->line.Contents[data->CPos_X] == '\''))
482 data->CPos_X++;
484 data->blockinfo.stopx = data->CPos_X;
485 data->blockinfo.stopline = line;
487 data->blockinfo.enabled = TRUE;
488 MarkText(data, data->blockinfo.startx, data->blockinfo.startline, data->blockinfo.stopx, data->blockinfo.stopline);
490 SetAttrs(data->SuggestWindow, MUIA_Window_Open, FALSE,
491 MUIA_Window_LeftEdge, left,
492 MUIA_Window_TopEdge, top,
493 TAG_DONE);
495 if(data->blockinfo.stopx-data->blockinfo.startx < 256)
497 char word[256];
499 strlcpy(word, line->line.Contents+data->blockinfo.startx, data->blockinfo.stopx-data->blockinfo.startx+1);
501 set(_win(data->object), MUIA_Window_Sleep, TRUE);
503 if(isFlagSet(data->flags, FLG_CheckWords) && LookupWord(data, word) == TRUE)
505 Object *group = (Object *)xget(data->SuggestWindow, MUIA_Window_RootObject);
507 set(group, MUIA_Group_ActivePage, MUIV_Group_ActivePage_First);
509 SetAttrs(data->SuggestWindow, MUIA_Window_Activate, TRUE,
510 MUIA_Window_DefaultObject, NULL,
511 MUIA_Window_Open, TRUE,
512 TAG_DONE);
514 else
516 BOOL res;
518 if(data->SuggestSpawn == FALSE)
519 res = SendCLI(word, data->SuggestCmd);
520 else
521 res = SendRexx(word, data->SuggestCmd);
523 if(res == TRUE)
525 BPTR fh;
527 if((fh = Open("T:Matches", MODE_OLDFILE)) != 0)
529 char entry[128];
530 Object *group;
532 DoMethod(data->SuggestListview, MUIM_List_Clear);
533 while(FGets(fh, entry, 128) != NULL)
535 entry[strlen(entry)-1] = '\0';
536 DoMethod(data->SuggestListview, MUIM_List_InsertSingle, entry, MUIV_List_Insert_Sorted);
538 Close(fh);
540 group = (Object *)xget(data->SuggestWindow, MUIA_Window_RootObject);
541 set(group, MUIA_Group_ActivePage, MUIV_Group_ActivePage_Last);
542 SetAttrs(data->SuggestWindow, MUIA_Window_Activate, TRUE,
543 MUIA_Window_DefaultObject, data->SuggestListview,
544 MUIA_Window_Open, TRUE,
545 TAG_DONE);
549 set(_win(data->object), MUIA_Window_Sleep, FALSE);
552 else
554 D(DBF_ALWAYS, "character '%lc' is non-alpha", line->line.Contents[data->CPos_X]);
555 DisplayBeep(NULL);
558 LEAVE();
562 /// SpellCheckWord()
563 void SpellCheckWord(struct InstData *data)
565 ENTER();
567 if(data->TypeAndSpell == TRUE && data->CPos_X != 0 && IsAlpha(data->mylocale, data->actualline->line.Contents[data->CPos_X-1]))
569 LONG start;
570 LONG end = data->CPos_X;
571 struct line_node *line = data->actualline;
575 GoPreviousWord(data);
577 while(data->CPos_X != 0 && data->actualline == line && (data->actualline->line.Contents[data->CPos_X-1] == '-' || data->actualline->line.Contents[data->CPos_X-1] == '\''));
579 start = data->CPos_X;
580 data->CPos_X = end;
582 if(start-end < 256 && data->actualline == line)
584 char word[256];
586 strlcpy(word, &data->actualline->line.Contents[start], end-start+1);
588 if(LookupWord(data, word) == FALSE)
589 DisplayBeep(NULL);
591 else
593 data->actualline = line;
597 LEAVE();
601 /// ParseKeywords
602 void ParseKeywords(struct InstData *data, const char *keywords)
604 ENTER();
606 FreeKeywords(data);
608 if(keywords != NULL)
610 char *copy;
611 size_t keywordslen = strlen(keywords);
613 if((copy = AllocVecPooled(data->mypool, (keywordslen+1)*sizeof(char))) != NULL)
615 char *p = copy;
616 LONG wordCnt;
618 strlcpy(copy, keywords, keywordslen+1);
620 // count the number of words, we have one at least
621 wordCnt = 1;
624 if((p = strchr(p, ',')) != NULL)
626 wordCnt++;
627 p++;
630 while(p != NULL);
632 if((data->Keywords = AllocVecPooled(data->mypool, (wordCnt+1)*sizeof(char *))) != NULL)
634 LONG i = 0;
635 char *word = copy;
637 // split the string
640 char *e;
641 size_t maxlen;
643 if((e = strpbrk(word, ",")) != NULL)
644 *e++ = '\0';
646 maxlen = strlen(word)+1;
647 if((data->Keywords[i] = AllocVecPooled(data->mypool, maxlen)) != NULL)
648 strlcpy(data->Keywords[i], word, maxlen);
649 else
650 break;
652 i++;
654 word = e;
656 while(word != NULL && i < wordCnt);
658 // NUL terminate
659 data->Keywords[i] = NULL;
662 FreeVecPooled(data->mypool, copy);
666 LEAVE();
670 /// FreeKeywords
671 void FreeKeywords(struct InstData *data)
673 ENTER();
675 if(data->Keywords != NULL)
677 LONG i = 0;
679 while(data->Keywords[i] != NULL)
681 FreeVecPooled(data->mypool, data->Keywords[i]);
682 i++;
685 FreeVecPooled(data->mypool, data->Keywords);
686 data->Keywords = NULL;
689 LEAVE();
693 /// CheckSingleWordAgainstKeywords
694 void CheckSingleWordAgainstKeywords(struct InstData *data, const char *word)
696 ENTER();
698 D(DBF_SPELL, "check word '%s' against keywords", word);
699 if(word[0] != '\0')
701 ULONG i = 0;
703 while(data->Keywords[i] != NULL)
705 if(data->Keywords[i][0] == '.')
707 // check a file name extension at the end of the word
708 char *p = strchr(word, '.');
710 if((p = strchr(word, '.')) != NULL && stricmp(p, data->Keywords[i]) == 0)
712 D(DBF_SPELL, "matched keyword '%s'", data->Keywords[i]);
713 set(data->object, MUIA_TextEditor_MatchedKeyword, data->Keywords[i]);
714 break;
717 else
719 // check for a complete word
720 if(stricmp(word, data->Keywords[i]) == 0)
722 D(DBF_SPELL, "matched keyword '%s'", data->Keywords[i]);
723 set(data->object, MUIA_TextEditor_MatchedKeyword, data->Keywords[i]);
724 break;
728 i++;
732 LEAVE();
736 /// KeywordCheck
737 void KeywordCheck(struct InstData *data)
739 ENTER();
741 if(data->Keywords != NULL)
743 ULONG start = data->CPos_X;
744 char *contents = data->actualline->line.Contents;
745 char word[256];
747 // extract the last entered word
748 while(start > 0 && contents[start-1] != ' ')
749 start--;
751 strlcpy(word, &contents[start], MIN(sizeof(word), data->CPos_X-start+1));
752 CheckSingleWordAgainstKeywords(data, word);
755 LEAVE();