/*
 * Copyright (c) 2003-2010 by FlashCode <flashcode@flashtux.org>
 * See README for License detail, AUTHORS for developers list.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * gui-input.c: input functions (used by all GUI)
 */


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <string.h>

#include "../core/weechat.h"
#include "../core/wee-config.h"
#include "../core/wee-hook.h"
#include "../core/wee-input.h"
#include "../core/wee-string.h"
#include "../core/wee-utf8.h"
#include "../plugins/plugin.h"
#include "gui-input.h"
#include "gui-buffer.h"
#include "gui-completion.h"
#include "gui-history.h"
#include "gui-hotlist.h"
#include "gui-keyboard.h"
#include "gui-line.h"
#include "gui-window.h"


char *gui_input_clipboard = NULL;      /* clipboard content                 */


/*
 * gui_input_optimize_size: optimize input buffer size by adding
 *                          or deleting data block (predefined size)
 */

void
gui_input_optimize_size (struct t_gui_buffer *buffer)
{
    int optimal_size;
    
    if (buffer->input)
    {
        optimal_size = ((buffer->input_buffer_size / GUI_BUFFER_INPUT_BLOCK_SIZE) *
                        GUI_BUFFER_INPUT_BLOCK_SIZE) + GUI_BUFFER_INPUT_BLOCK_SIZE;
        if (buffer->input_buffer_alloc != optimal_size)
        {
            buffer->input_buffer_alloc = optimal_size;
            buffer->input_buffer = realloc (buffer->input_buffer, optimal_size);
        }
    }
}

/*
 * gui_input_replace_input: replace full input by another string, trying to
 *                          keep cursor position is new string is long enough
 */

void
gui_input_replace_input (struct t_gui_buffer *buffer, const char *new_input)
{
    int size, length;
    
    size = strlen (new_input);
    length = utf8_strlen (new_input);
    
    /* compute new buffer size */
    buffer->input_buffer_size = size;
    buffer->input_buffer_length = length;
    gui_input_optimize_size (buffer);
    
    /* copy new string to input */
    strcpy (buffer->input_buffer, new_input);
    
    /* move cursor to the end of new input if it is now after the end */
    if (buffer->input_buffer_pos > buffer->input_buffer_length)
        buffer->input_buffer_pos = buffer->input_buffer_length;
}

/*
 * gui_input_paste_pending_signal: send signal "input_paste_pending"
 */

void
gui_input_paste_pending_signal ()
{
    hook_signal_send ("input_paste_pending", WEECHAT_HOOK_SIGNAL_STRING, NULL);
}

/*
 * gui_input_text_changed_modifier_and_signal: send modifier and signal
 *                                             "input_text_changed"
 */

void
gui_input_text_changed_modifier_and_signal (struct t_gui_buffer *buffer)
{
    char str_buffer[128], *new_input;
    
    /* send modifier, and change input if needed */
    snprintf (str_buffer, sizeof (str_buffer),
              "0x%lx", (long unsigned int)buffer);
    new_input = hook_modifier_exec (NULL,
                                    "input_text_content",
                                    str_buffer,
                                    (buffer->input_buffer) ?
                                    buffer->input_buffer : "");
    if (new_input)
    {
        if (strcmp (new_input, buffer->input_buffer) != 0)
        {
            /* input has been changed by modifier, use it */
            gui_input_replace_input (buffer, new_input);
        }
        free (new_input);
    }
    
    /* send signal */
    hook_signal_send ("input_text_changed", WEECHAT_HOOK_SIGNAL_STRING, NULL);
}

/*
 * gui_input_text_cursor_moved_signal: send signal "input_text_cursor_moved"
 */

void
gui_input_text_cursor_moved_signal ()
{
    hook_signal_send ("input_text_cursor_moved", WEECHAT_HOOK_SIGNAL_STRING, NULL);
}

/*
 * gui_input_search_signal: send signal "input_search"
 */

void
gui_input_search_signal ()
{
    hook_signal_send ("input_search", WEECHAT_HOOK_SIGNAL_STRING, NULL);
}

/*
 * gui_input_move: move data in input buffer
 */

void
gui_input_move (struct t_gui_buffer *buffer, char *target, const char *source,
                int size)
{
    int pos_source, pos_target;
    
    pos_target = target - buffer->input_buffer;
    pos_source = source - buffer->input_buffer;
    
    memmove (target, source, size);
}

/*
 * gui_input_set_pos: set position in input line
 */

void
gui_input_set_pos (struct t_gui_buffer *buffer, int pos)
{
    if ((pos >= 0) && (buffer->input_buffer_pos != pos))
    {
        buffer->input_buffer_pos = pos;
        if (buffer->input_buffer_pos > buffer->input_buffer_length)
            buffer->input_buffer_pos = buffer->input_buffer_length;
        gui_input_text_cursor_moved_signal ();
    }
}

