degesch: more GNU Readline work
I'm not so sure anymore I will be able to achieve my goals with this library. It's really a terrible mess. A consistent and neatly formatted terrible mess.
This commit is contained in:
		
							parent
							
								
									3df841f088
								
							
						
					
					
						commit
						4a0c774e75
					
				
							
								
								
									
										221
									
								
								degesch.c
									
									
									
									
									
								
							
							
						
						
									
										221
									
								
								degesch.c
									
									
									
									
									
								
							| @ -187,6 +187,11 @@ struct buffer | ||||
| 	enum buffer_type type;              ///< Type of the buffer
 | ||||
| 	char *name;                         ///< The name of the buffer
 | ||||
| 
 | ||||
| 	HISTORY_STATE *history;             ///< Saved history state
 | ||||
| 	char *saved_line;                   ///< Saved line
 | ||||
| 	int saved_point;                    ///< Saved position in line
 | ||||
| 	int saved_mark;                     ///< Saved mark
 | ||||
| 
 | ||||
| 	// Buffer contents:
 | ||||
| 
 | ||||
| 	struct buffer_line *lines;          ///< All lines in this buffer
 | ||||
| @ -215,6 +220,8 @@ static void | ||||
| buffer_destroy (struct buffer *self) | ||||
| { | ||||
| 	free (self->name); | ||||
| 	// Can't really free "history" here
 | ||||
| 	free (self->saved_line); | ||||
| 	free (self->topic); | ||||
| 	str_map_free (&self->nicks); | ||||
| 	free (self); | ||||
| @ -361,6 +368,8 @@ app_context_free (struct app_context *self) | ||||
| 	free (self->readline_prompt); | ||||
| } | ||||
| 
 | ||||
| static void refresh_prompt (struct app_context *ctx); | ||||
| 
 | ||||
| // --- Attributed output -------------------------------------------------------
 | ||||
| 
 | ||||
| static struct | ||||
| @ -417,12 +426,14 @@ struct app_readline_state | ||||
| { | ||||
| 	char *saved_line; | ||||
| 	int saved_point; | ||||
| 	int saved_mark; | ||||
| }; | ||||
| 
 | ||||
| static void | ||||
| app_readline_hide (struct app_readline_state *state) | ||||
| { | ||||
| 	state->saved_point = rl_point; | ||||
| 	state->saved_point = rl_mark; | ||||
| 	state->saved_line = rl_copy_text (0, rl_end); | ||||
| 	rl_set_prompt (""); | ||||
| 	rl_replace_line ("", 0); | ||||
| @ -435,6 +446,7 @@ app_readline_restore (struct app_readline_state *state, const char *prompt) | ||||
| 	rl_set_prompt (prompt); | ||||
| 	rl_replace_line (state->saved_line, 0); | ||||
| 	rl_point = state->saved_point; | ||||
| 	rl_mark = state->saved_mark; | ||||
| 	rl_redisplay (); | ||||
| 	free (state->saved_line); | ||||
| } | ||||
| @ -677,6 +689,12 @@ buffer_line_display (struct app_context *ctx, struct buffer_line *line) | ||||
| 	char *object = iconv_xstrdup (ctx->term_from_utf8, line->object, -1, NULL); | ||||
| 	char *reason = iconv_xstrdup (ctx->term_from_utf8, line->reason, -1, NULL); | ||||
| 
 | ||||
| 	// TODO: colorize the output, note that we shouldn't put everything through
 | ||||
| 	//   tputs but only the attribute strings.  That might prove a bit
 | ||||
| 	//   challenging.  Maybe we could create a helper object to pust text
 | ||||
| 	//   and formatting into.  We could have a varargs function to make it a bit
 | ||||
| 	//   more friendly, e.g. push(&x, ATTR_JOIN, "--> ", ATTR_RESET, who, NULL)
 | ||||
| 
 | ||||
