about summary refs log blame commit diff stats
path: root/tests/unittests/test_keyhandlers.c
blob: a6d39143b49f0357d738628d285690611aa9e287 (plain) (tree)
# -*- coding: utf-8 -*-
# This file is part of ranger, the console file manager.
# License: GNU GPL version 3, see the file "AUTHORS" for details.

"""The BrowserColumn widget displays the contents of a directory or file."""

import curses
import stat
from time import time
from os.path import splitext

from . import Widget
from .pager import Pager
from ranger.ext.widestring import WideString

from ranger.core import linemode

from ranger.gui.color import *

class BrowserColumn(Pager):
    main_column = False
    display_infostring = False
    display_vcsstate   = True
    scroll_begin = 0
    target = None
    last_redraw_time = -1
    ellipsis = { False: '~', True: '…' }

    old_dir = None
    old_thisfile = None

    def __init__(self, win, level):
        """Initializes a Browser Column Widget

        win = the curses window object of the BrowserView
        level = what to display?

        level >0 => previews
        level 0 => current file/directory
        level <0 => parent directories
        """
        Pager.__init__(self, win)
        Widget.__init__(self, win)
        self.level = level
        self.original_level = level

        self.settings.signal_bind('setopt.display_size_in_main_column',
                self.request_redraw, weak=True)

    def request_redraw(self):
        self.need_redraw = True

    def resize(self, y, x, hei, wid):
        Widget.resize(self, y, x, hei, wid)

    def click(self, event):
        """Handle a MouseEvent"""
        direction = event.mouse_wheel_direction()
        if not (event.pressed(1) or event.pressed(3) or direction):
            return False

        if self.target is None:
            pass

        elif self.target.is_directory:
            if self.target.accessible and self.target.content_loaded:
                index = self.scroll_begin + event.y - self.y

                if direction:
                    if self.level == -1:
                        self.fm.move_parent(direction)
                    else:
                        return False
                elif event.pressed(1):
                    if not self.main_column:
                        self.fm.enter_dir(self.target.path)

                    if index < len(self.target):
                        self.fm.move(to=index)
                elif event.pressed(3):
                    try:
                        clicked_file = self.target.files[index]
                        if clicked_file.is_directory:
                            self.fm.enter_dir(clicked_file.path)
                        elif self.level == 0:
                            self.fm.thisdir.move_to_obj(clicked_file)
                            self.fm.execute_file(clicked_file)
                    except:
                        pass

        else:
            if self.level > 0 and not direction:
                self.fm.move(right=0)

        return True

    def execute_curses_batch(self, line, commands):
        """Executes a list of "commands" which can be easily cached.

        "commands" is a list of lists.    Each element contains
        a text and an attribute.  First, the attribute will be
        set with attrset, then the text is printed.

        Example:
        execute_curses_batch(0, [["hello ", 0], ["world", curses.A_BOLD]])
        """
        try:
            self.win.move(line, 0)
        except:
            return
        for entry in commands:
            text, attr = entry
            self.addstr(text, attr)

    def has_preview(self):
        if self.target is None:
            return False

        if self.target.is_file:
            if not self.target.has_preview():
                return False

        if self.target.is_directory:
            if self.level > 0 and not self.settings.preview_directories:
                return False

        return True

    def level_shift(self, amount):
        self.level = self.original_level + amount

    def level_restore(self):
        self.level = self.original_level

    def poke(self):
        Widget.poke(self)
        self.target = self.fm.thistab.at_level(self.level)

    def draw(self):
        """Call either _draw_file() or _draw_directory()"""
        if self.target != self.old_dir:
            self.need_redraw = True
            self.old_dir = self.target

        if self.target:     # don't garbage collect this directory please
            self.target.use()

        if self.target and self.target.is_directory \
                and (self.level <= 0 or self.settings.preview_directories):
            if self.target.pointed_obj != self.old_thisfile:
                self.need_redraw = True
                self.old_thisfile = self.target.pointed_obj

            if self.target.load_content_if_outdated() \
            or self.target.sort_if_outdated() \
            or self.last_redraw_time < self.target.last_update_time:
                self.need_redraw = True

        if self.need_redraw:
            self.win.erase()
            if self.target is None:
                pass
            elif self.target.is_file:
                Pager.open(self)
                self._draw_file()
            elif self.target.is_directory:
                self._draw_directory()
                Widget.draw(self)
            self.need_redraw = False
            self.last_redraw_time = time()

    def _draw_file(self):
        """Draw a preview of the file, if the settings allow it"""
        self.win.move(0, 0)
        if not self.target.accessible:
            self.addnstr("not accessible", self.wid)
            Pager.close(self)
            return

        if self.target is None or not self.target.has_preview():
            Pager.close(self)
            return

        f = self.target.get_preview_source(self.wid, self.hei)
        if f is None:
            Pager.close(self)
        else:
            if self.target.is_image_preview():
                self.set_image(f)
            else:
                self.set_source(f)
            Pager.draw(self)

    def _draw_directory(self):
        """Draw the contents of a directory"""
        if self.image:
            self.image = None
            self.need_clear_image = True
            Pager.clear_image(self)

        if self.level > 0 and not self.settings.preview_directories:
            return

        base_color = ['in_browser']

        self.win.move(0, 0)

        if not self.target.content_loaded:
            self.color(tuple(base_color))
            self.addnstr("...", self.wid)
            self.color_reset()
            return

        if self.main_column:
            base_color.append('main_column')

        if not self.target.accessible:
            self.color(tuple(base_color + ['error']))
            self.addnstr("not accessible", self.wid)
            self.color_reset()
            return

        if self.target.empty():
            self.color(tuple(base_color + ['empty']))
            self.addnstr("empty", self.wid)
            self.color_reset()
            return

        self._set_scroll_begin()

        copied = [f.path for f in self.fm.copy_buffer]

        selected_i = self.target.pointer
        for line in range(self.hei):
            i = line + self.scroll_begin
            if line > self.hei:
                break

            try:
                drawn = self.target.files[i]
            except IndexError:
                break

            tagged = self.fm.tags and drawn.realpath in self.fm.tags
            if tagged:
                tagged_marker = self.fm.tags.marker(drawn.realpath)
            else:
                tagged_marker = " "

            # Extract linemode-related information from the drawn object
            metadata = None
            current_linemode = drawn.linemode_dict[drawn._linemode]
            if current_linemode.uses_metadata:
                metadata = self.fm.metadata.get_metadata(drawn.path)
                if not all(getattr(metadata, tag)
                           for tag in current_linemode.required_metadata):
                    current_linemode = drawn.linemode_dict[linemode.DEFAULT_LINEMODE]

            metakey = hash(repr(sorted(metadata.items()))) if metadata else 0
            key = (self.wid, selected_i == i, drawn.marked, self.main_column,
                    drawn.path in copied, tagged_marker, drawn.infostring,
                    drawn.vcsfilestatus, drawn.vcsremotestatus, self.fm.do_cut,
                    current_linemode.name, metakey)

            if key in drawn.display_data:
                self.execute_curses_batch(line, drawn.display_data[key])
                self.color_reset()
                continue

            text = current_linemode.filetitle(drawn, metadata)

            if drawn.marked and (self.main_column or \
                    self.settings.display_tags_in_all_columns):
                text = " " + text

            # Computing predisplay data. predisplay contains a list of lists
            # [string, colorlst] where string is a piece of string to display,
            # and colorlst a list of contexts that we later pass to the
            # colorscheme, to compute the curses attribute.
            predisplay_left = []
            predisplay_right = []
            space = self.wid

            # selection mark
            tagmark = self._draw_tagged_display(tagged, tagged_marker)
            tagmarklen = self._total_len(tagmark)
            if space - tagmarklen > 2:
                predisplay_left += tagmark
                space -= tagmarklen

            # vcs data
            vcsstring = self._draw_vcsstring_display(drawn)
            vcsstringlen = self._total_len(vcsstring)
            if space - vcsstringlen > 2:
                predisplay_right += vcsstring
                space -= vcsstringlen

            # info string
            infostring = []
            infostringlen = 0
            try:
                infostringdata = current_linemode.infostring(drawn, metadata)
                if infostringdata:
                    infostring.append([" " + infostringdata + " ",
                                       ["infostring"]])
            except NotImplementedError:
                infostring = self._draw_infostring_display(drawn, space)
            if infostring:
                infostringlen = self._total_len(infostring)
                if space - infostringlen > 2:
                    predisplay_right = infostring + predisplay_right
                    space -= infostringlen

            textstring = self._draw_text_display(text, space)
            textstringlen = self._total_len(textstring)
            predisplay_left += textstring
            space -= textstringlen

            if space > 0:
                predisplay_left.append([' ' * space, []])
            elif space < 0:
                raise Exception("Error: there is not enough space to write "
                        "the text. I have computed spaces wrong.")

            # Computing display data. Now we compute the display_data list
            # ready to display in curses. It is a list of lists [string, attr]

            this_color = base_color + list(drawn.mimetype_tuple) + \
                    self._draw_directory_color(i, drawn, copied)
            display_data = []
            drawn.display_d
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include <stdlib.h>
#include <string.h>