/*
 * gui_input_insert_string: insert a string into the input buffer
 *                          if pos == -1, string is inserted at cursor position
 *                          return: number of chars inserted
 *                          (may be different of strlen if UTF-8 string)
 */

int
gui_input_insert_string (struct t_gui_buffer *buffer, const char *string,
                         int pos)
{
    int pos_start, size, length;
    char *ptr_start;
    
    if (buffer->input)
    {
        if (pos == -1)
            pos = buffer->input_buffer_pos;
        
        size = strlen (string);
        length = utf8_strlen (string);
        
        /* increase buffer size */
        buffer->input_buffer_size += size;
        buffer->input_buffer_length += length;
        gui_input_optimize_size (buffer);
        buffer->input_buffer[buffer->input_buffer_size] = '\0';
        
        /* move end of string to the right */
        ptr_start = utf8_add_offset (buffer->input_buffer, pos);
        pos_start = ptr_start - buffer->input_buffer;
        memmove (ptr_start + size, ptr_start, strlen (ptr_start));
        
        /* insert new string */
        ptr_start = utf8_add_offset (buffer->input_buffer, pos);
        pos_start = ptr_start - buffer->input_buffer;
        strncpy (ptr_start, string, size);
        
        buffer->input_buffer_pos += length;
        
        return length;
    }
    return 0;
}

/*
 * gui_input_clipboard_copy: copy string into clipboard
 */

void
gui_input_clipboard_copy (const char *buffer, int size)
{
    if (size <= 0)
        return;
    
    if (gui_input_clipboard != NULL)
        free (gui_input_clipboard);
    
    gui_input_clipboard = malloc((size + 1) * sizeof(*gui_input_clipboard));
    
    if (gui_input_clipboard)
    {
        memcpy (gui_input_clipboard, buffer, size);
        gui_input_clipboard[size] = '\0';
    }
}

/*
 * gui_action_clipboard_paste: paste clipboard at cursor pos in input line
 */

void
gui_input_clipboard_paste (struct t_gui_buffer *buffer)
{
    if (buffer->input && gui_input_clipboard)
    {
        gui_input_insert_string (buffer,
                                 gui_input_clipboard, -1);
        gui_completion_stop (buffer->completion, 1);
        
        gui_input_text_changed_modifier_and_signal (buffer);
    }
}

/*
 * gui_input_return: terminate line
 */

void
gui_input_return (struct t_gui_window *window)
{
    char *command;
    
    if (window->buffer->input)
    {
        if (window->buffer->text_search != GUI_TEXT_SEARCH_DISABLED)
        {
            gui_window_search_stop (window);
            gui_input_search_signal ();
        }
        else if (window->buffer->input_buffer_size > 0)
        {
            window->buffer->input_buffer[window->buffer->input_buffer_size] = '\0';
            command = strdup (window->buffer->input_buffer);
            if (command)
            {
                gui_history_add (window->buffer,
                                 window->buffer->input_buffer);
                window->buffer->input_buffer[0] = '\0';
                window->buffer->input_buffer_size = 0;
                window->buffer->input_buffer_length = 0;
                window->buffer->input_buffer_pos = 0;
                window->buffer->input_buffer_1st_display = 0;
                gui_completion_stop (window->buffer->completion, 1);
                window->buffer->ptr_history = NULL;
                history_global_ptr = NULL;
                gui_input_optimize_size (window->buffer);
                gui_input_text_changed_modifier_and_signal (window->buffer);
                input_data (window->buffer, command);
                free (command);
            }
        }
    }
}

/*
 * gui_input_complete: complete a word in input buffer
 */

void
gui_input_complete (struct t_gui_buffer *buffer)
{
    int i;
    
    if (!buffer->completion)
        return;
    
    if (buffer->completion->word_found)
    {
        /* replace word with new completed word into input buffer */
        if (buffer->completion->diff_size > 0)
        {
            buffer->input_buffer_size +=
                buffer->completion->diff_size;
            buffer->input_buffer_length +=
                buffer->completion->diff_length;
            gui_input_optimize_size (buffer);
            buffer->input_buffer[buffer->input_buffer_size] = '\0';
            for (i = buffer->input_buffer_size - 1;
                 i >=  buffer->completion->position_replace +
                     (int)strlen (buffer->completion->word_found); i--)
            {
                buffer->input_buffer[i] =
                    buffer->input_buffer[i - buffer->completion->diff_size];
            }
        }
        else
        {
            for (i = buffer->completion->position_replace +
                     strlen (buffer->completion->word_found);
                 i < buffer->input_buffer_size; i++)
            {
                buffer->input_buffer[i] =
                    buffer->input_buffer[i - buffer->completion->diff_size];
            }
            buffer->input_buffer_size += buffer->completion->diff_size;
            buffer->input_buffer_length += buffer->completion->diff_length;
            gui_input_optimize_size (buffer);
            buffer->input_buffer[buffer->input_buffer_size] = '\0';
        }
            
        strncpy (buffer->input_buffer + buffer->completion->position_replace,
                 buffer->completion->word_found,
                 strlen (buffer->completion->word_found));
        buffer->input_buffer_pos =
            utf8_pos (buffer->input_buffer,
                      buffer->completion->position_replace) +
            utf8_strlen (buffer->completion->word_found);
        
        /*
         * position is < 0 this means only one word was found to complete,
         * so reinit to stop completion
         */
        if (buffer->completion->position >= 0)
            buffer->completion->position = utf8_real_pos (buffer->input_buffer,
                                                      buffer->input_buffer_pos);
        
        /* add space if needed after completion */
        if (buffer->completion->add_space)
        {
            if (buffer->input_buffer[utf8_real_pos (buffer->input_buffer,
                                                    buffer->input_buffer_pos)] != ' ')
                gui_input_insert_string (buffer, " ",
                                         buffer->input_buffer_pos);
            else
                buffer->input_buffer_pos++;
            if (buffer->completion->position >= 0)
                buffer->completion->position++;
        }
        gui_input_text_changed_modifier_and_signal (buffer);
    }
}