| 	switch (line->type) | ||||
| 	{ | ||||
| 	case BUFFER_LINE_PRIVMSG: | ||||
| @ -724,12 +742,15 @@ buffer_line_display (struct app_context *ctx, struct buffer_line *line) | ||||
| 	free (object); | ||||
| 	free (reason); | ||||
| 
 | ||||
| 	// TODO: hide readline if needed
 | ||||
| 	struct app_readline_state state; | ||||
| 	if (ctx->readline_prompt_shown) | ||||
| 		app_readline_hide (&state); | ||||
| 
 | ||||
| 	printf ("%s\n", text.str); | ||||
| 	str_free (&text); | ||||
| 
 | ||||
| 	// TODO: unhide readline if hidden
 | ||||
| 	if (ctx->readline_prompt_shown) | ||||
| 		app_readline_restore (&state, ctx->readline_prompt); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| @ -773,7 +794,7 @@ buffer_add (struct app_context *ctx, struct buffer *buffer) | ||||
| 	str_map_set (&ctx->buffers_by_name, buffer->name, buffer); | ||||
| 	LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer); | ||||
| 
 | ||||
| 	// TODO: refresh the prompt?
 | ||||
| 	// TODO: refresh the prompt?  Or caller?
 | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| @ -781,6 +802,21 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer) | ||||
| { | ||||
| 	hard_assert (buffer != ctx->current_buffer); | ||||
| 
 | ||||
| 	if (buffer->history) | ||||
| 	{ | ||||
| 		// See buffer_activate() for why we need to do this BS
 | ||||
| 		rl_free_undo_list (); | ||||
| 
 | ||||
| 		// This is probably the only way we can free the history fully
 | ||||
| 		HISTORY_STATE *state = history_get_history_state (); | ||||
| 
 | ||||
| 		history_set_history_state (buffer->history); | ||||
| 		rl_clear_history (); | ||||
| 
 | ||||
| 		history_set_history_state (state); | ||||
| 		free (state); | ||||
| 	} | ||||
| 
 | ||||
| 	str_map_set (&ctx->buffers_by_name, buffer->name, NULL); | ||||
| 	LIST_UNLINK_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer); | ||||
| 	buffer_destroy (buffer); | ||||
| @ -792,22 +828,85 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer) | ||||
| 	if (buffer == ctx->server_buffer) | ||||
| 		ctx->server_buffer = NULL; | ||||
| 
 | ||||
| 	// TODO: refresh the prompt?
 | ||||
| 	// TODO: refresh the prompt?  Or caller?
 | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| buffer_activate (struct app_context *ctx, struct buffer *buffer) | ||||
