/* editcommands.c -- Lisp functions for editing
   Copyright (C) 1993, 1994 John Harper <jsh@ukc.ac.uk>

   This file is part of Jade.

   Jade 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 2, or (at your option)
   any later version.

   Jade 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 Jade; see the file COPYING.	If not, write to
   the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "jade.h"
#include "jade_protos.h"

#include <string.h>
#include <ctype.h>

#ifdef NEED_MEMORY_H
# include <memory.h>
#endif

_PR void edit_init(void);

static VALUE sym_upcase_table, sym_downcase_table;

_PR VALUE sym_inhibit_read_only;
VALUE sym_inhibit_read_only;

/* Some doc strings
::doc:upcase_table::
256-byte string holding translations to turn each character into its
upper-case equivalent.
::end::
::doc:downcase_table::
256-byte string holding translations to turn each character into its
lower-case equivalent.
::end::
::doc:inhibit_read_only::
When bound and non-nil this variable cancels the effect of the set-buffer-
read-only command; in that the buffer is always writeable. This is
intended to be bound while a command is executing that is allowed to
modify a buffer.
::end:: */

_PR VALUE cmd_split_line(void);
DEFUN_INT("split-line", cmd_split_line, subr_split_line, (void), V_Subr0, DOC_split_line, "") /*
::doc:split_line::
split-line

Splits the line into two at the cursor position.
::end:: */
{
    VW *vw = curr_vw;
    TX *tx = vw->vw_Tx;
    if(!read_only(tx))
    {
	POS old = vw->vw_CursorPos;
	if(pad_cursor(vw))
	{
	    if(split_line(tx, &vw->vw_CursorPos))
	    {
		undo_record_insertion(tx, &old, &vw->vw_CursorPos);
		flag_insertion(tx, &old, &vw->vw_CursorPos);
		return(sym_t);
	    }
	}
    }
    return(sym_nil);
}

_PR VALUE cmd_insert(VALUE string, VALUE pos, VALUE buff);
DEFUN_INT("insert", cmd_insert, subr_insert, (VALUE string, VALUE lpos, VALUE buff), V_Subr3, DOC_insert, "sString to insert:") /*
::doc:insert::
insert STRING [POS] [BUFFER]

Inserts STRING into BUFFER at POS. Returns the first position of the first
character after the end of the inserted text.
::end:: */
{
    POS pos;
    DECLARE1(string, STRINGP);
    if(POSP(lpos))
	pos = VPOS(lpos);
    else
	pos = curr_vw->vw_CursorPos;
    if(!BUFFERP(buff))
	buff = VAL(curr_vw->vw_Tx);
    if(!read_only(VTX(buff)) && pad_pos(VTX(buff), &pos))
    {
	if(insert_string(VTX(buff), VSTR(string), STRING_LEN(string), &pos))
	    return(make_lpos(&pos));
    }
    return(sym_nil);
}

_PR VALUE cmd_delete_area(VALUE start, VALUE end, VALUE buff);
DEFUN("delete-area", cmd_delete_area, subr_delete_area, (VALUE lstart, VALUE lend, VALUE buff), V_Subr3, DOC_delete_area) /*
::doc:delete_area::
delete-area START-POS END-POS [BUFFER]

Deletes from START-POS up to (but not including) END-POS.
::end:: */
{
    POS start, end;
    DECLARE1(lstart, POSP);
    DECLARE2(lend, POSP);
    start = VPOS(lstart);
    end = VPOS(lend);
    if(!BUFFERP(buff))
	buff = VAL(curr_vw->vw_Tx);
    if(!read_only(VTX(buff)) && pad_pos(VTX(buff), &start)
       && pad_pos(VTX(buff), &end) && check_section(VTX(buff), &start, &end))
    {
	delete_section(VTX(buff), &start, &end);
	return(sym_t);
    }
    return(sym_nil);
}

_PR VALUE cmd_copy_area(VALUE lstart, VALUE lend, VALUE buff);
DEFUN("copy-area", cmd_copy_area, subr_copy_area, (VALUE lstart, VALUE lend, VALUE buff), V_Subr3, DOC_copy_area) /*
::doc:copy_area::
copy-area START-POS END-POS [BUFFER]

Returns the string from START-POS up to END-POS.
::end:: */
{
    POS start, end;
    DECLARE1(lstart, POSP);
    DECLARE2(lend, POSP);
    start = VPOS(lstart);
    end = VPOS(lend);
    if(!BUFFERP(buff))
	buff = VAL(curr_vw->vw_Tx);
    if(check_section(VTX(buff), &start, &end))
    {
	long tlen = section_length(VTX(buff), &start, &end) + 1;
	VALUE str = make_string(tlen);
	if(str)
	{
	    copy_section(VTX(buff), &start, &end, VSTR(str));
	    VSTR(str)[tlen - 1] = 0;
	    return(str);
	}
    }
    return(sym_nil);
}