/*
 * gui_input_complete_next: complete with next word (default key: tab)
 */

void
gui_input_complete_next (struct t_gui_buffer *buffer)
{
    if (buffer->input && (buffer->text_search == GUI_TEXT_SEARCH_DISABLED))
    {
        gui_completion_search (buffer->completion,
                               1,
                               buffer->input_buffer,
                               buffer->input_buffer_size,
                               utf8_real_pos (buffer->input_buffer,
                                              buffer->input_buffer_pos));
        gui_input_complete (buffer);
        gui_input_text_changed_modifier_and_signal (buffer);
    }
}

/*
 * gui_complete_previous: complete with previous word (default key: shift-tab)
 */

void
gui_input_complete_previous (struct t_gui_buffer *buffer)
{
    if (buffer->input && (buffer->text_search == GUI_TEXT_SEARCH_DISABLED))
    {
        gui_completion_search (buffer->completion,
                               -1,
                               buffer->input_buffer,
                               buffer->input_buffer_size,
                               utf8_real_pos (buffer->input_buffer,
                                              buffer->input_buffer_pos));
        gui_input_complete (buffer);
        gui_input_text_changed_modifier_and_signal (buffer);
    }
}

/*
 * gui_input_search_text: search text in buffer history (default key: ctrl-r)
 */

void
gui_input_search_text (struct t_gui_window *window)
{
    if (window->buffer->type == GUI_BUFFER_TYPE_FORMATTED)
    {
        /* toggle search */
        if (window->buffer->text_search == GUI_TEXT_SEARCH_DISABLED)
            gui_window_search_start (window);
        else
        {
            window->buffer->text_search_exact ^= 1;
            gui_window_search_restart (window);
        }
        gui_input_search_signal ();
    }
}


/*
 * gui_input_delete_previous_char: delete previous char (default key: backspace)
 */

void
gui_input_delete_previous_char (struct t_gui_buffer *buffer)
{
    char *pos, *pos_last;
    int char_size, size_to_move;
    
    if (buffer->input)
    {
        if (buffer->input_buffer_pos > 0)
        {
            pos = utf8_add_offset (buffer->input_buffer,
                                   buffer->input_buffer_pos);
            pos_last = utf8_prev_char (buffer->input_buffer, pos);
            char_size = pos - pos_last;
            size_to_move = strlen (pos);
            gui_input_move (buffer, pos_last, pos, size_to_move);
            buffer->input_buffer_size -= char_size;
            buffer->input_buffer_length--;
            buffer->input_buffer_pos--;
            buffer->input_buffer[buffer->input_buffer_size] = '\0';
            gui_input_optimize_size (buffer);
            gui_completion_stop (buffer->completion, 1);
            gui_input_text_changed_modifier_and_signal (buffer);
        }
    }
}

/*
 * gui_input_delete_next_char: delete next char (default key: del)
 */

void
gui_input_delete_next_char (struct t_gui_buffer *buffer)
{
    char *pos, *pos_next;
    int char_size, size_to_move;
    
    if (buffer->input)
    {
        if (buffer->input_buffer_pos < buffer->input_buffer_length)
        {
            pos = utf8_add_offset (buffer->input_buffer,
                                   buffer->input_buffer_pos);
            pos_next = utf8_next_char (pos);
            char_size = pos_next - pos;
            size_to_move = strlen (pos_next);
            gui_input_move (buffer, pos, pos_next, size_to_move);
            buffer->input_buffer_size -= char_size;
            buffer->input_buffer_length--;
            buffer->input_buffer[buffer->input_buffer_size] = '\0';
            gui_input_optimize_size (buffer);
            gui_completion_stop (buffer->completion, 1);
            gui_input_text_changed_modifier_and_signal (buffer);
        }
    }
}

