1 /* manpage.c - draws an interactive man page browser
2    Copyright (C) 1996-2017 Paul Sheer
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17    02111-1307, USA.
18  */
19 
20 #include <config.h>
21 #include <stdio.h>
22 #include <my_string.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25 #ifdef HAVE_UNISTD_H
26 #include <unistd.h>
27 #endif
28 
29 #include <X11/Intrinsic.h>
30 #include "lkeysym.h"
31 
32 #include "stringtools.h"
33 #include "app_glob.c"
34 
35 #include "coolwidget.h"
36 #include "coollocal.h"
37 #include "loadfile.h"
38 
39 #include "mad.h"
40 #include "edit.h"
41 #include "editcmddef.h"
42 
43 CWidget *CManpageDialog (Window in, int x, int y, int columns, int lines, const char *manpage);
44 
45 /* must be a power of 2 */
46 #define NUM_HISTORY 16
47 static struct history {
48     char *text;
49     int line;
50 } history[NUM_HISTORY] =
51 
52 {
53     {
54 	0, 0
55     },
56     {
57 	0, 0
58     },
59     {
60 	0, 0
61     },
62     {
63 	0, 0
64     },
65 
66     {
67 	0, 0
68     },
69     {
70 	0, 0
71     },
72     {
73 	0, 0
74     },
75     {
76 	0, 0
77     },
78 
79     {
80 	0, 0
81     },
82     {
83 	0, 0
84     },
85     {
86 	0, 0
87     },
88     {
89 	0, 0
90     },
91 
92     {
93 	0, 0
94     },
95     {
96 	0, 0
97     },
98     {
99 	0, 0
100     },
101     {
102 	0, 0
103     }
104 };
105 
106 static unsigned int history_current = 0;
107 
108 #define my_is_letter(ch) (isalpha (ch) || ch == '\b' || ch == '_' || ch == '-')
109 
check_prev_next(void)110 void check_prev_next (void)
111 {
112     if (history[history_current & (NUM_HISTORY - 1)].text)
113 	CIdent ("mandisplayfile.next")->disabled = 0;
114     else {
115 	CIdent ("mandisplayfile.next")->disabled = 1;
116 	if (CGetFocus () == CIdent ("mandisplayfile.next")->winid) {
117 	    CFocus (CIdent ("mandisplayfile.text"));
118 	    CExpose ("mandisplayfile.next");
119 	}
120     }
121     if (history[(history_current - 2) & (NUM_HISTORY - 1)].text)
122 	CIdent ("mandisplayfile.prev")->disabled = 0;
123     else {
124 	CIdent ("mandisplayfile.prev")->disabled = 1;
125 	if (CGetFocus () == CIdent ("mandisplayfile.prev")->winid) {
126 	    CFocus (CIdent ("mandisplayfile.text"));
127 	    CExpose ("mandisplayfile.prev");
128 	}
129     }
130 }
131 
add_to_history(char * t)132 void add_to_history (char *t)
133 {
134     char **h;
135     history_current &= (NUM_HISTORY - 1);
136     if (history[history_current].text)
137 	free (history[history_current].text);
138     h = &(history[(history_current + 1) & (NUM_HISTORY - 1)].text);
139     if (*h) {
140 	free (*h);
141 	*h = 0;
142     }
143     history[history_current++].text = (char *) strdup (t);
144 }
145 
146 int mansearch_callback (CWidget * w, XEvent * x, CEvent * c);
147 int mansearchagain_callback (CWidget * w, XEvent * x, CEvent * c);
148 
manpageclear_callback(CWidget * w,XEvent * x,CEvent * c)149 int manpageclear_callback (CWidget * w, XEvent * x, CEvent * c)
150 {
151     int i;
152     CDestroyWidget ("mandisplayfile");
153     for (i = 0; i < NUM_HISTORY; i++) {
154 	if (history[i].text) {
155 	    free (history[i].text);
156 	    history[i].text = 0;
157 	    history[i].line = 0;
158 	}
159     }
160     history_current = 0;
161     return 0;
162 }
163 
164 int calc_text_pos2 (CWidget * w, long b, long *q, int l);
165 
manpage_callback(CWidget * w,XEvent * x,CEvent * c)166 int manpage_callback (CWidget * w, XEvent * x, CEvent * c)
167 {
168     int p, q;
169     unsigned char m[128] = "";
170     char *t;
171     if (c->command == CK_Cancel || !strcmp (c->ident, "mandisplayfile.clear")
172 	|| !strcmp (c->ident, "mandisplayfile.done")) {
173 	CDestroyWidget ("mandisplayfile");
174 	return 0;
175     }
176     if (c->command == CK_Find) {
177 	mansearch_callback (w, x, c);
178     }
179     if (c->command == CK_Find_Again) {
180 	mansearchagain_callback (w, x, c);
181     }
182     if (c->double_click) {
183 	long lq;
184 	unsigned char *text = (unsigned char *) w->text;
185 	q = strmovelines ((char *) text, w->current, c->yt - w->firstline, w->width);
186 	calc_text_pos2 (w, q, &lq, c->x - 4);
187 	p = q = lq;
188 	if (my_is_letter (text[q])) {
189 	    while (--q >= 0)
190 		if (!my_is_letter (text[q]))
191 		    break;
192 	    q++;
193 	    while (text[++p])
194 		if (!my_is_letter (text[p]))
195 		    break;
196 	    strncpy ((char *) m, (char *) text + q, min (p - q, 127));
197 	    m[min (p - q, 127)] = 0;
198 	    t = str_strip_nroff ((char *) m, 0);
199 	    if (*t != '-')
200 		CManpageDialog (0, 0, 0, 0, 0, t);
201 	    free (t);
202 	}
203     }
204     check_prev_next ();
205     return 0;
206 }
207 
record_line(void)208 void record_line (void)
209 {
210     CWidget *w;
211     if (!(w = CIdent ("mandisplayfile.text")))
212 	return;
213     if (!history[(history_current - 1) & (NUM_HISTORY - 1)].text)
214 	return;
215     history[(history_current - 1) & (NUM_HISTORY - 1)].line = w->firstline;
216 }
217 
manpageprev_callback(CWidget * w,XEvent * x,CEvent * c)218 int manpageprev_callback (CWidget * w, XEvent * x, CEvent * c)
219 {
220     record_line ();
221     history_current--;
222     check_prev_next ();
223     CRedrawTextbox ("mandisplayfile.text", history[(history_current - 1) & (NUM_HISTORY - 1)].text, 1);
224     CSetTextboxPos (CIdent ("mandisplayfile.text"), TEXT_SET_LINE, history[(history_current - 1) & (NUM_HISTORY - 1)].line);
225     CFocus (CIdent ("mandisplayfile.text"));
226     return 0;
227 }
228 
manpagenext_callback(CWidget * w,XEvent * x,CEvent * c)229 int manpagenext_callback (CWidget * w, XEvent * x, CEvent * c)
230 {
231     record_line ();
232     history_current++;
233     check_prev_next ();
234     CRedrawTextbox ("mandisplayfile.text", history[(history_current - 1) & (NUM_HISTORY - 1)].text, 1);
235     CSetTextboxPos (CIdent ("mandisplayfile.text"), TEXT_SET_LINE, history[(history_current - 1) & (NUM_HISTORY - 1)].line);
236     CFocus (CIdent ("mandisplayfile.text"));
237     return 0;
238 }
239 
240 extern int replace_scanf;
241 extern int replace_regexp;
242 extern int replace_whole;
243 extern int replace_case;
244 
text_get_byte(unsigned char * text,long index)245 int text_get_byte (unsigned char *text, long index)
246 {
247     return text[index];
248 }
249 
Ctextboxsearch(CWidget * w,int again)250 void Ctextboxsearch (CWidget * w, int again)
251 {
252     static char *old = NULL;
253     char *exp = "";
254     int isfocussed = 0;
255 
256     exp = old ? old : exp;
257     if (again) {
258 	if (!old)
259 	    return;
260 	exp = (char *) strdup (old);
261     } else {
262 	isfocussed = (w->winid == CGetFocus ());
263 	edit_search_replace_dialog (w->parentid, 20, 20, &exp, 0, 0, _(" Search "), 0);
264     }
265 
266     if (exp) {
267 	if (*exp) {
268 	    int len, l;
269 	    char *t;
270 	    long search_start;
271 	    if (old)
272 		free (old);
273 	    old = (char *) strdup (exp);
274 /* here we run strip on everything from here
275    to the end of the file then search through
276    the stripped text */
277 	    search_start = strmovelines (w->text, w->current, 1, 32000);
278 	    t = str_strip_nroff (w->text + search_start, &l);
279 	    search_start = edit_find (0, (unsigned char *) exp, &len, l, (int (*)(void *, long)) text_get_byte, (void *) t, 0);
280 	    if (search_start == -3) {
281 		CErrorDialog (w->mainid, 20, 20, _(" Error "), _(" Invalid regular expression. "));
282 	    } else if (search_start >= 0) {
283 		l = strcountlines (t, 0, search_start, 32000) + 1 + w->firstline;
284 		CSetTextboxPos (w, TEXT_SET_LINE, l);
285 		CExpose (w->ident);
286 	    } else {
287 		CErrorDialog (w->mainid, 20, 20, _(" Search "), _(" Search string not found. "));
288 	    }
289 	    free (t);
290 	}
291 	free (exp);
292     }
293 }
294 
mansearch_callback(CWidget * w,XEvent * x,CEvent * c)295 int mansearch_callback (CWidget * w, XEvent * x, CEvent * c)
296 {
297     Ctextboxsearch (CIdent ("mandisplayfile.text"), 0);
298     CFocus (CIdent ("mandisplayfile.text"));
299     return 0;
300 }
301 
mansearchagain_callback(CWidget * w,XEvent * x,CEvent * c)302 int mansearchagain_callback (CWidget * w, XEvent * x, CEvent * c)
303 {
304     Ctextboxsearch (CIdent ("mandisplayfile.text"), 1);
305     CFocus (CIdent ("mandisplayfile.text"));
306     return 0;
307 }
308 
309 char *option_man_cmdline = MAN_CMD;
310 
CManpageDialog(Window in,int x,int y,int columns,int lines,const char * manpage)311 CWidget *CManpageDialog (Window in, int x, int y, int columns, int lines, const char *manpage)
312 {
313     CWidget *res;
314     char *t;
315     char *argv[] =
316     {
317 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
318     };
319     int i = 0, man_pipe, fre = 1, line = 0;
320 
321     record_line ();
322 
323     if (manpage) {
324 	Window win = 0;
325 	CWidget *w;
326 	char *p, *q, *r;
327 	w = CIdent ("mandisplayfile");
328 	if (w)
329 	    win = w->winid;
330 	i = 0;
331 	p = q = (char *) strdup (option_man_cmdline);
332 	r = p;
333 	for (i = 0; i < 32; i++) {
334 	    q = strchr (p, ' ');
335 	    if (strcmp (p, "%m"))
336 		argv[i] = p;
337 	    else
338 		argv[i] = (char *) manpage;
339 	    if (!q)
340 		break;
341 	    *q = 0;
342 	    p = q + 1;
343 	}
344 	if (win)
345 	    CHourGlass (win);
346 	CHourGlass (CFirstWindow);
347 	if (triple_pipe_open (0, &man_pipe, 0, 1, argv[0], argv) <= 0) {	/* "1" is to pipe both stderr AND stdout into man_pipe */
348 	    CErrorDialog (CFirstWindow, 20, 20, _(" Manual page "), _(" Fail trying to run man, check 'option_man_cmdline' in the file ~/.cedit/.cooledit.ini "));
349 	    if (win)
350 		CUnHourGlass (win);
351 	    free (r);
352 	    return 0;
353 	}
354 	free (r);
355 	t = read_pipe (man_pipe, 0);
356 	close (man_pipe);
357 	if (win)
358 	    CUnHourGlass (win);
359 	CUnHourGlass (CFirstWindow);
360     } else {
361 	if (!(t = history[(history_current - 1) & (NUM_HISTORY - 1)].text))
362 	    return 0;
363 	line = history[(history_current - 1) & (NUM_HISTORY - 1)].line;
364 	fre = 0;
365     }
366 
367     if (!t) {
368 	CErrorDialog (CFirstWindow, 20, 20, _(" Manual Page "), get_sys_error (_(" Error reading from pipe, check 'option_man_cmdline' in the file ~/.cedit/.cooledit.ini ")));
369 	return 0;
370     } else if (*t) {
371 	if (fre)
372 	    add_to_history (t);
373 	if (CIdent ("mandisplayfile.text")) {
374 	    CRedrawTextbox ("mandisplayfile.text", t, 0);
375 	} else {
376 	    Window win;
377 	    CWidget *w;
378 	    if (in) {
379 		win = in;
380 	    } else {
381 		win = CDrawMainWindow ("mandisplayfile", _(" Manual Page "));
382 		CGetHintPos (&x, &y);
383 	    }
384 	    CPushFont ("editor", 0);
385 	    w = CDrawTextbox ("mandisplayfile.text", win,
386 			      x, y, columns * FONT_MEAN_WIDTH + 7,
387 			      lines * FONT_PIX_PER_LINE, line, 0, t,
388 			      TEXTBOX_MAN_PAGE | TEXTBOX_NO_CURSOR);
389 	    CPopFont ();
390 /* Toolhint */
391 	    CSetToolHint ("mandisplayfile.text", _("Double click on words to open new man pages"));
392 	    w->position |= POSITION_HEIGHT | POSITION_WIDTH;
393 	    (CIdent ("mandisplayfile.text.vsc"))->position = POSITION_HEIGHT | POSITION_RIGHT;
394 	    CGetHintPos (0, &y);
395 	    (CDrawButton ("mandisplayfile.prev", win, x, y, AUTO_WIDTH, AUTO_HEIGHT, _(" Previous ")))->position = POSITION_BOTTOM;
396 	    CGetHintPos (&x, 0);
397 	    (CDrawButton ("mandisplayfile.next", win, x, y, AUTO_WIDTH, AUTO_HEIGHT, _(" Next ")))->position = POSITION_BOTTOM;
398 	    CGetHintPos (&x, 0);
399 	    (CDrawButton ("mandisplayfile.search", win, x, y, AUTO_WIDTH, AUTO_HEIGHT, _(" Search ")))->position = POSITION_BOTTOM;
400 	    CGetHintPos (&x, 0);
401 	    (CDrawButton ("mandisplayfile.again", win, x, y, AUTO_WIDTH, AUTO_HEIGHT, _(" Again ")))->position = POSITION_BOTTOM;
402 	    if (!in) {
403 		CGetHintPos (&x, 0);
404 		(CDrawPixmapButton ("mandisplayfile.done", win, x, y, PIXMAP_BUTTON_TICK))->position = POSITION_BOTTOM;
405 		CGetHintPos (&x, 0);
406 		(CDrawPixmapButton ("mandisplayfile.clear", win, x, y, PIXMAP_BUTTON_CROSS))->position = POSITION_BOTTOM;
407 		CSetSizeHintPos ("mandisplayfile");
408 		CMapDialog ("mandisplayfile");
409 		CSetWindowResizable ("mandisplayfile", FONT_MEAN_WIDTH * 15, FONT_PIX_PER_LINE * 15, 1600, 1200);	/* minimum and maximum sizes */
410 	    }
411 	    CAddCallback ("mandisplayfile.done", manpage_callback);
412 	    CAddCallback ("mandisplayfile.clear", manpageclear_callback);
413 	    CAddCallback ("mandisplayfile.text", manpage_callback);
414 	    CAddCallback ("mandisplayfile.next", manpagenext_callback);
415 	    CAddCallback ("mandisplayfile.prev", manpageprev_callback);
416 	    CAddCallback ("mandisplayfile.search", mansearch_callback);
417 	    CAddCallback ("mandisplayfile.again", mansearchagain_callback);
418 	}
419 	check_prev_next ();
420 	if (t && fre)
421 	    free (t);
422     } else {
423 	CErrorDialog (CFirstWindow, 20, 20, _(" Manual page "), _(" Fail trying to popen man, check 'option_man_cmdline' in the file ~/.cedit/.cooledit.ini "));
424 	return 0;
425     }
426     res = CIdent ("mandisplayfile");
427     if (res)
428 	return res;
429     return CIdent ("mandisplayfile.text");
430 }
431