_PR VALUE cmd_cut_area(VALUE lstart, VALUE lend, VALUE buff);
DEFUN("cut-area", cmd_cut_area, subr_cut_area, (VALUE lstart, VALUE lend, VALUE buff), V_Subr3, DOC_cut_area) /*
::doc:cut_area::
cut-area START-POS END-POS [BUFFER]

The same as `copy-area' except that the section of text copied (START-POS to
END-POS) is deleted from the file after being duplicated.
::end:: */
{
    POS start, end;
    DECLARE1(lstart, POSP);
    DECLARE2(lend, POSP);
    start = VPOS(lstart);
    end = VPOS(lend);
    if(!BUFFERP(buff))
	buff = VAL(curr_vw->vw_Tx);
    if(!read_only(VTX(buff)) && pad_pos(VTX(buff), &start)
       && pad_pos(VTX(buff), &end) && check_section(VTX(buff), &start, &end))
    {
	/* Only one copy is made. */
	VALUE str = undo_push_deletion(VTX(buff), &start, &end);
	if(str)
	{
	    delete_section(VTX(buff), &start, &end);
	    return(str);
	}
    }
    return(sym_nil);
}

_PR VALUE cmd_block_toggle(void);
DEFUN_INT("block-toggle", cmd_block_toggle, subr_block_toggle, (void), V_Subr0, DOC_block_toggle, "") /*
::doc:block_toggle::
block-toggle
::end:: */
{
    VW *vw = curr_vw;
    switch(vw->vw_BlockStatus)
    {
	case 0:
	    vw->vw_BlockStatus = -1;
	    set_block_refresh(vw);
	    break;
	case 1:
	    vw->vw_BlockE = vw->vw_CursorPos;
	    vw->vw_BlockStatus = 0;
	    order_block(vw);
	    set_block_refresh(vw);
	    break;
	case 2:
	    vw->vw_BlockS = vw->vw_CursorPos;
	    vw->vw_BlockStatus = 0;
	    order_block(vw);
	    set_block_refresh(vw);
	    break;
	case -1:
	    vw->vw_BlockS = vw->vw_CursorPos;
	    vw->vw_BlockStatus = 1;
	    break;
    }
    return(sym_t);
}

_PR VALUE cmd_block_start(VALUE pos);
DEFUN("block-start", cmd_block_start, subr_block_start, (VALUE pos), V_Subr1, DOC_block_start) /*
::doc:block_start::
block-start [POS]

Always returns the position of the block-start as it is, if POS is given
it is used as the new position of the start of the block.
::end:: */
{
    VW *vw = curr_vw;
    VALUE res;
    if(!vw->vw_BlockStatus || (vw->vw_BlockStatus == 1))
	res = make_lpos(&vw->vw_BlockS);
    else
	res = sym_nil;
    if(POSP(pos) && check_line(vw->vw_Tx, &VPOS(pos)))
    {
	switch(vw->vw_BlockStatus)
	{
	    case 0:
		set_block_refresh(vw);
		vw->vw_BlockS = VPOS(pos);
		order_block(vw);
		set_block_refresh(vw);
		break;
	    case 2:
		vw->vw_BlockS = VPOS(pos);
		vw->vw_BlockStatus = 0;
		order_block(vw);
		set_block_refresh(vw);
		break;
	    case -1:
		vw->vw_BlockStatus = 1;
		/* FALL THROUGH */
	    case 1:
		vw->vw_BlockS = VPOS(pos);
		break;
	}
    }
    return(res);
}