/*
 * gui_input_delete_previous_word: delete previous word (default key: ctrl-w)
 */

void
gui_input_delete_previous_word (struct t_gui_buffer *buffer)
{
    int length_deleted, size_deleted;
    char *start, *string;
    
    if (buffer->input)
    {
        if (buffer->input_buffer_pos > 0)
        {
            start = utf8_add_offset (buffer->input_buffer,
                                     buffer->input_buffer_pos - 1);
            string = start;
            while (string && (string[0] == ' '))
            {
                string = utf8_prev_char (buffer->input_buffer, string);
            }
            if (string)
            {
                while (string && (string[0] != ' '))
                {
                    string = utf8_prev_char (buffer->input_buffer, string);
                }
                if (string)
                {
                    while (string && (string[0] == ' '))
                    {
                        string = utf8_prev_char (buffer->input_buffer, string);
                    }
                }
            }
            
            if (string)
                string = utf8_next_char (utf8_next_char (string));
            else
                string = buffer->input_buffer;
            
            size_deleted = utf8_next_char (start) - string;
            length_deleted = utf8_strnlen (string, size_deleted);
            
            gui_input_clipboard_copy (string, size_deleted);
            
            gui_input_move (buffer, string, string + size_deleted,
                            strlen (string + size_deleted));
            
            buffer->input_buffer_size -= size_deleted;
            buffer->input_buffer_length -= length_deleted;
            buffer->input_buffer[buffer->input_buffer_size] = '\0';
            buffer->input_buffer_pos -= length_deleted;
            gui_input_optimize_size (buffer);
            gui_completion_stop (buffer->completion, 1);
            gui_input_text_changed_modifier_and_signal (buffer);
        }
    }
}

/*
 * gui_input_delete_next_word: delete next word (default key: meta-d)
 */

void
gui_input_delete_next_word (struct t_gui_buffer *buffer)
{
    int size_deleted, length_deleted;
    char *start, *string;
    
    if (buffer->input)
    {
        start = utf8_add_offset (buffer->input_buffer,
                                 buffer->input_buffer_pos);
        string = start;
        length_deleted = 0;
        while (string[0])
        {
            if ((string[0] == ' ') && (string > start))
                break;
            string = utf8_next_char (string);
            length_deleted++;
        }
        size_deleted = string - start;
        
        gui_input_clipboard_copy (start, size_deleted);
        
        gui_input_move (buffer, start, string, strlen (string));
        
        buffer->input_buffer_size -= size_deleted;
        buffer->input_buffer_length -= length_deleted;
        buffer->input_buffer[buffer->input_buffer_size] = '\0';
        gui_input_optimize_size (buffer);
        gui_completion_stop (buffer->completion, 1);
        gui_input_text_changed_modifier_and_signal (buffer);
    }
}


/*
 * gui_input_delete_beginning_of_line: delete all from cursor pos to beginning of line
 *                                     (default key: ctrl-u)
 */

void
gui_input_delete_beginning_of_line (struct t_gui_buffer *buffer)
{
    int length_deleted, size_deleted, pos_start;
    char *start;
    
    if (buffer->input)
    {
        if (buffer->input_buffer_pos > 0)
        {
            start = utf8_add_offset (buffer->input_buffer,
                                     buffer->input_buffer_pos);
            pos_start = start - buffer->input_buffer;
            size_deleted = start - buffer->input_buffer;
            length_deleted = utf8_strnlen (buffer->input_buffer, size_deleted);
            gui_input_clipboard_copy (buffer->input_buffer,
                                      start - buffer->input_buffer);
            
            gui_input_move (buffer, buffer->input_buffer, start, strlen (start));
            
            buffer->input_buffer_size -= size_deleted;
            buffer->input_buffer_length -= length_deleted;
            buffer->input_buffer[buffer->input_buffer_size] = '\0';
            buffer->input_buffer_pos = 0;
            gui_input_optimize_size (buffer);
            gui_completion_stop (buffer->completion, 1);
            gui_input_text_changed_modifier_and_signal (buffer);
        }
    }
}

/*
 * gui_input_delete_end_of_line: delete all from cursor pos to end of line
 *                               (default key: ctrl-k)
 */

void
gui_input_delete_end_of_line (struct t_gui_buffer *buffer)
{
    char *start;
    int size_deleted, length_deleted, pos_start;
    
    if (buffer->input)
    {
        start = utf8_add_offset (buffer->input_buffer,
                                 buffer->input_buffer_pos);
        pos_start = start - buffer->input_buffer;
        size_deleted = strlen (start);
        length_deleted = utf8_strlen (start);
        gui_input_clipboard_copy (start, size_deleted);
        start[0] = '\0';
        buffer->input_buffer_size = strlen (buffer->input_buffer);
        buffer->input_buffer_length = utf8_strlen (buffer->input_buffer);
        gui_input_optimize_size (buffer);
        gui_completion_stop (buffer->completion, 1);
        gui_input_text_changed_modifier_and_signal (buffer);
    }
}