#include <locale.h>

#include "ui/keyhandlers.h"
#include "ui/inputwin.h"
#include "tests/helpers.h"

static char line[INP_WIN_MAX];

// append

void append_to_empty(void **state)
{
    setlocale(LC_ALL, "");
    line[0] = '\0';
    int line_utf8_pos = 0;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_printable(line, &line_utf8_pos, &col, &pad_start, 'a', 80);

    assert_string_equal("a", line);
    assert_int_equal(line_utf8_pos, 1);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void append_wide_to_empty(void **state)
{
    setlocale(LC_ALL, "");
    line[0] = '\0';
    int line_utf8_pos = 0;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_printable(line, &line_utf8_pos, &col, &pad_start, 0x56DB, 80);

    assert_string_equal("四", line);
    assert_int_equal(line_utf8_pos, 1);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void append_to_single(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "a", 1);
    line[1] = '\0';
    int line_utf8_pos = 1;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_printable(line, &line_utf8_pos, &col, &pad_start, 'b', 80);

    assert_string_equal("ab", line);
    assert_int_equal(line_utf8_pos, 2);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}


void append_wide_to_single_non_wide(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "a", 1);
    line[1] = '\0';
    int line_utf8_pos = 1;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_printable(line, &line_utf8_pos, &col, &pad_start, 0x56DB, 80);

    assert_string_equal("a四", line);
    assert_int_equal(line_utf8_pos, 2);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void append_non_wide_to_single_wide(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "四", 1);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 1;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_printable(line, &line_utf8_pos, &col, &pad_start, 'b', 80);

    assert_string_equal("四b", line);
    assert_int_equal(line_utf8_pos, 2);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void append_wide_to_single_wide(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "四", 1);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 1;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_printable(line, &line_utf8_pos, &col, &pad_start, 0x4E09, 80);

    assert_string_equal("四三", line);
    assert_int_equal(line_utf8_pos, 2);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void append_non_wide_when_overrun(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "0123456789四1234567", 18);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 18;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_printable(line, &line_utf8_pos, &col, &pad_start, 'z', 20);
    key_printable(line, &line_utf8_pos, &col, &pad_start, 'z', 20);
    key_printable(line, &line_utf8_pos, &col, &pad_start, 'z', 20);

    assert_string_equal("0123456789四1234567zzz", line);
    assert_int_equal(line_utf8_pos, 21);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 3);
}