_PR VALUE cmd_block_end(VALUE pos);
DEFUN("block-end", cmd_block_end, subr_block_end, (VALUE pos), V_Subr1, DOC_block_end) /*
::doc:block_end::
block-end [POS]

Always returns the position of the block-end as it is, if POS is given
it is used as the new position of the end of the block.
::end:: */
{
    VW *vw = curr_vw;
    VALUE res;
    if(!vw->vw_BlockStatus || (vw->vw_BlockStatus == 2))
	res = make_lpos(&vw->vw_BlockE);
    else
	res = sym_nil;
    if(POSP(pos) && check_line(vw->vw_Tx, &VPOS(pos)))
    {
	switch(vw->vw_BlockStatus)
	{
	    case 0:
		set_block_refresh(vw);
		vw->vw_BlockE = VPOS(pos);
		order_block(vw);
		set_block_refresh(vw);
		break;
	    case 1:
		vw->vw_BlockE = VPOS(pos);
		vw->vw_BlockStatus = 0;
		order_block(vw);
		set_block_refresh(vw);
		break;
	    case -1:
		vw->vw_BlockStatus = 2;
		/* FALL THROUGH */
	    case 2:
		vw->vw_BlockE = VPOS(pos);
		break;
	}
    }
    return(res);
}

_PR VALUE cmd_block_kill(void);
DEFUN_INT("block-kill", cmd_block_kill, subr_block_kill, (void), V_Subr0, DOC_block_kill, "") /*
::doc:block_kill::
block-kill

Unmarks the block.
::end:: */
{
    VW *vw = curr_vw;
    if(vw->vw_BlockStatus == 0)
    {
	set_block_refresh(vw);
	vw->vw_BlockStatus = -1;
    }
    return(sym_t);
}

_PR VALUE cmd_blockp(void);
DEFUN("blockp", cmd_blockp, subr_blockp, (void), V_Subr0, DOC_blockp) /*
::doc:blockp::
blockp

Returns true if a block is currently marked.
::end:: */
{
    if(curr_vw->vw_BlockStatus == 0)
	return(sym_t);
    return(sym_nil);
}

_PR VALUE cmd_translate_area(VALUE vstart, VALUE vend, VALUE table, VALUE tx);
DEFUN("translate-area", cmd_translate_area, subr_translate_area, (VALUE vstart, VALUE vend, VALUE table, VALUE tx), V_Subr4, DOC_translate_area) /*
::doc:translate_area:
translate-area START-POS END-POS TRANSLATION-TABLE [BUFFER]

Applies the TRANSLATION-TABLE to the text between START-POS and END-POS.
TRANSLATION-TABLE is a string, each character represents the translation
for an ascii character of that characters position in the string. If the
string is less than 256 chars long any undefined characters will remain
unchanged.
::end:: */
{
    POS start, end;
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    DECLARE1(vstart, POSP);
    DECLARE2(vend, POSP);
    DECLARE3(table, STRINGP);
    start = VPOS(vstart);
    end = VPOS(vend);
    if(!read_only(VTX(tx)) && check_section(VTX(tx), &start, &end))
    {
	LINE *line = VTX(tx)->tx_Lines + start.pos_Line;
	int tablen = STRING_LEN(table);
	register u_char *str;
	undo_record_modification(VTX(tx), &start, &end);
	flag_modification(VTX(tx), &start, &end);
	while(start.pos_Line < end.pos_Line)
	{
	    int llen = line->ln_Strlen - 1;
	    str = line->ln_Line + start.pos_Col;
	    while(start.pos_Col++ < llen)
	    {
		register u_char c = *str;
		*str++ = (c < tablen) ? VSTR(table)[c] : c;
	    }
	    start.pos_Col = 0;
	    start.pos_Line++;
	    line++;
	}
	str = line->ln_Line + start.pos_Col;
	while(start.pos_Col++ < end.pos_Col)
	{
	    register u_char c = *str;
	    *str++ = (c < tablen) ? VSTR(table)[c] : c;
	}
	return(sym_t);
    }
    return(NULL);
}

_PR VALUE cmd_translate_string(VALUE string, VALUE table);
DEFUN("translate-string", cmd_translate_string, subr_translate_string, (VALUE string, VALUE table), V_Subr2, DOC_translate_string) /*
::doc:translate_string:
translate-string STRING TRANSLATION-TABLE

Applies the TRANSLATION-TABLE to each character in the string STRING.
TRANSLATION-TABLE is a string, each character represents the translation
for an ascii character of that characters position in the string. If the
string is less than 256 chars long any undefined characters will remain
unchanged.
Note that the STRING really is modified, no copy is made!
::end:: */
{
    int tablen, slen;
    register u_char *str;
    DECLARE1(string, STRINGP);
    DECLARE2(table, STRINGP);
    tablen = STRING_LEN(table);
    if(!STRING_WRITEABLE_P(string))
	return(signal_arg_error(string, 1));
    str = VSTR(string);
    slen = STRING_LEN(string);
    while(slen-- > 0)
    {
	register u_char c = *str;
	*str++ = (c < tablen) ? VSTR(table)[c] : c;
    }
    return(string);
}