/*
 * gui_input_delete_line: delete entire line (default key: meta-r)
 */

void
gui_input_delete_line (struct t_gui_buffer *buffer)
{
    if (buffer->input)
    {
        buffer->input_buffer[0] = '\0';
        buffer->input_buffer_size = 0;
        buffer->input_buffer_length = 0;
        buffer->input_buffer_pos = 0;
        gui_input_optimize_size (buffer);
        gui_completion_stop (buffer->completion, 1);
        gui_input_text_changed_modifier_and_signal (buffer);
    }
}

/*
 * gui_input_transpose_chars: transpose chars (on lth left) at cursor pos
 *                            (default key: ctrl-t)
 */

void
gui_input_transpose_chars (struct t_gui_buffer *buffer)
{
    char *start, *prev_char, saved_char[5];
    int size_prev_char, size_start_char;
    int pos_prev_char, pos_start;
    
    if (buffer->input)
    {
        if ((buffer->input_buffer_pos > 0) && (buffer->input_buffer_length > 1))
        {
            if (buffer->input_buffer_pos == buffer->input_buffer_length)
                buffer->input_buffer_pos--;
            
            start = utf8_add_offset (buffer->input_buffer,
                                     buffer->input_buffer_pos);
            pos_start = start - buffer->input_buffer;
            prev_char = utf8_prev_char (buffer->input_buffer, start);
            pos_prev_char = prev_char - buffer->input_buffer;
            size_prev_char = start - prev_char;
            size_start_char = utf8_char_size (start);
            
            memcpy (saved_char, prev_char, size_prev_char);
            memcpy (prev_char, start, size_start_char);
            memcpy (prev_char + size_start_char, saved_char, size_prev_char);
            
            buffer->input_buffer_pos++;
            
            gui_completion_stop (buffer->completion, 1);
            
            gui_input_text_changed_modifier_and_signal (buffer);
        }
    }
}

/*
 * gui_input_move_beginning_of_line: move cursor to beginning of line (default key: home)
 */

void
gui_input_move_beginning_of_line (struct t_gui_buffer *buffer)
{
    if (buffer->input)
    {
        if (buffer->input_buffer_pos > 0)
        {
            buffer->input_buffer_pos = 0;
            
            gui_input_text_cursor_moved_signal ();
        }
    }
}

/*
 * gui_input_move_end_of_line: move cursor to end of line (default key: end)
 */

void
gui_input_move_end_of_line (struct t_gui_buffer *buffer)
{
    if (buffer->input)
    {
        if (buffer->input_buffer_pos < buffer->input_buffer_length)
        {
            buffer->input_buffer_pos = buffer->input_buffer_length;
            
            gui_input_text_cursor_moved_signal ();
        }
    }
}

/*
 * gui_input_move_previous_char: move cursor to previous char (default key: left)
 */

void
gui_input_move_previous_char (struct t_gui_buffer *buffer)
{
    if (buffer->input)
    {
        if (buffer->input_buffer_pos > 0)
        {
            buffer->input_buffer_pos--;
            
            gui_input_text_cursor_moved_signal ();
        }
    }
}

/*
 * gui_input_move_next_char: move cursor to next char (default key: right)
 */

void
gui_input_move_next_char (struct t_gui_buffer *buffer)
{
    if (buffer->input)
    {
        if (buffer->input_buffer_pos < buffer->input_buffer_length)
        {
            buffer->input_buffer_pos++;
            
            gui_input_text_cursor_moved_signal ();
        }
    }
}

/*
 * gui_input_move_previous_word: move cursor to beginning of previous word
 *                               (default key: meta-b or ctrl-left)
 */

void
gui_input_move_previous_word (struct t_gui_buffer *buffer)
{
    char *pos;
    
    if (buffer->input)
    {
        if (buffer->input_buffer_pos > 0)
        {
            pos = utf8_add_offset (buffer->input_buffer,
                                   buffer->input_buffer_pos - 1);
            while (pos && (pos[0] == ' '))
            {
                pos = utf8_prev_char (buffer->input_buffer, pos);
            }
            if (pos)
            {
                while (pos && (pos[0] != ' '))
                {
                    pos = utf8_prev_char (buffer->input_buffer, pos);
                }
                if (pos)
                    pos = utf8_next_char (pos);
                else
                    pos = buffer->input_buffer;
                buffer->input_buffer_pos = utf8_pos (buffer->input_buffer,
                                                     pos - buffer->input_buffer);
            }
            else
                buffer->input_buffer_pos = 0;
            
            gui_input_text_cursor_moved_signal ();
        }
    }
}

/*
 * gui_input_move_next_word: move cursor to the beginning of next word
 *                           (default key: meta-f or ctrl-right)
 */