void insert_non_wide_to_non_wide(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcd", 4);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 2;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_printable(line, &line_utf8_pos, &col, &pad_start, '0', 80);

    assert_string_equal("ab0cd", line);
    assert_int_equal(line_utf8_pos, 3);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void insert_single_non_wide_when_pad_scrolled(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "AAAAAAAAAAAAAAA", 15);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 2;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 2;

    key_printable(line, &line_utf8_pos, &col, &pad_start, 'B', 12);

    assert_string_equal("AABAAAAAAAAAAAAA", line);
    assert_int_equal(line_utf8_pos, 3);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 2);
}

void insert_many_non_wide_when_pad_scrolled(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "AAAAAAAAAAAAAAA", 15);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 2;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 2;

    key_printable(line, &line_utf8_pos, &col, &pad_start, 'B', 12);
    key_printable(line, &line_utf8_pos, &col, &pad_start, 'C', 12);
    key_printable(line, &line_utf8_pos, &col, &pad_start, 'D', 12);

    assert_string_equal("AABCDAAAAAAAAAAAAA", line);
    assert_int_equal(line_utf8_pos, 5);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 2);
}

void insert_single_non_wide_last_column(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcdefghijklmno", 15);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 7;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 2;

    key_printable(line, &line_utf8_pos, &col, &pad_start, '1', 5);

    assert_string_equal("abcdefg1hijklmno", line);
    assert_int_equal(line_utf8_pos, 8);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 3);
}