_PR VALUE cmd_get_char(VALUE vpos, VALUE tx);
DEFUN("get-char", cmd_get_char, subr_get_char, (VALUE vpos, VALUE tx), V_Subr2, DOC_get_char) /*
::doc:get_char::
get-char [POS] [BUFFER]

Returns the numerical value of the character at position POS in BUFFER. If no
character exists at that position, nil is returned.
::end:: */
{
    POS pos;
    LINE *line;
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    if(POSP(vpos))
	pos = VPOS(vpos);
    else
	pos = *(get_tx_cursor(VTX(tx)));
    if(!check_line(VTX(tx), &pos))
	return(sym_nil);
    line = VTX(tx)->tx_Lines + pos.pos_Line;
    if(pos.pos_Col >= line->ln_Strlen)
	return(sym_nil);
    else if(pos.pos_Col == line->ln_Strlen - 1)
    {
	if(pos.pos_Line == VTX(tx)->tx_NumLines - 1)
	    return(sym_nil);
	else
	    return(make_number('\n'));
    }
    else
	return(make_number(line->ln_Line[pos.pos_Col]));
}

_PR VALUE cmd_set_char(VALUE ch, VALUE vpos, VALUE tx);
DEFUN_INT("set-char", cmd_set_char, subr_set_char, (VALUE ch, VALUE vpos, VALUE tx), V_Subr3, DOC_set_char, "cCharacter:") /*
::doc:set_char::
set-char CHARACTER [POS] [BUFFER]

Sets the character at position POS in BUFFER to CHARACTER.
::end:: */
{
    POS pos, end;
    DECLARE1(ch, CHARP);
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    if(POSP(vpos))
	pos = VPOS(vpos);
    else
	pos = *(get_tx_cursor(VTX(tx)));
    if(!check_line(VTX(tx), &pos))
	return(sym_nil);
    end.pos_Col = pos.pos_Col + 1;
    end.pos_Line = pos.pos_Line;
    if(pad_pos(VTX(tx), &end))
    {
	LINE *line = VTX(tx)->tx_Lines + pos.pos_Line;
	undo_record_modification(VTX(tx), &pos, &end);
	line->ln_Line[pos.pos_Col] = VCHAR(ch);
	flag_modification(VTX(tx), &pos, &end);
	return(ch);
    }
    return(NULL);
}

_PR VALUE cmd_alpha_char_p(VALUE ch);
DEFUN("alpha-char-p", cmd_alpha_char_p, subr_alpha_char_p, (VALUE ch), V_Subr1, DOC_alpha_char_p) /*
::doc:alpha_char_p::
alpha-char-p CHAR

Returns t if CHAR is an alphabetic character.
::end:: */
{
    if(CHARP(ch) && isalpha(VCHAR(ch)))
	return(sym_t);
    return(sym_nil);
}

_PR VALUE cmd_upper_case_p(VALUE ch);
DEFUN("upper-case-p", cmd_upper_case_p, subr_upper_case_p, (VALUE ch), V_Subr1, DOC_upper_case_p) /*
::doc:upper_case_p::
upper-case-p CHAR

Returns t if CHAR is upper case.
::end:: */
{
    if(CHARP(ch) && isupper(VCHAR(ch)))
	return(sym_t);
    return(sym_nil);
}

_PR VALUE cmd_lower_case_p(VALUE ch);
DEFUN("lower-case-p", cmd_lower_case_p, subr_lower_case_p, (VALUE ch), V_Subr1, DOC_lower_case_p) /*
::doc:lower_case_p::
lower-case-p CHAR

Returns t if CHAR is lower case.
::end:: */
{
    if(CHARP(ch) && islower(VCHAR(ch)))
	return(sym_t);
    return(sym_nil);
}

_PR VALUE cmd_digit_char_p(VALUE ch);
DEFUN("digit-char-p", cmd_digit_char_p, subr_digit_char_p, (VALUE ch), V_Subr1, DOC_digit_char_p) /*
::doc:digit_char_p::
digit-char-p CHAR

Returns t if CHAR is a digit.
::end:: */
{
    if(CHARP(ch) && isdigit(VCHAR(ch)))
	return(sym_t);
    return(sym_nil);
}