void
gui_input_move_next_word (struct t_gui_buffer *buffer)
{
    char *pos;
    
    if (buffer->input)
    {
        if (buffer->input_buffer_pos < buffer->input_buffer_length)
        {
            pos = utf8_add_offset (buffer->input_buffer,
                                   buffer->input_buffer_pos);
            while (pos[0] && (pos[0] == ' '))
            {
                pos = utf8_next_char (pos);
            }
            if (pos[0])
            {
                while (pos[0] && (pos[0] != ' '))
                {
                    pos = utf8_next_char (pos);
                }
                if (pos[0])
                    buffer->input_buffer_pos =
                        utf8_pos (buffer->input_buffer,
                                  pos - buffer->input_buffer);
                else
                    buffer->input_buffer_pos = buffer->input_buffer_length;
            }
            else
                buffer->input_buffer_pos =
                    utf8_pos (buffer->input_buffer,
                              utf8_prev_char (buffer->input_buffer, pos) - buffer->input_buffer);
            
            gui_input_text_cursor_moved_signal ();
        }
    }
}

/*
 * gui_input_history_previous: recall previous command (default key: up)
 */

void
gui_input_history_previous (struct t_gui_window *window)
{
    if (window->buffer->input)
    {
        if (window->buffer->text_search == GUI_TEXT_SEARCH_DISABLED)
        {
            if (window->buffer->ptr_history)
            {
                if (!window->buffer->ptr_history->next_history)
                    return;
                window->buffer->ptr_history = window->buffer->ptr_history->next_history;
                if (!window->buffer->ptr_history)
                    window->buffer->ptr_history = window->buffer->history;
            }
            else
                window->buffer->ptr_history = window->buffer->history;
            if (window->buffer->ptr_history)
            {
                /* bash/readline like use of history */
                if (window->buffer->ptr_history->prev_history == NULL)
                {
                    if (window->buffer->input_buffer_size > 0)
                    {
                        window->buffer->input_buffer[window->buffer->input_buffer_size] = '\0';
                        gui_history_add (window->buffer,
                                         window->buffer->input_buffer);
                    }
                }
                else
                {
                    if (window->buffer->input_buffer_size > 0)
                    {
                        window->buffer->input_buffer[window->buffer->input_buffer_size] = '\0';
                        if (window->buffer->ptr_history->prev_history->text)
                            free(window->buffer->ptr_history->prev_history->text);
                        window->buffer->ptr_history->prev_history->text =
                            strdup (window->buffer->input_buffer);
                    }
                }
                window->buffer->input_buffer_size =
                    strlen (window->buffer->ptr_history->text);
                window->buffer->input_buffer_length =
                    utf8_strlen (window->buffer->ptr_history->text);
                gui_input_optimize_size (window->buffer);
                window->buffer->input_buffer_pos = window->buffer->input_buffer_length;
                window->buffer->input_buffer_1st_display = 0;
                strcpy (window->buffer->input_buffer,
                        window->buffer->ptr_history->text);
            }
            gui_input_text_changed_modifier_and_signal (window->buffer);
        }
        else
        {
            /* search backward in buffer history */
            window->buffer->text_search = GUI_TEXT_SEARCH_BACKWARD;
            (void) gui_window_search_text (window);
        }
    }
}

/*
 * gui_input_history_next: recall next command (default key: down)
 */

void
gui_input_history_next (struct t_gui_window *window)
{
    if (window->buffer->input)
    {
        if (window->buffer->text_search == GUI_TEXT_SEARCH_DISABLED)
        {
            if (window->buffer->ptr_history)
            {
                window->buffer->ptr_history =
                    window->buffer->ptr_history->prev_history;
                if (window->buffer->ptr_history)
                {
                    window->buffer->input_buffer_size =
                        strlen (window->buffer->ptr_history->text);
                    window->buffer->input_buffer_length =
                        utf8_strlen (window->buffer->ptr_history->text);
                }
                else
                {
                    window->buffer->input_buffer[0] = '\0';
                    window->buffer->input_buffer_size = 0;
                    window->buffer->input_buffer_length = 0;
                }
                gui_input_optimize_size (window->buffer);
                window->buffer->input_buffer_pos =
                    window->buffer->input_buffer_length;
                window->buffer->input_buffer_1st_display = 0;
                if (window->buffer->ptr_history)
                {
                    strcpy (window->buffer->input_buffer,
                            window->buffer->ptr_history->text);
                }
            }
            else
            {
                /* add line to history then clear input */
                if (window->buffer->input_buffer_size > 0)
                {
                    window->buffer->input_buffer[window->buffer->input_buffer_size] = '\0';
                    gui_history_add (window->buffer,
                                     window->buffer->input_buffer);
                    window->buffer->input_buffer[0] = '\0';
                    window->buffer->input_buffer_size = 0;
                    window->buffer->input_buffer_length = 0;
                    window->buffer->input_buffer_pos = 0;
                    window->buffer->input_buffer_1st_display = 0;
                    gui_input_optimize_size (window->buffer);
                }
            }
            gui_input_text_changed_modifier_and_signal (window->buffer);
        }
        else
        {
            /* search forward in buffer history */
            window->buffer->text_search = GUI_TEXT_SEARCH_FORWARD;
            (void) gui_window_search_text (window);
        }
    }
}

