1 #include <errno.h>
2 #include <string.h>
3 
4 #include "jimautoconf.h"
5 #include <jim.h>
6 
7 #ifdef USE_LINENOISE
8 #ifdef HAVE_UNISTD_H
9     #include <unistd.h>
10 #endif
11 #ifdef HAVE_SYS_STAT_H
12     #include <sys/stat.h>
13 #endif
14 #include "linenoise.h"
15 #else
16 #define MAX_LINE_LEN 512
17 #endif
18 
19 #ifdef USE_LINENOISE
20 static void JimCompletionCallback(const char *prefix, linenoiseCompletions *comp, void *userdata);
21 static const char completion_callback_assoc_key[] = "interactive-completion";
22 #endif
23 
24 /**
25  * Returns an allocated line, or NULL if EOF.
26  */
Jim_HistoryGetline(Jim_Interp * interp,const char * prompt)27 char *Jim_HistoryGetline(Jim_Interp *interp, const char *prompt)
28 {
29 #ifdef USE_LINENOISE
30     struct JimCompletionInfo *compinfo = Jim_GetAssocData(interp, completion_callback_assoc_key);
31     char *result;
32     Jim_Obj *objPtr;
33     long mlmode = 0;
34     /* Set any completion callback just during the call to linenoise()
35      * to allow for per-interp settings
36      */
37     if (compinfo) {
38         linenoiseSetCompletionCallback(JimCompletionCallback, compinfo);
39     }
40     objPtr = Jim_GetVariableStr(interp, "history::multiline", JIM_NONE);
41     if (objPtr && Jim_GetLong(interp, objPtr, &mlmode) == JIM_NONE) {
42         linenoiseSetMultiLine(mlmode);
43     }
44 
45     result = linenoise(prompt);
46     /* unset the callback */
47     linenoiseSetCompletionCallback(NULL, NULL);
48     return result;
49 #else
50     int len;
51     char *line = malloc(MAX_LINE_LEN);
52 
53     fputs(prompt, stdout);
54     fflush(stdout);
55 
56     if (fgets(line, MAX_LINE_LEN, stdin) == NULL) {
57         free(line);
58         return NULL;
59     }
60     len = strlen(line);
61     if (len && line[len - 1] == '\n') {
62         line[len - 1] = '\0';
63     }
64     return line;
65 #endif
66 }
67 
Jim_HistoryLoad(const char * filename)68 void Jim_HistoryLoad(const char *filename)
69 {
70 #ifdef USE_LINENOISE
71     linenoiseHistoryLoad(filename);
72 #endif
73 }
74 
Jim_HistoryAdd(const char * line)75 void Jim_HistoryAdd(const char *line)
76 {
77 #ifdef USE_LINENOISE
78     linenoiseHistoryAdd(line);
79 #endif
80 }
81 
Jim_HistorySave(const char * filename)82 void Jim_HistorySave(const char *filename)
83 {
84 #ifdef USE_LINENOISE
85 #ifdef HAVE_UMASK
86     mode_t mask;
87     /* Just u=rw, but note that this is only effective for newly created files */
88     mask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
89 #endif
90     linenoiseHistorySave(filename);
91 #ifdef HAVE_UMASK
92     umask(mask);
93 #endif
94 #endif
95 }
96 
Jim_HistoryShow(void)97 void Jim_HistoryShow(void)
98 {
99 #ifdef USE_LINENOISE
100     /* built-in history command */
101     int i;
102     int len;
103     char **history = linenoiseHistory(&len);
104     for (i = 0; i < len; i++) {
105         printf("%4d %s\n", i + 1, history[i]);
106     }
107 #endif
108 }
109 
110 #ifdef USE_LINENOISE
111 struct JimCompletionInfo {
112     Jim_Interp *interp;
113     Jim_Obj *command;
114 };
115 
JimCompletionCallback(const char * prefix,linenoiseCompletions * comp,void * userdata)116 static void JimCompletionCallback(const char *prefix, linenoiseCompletions *comp, void *userdata)
117 {
118     struct JimCompletionInfo *info = (struct JimCompletionInfo *)userdata;
119     Jim_Obj *objv[2];
120     int ret;
121 
122     objv[0] = info->command;
123     objv[1] = Jim_NewStringObj(info->interp, prefix, -1);
124 
125     ret = Jim_EvalObjVector(info->interp, 2, objv);
126 
127     /* XXX: Consider how best to handle errors here. bgerror? */
128     if (ret == JIM_OK) {
129         int i;
130         Jim_Obj *listObj = Jim_GetResult(info->interp);
131         int len = Jim_ListLength(info->interp, listObj);
132         for (i = 0; i < len; i++) {
133             linenoiseAddCompletion(comp, Jim_String(Jim_ListGetIndex(info->interp, listObj, i)));
134         }
135     }
136 }
137 
JimHistoryFreeCompletion(Jim_Interp * interp,void * data)138 static void JimHistoryFreeCompletion(Jim_Interp *interp, void *data)
139 {
140     struct JimCompletionInfo *compinfo = data;
141 
142     Jim_DecrRefCount(interp, compinfo->command);
143 
144     Jim_Free(compinfo);
145 }
146 #endif
147 
148 /**
149  * Sets a completion command to be used with Jim_HistoryGetline()
150  * If commandObj is NULL, deletes any existing completion command.
151  */
Jim_HistorySetCompletion(Jim_Interp * interp,Jim_Obj * commandObj)152 void Jim_HistorySetCompletion(Jim_Interp *interp, Jim_Obj *commandObj)
153 {
154 #ifdef USE_LINENOISE
155     if (commandObj) {
156         /* Increment now in case the existing object is the same */
157         Jim_IncrRefCount(commandObj);
158     }
159 
160     Jim_DeleteAssocData(interp, completion_callback_assoc_key);
161 
162     if (commandObj) {
163         struct JimCompletionInfo *compinfo = Jim_Alloc(sizeof(*compinfo));
164         compinfo->interp = interp;
165         compinfo->command = commandObj;
166 
167         Jim_SetAssocData(interp, completion_callback_assoc_key, JimHistoryFreeCompletion, compinfo);
168     }
169 #endif
170 }
171 
Jim_InteractivePrompt(Jim_Interp * interp)172 int Jim_InteractivePrompt(Jim_Interp *interp)
173 {
174     int retcode = JIM_OK;
175     char *history_file = NULL;
176 #ifdef USE_LINENOISE
177     const char *home;
178 
179     home = getenv("HOME");
180     if (home && isatty(STDIN_FILENO)) {
181         int history_len = strlen(home) + sizeof("/.jim_history");
182         history_file = Jim_Alloc(history_len);
183         snprintf(history_file, history_len, "%s/.jim_history", home);
184         Jim_HistoryLoad(history_file);
185     }
186 
187     Jim_HistorySetCompletion(interp, Jim_NewStringObj(interp, "tcl::autocomplete", -1));
188 #endif
189 
190     printf("Welcome to Jim version %d.%d\n",
191         JIM_VERSION / 100, JIM_VERSION % 100);
192     Jim_SetVariableStrWithStr(interp, JIM_INTERACTIVE, "1");
193 
194     while (1) {
195         Jim_Obj *scriptObjPtr;
196         const char *result;
197         int reslen;
198         char prompt[20];
199 
200         if (retcode != JIM_OK) {
201             const char *retcodestr = Jim_ReturnCode(retcode);
202 
203             if (*retcodestr == '?') {
204                 snprintf(prompt, sizeof(prompt) - 3, "[%d] . ", retcode);
205             }
206             else {
207                 snprintf(prompt, sizeof(prompt) - 3, "[%s] . ", retcodestr);
208             }
209         }
210         else {
211             strcpy(prompt, ". ");
212         }
213 
214         scriptObjPtr = Jim_NewStringObj(interp, "", 0);
215         Jim_IncrRefCount(scriptObjPtr);
216         while (1) {
217             char state;
218             char *line;
219 
220             line = Jim_HistoryGetline(interp, prompt);
221             if (line == NULL) {
222                 if (errno == EINTR) {
223                     continue;
224                 }
225                 Jim_DecrRefCount(interp, scriptObjPtr);
226                 retcode = JIM_OK;
227                 goto out;
228             }
229             if (Jim_Length(scriptObjPtr) != 0) {
230                 /* Line continuation */
231                 Jim_AppendString(interp, scriptObjPtr, "\n", 1);
232             }
233             Jim_AppendString(interp, scriptObjPtr, line, -1);
234             free(line);
235             if (Jim_ScriptIsComplete(interp, scriptObjPtr, &state))
236                 break;
237 
238             snprintf(prompt, sizeof(prompt), "%c> ", state);
239         }
240 #ifdef USE_LINENOISE
241         if (strcmp(Jim_String(scriptObjPtr), "h") == 0) {
242             /* built-in history command */
243             Jim_HistoryShow();
244             Jim_DecrRefCount(interp, scriptObjPtr);
245             continue;
246         }
247 
248         Jim_HistoryAdd(Jim_String(scriptObjPtr));
249         if (history_file) {
250             Jim_HistorySave(history_file);
251         }
252 #endif
253         retcode = Jim_EvalObj(interp, scriptObjPtr);
254         Jim_DecrRefCount(interp, scriptObjPtr);
255 
256         if (retcode == JIM_EXIT) {
257             break;
258         }
259         if (retcode == JIM_ERR) {
260             Jim_MakeErrorMessage(interp);
261         }
262         result = Jim_GetString(Jim_GetResult(interp), &reslen);
263         if (reslen) {
264             printf("%s\n", result);
265         }
266     }
267   out:
268     Jim_Free(history_file);
269 
270     return retcode;
271 }
272