_PR VALUE cmd_alphanumericp(VALUE ch);
DEFUN("alphanumericp", cmd_alphanumericp, subr_alphanumericp, (VALUE ch), V_Subr1, DOC_alphanumericp) /*
::doc:alphanumericp::
alphanumericp CHAR

Returns t if CHAR is alpha-numeric.
::end:: */
{
    if(CHARP(ch) && isalnum(VCHAR(ch)))
	return(sym_t);
    return(sym_nil);
}

_PR VALUE cmd_space_char_p(VALUE ch);
DEFUN("space-char-p", cmd_space_char_p, subr_space_char_p, (VALUE ch), V_Subr1, DOC_space_char_p) /*
::doc:space_char_p::
space-char-p CHAR

Returns t if CHAR is whitespace.
::end:: */
{
    if(CHARP(ch) && isspace(VCHAR(ch)))
	return(sym_t);
    return(sym_nil);
}

_PR VALUE cmd_char_upcase(VALUE ch);
DEFUN("char-upcase", cmd_char_upcase, subr_char_upcase, (VALUE ch), V_Subr1, DOC_char_upcase) /*
::doc:char_upcase::
char-upcase CHAR

Returns the upper-case equivalent of CHAR.
::end:: */
{
    DECLARE1(ch, CHARP);
    return(make_number(toupper(VCHAR(ch))));
}

_PR VALUE cmd_char_downcase(VALUE ch);
DEFUN("char-downcase", cmd_char_downcase, subr_char_downcase, (VALUE ch), V_Subr1, DOC_char_downcase) /*
::doc:char_downcase::
char-downcase CHAR

Returns the lower-case equivalent of CHAR.
::end:: */
{
    DECLARE1(ch, CHARP);
    return(make_number(toupper(VCHAR(ch))));
}

_PR VALUE cmd_pos_line(VALUE pos);
DEFUN("pos-line", cmd_pos_line, subr_pos_line, (VALUE pos), V_Subr1, DOC_pos_line) /*
::doc:pos_line::
pos-line POS

Returns the line number which POS points to.
::end:: */
{
    DECLARE1(pos, POSP);
    return(make_number(VPOS(pos).pos_Line));
}

_PR VALUE cmd_pos_col(VALUE pos);
DEFUN("pos-col", cmd_pos_col, subr_pos_col, (VALUE pos), V_Subr1, DOC_pos_col) /*
::doc:pos_col::
pos-col POS

Return the column number which POS points to.
::end:: */
{
    DECLARE1(pos, POSP);
    return(make_number(VPOS(pos).pos_Col));
}

_PR VALUE cmd_set_pos_line(VALUE pos, VALUE line);
DEFUN("set-pos-line", cmd_set_pos_line, subr_set_pos_line, (VALUE pos, VALUE line), V_Subr2, DOC_set_pos_line) /*
::doc:set_pos_line::
set-pos-line POS LINE

Sets the line number of POS to LINE.
::end:: */
{
    DECLARE1(pos, POSP);
    DECLARE2(line, NUMBERP);
    VPOS(pos).pos_Line = VNUM(line);
    return(line);
}

_PR VALUE cmd_set_pos_col(VALUE pos, VALUE col);
DEFUN("set-pos-col", cmd_set_pos_col, subr_set_pos_col, (VALUE pos, VALUE col), V_Subr2, DOC_set_pos_col) /*
::doc:set_pos_col::
set-pos-col POS COL

Sets the column number of POS to COL.
::end:: */
{
    DECLARE1(pos, POSP);
    DECLARE2(col, NUMBERP);
    VPOS(pos).pos_Col = VNUM(col);
    return(col);
}

_PR VALUE cmd_posp(VALUE arg);
DEFUN("posp", cmd_posp, subr_posp, (VALUE arg), V_Subr1, DOC_posp) /*
::doc:posp::
posp ARG

Returns t if ARG is a position object.
::end:: */
{
    if(POSP(arg))
	return(sym_t);
    return(sym_nil);
}

_PR VALUE cmd_cursor_pos(void);
DEFUN("cursor-pos", cmd_cursor_pos, subr_cursor_pos, (void), V_Subr0, DOC_cursor_pos) /*
::doc:cursor_pos::
cursor-pos

Returns the position of the cursor in the current window.
::end:: */
{
    return(make_lpos(&curr_vw->vw_CursorPos));
}