/*
 * gui_input_history_global_previous: recall previous command in global history
 *                                    (default key: ctrl-up)
 */

void
gui_input_history_global_previous (struct t_gui_buffer *buffer)
{
    if (buffer->input && (buffer->text_search == GUI_TEXT_SEARCH_DISABLED))
    {
        if (history_global_ptr)
        {
            history_global_ptr = history_global_ptr->next_history;
            if (!history_global_ptr)
                history_global_ptr = history_global;
        }
        else
            history_global_ptr = history_global;
        if (history_global_ptr)
        {
            buffer->input_buffer_size = strlen (history_global_ptr->text);
            buffer->input_buffer_length = utf8_strlen (history_global_ptr->text);
            gui_input_optimize_size (buffer);
            buffer->input_buffer_pos = buffer->input_buffer_length;
            buffer->input_buffer_1st_display = 0;
            strcpy (buffer->input_buffer, history_global_ptr->text);
            gui_input_text_changed_modifier_and_signal (buffer);
        }
    }
}

/*
 * gui_history_global_next: recall next command in global history
 *                          (default key: ctrl-down)
 */

void
gui_input_history_global_next (struct t_gui_buffer *buffer)
{
    if (buffer->input && (buffer->text_search == GUI_TEXT_SEARCH_DISABLED))
    {
        if (history_global_ptr)
        {
            history_global_ptr = history_global_ptr->prev_history;
            if (history_global_ptr)
            {
                buffer->input_buffer_size = strlen (history_global_ptr->text);
                buffer->input_buffer_length = utf8_strlen (history_global_ptr->text);
            }
            else
            {
                buffer->input_buffer[0] = '\0';
                buffer->input_buffer_size = 0;
                buffer->input_buffer_length = 0;
            }
            gui_input_optimize_size (buffer);
            buffer->input_buffer_pos = buffer->input_buffer_length;
            buffer->input_buffer_1st_display = 0;
            if (history_global_ptr)
            {
                strcpy (buffer->input_buffer, history_global_ptr->text);
            }
            gui_input_text_changed_modifier_and_signal (buffer);
        }
    }
}

/*
 * gui_input_jump_smart: jump to buffer with activity (default key: alt-A)
 */

void
gui_input_jump_smart (struct t_gui_window *window)
{
    if (window->buffer->text_search == GUI_TEXT_SEARCH_DISABLED)
    {
        if (gui_hotlist)
        {
            if (!gui_hotlist_initial_buffer)
                gui_hotlist_initial_buffer = window->buffer;
            gui_window_switch_to_buffer (window, gui_hotlist->buffer, 1);
            gui_window_scroll_bottom (window);
        }
        else
        {
            if (gui_hotlist_initial_buffer)
            {
                gui_window_switch_to_buffer (window,
                                             gui_hotlist_initial_buffer, 1);
                gui_window_scroll_bottom (window);
                gui_hotlist_initial_buffer = NULL;
            }
        }
    }
}

/*
 * gui_input_jump_last_buffer: jump to last buffer
 *                             (default key: meta-j, meta-l)
 */

void
gui_input_jump_last_buffer (struct t_gui_window *window)
{
    if ((window->buffer->text_search == GUI_TEXT_SEARCH_DISABLED)
        && last_gui_buffer)
    {
        gui_buffer_switch_by_number (window, last_gui_buffer->number);
    }
}

/*
 * gui_input_jump_previously_visited_buffer: jump to previously visited buffer
 *                                           (buffer displayed before current one)
 *                                           (default key: meta-<)
 */

void
gui_input_jump_previously_visited_buffer (struct t_gui_window *window)
{
    int index;
    struct t_gui_buffer_visited *ptr_buffer_visited;
    
    if (window->buffer->text_search == GUI_TEXT_SEARCH_DISABLED)
    {
        index = gui_buffer_visited_get_index_previous ();
        if (index >= 0)
        {
            gui_buffers_visited_index = index;
            
            ptr_buffer_visited =
                gui_buffer_visited_search_by_number (gui_buffers_visited_index);
            if (ptr_buffer_visited)
            {
                gui_buffers_visited_frozen = 1;
                gui_buffer_switch_by_number (window,
                                             ptr_buffer_visited->buffer->number);
                gui_buffers_visited_frozen = 0;
            }
        }
    }
}

/*
 * gui_input_jump_next_visited_buffer: jump to next visited buffer
 *                                     (buffer displayed after current one)
 *                                     (default key: meta->)
 */