void insert_many_non_wide_last_column(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcdefghijklmno", 15);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 7;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 2;

    key_printable(line, &line_utf8_pos, &col, &pad_start, '1', 5);
    key_printable(line, &line_utf8_pos, &col, &pad_start, '2', 5);

    assert_string_equal("abcdefg12hijklmno", line);
    assert_int_equal(line_utf8_pos, 9);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 4);
}

void ctrl_left_when_no_input(void **state)
{
    setlocale(LC_ALL, "");
    line[0] = '\0';
    int line_utf8_pos = 0;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 0);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_at_start(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 0;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 0);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_in_first_word(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 2;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 0);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_in_first_space(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 4;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 0);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_at_start_of_second_word(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 5;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 0);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_in_second_word(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 8;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 5);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_at_end_of_second_word(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 10;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 5);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_in_second_space(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 11;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 5);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_at_start_of_third_word(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 12;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 5);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_in_third_word(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 14;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 12);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_at_end_of_third_word(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 15;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 12);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_in_third_space(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 16;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 12);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_at_end(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 20;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 17);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_in_only_whitespace(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "       ", 7);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 5;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 0);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_start_whitespace_start_of_word(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "    hello", 9);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 4;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 0);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_start_whitespace_middle_of_word(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "    hello", 9);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 7;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 4);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_in_whitespace_between_words(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "hey    hello", 12);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 5;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 0);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_in_whitespace_between_words_start_of_word(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "hey    hello", 12);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 7;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 0);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_in_whitespace_between_words_middle_of_word(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "hey    hello", 12);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 9;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 7);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_left_when_word_overrun_to_left(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "someword anotherword", 20);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 18;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 14;

    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 9);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 9);
}

void ctrl_right_when_no_input(void **state)
{
    setlocale(LC_ALL, "");
    line[0] = '\0';
    int line_utf8_pos = 0;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 0);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_right_when_at_end(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "someword anotherword", 20);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 20;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 20);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_right_one_word_at_start(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "someword", 8);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 0;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 8);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_right_one_word_in_middle(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "someword", 8);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 3;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 8);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_right_one_word_at_end(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "someword", 8);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 7;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 8);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_right_two_words_from_middle_first(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "someword anotherword", 20);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 4;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 8);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_right_two_words_from_end_first(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "someword anotherword", 20);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 7;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 8);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_right_two_words_from_space(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "someword anotherword", 20);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 8;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 20);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_right_two_words_from_start_second(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "someword anotherword", 20);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 9;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 20);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_right_one_word_leading_whitespace(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "       someword", 15);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 3;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 15);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_right_two_words_in_whitespace(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "       someword        adfasdf", 30);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 19;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 30);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}

void ctrl_right_trailing_whitespace_from_middle(void **state)
{
    setlocale(LC_ALL, "");
    g_utf8_strncpy(line, "someword        ", 16);
    line[strlen(line)] = '\0';
    int line_utf8_pos = 3;
    int col = utf8_pos_to_col(line, line_utf8_pos);
    int pad_start = 0;

    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);

    assert_int_equal(line_utf8_pos, 8);
    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
    assert_int_equal(pad_start, 0);
}