_PR VALUE cmd_empty_line_p(VALUE lpos, VALUE tx);
DEFUN("empty-line-p", cmd_empty_line_p, subr_empty_line_p, (VALUE lpos, VALUE tx), V_Subr2, DOC_empty_line_p) /*
::doc:empty_line_p::
empty-line-p [POS] [BUFFER]

Returns t if the line pointer to by POS (or the cursor) in BUFFER is
empty, ie, blank or only containing spaces.
::end:: */
{
    VW *vw = curr_vw;
    POS pos;
    LINE *line;
    if(POSP(lpos))
	pos = VPOS(lpos);
    else
	pos = vw->vw_CursorPos;
    if(!BUFFERP(tx))
	tx = VAL(vw->vw_Tx);
    line = VTX(tx)->tx_Lines + pos.pos_Line;
    if(line->ln_Strlen == 1)
	return(sym_t);
    else
    {
	u_char *s = line->ln_Line;
	while(*s && isspace(*s))
	    s++;
	if(!(*s))
	    return(sym_t);
    }
    return(sym_nil);
}

_PR VALUE cmd_indent_pos(VALUE lpos, VALUE tx);
DEFUN("indent-pos", cmd_indent_pos, subr_indent_pos, (VALUE lpos, VALUE tx), V_Subr2, DOC_indent_pos) /*
::doc:indent_pos::
indent-pos [POS] [BUFFER]

Returns the glyph position of the first non-space character in the line
pointed to by POS (or the cursor), in BUFFER.
::end:: */
{
    VW *vw = curr_vw;
    POS pos;
    long len;
    u_char *line;
    if(!BUFFERP(tx))
	tx = VAL(vw->vw_Tx);
    if(POSP(lpos) && check_line(VTX(tx), &VPOS(lpos)))
	pos = VPOS(lpos);
    else
	pos = vw->vw_CursorPos;
    line = VTX(tx)->tx_Lines[pos.pos_Line].ln_Line;
    for(len = 0; *line && isspace(*line); len++, line++)
	;
    len = glyph_col(VTX(tx), len, pos.pos_Line);
    return(make_lpos2(len, pos.pos_Line));
}

_PR VALUE cmd_set_indent_pos(VALUE indpos, VALUE tx, VALUE spaces_p);
DEFUN("set-indent-pos", cmd_set_indent_pos, subr_set_indent_pos, (VALUE indpos, VALUE tx, VALUE spaces_p), V_Subr3, DOC_set_indent_pos) /*
::doc:set_indent_pos::
set-indent-pos POS [BUFFER] [ONLY-SPACES]

Sets the indentation of the line pointed to by POS to the column pointed
to by POS by putting the optimal sequence of TAB and SPC characters at the
start of the line.

If ONLY-SPACES in non-nil no tab characters are used.
::end:: */
{
    DECLARE1(indpos, POSP);
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    if((!read_only(VTX(tx))) && check_line(VTX(tx), &VPOS(indpos)))
    {
	LINE *line = VTX(tx)->tx_Lines + VPOS(indpos).pos_Line;
	u_char *s = line->ln_Line;
	POS pos = VPOS(indpos), end;
	long oldind, diff;
	long tabs, spaces;
	while(*s && isspace(*s))
	    s++;
	oldind = s - line->ln_Line;
	if(NILP(spaces_p))
	{
	    tabs = pos.pos_Col / VTX(tx)->tx_TabSize;
	    spaces = pos.pos_Col % VTX(tx)->tx_TabSize;
	}
	else
	{
	    tabs = 0;
	    spaces = pos.pos_Col;
	}
	diff = oldind - (tabs + spaces);
	pos.pos_Col = 0;
	end.pos_Col = diff;
	end.pos_Line = pos.pos_Line;
	if(diff > 0)
	{
	    undo_record_deletion(VTX(tx), &pos, &end);
	    delete_chars(VTX(tx), &pos, diff);
	    flag_deletion(VTX(tx), &pos, &end);
	    end.pos_Col = tabs + spaces;
	    undo_record_modification(VTX(tx), &pos, &end);
	    memset(line->ln_Line, '\t', tabs);
	    memset(line->ln_Line + tabs, ' ', spaces);
	    flag_modification(VTX(tx), &pos, &end);
	}
	else if(diff < 0)
	{
	    diff = -diff;
	    end.pos_Col = diff;
	    insert_gap(VTX(tx), diff, &pos);
	    undo_record_insertion(VTX(tx), &pos, &end);
	    flag_insertion(VTX(tx), &pos, &end);
	    pos.pos_Col = diff;
	    end.pos_Col = tabs + spaces;
	    memset(line->ln_Line, '\t', tabs);
	    memset(line->ln_Line + tabs, ' ', spaces);
	    undo_record_modification(VTX(tx), &pos, &end);
	    flag_modification(VTX(tx), &pos, &end);
	}
	else
	{
	    u_char *s = line->ln_Line;
	    long i;
	    end.pos_Col = tabs + spaces;
	    for(i = 0; i < tabs; i++)
	    {
		if(*s++ != '\t')
		{
		    undo_record_modification(VTX(tx), &pos, &end);
		    memset(line->ln_Line, '\t', tabs);
		    memset(line->ln_Line + tabs, ' ', spaces);
		    flag_modification(VTX(tx), &pos, &end);
		    return(indpos);
		}
	    }
	    for(i = 0; i < spaces; i++)
	    {
		if(*s++ != ' ')
		{
		    pos.pos_Col = tabs;
		    undo_record_modification(VTX(tx), &pos, &end);
		    memset(line->ln_Line + tabs, ' ', spaces);
		    flag_modification(VTX(tx), &pos, &end);
		    return(indpos);
		}
	    }
	    /* No modifications required. */
	}
	return(indpos);
    }
    return(sym_nil);
}