void
gui_input_jump_next_visited_buffer (struct t_gui_window *window)
{
    int index;
    struct t_gui_buffer_visited *ptr_buffer_visited;
    
    if (window->buffer->text_search == GUI_TEXT_SEARCH_DISABLED)
    {
        index = gui_buffer_visited_get_index_next ();
        if (index >= 0)
        {
            gui_buffers_visited_index = index;
            
            ptr_buffer_visited = gui_buffer_visited_search_by_number (gui_buffers_visited_index);
            if (ptr_buffer_visited)
            {
                gui_buffers_visited_frozen = 1;
                gui_buffer_switch_by_number (window,
                                             ptr_buffer_visited->buffer->number);
                gui_buffers_visited_frozen = 0;
            }
        }
    }
}

/*
 * gui_input_hotlist_clear: clear hotlist (default key: meta-h)
 */

void
gui_input_hotlist_clear (struct t_gui_window *window)
{
    gui_hotlist_clear ();
    gui_hotlist_initial_buffer = window->buffer;
}

/*
 * gui_input_grab_key: init "grab key mode" (next key will be inserted into
 *                     input buffer) (default key: meta-k)
 */

void
gui_input_grab_key (struct t_gui_buffer *buffer)
{
    if (buffer->input)
        gui_keyboard_grab_init (0);
}

/*
 * gui_input_grab_key_command: init "grab key mode" (next key and command
 *                             bound will be inserted into input buffer)
 */

void
gui_input_grab_key_command (struct t_gui_buffer *buffer)
{
    if (buffer->input)
        gui_keyboard_grab_init (1);
}

/*
 * gui_input_scroll_unread: scroll to first unread line of buffer
 *                          (default key: meta-u)
 */

void
gui_input_scroll_unread (struct t_gui_window *window)
{
    if (window->buffer->text_search == GUI_TEXT_SEARCH_DISABLED)
    {
        if (CONFIG_STRING(config_look_read_marker) &&
            CONFIG_STRING(config_look_read_marker)[0] &&
            (window->buffer->type == GUI_BUFFER_TYPE_FORMATTED) &&
            (window->buffer->lines->first_line_not_read ||
             (window->buffer->lines->last_read_line &&
              window->buffer->lines->last_read_line != window->buffer->lines->last_line)))
        {
            if (window->buffer->lines->first_line_not_read)
                window->start_line = window->buffer->lines->first_line;
            else
                window->start_line = window->buffer->lines->last_read_line->next_line;
            window->start_line_pos = 0;
            window->first_line_displayed =
                (window->start_line == gui_line_get_first_displayed (window->buffer));
            gui_buffer_ask_chat_refresh (window->buffer, 2);
        }
    }
}

/*
 * gui_input_set_unread: set unread marker for all buffers
 *                       (default key: ctrl-s, ctrl-u)
 */

void
gui_input_set_unread ()
{
    struct t_gui_buffer *ptr_buffer;
    
    /* set read marker for all standard buffers */
    for (ptr_buffer = gui_buffers; ptr_buffer;
         ptr_buffer = ptr_buffer->next_buffer)
    {
        gui_buffer_set_unread (ptr_buffer);
    }
}

/*
 * gui_input_set_unread_buffer: set unread marker for a buffer
 */

void
gui_input_set_unread_buffer (struct t_gui_buffer *buffer)
{
    gui_buffer_set_unread (buffer);
}

/*
 * gui_input_switch_active_buffer: switch active buffer to next buffer
 *                                 (when many buffers are merged)
 */

void
gui_input_switch_active_buffer (struct t_gui_window *window)
{
    struct t_gui_buffer *ptr_buffer;
    
    ptr_buffer = gui_buffer_get_next_active_buffer (window->buffer);
    if (ptr_buffer)
    {
        gui_buffer_set_active_buffer (ptr_buffer);
        gui_window_switch_to_buffer (window, ptr_buffer, 1);
    }
}

/*
 * gui_input_switch_active_buffer_previous: switch active buffer to previous
 *                                          buffer (when many buffers are
 *                                          merged)
 */

void
gui_input_switch_active_buffer_previous (struct t_gui_window *window)
{
    struct t_gui_buffer *ptr_buffer;
    
    ptr_buffer = gui_buffer_get_previous_active_buffer (window->buffer);
    if (ptr_buffer)
    {
        gui_buffer_set_active_buffer (ptr_buffer);
        gui_window_switch_to_buffer (window, ptr_buffer, 1);
    }
}

/*
 * gui_input_insert: insert a string in command line
 *                   (many default keys are bound to this function)
 */

void
gui_input_insert (struct t_gui_buffer *buffer, const char *args)
{
    char *args2;
    
    if (args)
    {
        args2 = string_convert_hex_chars (args);
        gui_input_insert_string (buffer, (args2) ? args2 : args, -1);
        gui_input_text_changed_modifier_and_signal (buffer);
        if (args2)
            free (args2);
    }
}
