/* Copyright (C) 2008 Emmanuel Varoquaux
This file is part of XOS.
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 . */
#include "output.h"
#include "tty.h"
#include "terminal.h"
#include "prompt.h"
#include "line.h"
#include "misc.h"
#include "xmalloc.h"
#include
#include
#include
#define min(a, b) ((a) < (b) ? (a) : (b))
/* position dans la ligne de saisie */
struct position_struct {
int row;
int col;
};
static struct position_struct start_pos; /* position du debut de la saisie */
static struct position_struct cursor_pos; /* position du curseur */
/* Tampon */
#define OUTBUF_INITIAL_SIZE 1024
/* tampon pour les sorties */
static char *outbuf;
static size_t outbuf_size;
static size_t outbuf_pos;
/* Agrandit le tampon si necessaire pour obtenir incr cases libres
* supplementaires.
* Si le tampon est non vide, la derniere case est toujours libre. */
static void grow_outbuf(size_t incr)
{
size_t min_size;
size_t new_size;
if (incr == 0)
return;
min_size = outbuf_pos + incr + 1;
if (min_size <= outbuf_size)
return;
new_size = outbuf_size ? : OUTBUF_INITIAL_SIZE;
while (min_size > new_size)
new_size *= 2;
outbuf = xrealloc(outbuf, new_size);
outbuf_size = new_size;
}
static void outbuf_init()
{
outbuf = NULL;
outbuf_size = 0;
outbuf_pos = 0;
}
static void outbuf_putc(char c)
{
grow_outbuf(1);
outbuf[outbuf_pos++] = c;
}
static void outbuf_puts(const char *s)
{
size_t len;
len = strlen(s);
grow_outbuf(len);
memcpy(&outbuf[outbuf_pos], s, len);
outbuf_pos += len;
}
static void outbuf_flush()
{
if (outbuf_pos == 0) /* en particulier si outbuf == NULL ou outbuf_size == 0 */
return;
outbuf[outbuf_pos] = '\0'; /* il reste toujours au moins une case libre */
fputs(outbuf, rl_outstream);
fflush(rl_outstream);
outbuf_pos = 0;
}
static void free_outbuf()
{
free(outbuf);
}
/* Fonctions elementaires d'affichage */
static void put_bs()
{
outbuf_putc('\b');
cursor_pos.col--;
}
static void put_lf()
{
outbuf_putc('\n');
cursor_pos.row++;
}
static void put_cr()
{
outbuf_putc('\r');
cursor_pos.col = 0;
}
static void put_print(char c)
{
outbuf_putc(c);
cursor_pos.col++;
}
static void put_cursor_up_seq(int n)
{
outbuf_puts(cursor_up_seq(n));
cursor_pos.row -= n;
}
static void put_cursor_down_seq(int n)
{
outbuf_puts(cursor_down_seq(n));
cursor_pos.row += n;
}
static void put_cursor_forward_seq(int n)
{
outbuf_puts(cursor_forward_seq(n));
cursor_pos.col += n;
}
static void put_cursor_backward_seq(int n)
{
outbuf_puts(cursor_backward_seq(n));
cursor_pos.col -= n;
}
static void wrap(char c)
{
outbuf_putc(c);
cursor_pos.row++;
put_cr();
}
/* Fonctions evoluees d'affichage */
static void print_prompt()
{
if (!local_prompt)
return;
if (local_prompt_prefix)
outbuf_puts(local_prompt_prefix);
outbuf_puts(local_prompt);
cursor_pos.row = (prompt_visible_length - 1) / screenwidth;
cursor_pos.col = (prompt_visible_length - 1) % screenwidth + 1;
}
static void print_tab()
{
int newcol;
if (cursor_pos.col == screenwidth)
wrap(' ');
newcol = (cursor_pos.col | 7) + 1;
do
put_print(' ');
while (cursor_pos.col != screenwidth && cursor_pos.col != newcol);
}
static void print_linefeed()
{
if (cursor_pos.col == screenwidth)
wrap('\n');
outbuf_puts(erase_end_of_line_seq);
put_lf();
put_cr();
}
static void print_print(char c)
{
if (cursor_pos.col == screenwidth)
wrap(c);
put_print(c);
}
static void print_char(char c)
{
if (c == '\t')
print_tab();
else if (c == '\n')
print_linefeed();
else if (ctrl_char(c) || c == RUBOUT) {
print_print('^');
print_print(ctrl_char(c) ? unctrl(c) : '?');
}
else
print_print(c);
}
static void print_string(const char *s)
{
while (*s)
print_char(*s++);
}
static void erase(int n)
{
while (n) {
print_char(' ');
n--;
}
}
static void wrap_if_needed()
{
if (cursor_pos.col == screenwidth)
wrap(' ');
}
static void move_cursor_to(struct position_struct pos)
{
if (cursor_pos.row < pos.row)
put_cursor_down_seq(pos.row - cursor_pos.row);
else if (cursor_pos.row > pos.row)
put_cursor_up_seq(cursor_pos.row - pos.row);
if (cursor_pos.col < pos.col)
put_cursor_forward_seq(pos.col - cursor_pos.col);
else if (cursor_pos.col > pos.col) {
if (pos.col == 0)
put_cr();
else if (pos.col == cursor_pos.col - 1)
put_bs();
else
put_cursor_backward_seq(cursor_pos.col - pos.col);
}
}
static void move_cursor_to_origin()
{
if (cursor_pos.row > 0)
put_cursor_up_seq(cursor_pos.row);
put_cr();
}
/* Fonctions sur les positions */
static void pos_wrap(struct position_struct *pos)
{
pos->row++;
pos->col = 0;
}
static void pos_move_forward_tab(struct position_struct *pos)
{
if (pos->col == screenwidth)
pos_wrap(pos);
pos->col = min((pos->col | 7) + 1, screenwidth);
}
static void pos_move_forward_linefeed(struct position_struct *pos)
{
if (pos->col == screenwidth)
pos_wrap(pos);
pos->row++;
pos->col = 0;
}
static void pos_move_forward_print(struct position_struct *pos)
{
if (pos->col == screenwidth)
pos_wrap(pos);
pos->col++;
}
static void pos_move_forward_char(struct position_struct *pos, char c)
{
if (c == '\t')
pos_move_forward_tab(pos);
else if (c == '\n')
pos_move_forward_linefeed(pos);
else if (ctrl_char(c) || c == RUBOUT) {
pos_move_forward_print(pos);
pos_move_forward_print(pos);
}
else
pos_move_forward_print(pos);
}
static void pos_move_forward_string(struct position_struct *pos, const char *s)
{
while (*s)
pos_move_forward_char(pos, *s++);
}
static void pos_move_forward_region(struct position_struct *pos, const char *start, const char *end)
{
while (start < end)
pos_move_forward_char(pos, *start++);
}
static void pos_wrap_if_needed(struct position_struct *pos)
{
if (pos->col == screenwidth)
pos_wrap(pos);
}
static int pos_coincide(struct position_struct pos1, struct position_struct pos2)
{
pos_wrap_if_needed(&pos1);
pos_wrap_if_needed(&pos2);
return pos1.row == pos2.row && pos1.col == pos2.col;
}
/* Commandes d'affichage */
static void print_line()
{
struct position_struct pos;
print_prompt();
print_string(line);
wrap_if_needed();
pos = start_pos;
pos_move_forward_region(&pos, line, line + point);
pos_wrap_if_needed(&pos);
move_cursor_to(pos);
}
static void move_cursor_to_point(int to_point)
{
int from_point;
struct position_struct pos;
if (to_point >= point) {
from_point = point;
pos = cursor_pos;
}
else {
from_point = 0;
pos = start_pos;
}
pos_move_forward_region(&pos, line + from_point, line + to_point);
pos_wrap_if_needed(&pos);
move_cursor_to(pos);
}
/* Deplace le curseur au point start et positionne pos a la position du point
* end. */
static void select_region(int start, int end, struct position_struct *pos)
{
if (start != point)
move_cursor_to_point(start);
*pos = cursor_pos;
if (end != start)
pos_move_forward_region(pos, line + start, line + end);
}
/* Reecrit la ligne a partir du point start a la position du curseur. oldpos
* est la position ou la ligne a partir du point start etait precedemment
* ecrite. Le curseur est en suite deplace a la position pos. */
static void reprint(int start, struct position_struct oldpos, struct position_struct pos)
{
char c;
while ((c = line[start]) && !pos_coincide(cursor_pos, oldpos)) {
print_char(c);
pos_move_forward_char(&oldpos, c);
start++;
}
wrap_if_needed();
pos_wrap_if_needed(&oldpos);
while (cursor_pos.row < oldpos.row) {
outbuf_puts(erase_end_of_line_seq);
put_lf();
put_cr();
}
if (cursor_pos.row == oldpos.row)
while (cursor_pos.col < oldpos.col)
print_print(' ');
pos_wrap_if_needed(&pos);
move_cursor_to(pos);
}
void begin_output(const char *prompt)
{
set_prompt(prompt);
outbuf_init();
print_prompt();
if (tty_echo)
wrap_if_needed();
outbuf_flush();
start_pos = cursor_pos;
}
void output_replace_region_with_char(int start, int end, char c)
{
struct position_struct pos;
if (!tty_echo)
return;
select_region(start, end, &pos);
print_char(c);
reprint(end, pos, cursor_pos);
outbuf_flush();
}
void output_replace_region_with_text(int start, int end, const char *text)
{
struct position_struct pos;
if (!tty_echo)
return;
select_region(start, end, &pos);
print_string(text);
reprint(end, pos, cursor_pos);
outbuf_flush();
}
void output_erase_region(int start, int end)
{
struct position_struct pos1, pos2;
if (!tty_echo)
return;
select_region(start, end, &pos1);
pos2 = cursor_pos;
erase(end - start);
reprint(end, pos1, pos2);
outbuf_flush();
}
void output_delete_region(int start, int end)
{
struct position_struct pos;
if (!tty_echo)
return;
select_region(start, end, &pos);
reprint(end, pos, cursor_pos);
outbuf_flush();
}
void output_move_cursor(int newpoint)
{
if (!tty_echo)
return;
move_cursor_to_point(newpoint);
outbuf_flush();
}
void output_clear_line_and_reprint()
{
if (tty_echo)
move_cursor_to_origin();
outbuf_puts(erase_end_of_line_seq);
outbuf_flush();
if (!tty_echo)
return;
print_line();
outbuf_flush();
}
void output_clear_screen_and_reprint()
{
outbuf_puts(cursor_home_seq);
outbuf_puts(erase_screen_seq);
outbuf_flush();
if (!tty_echo)
return;
print_line();
outbuf_flush();
}
void output_new_line()
{
struct position_struct pos;
if (!tty_echo)
return;
pos = cursor_pos;
pos_move_forward_string(&pos, line + point);
pos_wrap_if_needed(&pos);
pos.col = cursor_pos.col; /* mouvement uniquement vertical */
move_cursor_to(pos);
outbuf_puts("\r\n");
outbuf_flush();
}
void end_output()
{
free_outbuf();
free_prompt();
}
void ding()
{
if (!tty_echo)
return;
ring_bell();
}