_PR VALUE cmd_indent_to(VALUE col, VALUE spaces_p);
DEFUN_INT("indent-to", cmd_indent_to, subr_indent_to, (VALUE col, VALUE spaces_p), V_Subr2, DOC_indent_to, "NIndent to column:") /*
::doc:indent_to::
indent-to COLUMN [ONLY-SPACES]

Inserts enough tabs and spaces to move the cursor to glyph column COLUMN.
If ONLY-SPACES is non-nil no tabs are used.
COLUMN counts from zero.
::end:: */
{
    VW *vw = curr_vw;
    TX *tx = vw->vw_Tx;
    DECLARE1(col, NUMBERP);
    if(!read_only(tx) && pad_cursor(vw))
    {
	int spaces, tabs;
	long curr_col, dest_col;
        calc_cursor_offset(vw);
        curr_col = vw->vw_LastCursorOffset;
        dest_col = VNUM(col);
        if(dest_col <= curr_col)
            return(sym_t);
	if(NILP(spaces_p))
	{
	    tabs = (dest_col / tx->tx_TabSize) - (curr_col / tx->tx_TabSize);
	    if(tabs == 0)
		spaces = dest_col - curr_col;
	    else
		spaces = dest_col - ((dest_col / tx->tx_TabSize) * tx->tx_TabSize);
	}
	else
	{
	    tabs = 0;
	    spaces = dest_col - curr_col;
	}
	if(spaces + tabs > 0)
	{
	    POS tmp = vw->vw_CursorPos;
	    if(insert_gap(tx, spaces + tabs, &tmp))
	    {
		u_char *line = tx->tx_Lines[tmp.pos_Line].ln_Line;
		memset(line + tmp.pos_Col, '\t', tabs);
		memset(line + tmp.pos_Col + tabs, ' ', spaces);
		undo_record_insertion(tx, &tmp, &vw->vw_CursorPos);
		flag_insertion(tx, &tmp, &vw->vw_CursorPos);
		return(col);
	    }
	}
	return(sym_nil);
    }
    return(NULL);
}
	    
_PR VALUE cmd_clear_buffer(VALUE tx);
DEFUN_INT("clear-buffer", cmd_clear_buffer, subr_clear_buffer, (VALUE tx), V_Subr1, DOC_clear_buffer, "") /*
::doc:clear_buffer::
clear-buffer [BUFFER]

Remove all text from BUFFER, leaving just one empty line.
::end:: */
{
    POS start, end;
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    start.pos_Col = start.pos_Line = 0;
    end.pos_Line = VTX(tx)->tx_NumLines - 1;
    end.pos_Col = VTX(tx)->tx_Lines[end.pos_Line].ln_Strlen - 1;
    undo_record_deletion(VTX(tx), &start, &end);
    if(clear_line_list(VTX(tx)))
    {
	reset_all_views(VTX(tx));
	return(tx);
    }
    return(sym_nil);
}

