summaryrefslogtreecommitdiff
path: root/glistener.h
blob: 1cdde9d3a157b8fb4157d60899a931b3b73a180a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
#ifndef GLISTENER_H
#define GLISTENER_H

#include <ctype.h>
#include <stddef.h>
#include <math.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <stdbool.h>

#include <gtk/gtk.h>

#if (GTK_CHECK_VERSION(3, 0, 0))
  #include <gdk/gdk.h>
#else
  #include <gdk/gdkkeysyms.h>
#endif

#if GTK_CHECK_VERSION(3, 16, 0)
  #define GDK_CURSOR_NEW(Type) gdk_cursor_new_for_display(gdk_display_get_default(), Type)
#else
  #define GDK_CURSOR_NEW(Type) gdk_cursor_new(Type)
#endif

typedef struct glistener glistener;

typedef enum {GLISTENER_STRING, GLISTENER_COMMENT, GLISTENER_BLOCK_COMMENT,
              GLISTENER_ATOM, GLISTENER_LIST, GLISTENER_BRACKET, GLISTENER_CHARACTER
             } glistener_colorizer_t;

glistener *glistener_new(GtkWidget *parent, void (*initializations)(glistener *g, GtkWidget *new_listener));

void glistener_append_text         (glistener *g, const char *msg);
void glistener_insert_text         (glistener *g, const char *text);
char *glistener_text               (glistener *g, int start, int end);

void glistener_append_prompt       (glistener *g);
void glistener_set_prompt          (glistener *g, const char *str);
void glistener_set_prompt_tag      (glistener *g, GtkTextTag *m);
int glistener_prompt_position      (glistener *g);

void glistener_set_cursor_shape    (glistener *g, GdkCursor *cursor_shape);
int glistener_cursor_position      (glistener *g);
void glistener_set_cursor_position (glistener *g, int position);
void glistener_scroll_to_end       (glistener *g);

void glistener_post_status         (glistener *g, const char *msg);
void glistener_clear_status        (glistener *g);

void glistener_clear               (glistener *g);
bool glistener_write               (glistener *g, FILE *fp);

void glistener_set_highlight_tag   (glistener *g, GtkTextTag *m);
void glistener_set_font            (glistener *g, PangoFontDescription *font);
#if (!GTK_CHECK_VERSION(3, 0, 0))
void glistener_set_text_color      (glistener *g, GdkColor *p);
void glistener_set_background_color(glistener *g, GdkColor *p);
#else
void glistener_set_text_color      (glistener *g, GdkRGBA *p);
void glistener_set_background_color(glistener *g, GdkRGBA *p);
#endif

void glistener_key_bindings        (glistener *g, gpointer cls);
void glistener_is_schemish         (glistener *g, bool filtering);

void glistener_set_completer       (glistener *g, void (*completer)(glistener *g, bool (*symbol_func)(const char *symbol_name, void *data), void *data));
void glistener_set_helper          (glistener *g, const char *(*help)(glistener *g, const char *text));
void glistener_set_checker         (glistener *g, const char *(*check)(glistener *g, const char *text));
void glistener_set_evaluator       (glistener *g, void (*eval)(glistener *g, const char *text));
void glistener_set_colorizer       (glistener *g, void (*colorizer)(glistener *g, glistener_colorizer_t type, int start, int end));
#if (GTK_CHECK_VERSION(3, 92, 1))
void glistener_set_keyer           (glistener *g, bool (*key)(glistener *g, GtkWidget *w, GdkEvent *e));
#else
void glistener_set_keyer           (glistener *g, bool (*key)(glistener *g, GtkWidget *w, GdkEventKey *e));
#endif

/* these are for regression testing */
char *glistener_evaluate           (glistener *g);
char *glistener_complete           (glistener *g);


GtkWidget *glistener_text_widget(glistener *g);
GtkTextBuffer *glistener_text_buffer(glistener *g);
GtkWidget *glistener_status_widget(glistener *g);


