jcs
/wikipedia
/amendments
/13
browser: Add live search drop-down list
Move request buffers into the http_request struct so each request
        automatically gets them.
    jcs made amendment 13 over 3 years ago
--- browser.c	Wed Aug 24 20:50:52 2022
+++ browser.c	Thu Aug 25 14:14:13 2022
@@ -35,6 +35,8 @@ void browser_mouse_down(struct focusable *focusable, E
 bool browser_handle_menu(struct focusable *focusable, short menu,
   short item);
 void browser_atexit(struct focusable *focusable);
+void browser_live_search(struct browser *browser);
+void browser_hide_search_results(struct browser *browser);
 
 Pattern fill_pattern;
 
@@ -44,57 +46,41 @@ browser_idle(struct focusable *focusable, EventRecord 
 	struct browser *browser = (struct browser *)focusable->cookie;
 	size_t len;
 
-	TEIdle(browser->input_te);
-	
-	if (browser->wpr)
-		wikipedia_request_process(browser->wpr);
-
-#if 0
 	switch (browser->state) {
 	case BROWSER_STATE_IDLE:
-		if (browser->need_refresh) {
-			browser->need_refresh = false;
-			browser->state = BROWSER_STATE_UPDATE_AMENDMENT_LIST;
+		TEIdle(browser->input_te);
+		
+		if (browser->last_input_for_search != 0 &&
+		  (Ticks - browser->last_input_for_search > 30)) {
+			browser->last_input_for_search = 0;
+			browser_live_search(browser);
 		}
 		break;
-	case BROWSER_STATE_ADD_FILE:
-		if (repo_add_file(browser->repo) == NULL)
+	case BROWSER_STATE_DO_SEARCH: {
+		TERec *te;
+		char *input;
+
+		HLock(browser->input_te);
+		te = *(browser->input_te);
+		HLock(te->hText);
+		(*(te->hText))[te->teLength] = '\0';
+		input = xstrdup(*(te->hText), "browser te input");
+		HUnlock(te->hText);
+		HUnlock(browser->input_te);
+		browser->wpr = wikipedia_fetch_article(browser, input);
+		xfree(&input);
+		browser->state = BROWSER_STATE_PROCESS_SEARCH;
+		break;
+	}
+	case BROWSER_STATE_PROCESS_SEARCH:
+		if (browser->wpr == NULL) {
 			browser->state = BROWSER_STATE_IDLE;
-		else
-			browser->state = BROWSER_STATE_UPDATE_FILE_LIST;
+			break;
+		}
+		
+		wikipedia_request_process(browser->wpr);
 		break;
-	case BROWSER_STATE_UPDATE_FILE_LIST:
-		browser_add_files(browser);
-		browser->state = BROWSER_STATE_IDLE;
-		break;
-	case BROWSER_STATE_UPDATE_AMENDMENT_LIST:
-		browser_filter_amendments(browser);
-		browser->state = BROWSER_STATE_IDLE;
-		break;
-	case BROWSER_STATE_OPEN_COMMITTER:
-		committer_init(browser);
-		browser->state = BROWSER_STATE_WAITING_FOR_COMMITTER;
-		break;
-	case BROWSER_STATE_WAITING_FOR_COMMITTER:
-		break;
-	case BROWSER_STATE_DISCARD_CHANGES:
-		browser_discard_changes(browser);
-		browser->state = BROWSER_STATE_IDLE;
-		break;
-	case BROWSER_STATE_EXPORT_PATCH:
-		browser_export_patch(browser);
-		browser->state = BROWSER_STATE_IDLE;
-		break;
-	case BROWSER_STATE_APPLY_PATCH:
-		browser_apply_patch(browser);
-		browser->state = BROWSER_STATE_IDLE;
-		break;
-	case BROWSER_STATE_EDIT_AMENDMENT:
-		browser_edit_amendment(browser);
-		browser->state = BROWSER_STATE_IDLE;
-		break;
 	}
-#endif
 }
 
 struct browser *
@@ -107,7 +93,7 @@ browser_init(void)
 	Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */
 	Point cell_size = { 0, 0 };
 	Cell cell = { 0 };
-	short n;
+	short n, width, height;
 	
 	browser = xmalloczero(sizeof(struct browser), "browser");
 	browser->state = BROWSER_STATE_IDLE;
@@ -115,11 +101,14 @@ browser_init(void)
 	GetIndPattern(&fill_pattern, sysPatListID, 22);
 
 	/* main window */
-	bounds.left = (PADDING / 2);
-	bounds.top = screenBits.bounds.top + (GetMBarHeight() * 2) - 1 +
-	  (PADDING / 2);
-	bounds.right = screenBits.bounds.right - 1 - (PADDING / 2);
-	bounds.bottom = screenBits.bounds.bottom - 1 - (PADDING / 2);
+	width = screenBits.bounds.right - screenBits.bounds.left - PADDING;
+	if (width > 620)
+		width = 620;
+	height = screenBits.bounds.bottom - screenBits.bounds.top -
+	  PADDING - (GetMBarHeight() * 2);
+	if (height > 340)
+		height = 340;
+	center_in_screen(width, height, true, &bounds);
 	
 	snprintf(title, sizeof(title), "%s", PROGRAM_NAME);
 	CtoPstr(title);
@@ -129,6 +118,7 @@ browser_init(void)
 		err(1, "Can't create window");
 	SetPort(browser->win);
 	
+	/* search input TE */
 	bounds.top = PADDING;
 	bounds.left = PADDING;
 	bounds.right = browser->win->portRect.right - PADDING;
@@ -141,7 +131,8 @@ browser_init(void)
 	TEAutoView(true, browser->input_te);
 	TEActivate(browser->input_te);
 
-	bounds.top = bounds.bottom + PADDING;
+	/* main article TE */
+	bounds.top = (*(browser->input_te))->viewRect.bottom + PADDING;
 	bounds.left = PADDING;
 	bounds.right = browser->win->portRect.right - SCROLLBAR_WIDTH - PADDING;
 	bounds.bottom = browser->win->portRect.bottom - PADDING;
@@ -191,7 +182,7 @@ browser_close(struct focusable *focusable)
 	TEDispose(browser->te);
 	DisposeWindow(browser->win);
 	
-	free(browser);
+	xfree(&browser);
 
 	return true;
 }
@@ -262,6 +253,14 @@ browser_update(struct focusable *focusable, EventRecor
 		InsetRect(&r, -1, -1);
 		FrameRect(&r);
 
+		if (browser->search_results != NULL) {
+			r = (*(browser->search_results))->rView;
+			InsetRect(&r, -1, -1);
+			FillRect(&r, white);
+			FrameRect(&r);
+			LUpdate(browser->win->visRgn, browser->search_results);
+		}
+		
 		UpdtControl(browser->win, browser->win->visRgn);
 		
 		browser_update_menu(browser);
@@ -273,13 +272,13 @@ browser_update(struct focusable *focusable, EventRecor
 void
 browser_mouse_down(struct focusable *focusable, EventRecord *event)
 {
-	Cell selected = { 0 }, now = { 0 };
+	struct browser *browser = (struct browser *)focusable->cookie;
+	char str[255];
+	Cell selected = { 0 };
 	Point p;
 	ControlHandle control;
 	Rect r;
-	struct browser *browser = (struct browser *)focusable->cookie;
-	short val, adj, page, was_selected, part, i, data_len;
-	char *path;
+	short val, adj, page, i, len, part;
 
 	p = event->where;
 	GlobalToLocal(&p);
@@ -291,6 +290,25 @@ browser_mouse_down(struct focusable *focusable, EventR
 		return;
 	}
 
+	if (browser->search_results != NULL) {
+		r = (*(browser->search_results))->rView;
+		r.right += SCROLLBAR_WIDTH;
+		if (PtInRect(p, &r)) {
+			LClick(p, event->modifiers, browser->search_results);
+			selected.v = 0;
+			if (LGetSelect(true, &selected, browser->search_results)) {
+				len = sizeof(str);
+				LGetCell(&str, &len, selected, browser->search_results);
+				TESetText(str, len, browser->input_te);
+				InvalRect(&(*(browser->input_te))->viewRect);
+				browser_hide_search_results(browser);
+				browser->state = BROWSER_STATE_DO_SEARCH;
+			}
+	
+			return;
+		}
+	}
+	
 	r = (*(browser->te))->viewRect;
 	if (PtInRect(p, &r)) {
 		TEClick(p, ((event->modifiers & shiftKey) != 0), browser->te);
@@ -331,26 +349,96 @@ void
 browser_key_down(struct focusable *focusable, EventRecord *event)
 {
 	struct browser *browser = (struct browser *)(focusable->cookie);
-	TERec *te;
-	char *input, k;
-	
+	char k;
+
 	k = (event->message & charCodeMask);
 	if (k == '\r') {
-		HLock(browser->input_te);
-		te = *(browser->input_te);
-		HLock(te->hText);
-		(*(te->hText))[te->teLength] = '\0';
-		input = xstrdup(*(te->hText), "browser te input");
-		HUnlock(te->hText);
-		HUnlock(browser->input_te);
-		browser->wpr = wikipedia_fetch_article(browser, input);
-		free(input);
+		browser->state = BROWSER_STATE_DO_SEARCH;
 	} else {
 		TEKey(k, browser->input_te);
 		TESelView(browser->input_te);
+		browser->last_input_for_search = Ticks;
 	}
 }
 
+void
+browser_live_search(struct browser *browser)
+{
+	TERec *te;
+	Rect bounds = { 0 };
+	Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */
+	char *input, **results, k;
+	size_t nresults, n;
+	Point cell_size = { 0, 0 };
+	Cell cell = { 0, 0 };
+	Rect r;
+
+	HLock(browser->input_te);
+	te = *(browser->input_te);
+	
+	if (te->teLength == 0) {
+		HUnlock(browser->input_te);
+		browser_hide_search_results(browser);
+		return;
+	}
+	
+	SetCursor(*(GetCursor(watchCursor)));
+	
+	HLock(te->hText);
+	(*(te->hText))[te->teLength] = '\0';
+	input = xstrdup(*(te->hText), "browser te input");
+	HUnlock(te->hText);
+	HUnlock(browser->input_te);
+	nresults = wikipedia_fetch_search_results(browser, input, &results);
+	xfree(&input);
+
+	bounds.top = (*(browser->input_te))->viewRect.bottom + 1;
+	bounds.left = PADDING;
+	bounds.bottom = bounds.top + 70;
+	bounds.right = bounds.left + 200;
+	
+	browser->search_results = LNew(&bounds, &data_bounds, cell_size, 0,
+	  browser->win, false, false, false, true);
+	if (!browser->search_results)
+		panic("LNew failed");
+	(*(browser->search_results))->selFlags = lOnlyOne;
+	LAddColumn(1, 0, browser->search_results);
+
+	for (n = 0; n < nresults; n++) {
+		LAddRow(1, cell.v, browser->search_results);
+		LSetCell(results[n], strlen(results[n]), cell,
+		  browser->search_results);
+		cell.v++;
+		xfree(&results[n]);
+	}
+	
+	r = (*(browser->search_results))->rView;
+	FillRect(&r, white);
+	InsetRect(&r, -1, -1);
+	FrameRect(&r);
+	LDoDraw(true, browser->search_results);
+	LUpdate(browser->win->visRgn, browser->search_results);
+	
+	SetCursor(&arrow);
+}
+
+void
+browser_hide_search_results(struct browser *browser)
+{
+	Rect r;
+	
+	if (browser->search_results == NULL)
+		return;
+		
+	r = (*(browser->search_results))->rView;
+	r.right += SCROLLBAR_WIDTH;
+	InsetRect(&r, -1, -1);
+	InvalRect(&r);
+	
+	LDispose(browser->search_results);
+	browser->search_results = NULL;
+}
+
 bool
 browser_handle_menu(struct focusable *focusable, short menu, short item)
 {
@@ -483,9 +571,9 @@ no_overflow:
 	progress(NULL);
 	TEStylInsert(str, len, scrp_rec_h, browser->te);
 	
-	TEPinScroll(0, -INT_MAX, browser->te);
-	SetCtlValue(browser->te_scroller, GetCtlMax(browser->te_scroller));
+	SetCtlValue(browser->te_scroller, GetCtlMin(browser->te_scroller));
 	UpdateScrollbarForTE(browser->te_scroller, browser->te, false);
+	UpdtControl(browser->win, browser->win->visRgn);
 	HUnlock(browser->te);
 	
 	last_style = style;
--- browser.h	Wed Aug 24 17:30:16 2022
+++ browser.h	Thu Aug 25 12:19:52 2022
@@ -21,7 +21,9 @@
 #include "http.h"
 
 enum {
-	BROWSER_STATE_IDLE
+	BROWSER_STATE_IDLE,
+	BROWSER_STATE_DO_SEARCH,
+	BROWSER_STATE_PROCESS_SEARCH
 };
 
 #define STYLE_BOLD		(1 << 0)
@@ -37,8 +39,10 @@ struct browser {
 	short state;
 	WindowPtr win;
 	TEHandle input_te;
+	unsigned long last_input_for_search;
 	TEHandle te;
 	ControlHandle te_scroller;
+	ListHandle search_results;
 	struct wikipedia_request *wpr;
 };
 
--- http.c	Wed Aug 24 13:19:56 2022
+++ http.c	Thu Aug 25 11:06:19 2022
@@ -91,6 +91,42 @@ cleanup:
 	return url;
 }
 
+char *
+url_encode(char *str)
+{
+	char *ret = NULL;
+	size_t len, n;
+	bool encode = false;
+	char a, b;
+	
+encode:
+	for (n = 0, len = 0; str[n] != '\0'; n++) {
+		if ((str[n] >= 'A' && str[n] <= 'Z') ||
+		  (str[n] >= 'a' && str[n] <= 'z') ||
+		  (str[n] >= '0' && str[n] <= '9') ||
+		  (str[n] == '-' || str[n] == '_' || str[n] == '.' ||
+		  str[n] == '~')) {
+			if (ret)
+				ret[len] = str[n];
+			len++;
+		} else {
+			if (ret) {
+				sprintf(ret + len, "%%%02X", str[n]);
+			}
+			len += 3;
+		}
+	}
+	
+	if (ret) {
+		ret[len] = '\0';
+		return ret;
+	}
+		
+	ret = xmalloc(len + 1, "url_encode");
+	len = 0;
+	goto encode;
+}
+
 struct http_request *
 http_get(const char *surl)
 {
@@ -201,6 +237,75 @@ http_req_read(struct http_request *req, char *data, si
 	return rlen;
 }
 
+bool
+http_req_skip_header(struct http_request *req)
+{
+	size_t len, n;
+	
+	for (;;) {
+		if (req->chunk_len > 3) {
+			/*
+			 * Leave last 3 bytes of previous read in case \r\n\r\n happens
+			 * across reads.
+			 */
+			memmove(req->chunk, req->chunk + req->chunk_len - 3,
+			  req->chunk_len - 3);
+			req->chunk_len = 3;
+		}
+		len = http_req_read(req, req->chunk + req->chunk_len,
+		  sizeof(req->chunk) - req->chunk_len);
+		if (len < 0)
+			return false;
+		if (len == 0)
+			continue;
+		req->chunk_len += len;
+		
+		for (n = 3; n < req->chunk_len; n++) {
+			if (req->chunk[n - 3] != '\r' || req->chunk[n - 2] != '\n' ||
+			  req->chunk[n - 1] != '\r' || req->chunk[n] != '\n')
+				continue;
+			
+			req->chunk_len -= n + 1;
+			memmove(req->chunk, req->chunk + n + 1, req->chunk_len);
+			req->chunk_off = 0;
+			return true;
+		}
+	}
+	
+	return false;
+}
+
+short
+http_req_chunk_peek(void *cookie)
+{
+	struct http_request *req = (struct http_request *)cookie;
+
+	if (req->chunk_len == 0 || (req->chunk_off + 1 > req->chunk_len)) {
+		req->chunk_len = http_req_read(req, req->chunk, sizeof(req->chunk));
+		req->chunk_off = 0;
+	}
+	
+	if (req->chunk_len == 0 || (req->chunk_off + 1 > req->chunk_len))
+		return EOF;
+	
+	return req->chunk[req->chunk_off];
+}
+
+short
+http_req_chunk_read(void *cookie)
+{
+	struct http_request *req = (struct http_request *)cookie;
+	short c;
+	
+	c = http_req_chunk_peek(req);
+	if (c == EOF)
+		return c;
+
+	req->chunk_off++;
+	
+	return c;
+}
+
 void
 http_req_free(void *reqptr)
 {
@@ -213,9 +318,9 @@ http_req_free(void *reqptr)
 		
 	_TCPRelease(&req->tcp_iopb, req->tcp_stream, nil, nil, false);
 
-	if (req->message != NULL)
-		xfree(&req->message);
-	xfree(&req->tcp_buf);
-	xfree(&req->url);
-	xfree(reqptr);
+//	if (req->message != NULL)
+//		xfree(&req->message);
+//	xfree(&req->tcp_buf);
+//	xfree(&req->url);
+//	xfree(reqptr);
 }
--- http.h	Fri Aug 19 14:08:56 2022
+++ http.h	Wed Aug 24 22:17:37 2022
@@ -39,12 +39,20 @@ struct http_request {
 	TCPStatusPB tcp_status_pb;
 	
 	char *message;
+
+	char chunk[1024];
+	size_t chunk_len;
+	size_t chunk_off;
 };
 	
 struct url * url_parse(const char *str);
+char * url_encode(char *str);
 
 struct http_request * http_get(const char *url);
 ssize_t http_req_read(struct http_request *req, char *data, size_t len);
+bool http_req_skip_header(struct http_request *req);
+short http_req_chunk_peek(void *req);
+short http_req_chunk_read(void *req);
 void http_req_free(void *reqptr);
 
 #endif
--- wikipedia.c	Wed Aug 24 17:56:32 2022
+++ wikipedia.c	Thu Aug 25 14:13:25 2022
@@ -26,7 +26,10 @@
 /* en.wikipedia.org doesn't support non-TLS :( */
 #define WIKIPEDIA_HOST "wikipedia.jcs.org"
 
+/* {"query":{"normalized":[{"to": */
 #define NORMALIZED_TITLE_CONTEXT "{\"query\":{\"normalized\":[{\"to\":"
+
+/* {"query":{"pages":[{"revisions":[{"slots":{"main":{"content": */
 #define ARTICLE_TEXT_CONTEXT "{\"query\":{\"pages\":[{\"revisions\":[{\"slots\":{\"main\":{\"content\":"
 
 short wikipedia_json_peek(void *cookie);
@@ -37,11 +40,10 @@ wikipedia_fetch_article(struct browser *browser, char 
 {
 	static char url[256];
 	struct wikipedia_request *wpr;
-	struct http_request *req;
 	short state;
 	char *c;
 	
-	/* XXX */
+	/* "Macintosh Plus" -> "Macintosh_Plus" */
 	for (c = name; *c != '\0'; c++) {
 		if (*c == ' ')
 			*c = '_';
@@ -54,15 +56,71 @@ wikipedia_fetch_article(struct browser *browser, char 
 	progress("Contacting Wikipedia...");
 
 	snprintf(url, sizeof(url), "http://%s/w/api.php?action=query&"
-	  "prop=revisions&rvslots=*&rvprop=content&formatversion=2&"
-	  "format=json&titles=%s", WIKIPEDIA_HOST, name);
+	  "prop=revisions&rvslots=*&rvprop=content&"
+	  "format=json&formatversion=2&titles=%s", WIKIPEDIA_HOST, name);
 	wpr->http_request = http_get(url);
-	wpr->state = WP_STATE_FIND_BODY;
+	http_req_skip_header(wpr->http_request);
+	wpr->state = WP_STATE_PARSE_JSON;
 	wpr->normalized_title = xstrdup(name, "normalized_title");
 
 	return wpr;
 }
 
+size_t
+wikipedia_fetch_search_results(struct browser *browser, char *query,
+  char ***results)
+{
+	static char url[256];
+	json_stream json;
+	struct http_request *req;
+	char *qencoded;
+	enum json_type type;
+	short strings = 0;
+	char **rets = NULL;
+	size_t nrets = 0;
+
+	qencoded = url_encode(query);
+	
+	snprintf(url, sizeof(url), "http://%s/w/api.php?action=opensearch&"
+	  "format=json&formatversion=2&namespace=0&limit=10&"
+	  "search=%s", WIKIPEDIA_HOST, qencoded);
+	xfree(&qencoded);
+	req = http_get(url);
+	http_req_skip_header(req);
+	
+	json_open_user(&json, http_req_chunk_read, http_req_chunk_peek, req);
+	
+	for (;;) {
+		type = json_next(&json);
+		
+		if (type == JSON_ERROR || type == JSON_DONE ||
+		  type == JSON_ARRAY_END)
+			break;
+			
+		if (type == JSON_STRING) {
+			strings++;
+			
+			/* skip first, it'll be our query */
+			if (strings == 1)
+				continue;
+			
+			nrets++;
+			rets = xreallocarray(rets, sizeof(Ptr), nrets);
+			rets[nrets - 1] = xstrdup(json_get_string(&json, NULL),
+			  "search result");
+		} else if (type == JSON_ARRAY_END) {
+			break;
+		}
+	}
+	
+	json_close(&json);
+	http_req_free(&req);
+	
+	*results = rets;
+	
+	return nrets;
+}
+
 struct wikipedia_request *
 wikipedia_read_cached_article(struct browser *browser, char *name)
 {
@@ -103,7 +161,7 @@ wikipedia_read_cached_article(struct browser *browser,
 	
 	FSClose(frefnum);
 	
-	wpr->state = WP_STATE_FIND_BODY;
+	wpr->state = WP_STATE_PARSE_JSON;
 	wpr->normalized_title = xstrdup(name, "normalized_title");
 
 	return wpr;
@@ -113,172 +171,138 @@ short
 wikipedia_json_peek(void *cookie)
 {
 	struct wikipedia_request *wpr = (struct wikipedia_request *)cookie;
+	struct http_request *req = wpr->http_request;
 	
-	if (wpr->buf_len == 0 || (wpr->buf_off + 1 > wpr->buf_len)) {
-		wpr->buf_len = http_req_read(wpr->http_request, wpr->buf,
-		  sizeof(wpr->buf));
-		wpr->buf_off = 0;
-	}
-	
-	if (wpr->buf_len == 0 || (wpr->buf_off + 1 > wpr->buf_len))
-		return EOF;
-	
-	return wpr->buf[wpr->buf_off];
+	return http_req_chunk_peek(req);
 }
 
 short
 wikipedia_json_get(void *cookie)
 {
 	struct wikipedia_request *wpr = (struct wikipedia_request *)cookie;
-	short c;
+	struct http_request *req = wpr->http_request;
 	
-	c = wikipedia_json_peek(cookie);
-	if (c == EOF)
-		return c;
-
-	wpr->buf_off++;
-	
-	return c;
+	return http_req_chunk_read(req);
 }
 
 void
 wikipedia_request_process(struct wikipedia_request *wpr)
 {
+	struct http_request *req = wpr->http_request;
 	size_t len, n;
 
 	switch (wpr->state) {
-	case WP_STATE_FIND_BODY:
-		if (wpr->buf_len > 3) {
-			/*
-			 * Leave last 3 bytes of previous read in case \r\n\r\n happens
-			 * across reads.
-			 */
-			memmove(wpr->buf, wpr->buf + wpr->buf_len - 3,
-			  wpr->buf_len - 3);
-			wpr->buf_len = 3;
-		}
-		len = http_req_read(wpr->http_request, wpr->buf + wpr->buf_len,
-		  sizeof(wpr->buf) - wpr->buf_len);
-		wpr->buf_len += len;
-		if (!len)
-			break;
-			
-		for (n = 3; n < wpr->buf_len; n++) {
-			if (wpr->buf[n - 3] != '\r' || wpr->buf[n - 2] != '\n' ||
-			  wpr->buf[n - 1] != '\r' || wpr->buf[n] != '\n')
-				continue;
-			
-			wpr->buf_off = n + 1;
-			wpr->state = WP_STATE_PARSE_JSON;
-			wpr->json_context_depth = 0;
-			
-			progress("Parsing JSON response...");
-			json_open_user(&wpr->json, wikipedia_json_get,
-			  wikipedia_json_peek, wpr);
-			break;
-		}
-		
-		break;
 	case WP_STATE_PARSE_JSON: {
-		static char context_str[PDJSON_STACK_MAX * 32];
+		char context_str[PDJSON_STACK_MAX * 32];
 		const char *str;
 		enum json_type type, context_type;
 		size_t tmp_depth;
 		
-		type = json_next(&wpr->json);
+		wpr->json_context_depth = 0;
+			
+		progress("Parsing JSON response...");
+		json_open_user(&wpr->json, wikipedia_json_get, wikipedia_json_peek,
+		  wpr);
+
+		for (;;) {
+			type = json_next(&wpr->json);
 		
-		if (type == JSON_ERROR || type == JSON_DONE) {
-			if (type == JSON_ERROR) {
-				char err[100];
-				size_t len;
-				len = snprintf(err, sizeof(err), "%s at line %ld pos %ld",
-				  json_get_error(&wpr->json), json_get_lineno(&wpr->json),
-				  json_get_position(&wpr->json));
-				browser_print(wpr->browser, err, len, 0);
+			if (type == JSON_ERROR || type == JSON_DONE) {
+				if (type == JSON_ERROR) {
+					char err[100];
+					size_t len;
+					len = snprintf(err, sizeof(err),
+					  "%s at line %ld pos %ld", json_get_error(&wpr->json),
+					  json_get_lineno(&wpr->json),
+					  json_get_position(&wpr->json));
+					browser_print(wpr->browser, err, len, 0);
+				}
+				json_close(&wpr->json);
+				if (wpr->http_request != NULL)
+					http_req_free(&wpr->http_request);
+				if (type == JSON_ERROR)
+					wpr->state = WP_STATE_DONE;
+				else {
+					wpr->state = WP_STATE_PARSE_WIKITEXT;
+					progress("Formatting article...");
+				}
+				break;
 			}
-			json_close(&wpr->json);
-			if (wpr->http_request != NULL)
-				http_req_free(&wpr->http_request);
-			if (type == JSON_ERROR)
-				wpr->state = WP_STATE_DONE;
-			else {
-				wpr->state = WP_STATE_PARSE_WIKITEXT;
-				progress("Formatting article...");
-			}
-			break;
-		}
-		
-		context_type = json_get_context(&wpr->json, &tmp_depth);
 
+			context_type = json_get_context(&wpr->json, &tmp_depth);
+
 #define wprjcd wpr->json_context[wpr->json_context_depth]
 		
-		switch (type) {
-		case JSON_OBJECT:
-			snprintf(wprjcd, sizeof(wprjcd), "{");
-			wpr->json_context_depth++;
-			break;
-		case JSON_OBJECT_END:
-			snprintf(wprjcd, sizeof(wprjcd), "}");
-			wpr->json_context_depth--;
-			break;
-		case JSON_ARRAY:
-			snprintf(wprjcd, sizeof(wprjcd), "[");
-			wpr->json_context_depth++;
-			break;
-		case JSON_ARRAY_END:
-			snprintf(wprjcd, sizeof(wprjcd), "]");
-			wpr->json_context_depth--;
-			break;
-		case JSON_STRING:
-			snprintf(wprjcd, sizeof(wprjcd), "\"%s\"",
-			  json_get_string(&wpr->json, NULL));
-			break;
-		case JSON_NUMBER:
-			snprintf(wprjcd, sizeof(wprjcd), "%s",
-			  json_get_string(&wpr->json, NULL));
-			break;
-		case JSON_TRUE:
-			snprintf(wprjcd, sizeof(wprjcd), "true");
-			break;
-		case JSON_FALSE:
-			snprintf(wprjcd, sizeof(wprjcd), "false");
-			break;
-		case JSON_NULL:
-			snprintf(wprjcd, sizeof(wprjcd), "null");
-			break;
-		}
-		
-		if (tmp_depth > 0 && (tmp_depth % 2) != 0)
-			strlcat(wprjcd, ":", sizeof(wprjcd));
-
-		if (type != JSON_STRING)
-			goto next_context;
+			switch (type) {
+			case JSON_OBJECT:
+				snprintf(wprjcd, sizeof(wprjcd), "{");
+				wpr->json_context_depth++;
+				break;
+			case JSON_OBJECT_END:
+				snprintf(wprjcd, sizeof(wprjcd), "}");
+				wpr->json_context_depth--;
+				break;
+			case JSON_ARRAY:
+				snprintf(wprjcd, sizeof(wprjcd), "[");
+				wpr->json_context_depth++;
+				break;
+			case JSON_ARRAY_END:
+				snprintf(wprjcd, sizeof(wprjcd), "]");
+				wpr->json_context_depth--;
+				break;
+			case JSON_STRING:
+				snprintf(wprjcd, sizeof(wprjcd), "\"%s\"",
+				  json_get_string(&wpr->json, NULL));
+				break;
+			case JSON_NUMBER:
+				snprintf(wprjcd, sizeof(wprjcd), "%s",
+				  json_get_string(&wpr->json, NULL));
+				break;
+			case JSON_TRUE:
+				snprintf(wprjcd, sizeof(wprjcd), "true");
+				break;
+			case JSON_FALSE:
+				snprintf(wprjcd, sizeof(wprjcd), "false");
+				break;
+			case JSON_NULL:
+				snprintf(wprjcd, sizeof(wprjcd), "null");
+				break;
+			}
 			
-		context_str[0] = '\0';
-		for (n = 0; n < wpr->json_context_depth; n++)
-			strlcat(context_str, wpr->json_context[n], sizeof(context_str));
+			if (tmp_depth > 0 && (tmp_depth % 2) != 0)
+				strlcat(wprjcd, ":", sizeof(wprjcd));
+	
+			if (type != JSON_STRING)
+				goto next_context;
+				
+			context_str[0] = '\0';
+			for (n = 0; n < wpr->json_context_depth; n++)
+				strlcat(context_str, wpr->json_context[n],
+				  sizeof(context_str));
 
-		if (strcmp(context_str, NORMALIZED_TITLE_CONTEXT) == 0) {
-			xfree(&wpr->normalized_title);
-			wpr->normalized_title =
-			  xstrdup(json_get_string(&wpr->json, NULL), "normalized_title");
-		} else if (strcmp(context_str, ARTICLE_TEXT_CONTEXT) == 0) {
-			str = json_get_string(&wpr->json, &wpr->article_len);
-			wpr->article = xmalloc(wpr->article_len, "article");
-			for (n = 0; n < wpr->article_len; n++) {
-				if (str[n] == '\n')
-					wpr->article[n] = '\r';
-				else
-					wpr->article[n] = str[n];
+			if (strcmp(context_str, NORMALIZED_TITLE_CONTEXT) == 0) {
+				xfree(&wpr->normalized_title);
+				wpr->normalized_title =
+				  xstrdup(json_get_string(&wpr->json, NULL),
+				  "normalized_title");
+			} else if (strcmp(context_str, ARTICLE_TEXT_CONTEXT) == 0) {
+				str = json_get_string(&wpr->json, &wpr->article_len);
+				wpr->article = xmalloc(wpr->article_len, "article");
+				for (n = 0; n < wpr->article_len; n++) {
+					if (str[n] == '\n')
+						wpr->article[n] = '\r';
+					else
+						wpr->article[n] = str[n];
+				}
 			}
-		}
 		
 next_context:
-		if (context_type == JSON_OBJECT && tmp_depth > 0) {
-			if (tmp_depth % 2 == 0)
-				wpr->json_context_depth--;
-			else
-				wpr->json_context_depth++;
+			if (context_type == JSON_OBJECT && tmp_depth > 0) {
+				if (tmp_depth % 2 == 0)
+					wpr->json_context_depth--;
+				else
+					wpr->json_context_depth++;
+			}
 		}
 		break;
 	}
--- wikipedia.h	Wed Aug 24 17:20:19 2022
+++ wikipedia.h	Thu Aug 25 08:50:29 2022
@@ -42,7 +42,6 @@ extern MenuHandle file_menu, edit_menu;
 void menu_defaults(void);
 
 enum {
-	WP_STATE_FIND_BODY,
 	WP_STATE_PARSE_JSON,
 	WP_STATE_PARSE_WIKITEXT,
 	WP_STATE_DONE
@@ -53,9 +52,6 @@ struct wikipedia_request {
 	struct browser *browser;
 	struct http_request *http_request;
 	char *normalized_title;
-	char buf[1024];
-	size_t buf_len;
-	size_t buf_off;
 	json_stream json;
 	char *article;
 	size_t article_len;
@@ -65,6 +61,8 @@ struct wikipedia_request {
 
 struct wikipedia_request * wikipedia_fetch_article(struct browser *,
   char *);
+size_t wikipedia_fetch_search_results(struct browser *browser, char *query,
+  char ***results);
 struct wikipedia_request * wikipedia_read_cached_article(struct browser *browser,
   char *name);
 void wikipedia_request_process(struct wikipedia_request *wpr);