_PR VALUE cmd_pos_to_offset(VALUE vpos, VALUE tx);
DEFUN("pos-to-offset", cmd_pos_to_offset, subr_pos_to_offset, (VALUE vpos, VALUE tx), V_Subr2, DOC_pos_to_offset) /*
::doc:pos_to_offset::
pos-to-offset [POS] [BUFFER]

Returns the number of characters (counting from zero) that POS (or the cursor)
is from the beginning of the buffer.
::end:: */
{
    POS pos;
    LINE *line;
    long offset, line_num;
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    if(POSP(vpos))
	pos = VPOS(vpos);
    else
	pos = *get_tx_cursor(VTX(tx));
    check_pos(VTX(tx), &pos);
    line = VTX(tx)->tx_Lines;
    for(offset = line_num = 0; line_num < pos.pos_Line; line++, line_num++)
	offset += line->ln_Strlen; /* includes the theoretical '\n' */
    offset += pos.pos_Col;
    return(make_number(offset));
}

_PR VALUE cmd_offset_to_pos(VALUE voffset, VALUE tx);
DEFUN("offset-to-pos", cmd_offset_to_pos, subr_offset_to_pos, (VALUE voffset, VALUE tx), V_Subr2, DOC_offset_to_pos) /*
::doc:offset_to_pos::
offset-to-pos OFFSET [BUFFER]

Returns the position which is OFFSET characters from the start of the buffer.
::end:: */
{
    POS pos;
    long offset;
    LINE *line;
    DECLARE1(voffset, NUMBERP);
    offset = VNUM(voffset);
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    pos.pos_Col = 0;
    pos.pos_Line = 0;
    line = VTX(tx)->tx_Lines + pos.pos_Line;
    while(offset >= line->ln_Strlen)
    {
	offset -= line->ln_Strlen;
	pos.pos_Line++;
	line++;
    }
    pos.pos_Col = offset;
    return(make_lpos(&pos));
}

void
edit_init(void)
{
    int i;
    ADD_SUBR(subr_split_line);
    ADD_SUBR(subr_insert);
    ADD_SUBR(subr_delete_area);
    ADD_SUBR(subr_copy_area);
    ADD_SUBR(subr_cut_area);
    ADD_SUBR(subr_block_toggle);
    ADD_SUBR(subr_block_start);
    ADD_SUBR(subr_block_end);
    ADD_SUBR(subr_block_kill);
    ADD_SUBR(subr_blockp);
    ADD_SUBR(subr_translate_area);
    ADD_SUBR(subr_translate_string);
    ADD_SUBR(subr_get_char);
    ADD_SUBR(subr_set_char);
    ADD_SUBR(subr_alpha_char_p);
    ADD_SUBR(subr_upper_case_p);
    ADD_SUBR(subr_lower_case_p);
    ADD_SUBR(subr_digit_char_p);
    ADD_SUBR(subr_alphanumericp);
    ADD_SUBR(subr_space_char_p);
    ADD_SUBR(subr_char_upcase);
    ADD_SUBR(subr_char_downcase);
    ADD_SUBR(subr_pos_line);
    ADD_SUBR(subr_pos_col);
    ADD_SUBR(subr_set_pos_line);
    ADD_SUBR(subr_set_pos_col);
    ADD_SUBR(subr_posp);
    ADD_SUBR(subr_cursor_pos);
    ADD_SUBR(subr_empty_line_p);
    ADD_SUBR(subr_indent_pos);
    ADD_SUBR(subr_set_indent_pos);
    ADD_SUBR(subr_indent_to);
    ADD_SUBR(subr_clear_buffer);
    ADD_SUBR(subr_offset_to_pos);
    ADD_SUBR(subr_pos_to_offset);

    INTERN(sym_upcase_table, "upcase-table");
    DOC_VAR(sym_upcase_table, DOC_upcase_table);
    VSYM(sym_upcase_table)->sym_Value = make_string(257);
    INTERN(sym_downcase_table, "downcase-table");
    DOC_VAR(sym_downcase_table, DOC_downcase_table);
    VSYM(sym_downcase_table)->sym_Value = make_string(257);
    for(i = 0; i < 256; i++)
    {
	VSTR(VSYM(sym_upcase_table)->sym_Value)[i] = toupper(i);
	VSTR(VSYM(sym_downcase_table)->sym_Value)[i] = tolower(i);
    }
    VSTR(VSYM(sym_upcase_table)->sym_Value)[256] = 0;
    VSTR(VSYM(sym_downcase_table)->sym_Value)[256] = 0;

    INTERN(sym_inhibit_read_only, "inhibit-read-only");
    DOC_VAR(sym_inhibit_read_only, DOC_inhibit_read_only);
}