/* -------------------------------------------------------------------------------- */
/* an annotated version of the same info:
 *
 * typedef struct glistener glistener;
 *
 *    our opaque handle on the info for each listener.  You can run any number of listeners
 *    at the same time.  For a simple example, see s7.html#glistener.  Another is snd-glistener.c
 *    in the Snd package, and tools/gcall.c.
 *
 *
 * --------
 * glistener *glistener_new(GtkWidget *parent, void (*initializations)(glistener *g, GtkWidget *new_listener));
 *
 *   This creates a new listener.  If parent is not NULL, the listener is added to it
 *   via gtk_container_add.  The second argument is an optional function that will be called
 *   during the listener initialization, just before the signals are connected.  It
 *   can add its own signal connections or set defaults.  The "new_listener" argument
 *   passed to it is the new GtkTextView widget.  Its parent is a GtkScrolledWindow,
 *   which is placed in a grid (or table in gtk-2) along with the listener's statusbar.
 *
 *
 * --------
 * void glistener_append_text(glistener *g, const char *msg);
 *
 *   This appends "msg" to the end of the listener text.
 *
 *
 * --------
 * void glistener_insert_text(glistener *g, const char *text);
 *
 *   This inserts "text" at the cursor.
 *
 *
 * --------
 * char *glistener_text(glistener *g, int start, int end);
 *
 *  This returns the text in the listener between the offsets (unicode positions)
 *  "start" and "end".
 *
 *
 * --------
 * void glistener_append_prompt(glistener *g);
 *  
 *  This appends a prompt at the bottom of the listener text.  Everything
 *  depends on the prompt, so you need to send one out at the end of each
 *  evaluation.  The usual sequence is:
 *    user types in an expression followed by <cr> -> listener-text
 *    we_eval(listener-text) -> some string
 *    glistener_append_text(g, that string);
 *    glistener_append_prompt(g);
 *
 *
 * --------
 * void glistener_set_prompt(glistener *g, const char *str);
 *
 *  This sets the text of the prompt.  It defaults to ">" but can be anything.
 *  One way to get, for example, the unicode lower-case lambda followed by ">" as the prompt is:
 *    uint8_t prompt[4] = {0xce, 0xbb, '>', '\0'}; 
 *    glistener_set_prompt(g, prompt);
 *  UTF8 lambda is 0xce 0xbb.
 *
 *
 * --------
 * void glistener_set_prompt_tag(glistener *g, GtkTextTag *m);
 *
 *  This sets the GtkTextTag for the prompt.  It defaults to NULL, but it's nicer to
 *  set off the prompt in some way (like bold-face).  Here's a tag that makes our prompt
 *  bold red:   
 *    glistener_set_prompt_tag(g, 
 *      gtk_text_buffer_create_tag(buffer, "glistener_prompt_tag", "weight", PANGO_WEIGHT_BOLD, "foreground", "red", NULL));
 *
 *   
 * --------
 * int glistener_prompt_position(glistener *g);
 *
 *  This returns the current (active) prompt offset in the listener text buffer.
 *
 * 
 * --------
 * void glistener_set_cursor_shape(glistener *g, GdkCursor *cursor_shape);
 *
 *  This sets the current cursor shape.  It is normally an arrow, but changes to
 *  a watch or hour-glass during evaluation:
 *    gdk_cursor_new(GDK_WATCH) or gdk_cursor_new(GDK_LEFT_PTR)
 *
 *
 * --------
 * int glistener_cursor_position(glistener *g);
 *
 *  This returns the cursor offset in the listener.
 *
 *
 * --------
 * void glistener_set_cursor_position(glistener *g, int position);
 *
 *  This moves the listener's cursor to the offset "position".
 *
 *				      
 * --------
 * void glistener_scroll_to_end(glistener *g);
 *
 *  This scrolls the view of the listener's text to the end of the text.
 *
 *				      
 * --------
 * void glistener_post_status(glistener *g, const char *msg);
 *
 *  This places "msg" in the listener's statusbar.  The previous text, if any
 *  is removed, so you don't need to worry about gtk_statusbar_pop.
 *
 *
 * --------
 * void glistener_clear_status(glistener *g);
 *
 *  This removes all messages from the statusbar.
 *
 *
 * --------
 * void glistener_clear(glistener *g);
 *
 *  This deletes all the text in the listener, leaving only the initial prompt.
 *
 *
 * --------
 * bool glistener_write(glistener *g, FILE *fp);
 *
 *  This writes the current listener text contents to the file "fp".
 *
 *
 * --------
 * void glistener_set_highlight_tag(glistener *g, GtkTextTag *m);
 *
 *  This sets the GtkTexTag for highlighted text, normally marking an unmatched
 *  open parenthesis.  The default is bold face, red foreground, but I may change
 *  that to red backgound.  See glistener_set_prompt_tag above for an example.
 *				      
 *
 * --------
 * void glistener_set_font(glistener *g, PangoFontDescription *font);
 *
 *  This sets the listener text font.  It defaults to "Monospace 11".  Indentation
 *  code assumes fixed-width spacing, but that's not a big deal.  
 *    glistener_set_font(g, pango_font_description_from_string("Monospace 10"));
 *
 *				      
 * --------
 * void glistener_set_text_color(glistener *g, GdkRGBA *p);
 *
 *  This sets the text foreground color, normally black.  In gtk 2, the color
 *  is GdkColor*. This snippet makes the text green:
 *    GdkRGBA color;
 *    color.red = 0.0; color.green = 0.97; color.blue = 0.0; color.alpha = 1.0;
 *    glistener_set_text_color(g1, &color);
 *
 *				      
 * --------
 * void glistener_set_background_color(glistener *g, GdkRGBA *p);
 *
 *  This sets the listener background color, normally white.  In gtk 2, use
 *  a GdkColor* instead.  The following code gives the listener a light blue
 *  background:
 *    GdkRGBA color;
 *    color.red = 0.94; color.green = 0.97; color.blue = 1.0; color.alpha = 1.0;
 *    glistener_set_background_color(g1, &color);
 *
 *				      
 * --------
 * void glistener_key_bindings(glistener *g, gpointer cls);
 *
 *  This establishes the listener's emacs-inspired keybindings in the gtk
 *  widget class "cls" (normally gtk_entry).  In Snd I use this to make sure
 *  all the text-oriented widgets share the same basic set:
 *    glistener_key_bindings(g, GTK_ENTRY_GET_CLASS(GTK_ENTRY(text)));
 *
 *				      
 * --------
 * void glistener_is_schemish(glistener *g, bool filtering);
 *
 *  This determines whether the listener tries to parse the text before sending it to
 *  the evaluator.  It defaults to true, expecting more-or-less lispish syntax.  In other
 *  languages (Ruby and Forth in Snd, for example), set this to false.
 *				      
 *
 *
 * --------
 * void glistener_set_completer(glistener *g, void (*completer)(glistener *g, bool (*symbol_func)(const char *symbol_name, void *data), void *data))
 *
 *  The completer is called whenever the user types TAB after what appears to be a
 *  partial symbol name (in a string, the filename completer is called instead).
 *  To find a plausible completion, the listener needs access to the table of currently
 *  known symbols.  In s7, s7_for_each_symbol_name runs through the symbol table,
 *  calling a function on each symbol, and stopping when the function returns true,
 *  so symbol completion in s7 is:
 *
 *    s7_for_each_symbol_name(s7, symbol_func, data);
 *
 *  The completer function in s7 is:
 *
 *    static void completer(glistener *g, bool (*symbol_func)(const char *symbol_name, void *data), void *data)
 *    {
 *      s7_for_each_symbol_name(s7, symbol_func, data);
 *    }
 *
 *  and it is tied into the listener via:
 *
 *    glistener_set_completer(g1, completer);
 *
 *
 *				      
 * --------
 * void glistener_set_helper(glistener *g, const char *(*help)(glistener *g, const char *text))
 *
 *  The helper is called whenever the listener thinks a help string is in order.  It is passed 
 *  a string ("text") about which it hopes to get help.  If the helper returns NULL, nothing 
 *  happens, but otherwise, the returned help is posted in the status area.  In s7, it is:
 *
 *    static const char *helper(glistener *g, const char *text)
 *    {
 *      s7_pointer sym;
 *      sym = s7_symbol_table_find_name(s7, text);
 *      if (sym)
 *        return(s7_help(s7, sym));
 *      glistener_clear_status(g);
 *      return(NULL);
 *    }
 *
 *  and it is tied into the listener via:
 *
 *    glistener_set_helper(g1, helper);
 *  
 *
 *				      
 * --------
 * void glistener_set_evaluator(glistener *g, void (*eval)(glistener *g, const char *text))
 *
 *  This is the heart of the listener.  It gets a string ("text") and
 *  does something debonair.  In s7, it calls s7_eval_c_string, and then
 *  displays the result.  Although error handling makes the code complicated,
 *  the basic idea is:
 *
 *    static void evaluator(glistener *g, const char *text)
 *    {
 *      s7_pointer result;
 *      char *msg;
 *      result = s7_eval_c_string(s7, text);
 *      glistener_append_text(g, "\n");
 *      msg = s7_object_to_c_string(s7, result);
 *      glistener_append_text(g, msg);
 *      if (msg) free(msg);
 *      glistener_append_prompt(g);
 *    }
 *    
 *  tied into the listener via:
 *
 *    glistener_set_evaluator(g1, evaluator);
 *
 *				      
 *
 * --------
 * void glistener_set_checker(glistener *g, const char *(*check)(glistener *g, const char *text))
 *
 *  The checker is called when close-paren is typed and the matching open-paren can be found.
 *  "text" in this case is the expression. If it notices something awry in the expression, 
 *  it can squawk in the status area.  The checker in snd-glistener.c calls s7_read on the text,
 *  then runs various checks on that code (function argument checks and so on).
 *
 *
 *
 * --------
 * void glistener_set_colorizer(glistener *g, void (*colorizer)(glistener *g, glistener_colorizer_t type, int start, int end))
 *
 *  The colorizer highlights portions of the code, normally using colors, but the GtkTextBuffer
 *  is at your disposal.  This function is called whenever there is any change in the listener
 *  buffer's contents.  The "start" and "end" ints are offsets into the buffer giving what the
 *  listener thinks are the current bounds where there is an entity of "type", one of the glistener_colorizer_t
 *  enums given above.  See the colorizer function in snd-glistener.c.
 *
 *
 *
 * --------
 * void glistener_set_keyer(glistener *g, bool (*key)(glistener *g, GtkWidget *w, GdkEventKey *e))
 *
 *  The keyer is called upon key-press.  It is passed the widget involved (the listener
 *  text-view widget), and the GdkEvent.  The event has fields giving the key itself, and
 *  any auxiliary keys (control, shift, etc).  If you don't want to handle that key, or
 *  want the built-in action to be called, return false.  If keyer returns true, any
 *  further handling of the signal is blocked.  The default keyer simply returns false.
 *
 *  glistener rebinds some of the keys to mimic Emacs, then adds a few others:
 *    M-<  go to start of text (also up-arrow)
 *    M->  go to end of text   (also down-arrow)
 *    M-a  go to previous prompt
 *    M-e  go to next prompt
 *    M-n  restore previous expression, moving back in the history
 *    M-p  restore previous expression, moving forward in the history
 *    Tab  complete preceding name, if any, else look for indentation opportunity
 *    Return send current expression, if any, to evaluator, else insert return
 *
 *
 *
 * --------
 * char *glistener_evaluate(glistener *g);
 * char *glistener_complete(glistener *g);
 *
 *  These two functions are intended for regression testing.  glistener_evaluate
 *  simulates typing <cr>, and glistener_complete simulates <tab>.
 */


#endif