| { | ||||
| 	ctx->current_buffer = buffer; | ||||
| 	if (ctx->current_buffer == buffer) | ||||
| 		return; | ||||
| 
 | ||||
| 	print_status ("%s", buffer->name); | ||||
| 
 | ||||
| 	// That is, minus the buffer switch line and the readline prompt
 | ||||
| 	int to_display = ctx->lines - 2; | ||||
| 	// TODO: find the to_display-th line from the back
 | ||||
| 	// TODO: print all the lines in order
 | ||||
| 	int to_display = MAX (10, ctx->lines - 2); | ||||
| 	struct buffer_line *line = buffer->lines_tail; | ||||
| 	while (line && line->prev && --to_display > 0) | ||||
| 		line = line->prev; | ||||
| 
 | ||||
| 	// TODO: switch readline history stack
 | ||||
| 	// TODO: refresh the prompt?
 | ||||
| 	// Once we've found where we want to start with the backlog, print it
 | ||||
| 	for (; line; line = line->next) | ||||
| 		buffer_line_display (ctx, line); | ||||
| 
 | ||||
| 	// The following part shows you why it's not a good idea to use
 | ||||
| 	// GNU Readline for this kind of software.  Or for anything else, really.
 | ||||
| 
 | ||||
| 	// There could possibly be occurences of the current undo list in some
 | ||||
| 	// history entry.  We either need to free the undo list, or move it
 | ||||
| 	// somewhere else to load back later, as the buffer we're switching to
 | ||||
| 	// has its own history state.
 | ||||
| 	rl_free_undo_list (); | ||||
| 
 | ||||
| 	// Save this buffer's history so that it's independent for each buffer
 | ||||
| 	if (ctx->current_buffer) | ||||
| 	{ | ||||
| 		ctx->current_buffer->history = history_get_history_state (); | ||||
| 		ctx->current_buffer->saved_line = rl_copy_text (0, rl_end); | ||||
| 		ctx->current_buffer->saved_point = rl_point; | ||||
| 		ctx->current_buffer->saved_mark = rl_mark; | ||||
| 	} | ||||
| 	else | ||||
| 		// Just throw it away; there should always be an active buffer however
 | ||||
| 		rl_clear_history (); | ||||
| 
 | ||||
| 	// Now at last we can switch the pointers
 | ||||
| 	ctx->current_buffer = buffer; | ||||
| 
 | ||||
| 	// Restore the target buffer's history
 | ||||
| 	if (buffer->history) | ||||
| 	{ | ||||
| 		// history_get_history_state() just allocates a new HISTORY_STATE
 | ||||
| 		// and fills it with its current internal data.  We don't need that
 | ||||
| 		// shell anymore after reviving it.
 | ||||
| 		history_set_history_state (buffer->history); | ||||
| 		free (buffer->history); | ||||
| 		buffer->history = NULL; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		// This should get us a clean history while keeping the flags.
 | ||||
| 		// Note that we've either saved the previous history entries, or we've
 | ||||
| 		// cleared them altogether, so there should be nothing to leak.
 | ||||
| 		HISTORY_STATE *state = history_get_history_state (); | ||||
| 		state->offset = state->length = state->size = 0; | ||||
| 		history_set_history_state (state); | ||||
| 		free (state); | ||||
| 	} | ||||
| 
 | ||||
| 	// Try to restore the target buffer's readline state
 | ||||
| 	if (buffer->saved_line) | ||||
| 	{ | ||||
| 		rl_replace_line (buffer->saved_line, 0); | ||||
| 		rl_point = buffer->saved_point; | ||||
| 		rl_mark = buffer->saved_mark; | ||||
| 		free (buffer->saved_line); | ||||
| 
 | ||||
| 		if (ctx->readline_prompt_shown) | ||||
| 			rl_redisplay (); | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: refresh the prompt?  Or caller?
 | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| @ -1099,6 +1198,8 @@ irc_establish_connection (struct app_context *ctx, | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| // --- More readline funky stuff -----------------------------------------------
 | ||||
| 
 | ||||
| static void | ||||
| refresh_prompt (struct app_context *ctx) | ||||
| { | ||||
| @ -1140,8 +1241,67 @@ refresh_prompt (struct app_context *ctx) | ||||
| 	} | ||||
| 	str_free (&prompt); | ||||
| 
 | ||||
| 	// FIXME: when the program hasn't displayed the prompt yet, is this okay?
 | ||||
| 	rl_redisplay (); | ||||
| 	// We need to be somehow able to initialize it
 | ||||
| 	rl_set_prompt (ctx->readline_prompt); | ||||
| 	if (ctx->readline_prompt_shown) | ||||
| 		rl_redisplay (); | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| on_readline_goto_buffer (int count, int key) | ||||
| { | ||||
| 	(void) count; | ||||
| 
 | ||||
| 	if (!(key & 0x80)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	int n = (key & 0x7F) - '0'; | ||||
| 	if (n < 0 || n > 9) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	// TODO: switch to the n-th buffer
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| on_readline_previous_buffer (int count, int key) | ||||
| { | ||||
| 	(void) key; | ||||
| 
 | ||||
| 	// TODO: switch "count" times to the previous buffer
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| on_readline_next_buffer (int count, int key) | ||||
| { | ||||
| 	(void) key; | ||||
| 
 | ||||
| 	// TODO: switch "count" times to the next buffer
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| init_readline (void) | ||||
| { | ||||
| 	// TODO: maybe use rl_make_bare_keymap() and start from there;
 | ||||
| 	//   our dear user could potentionally rig things up in a way that might
 | ||||
| 	//   result in some funny unspecified behaviour
 | ||||
| 
 | ||||
| 	rl_add_defun ("previous-buffer", on_readline_previous_buffer, -1); | ||||
| 	rl_add_defun ("next-buffer", on_readline_next_buffer, -1); | ||||
| 
 | ||||
| 	// Redefine M-0 through M-9 to switch buffers
 | ||||
| 	for (int i = 0; i <= 9; i++) | ||||
| 		rl_bind_key (0x80 /* this is the Meta modifier for Readline */ | ||||
| 			| ('0' + i), on_readline_goto_buffer); | ||||
| 
 | ||||
| 	rl_bind_keyseq ("C-p", rl_named_function ("previous-buffer")); | ||||
| 	rl_bind_keyseq ("C-n", rl_named_function ("next-buffer")); | ||||
| 	rl_bind_keyseq ("M-p", rl_named_function ("previous-history")); | ||||
| 	rl_bind_keyseq ("M-n", rl_named_function ("next-history")); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| // --- Input handling ----------------------------------------------------------
 | ||||
| @ -1268,24 +1428,6 @@ fail: | ||||
| 	free (input); | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| on_readline_previous_buffer (int count, int key) | ||||
| { | ||||
| 	(void) key; | ||||
| 
 | ||||
| 	// TODO: switch to the previous buffer
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| on_readline_next_buffer (int count, int key) | ||||
| { | ||||
| 	(void) key; | ||||
| 
 | ||||
| 	// TODO: switch to the next buffer
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| // --- Supporting code (continued) ---------------------------------------------
 | ||||
| 
 | ||||
| enum irc_read_result | ||||
| @ -1898,6 +2040,7 @@ main (int argc, char *argv[]) | ||||
| 	atexit (ERR_free_strings); | ||||
| 
 | ||||
| 	using_history (); | ||||
| 	// This can cause memory leaks, or maybe even a segfault.  Funny, eh?
 | ||||
| 	stifle_history (HISTORY_LIMIT); | ||||
| 
 | ||||
| 	setup_signal_handlers (); | ||||
| @ -1905,6 +2048,7 @@ main (int argc, char *argv[]) | ||||
| 	init_colors (&ctx); | ||||
| 	init_poller_events (&ctx); | ||||
| 	init_buffers (&ctx); | ||||
| 	ctx.current_buffer = ctx.global_buffer; | ||||
| 	refresh_prompt (&ctx); | ||||
| 
 | ||||
| 	// TODO: connect asynchronously (first step towards multiple servers)
 | ||||
| @ -1917,22 +2061,7 @@ main (int argc, char *argv[]) | ||||
| 		exit (EXIT_FAILURE); | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: maybe use rl_make_bare_keymap() and start from there
 | ||||
| 
 | ||||
| 	// XXX: Since readline() installs a set of default key bindings the first
 | ||||
| 	//   time it is called, there is always the danger that a custom binding
 | ||||
| 	//   installed before the first call to readline() will be overridden.
 | ||||
| 	//   An alternate mechanism is to install custom key bindings in an
 | ||||
| 	//   initialization function assigned to the rl_startup_hook variable.
 | ||||
| 	rl_add_defun ("previous-buffer", on_readline_previous_buffer, -1); | ||||
| 	rl_add_defun ("next-buffer", on_readline_next_buffer, -1); | ||||
| 
 | ||||
| 	// TODO: redefine M-0 through M-9 to switch buffers
 | ||||
| 	rl_bind_keyseq ("C-p", rl_named_function ("previous-buffer")); | ||||
| 	rl_bind_keyseq ("C-n", rl_named_function ("next-buffer")); | ||||
| 	rl_bind_keyseq ("M-p", rl_named_function ("previous-history")); | ||||
| 	rl_bind_keyseq ("M-n", rl_named_function ("next-history")); | ||||
| 
 | ||||
| 	rl_startup_hook = init_readline; | ||||
| 	rl_catch_sigwinch = false; | ||||
| 	rl_callback_handler_install (ctx.readline_prompt, on_readline_input); | ||||
| 	rl_get_screen_size (&ctx.lines, &ctx.columns); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user