/* $Id$ */
/**
* edit.c, 用來提供 bbs上的文字編輯器, 即 ve.
* 現在這一個是惡搞過的版本, 比較不穩定, 用比較多的 cpu, 但是可以省下許多
* 的記憶體 (以 Ptt為例, 在九千人上站的時候, 約可省下 50MB 的記憶體)
* 如果您認為「拿 cpu換記憶體」並不合乎您的須求, 您可以考慮改使用修正前的
* 版本 (Revision 782)
*
* 原本 ve 的做法是, 因為每一行最大可以輸入 WRAPMARGIN 個字, 於是就替每一
* 行保留了 WRAPMARGIN 這麼大的空間 (約 512 bytes) . 但是實際上, 站在修正
* 成本最小的考量上, 我們只須要使得游標所在這一行維持 WRAPMARGIN 這麼大,
* 其他每一行其實不須要這麼多的空間. 於是這個 patch就在每次游標在行間移動
* 的時候, 將原本的那行記憶體縮小, 再將新移到的那行重新加大, 以達成最小的
* 記憶體用量.
* 以上說的這個動作在 adjustline() 中完成, adjustline()另外包括修正數個
* global pointer, 以避免 dangling pointer .
* 另外若定義 DEBUG, 在 textline_t 結構中將加入 mlength, 表示該行實際佔的
* 記憶體大小. 以方便測試結果.
* 這個版本似乎還有地方沒有修正好, 可能導致 segmentation fault .
*
* FIXME 在區塊標記模式(blockln>=0)中對增刪修改可能會造成 blockln, blockpnt,
* and/or blockline 錯誤. 甚至把 blockline 砍掉會 access 到已被 free 掉的
* memory. 可能要改成標記模式 readonly, 或是做某些動作時自動取消標記模式
* (blockln=-1)
*
* FIXME 20071201 piaip
* block selection 不知何時已變為 line level 而非 character level 了,
* 這樣也比較好寫,所以把 blockpnt 拿掉吧!
*
* 20071230 piaip
* BBSmovie 有人作出了 1.9G 的檔案, 看來要分 hard limit 跟 soft limit
* [第 7426572/7426572 頁 (100%) 目前顯示: 第 163384551~163384573 行]
* 當日調查 BBSmovie 看板與精華區,平均檔案皆在 5M 以下
* 最大的為 16M 的 Haruhi OP (avi 轉檔 with massive ANSI)
* [第 2953/2953 頁 (100%) 目前顯示: 第 64942~64964 行]
* 另外互動迷宮的大小為
* [第 1408/1408 頁 (100%) 目前顯示: 第 30940~30962 行]
* 是以定義:
* 32M 為 size limit
* 1M 為 line limit
* 又,忽然發現之前 totaln 之類都是 short... 所以 65536 就夠了?
* 後註: 似乎是用 announce 的 append 作出來的,有看到 > --- <- mark。
*/
#include "bbs.h"
#define EDIT_SIZE_LIMIT (32768*1024)
#define EDIT_LINE_LIMIT (65530) // (1048576)
#if 0
#define register
#define DEBUG
#define inline
#endif
/**
* data 欄位的用法:
* 每次 allocate 一個 textline_t 時,會配給他 (sizeof(textline_t) + string
* length - 1) 的大小。如此可直接存取 data 而不需額外的 malloc。
*/
typedef struct textline_t {
struct textline_t *prev;
struct textline_t *next;
short len;
#ifdef DEBUG
short mlength;
#endif
char data[1];
} textline_t;
#define KEEP_EDITING -2
enum {
NOBODY, MANAGER, SYSOP
};
/**
* 這個說明會將整個 edit.c 運作的概念帶過,主要會從 editor_internal_t 的
* data structure 談起。對於每一個 data member 的詳細功能,請見 sturcture
* 中的註解。
*
* 文章的內容 (以下稱 content) 以「行」為單位,主要以 firstline, lastline,
* totaln 來記錄。
*
* User 在畫面中看到的畫面,置於一個「 window 」中,這個 window 會在
* content 上移動,window 裡面的範圍即為要 show 出來的範圍。它用了不少
* 欄位來記錄,包括 currline, top_of_win, currln, currpnt, curr_window_line,
* edit_margin。顯示出來的效果當然不只是靠這幾個資料,還會跟其他欄位有交互
* 作用,例如 彩色編輯模式、特殊符號編輯 等等。其中最複雜的部分是在選取 block
* (見後)的時候。比較不直覺的行為是:除非游標在開始選取跟目前(結束)的位置
* 是同一個(此時這個範圍是選取的範圍),否則就是從開始那一列一直到目前(結束)
* 這一列。
*
* editor 的使用上目前有五種 inclusive 的 mode:
* insert mode:
* 插入/取代
* ansi mode:
* 彩色編輯
* indent mode:
* 自動縮排
* phone mode:
* 特殊符號編輯
* raw mode:
* ignore Ctrl('S'), Ctrl('Q'), Ctrl('T')
* 贊曰: 這有什麼用? 看起來是 modem 上傳用 (沒人在用這個了吧)
* 拿來當 dbcs option 吧
*
* editor 支援了區塊選擇的功能(多行選取 或 單行中的片段),對於一個 selected
* block,可以 cut, copy, cancel, 或者存到暫取檔,甚至是往左/右 shift。詳見
* block_XXX。
*
* 用 Ctrl('Y') 刪除的那一行會被記到 deleted_line 這個欄位中。undelete_line()
* 可以做 undelete 的動作。 deleted_line 初始值為 NULL,每次只允許存一行,所以
* 在存下來時,發現值不為 NULL 會先做 free 的動作。editor 結束時,在
* edit_buffer_destructor() 中如果發現有 deleted_line,會在這邊釋放掉。其他地
* 方也有相同的作法,例如 searched_string。searched_string 是在搜尋文章內容
* 時,要尋找的 key word。
*
* 還有一個有趣的特點,「括號匹配」!行為就如同在 vim 裡面一樣。呃,當然沒那
* 麼強啦,但至少在含有 c-style comment 跟 c-style string 時是對的喔。這個動
* 作定義於 match_paren() 中。
*
* 另外,如果有需要新增新的欄位,請將初始化(有需要的話)的動作寫在
* edit_buffer_constructor 中。當然也有個 edit_buffer_destructor 可以使用。
*
* 此外,為了提供一個 reentrant 的 editor,prev 指向前一個 editor 的
* editor_internal_t。enter_edit_buffer 跟 exit_edit_buffer 提供進出的介面,
* 裡面分別會呼叫 constructor 跟 destructor。
*
* TODO
* vedit 裡面有個 curr_buf->oldcurrline,用來記上一次的 currline。由於只有 currline 擁
* 有 WRAPMARGIN 的空間,所以目前的作法是當 curr_buf->oldcurrline != currline 時,就
* resize curr_buf->oldcurrline 跟 currline。但是糟糕的是目前必須人工追蹤 currline 的行
* 為,而且若不幸遇到 curr_buf->oldcurrline 指到的那一行已經被 free 掉,就完了。最好是
* 把這些東西包起來。不過我沒空做了,patch is welcome :P
*
* Victor Hsieh <victor@csie.org>
* Thu, 03 Feb 2005 15:18:00 +0800
*/
typedef struct editor_internal_t {
textline_t *firstline; /* first line of the article. */
textline_t *lastline; /* last line of the article. */
textline_t *currline; /* current line of the article(window). */
textline_t *blockline; /* the first selected line of the block. */
textline_t *top_of_win; /* top line of the article in the window. */
textline_t *deleted_line; /* deleted line. Just keep one deleted line. */
textline_t *oldcurrline;
int currln; /* current line of the article. */
short currpnt; /* current column of the article. */
int totaln; /* total lines of the article. */
int curr_window_line; /* current line to the window. */
short last_margin;
short edit_margin; /* when the cursor moves out of range (say,
t_columns), shift this length of the string
so you won't see the first edit_margin-th
character. */
short lastindent;
int blockln; /* the row you started to select block. */
char insert_c; /* insert this character when shift something
in order to compensate the new space. */
char last_phone_mode;
char ifuseanony :1;
char redraw_everything :1;
char insert_mode :1;
char ansimode :1;
char indent_mode :1;
char phone_mode :1;
char raw_mode :1;
char *searched_string;
char *sitesig_string;
char *(*substr_fp) ();
struct editor_internal_t *prev;
} editor_internal_t;
// } __attribute__ ((packed))
static editor_internal_t *curr_buf = NULL;
static const char fp_bak[] = "bak";
static const char * const BIG5[13] = {
",;:、﹑。?!‧﹗()〝〞‵′",
"▁▂▃▄▅▆▇█▏▎▍▌▋▊▉▓ ",
"○⊙◎●☆★□■▼▲▽△◇◆♀♂",
"﹌﹏\︴¯_—∥∣▕/\╳╱╲∕﹨",
"+-×÷√±=≡≠≒≦≧<>∵∴",
"∞~∩∪∫∮&⊥∠∟⊿﹢﹣﹤﹥﹦",
"↑↓←→↖↗↙↘",
"【】「」『』〈〉《》〔〕{}︵︶",
"︹︺︷︸︻︼︿﹀︽︾﹁﹂﹃﹄",
"◢◣◥◤﹡*※§@⊕㊣…‥﹉﹍",
"α\βγδεζηθικλμνξοπ",
"ρστυφχψωΔΘΛΠΣΦΨΩ",
"ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ"
};
static const char * const BIG_mode[13] = {
"標點",
"圖塊",
"標記",
"標線",
"數一",
"數二",
"箭頭",
"括一",
"括二",
"其他",
"希一",
"希二",
"數字"
};
static const char *table[8] = {
"│─└┴┘├┼┤┌┬┐",
"║═╚╩╝╠╬╣╔╦╗",
"║─╙╨╜╟╫╢╓╥╖",
"│═╘╧╛╞╪╡╒╤╕",
"│─╰┴╯├┼┤╭┬╮",
"║═╰╩╯╠╬╣╭╦╮",
"║─╙╨╜╟╫╢╓╥╖",
"│═╘╧╛╞╪╡╒╤╕"
};
static const char *table_mode[6] = {
"直角",
"彎弧",
"┼",
"╬",
"╫",
"╪"
};
#ifdef DBCSAWARE
static char mbcs_mode =1;
#define IS_BIG5_HI(x) (0x81 <= (x) && (x) <= 0xfe)
#define IS_BIG5_LOS(x) (0x40 <= (x) && (x) <= 0x7e)
#define IS_BIG5_LOE(x) (0x80 <= (x) && (x) <= 0xfe)
#define IS_BIG5_LO(x) (IS_BIG5_LOS(x) || IS_BIG5_LOE(x))
#define IS_BIG5(hi,lo) (IS_BIG5_HI(hi) && IS_BIG5_LO(lo))
int mchar_len(unsigned char *str)
{
return ((str[0] != '\0' && str[1] != '\0' && IS_BIG5(str[0], str[1])) ?
2 :
1);
}
#define FC_RIGHT (0)
#define FC_LEFT (~FC_RIGHT)
int fix_cursor(char *str, int pos, unsigned int dir)
{
int newpos, w;
for(newpos = 0;
*str != '\0' &&
(w = mchar_len((unsigned char*)str),
newpos + 1 + (dir & (w - 1))) <= pos;
str += w, newpos += w)
;
return newpos;
}
#endif
/* 記憶體管理與編輯處理 */
static void
indigestion(int i)
{
vmsgf("嚴重內傷 (%d)\n", i);
u_exit("EDITOR FAILED");
assert(0);
exit(0);
}
static inline void
edit_buffer_constructor(editor_internal_t *buf)
{
/* all unspecified columns are 0 */
buf->blockln = -1;
buf->insert_c = ' ';
buf->insert_mode = 1;
buf->redraw_everything = 1;
buf->lastindent = -1;
}
static inline void
enter_edit_buffer(void)
{
editor_internal_t *p = curr_buf;
curr_buf = (editor_internal_t *)malloc(sizeof(editor_internal_t));
memset(curr_buf, 0, sizeof(editor_internal_t));
curr_buf->prev = p;
edit_buffer_constructor(curr_buf);
}
static inline void
free_line(textline_t *p)
{
p->next = (textline_t*)0x12345678;
p->prev = (textline_t*)0x87654321;
p->len = -12345;
free(p);
}
static inline void
edit_buffer_destructor(void)
{
if (curr_buf->deleted_line != NULL)
free_line(curr_buf->deleted_line);
if (curr_buf->searched_string != NULL)
free(curr_buf->searched_string);
if (curr_buf->sitesig_string != NULL)
free(curr_buf->sitesig_string);
}
static inline void
exit_edit_buffer(void)
{
editor_internal_t *p = curr_buf;
edit_buffer_destructor();
curr_buf = p->prev;
free(p);
}
/**
* transform position ansix in an ansi string of textline_t to the same
* string without escape code.
* @return position in the string without escape code.
*/
static int
ansi2n(int ansix, textline_t * line)
{
register char *data, *tmp;
register char ch;
data = tmp = line->data;
while (*tmp) {
if (*tmp == KEY_ESC) {
while ((ch = *tmp) && !isalpha((int)ch))
tmp++;
if (ch)
tmp++;
continue;
}
if (ansix <= 0)
break;
tmp++;
ansix--;
}
return tmp - data;
}
/**
* opposite to ansi2n, according to given textline_t.
* @return position in the string with escape code.
*/
static short
n2ansi(short nx, textline_t * line)
{
register short ansix = 0;
register char *tmp, *nxp;
register char ch;
tmp = nxp = line->data;
nxp += nx;
while (*tmp) {
if (*tmp == KEY_ESC) {
while ((ch = *tmp) && !isalpha((int)ch))
tmp++;
if (ch)
tmp++;
continue;
}
if (tmp >= nxp)
break;
tmp++;
ansix++;
}
return ansix;
}
/* 螢幕處理:輔助訊息、顯示編輯內容 */
static inline void
show_phone_mode_panel(void)
{
int i;
move(b_lines - 1, 0);
clrtoeol();
if (curr_buf->last_phone_mode < 20) {
int len;
prints(ANSI_COLOR(1;46) "【%s輸入】 ", BIG_mode[curr_buf->last_phone_mode - 1]);
len = strlen(BIG5[curr_buf->last_phone_mode - 1]) / 2;
for (i = 0; i < len; i++)
prints(ANSI_COLOR(37) "%c" ANSI_COLOR(34) "%2.2s",
i + 'A', BIG5[curr_buf->last_phone_mode - 1] + i * 2);
for (i = 0; i < 16 - len; i++)
outs(" ");
outs(ANSI_COLOR(37) " `1~9-=切換 Z表格" ANSI_RESET);
}
else {
prints(ANSI_COLOR(1;46) "【表格繪製】 /=%s *=%s形 ",
table_mode[(curr_buf->last_phone_mode - 20) / 4],
table_mode[(curr_buf->last_phone_mode - 20) % 4 + 2]);
for (i = 0;i < 11;i++)
prints(ANSI_COLOR(37) "%c" ANSI_COLOR(34) "%2.2s", i ? i + '/' : '.',
table[curr_buf->last_phone_mode - 20] + i * 2);
outs(ANSI_COLOR(37) " Z內碼 " ANSI_RESET);
}
}
/**
* Show the bottom status/help bar, and BIG5/table in phone_mode.
*/
static void
edit_msg(void)
{
int n = curr_buf->currpnt;
if (curr_buf->ansimode) /* Thor: 作 ansi 編輯 */
n = n2ansi(n, curr_buf->currline);
if (curr_buf->phone_mode)
show_phone_mode_panel();
move(b_lines, 0);
clrtoeol();
outs( ANSI_COLOR(37;44) " 編輯文章 "
ANSI_COLOR(31;47) " (^Z/F1)" ANSI_COLOR(30) "說明 "
ANSI_COLOR(31;47) "(^P/^G)" ANSI_COLOR(30) "插入符號/圖片 "
ANSI_COLOR(31) "(^X/^Q)" ANSI_COLOR(30) "離開");
prints( "║%s│%c%c%c%c║ %3d:%3d ",
curr_buf->insert_mode ? "插入" : "取代",
curr_buf->ansimode ? 'A' : 'a',
curr_buf->indent_mode ? 'I' : 'i',
curr_buf->phone_mode ? 'P' : 'p',
curr_buf->raw_mode ? 'R' : 'r',
curr_buf->currln + 1, n + 1);
outslr("", 78, ANSI_RESET, 0);
}
/**
* return the middle line of the window.
*/
static inline int
middle_line(void)
{
return p_lines / 2 + 1;
}
/**
* Return the previous 'num' line. Stop at the first line if there's
* not enough lines.
*/
static textline_t *
back_line(textline_t * pos, int num)
{
while (num-- > 0) {
register textline_t *item;
if (pos && (item = pos->prev)) {
pos = item;
curr_buf->currln--;
}
else
break;
}
return pos;
}
/* calculate if cursor is at bottom, scroll required?
* currently vedit does NOT handle if curr_window_line > b_lines,
* take care if you changed curr_window_line!
*/
static inline int
cursor_at_bottom_line(void)
{
return curr_buf->curr_window_line == b_lines ||
(curr_buf->phone_mode && curr_buf->curr_window_line == b_lines - 1);
}
/**
* Return the next 'num' line. Stop at the last line if there's not
* enough lines.
*/
static textline_t *
forward_line(textline_t * pos, int num)
{
while (num-- > 0) {
register textline_t *item;
if (pos && (item = pos->next)) {
pos = item;
curr_buf->currln++;
}
else
break;
}
return pos;
}
/**
* move the cursor to the next line with ansimode fixed.
*/
static inline void
cursor_to_next_line(void)
{
short pos;
if (curr_buf->currline->next == NULL)
return;
curr_buf->currline = curr_buf->currline->next;
curr_buf->curr_window_line++;
curr_buf->currln++;
if (curr_buf->ansimode) {
pos = n2ansi(curr_buf->currpnt, curr_buf->currline->prev);
curr_buf->currpnt = ansi2n(pos, curr_buf->currline);
}
else {
curr_buf->currpnt = (curr_buf->currline->len > curr_buf->lastindent)
? curr_buf->lastindent : curr_buf->currline->len;
}
}
/**
* opposite to cursor_to_next_line.
*/
static inline void
cursor_to_prev_line(void)
{
short pos;
if (curr_buf->currline->prev == NULL)
return;
curr_buf->curr_window_line--;
curr_buf->currln--;
curr_buf->currline = curr_buf->currline->prev;
if (curr_buf->ansimode) {
pos = n2ansi(curr_buf->currpnt, curr_buf->currline->next);
curr_buf->currpnt = ansi2n(pos, curr_buf->currline);
}
else {
curr_buf->currpnt = (curr_buf->currline->len > curr_buf->lastindent)
? curr_buf->lastindent : curr_buf->currline->len;
}
}
static inline void
window_scroll_down(void)
{
curr_buf->curr_window_line = 0;
if (!curr_buf->top_of_win->prev)
indigestion(6);
else {
curr_buf->top_of_win = curr_buf->top_of_win->prev;
rscroll();
}
}
static inline void
window_scroll_up(void)
{
curr_buf->curr_window_line = b_lines - (curr_buf->phone_mode ? 2 : 1);
if (unlikely(!curr_buf->top_of_win->next))
indigestion(7);
else {
curr_buf->top_of_win = curr_buf->top_of_win->next;
if(curr_buf->phone_mode)
move(b_lines-1, 0);
else
move(b_lines, 0);
clrtoeol();
scroll();
}
}
/**
* Get the current line number in the window now.
*/
static int
get_lineno_in_window(void)
{
int cnt = 0;
textline_t *p = curr_buf->currline;
while (p && (p != curr_buf->top_of_win)) {
cnt++;
p = p->prev;
}
return cnt;
}
/**
* shift given raw data s with length len to left by one byte.
*/
static void
raw_shift_left(char *s, int len)
{
int i;
for (i = 0; i < len && s[i] != 0; ++i)
s[i] = s[i + 1];
}
/**
* shift given raw data s with length len to right by one byte.
*/
static void
raw_shift_right(char *s, int len)
{
int i;
for (i = len - 1; i >= 0; --i)
s[i + 1] = s[i];
}
/**
* Return the pointer to the next non-space position.
*/
static char *
next_non_space_char(char *s)
{
while (*s == ' ')
s++;
return s;
}
/**
* allocate a textline_t with length length.
*/
static textline_t *
alloc_line(short length)
{
textline_t *p;
if ((p = (textline_t *) malloc(length + sizeof(textline_t)))) {
memset(p, 0, length + sizeof(textline_t));
#ifdef DEBUG
p->mlength = length;
#endif
return p;
}
indigestion(13);
abort_bbs(0);
return NULL;
}
/**
* Insert p after line in list. Keeps up with last line
*/
static void
insert_line(textline_t *line, textline_t *p)
{
textline_t *n;
if ((p->next = n = line->next))
n->prev = p;
else
curr_buf->lastline = p;
line->next = p;
p->prev = line;
}
/**
* delete_line deletes 'line' from the line list.
* @param saved true if you want to keep the line in deleted_line
*/
static void
delete_line(textline_t * line, int saved)
{
register textline_t *p = line->prev;
register textline_t *n = line->next;
if (!p && !n) {
line->data[0] = line->len = 0;
return;
}
assert(line != curr_buf->top_of_win);
if (n)
n->prev = p;
else
curr_buf->lastline = p;
if (p)
p->next = n;
else
curr_buf->firstline = n;
curr_buf->totaln--;
if (saved) {
if (curr_buf->deleted_line != NULL)
free_line(curr_buf->deleted_line);
curr_buf->deleted_line = line;
curr_buf->deleted_line->next = NULL;
curr_buf->deleted_line->prev = NULL;
}
else {
free_line(line);
}
}
/**
* Return the indent space number according to CURRENT line and the FORMER
* line. It'll be the first line contains non-space character.
* @return space number from the beginning to the first non-space character,
* return 0 if non or not in indent mode.
*/
static int
indent_space(void)
{
textline_t *p;
int spcs;
if (!curr_buf->indent_mode)
return 0;
for (p = curr_buf->currline; p; p = p->prev) {
for (spcs = 0; p->data[spcs] == ' '; ++spcs);
/* empty loop */
if (p->data[spcs])
return spcs;
}
return 0;
}
/**
* adjustline(oldp, len);
* 用來將 oldp 指到的那一行, 重新修正成 len這麼長.
*
* 呼叫了 adjustline 後記得檢查有動到 currline, 如果是的話 oldcurrline 也要動
*
* In FreeBSD:
* 在這邊一共做了兩次的 memcpy() , 第一次從 heap 拷到 stack ,
* 把原來記憶體 free() 後, 又重新在 stack上 malloc() 一次,
* 然後再拷貝回來.
* 主要是用 sbrk() 觀察到的結果, 這樣子才真的能縮減記憶體用量.
* 詳見 /usr/share/doc/papers/malloc.ascii.gz (in FreeBSD)
*/
static textline_t *
adjustline(textline_t *oldp, short len)
{
// XXX write a generic version ?
char tmpl[sizeof(textline_t) + WRAPMARGIN];
textline_t *newp;
#ifdef deBUG
if(oldp->len > WRAPMARGIN || oldp->len < 0) {
kill(currpid, SIGSEGV);
}
#endif
memcpy(tmpl, oldp, oldp->len + sizeof(textline_t));
free_line(oldp);
newp = alloc_line(len);
memcpy(newp, tmpl, len + sizeof(textline_t));
#ifdef DEBUG
newp->mlength = len;
#endif
if( oldp == curr_buf->firstline ) curr_buf->firstline = newp;
if( oldp == curr_buf->lastline ) curr_buf->lastline = newp;
if( oldp == curr_buf->currline ) curr_buf->currline = newp;
if( oldp == curr_buf->blockline ) curr_buf->blockline = newp;
if( oldp == curr_buf->top_of_win) curr_buf->top_of_win= newp;
if( newp->prev != NULL ) newp->prev->next = newp;
if( newp->next != NULL ) newp->next->prev = newp;
// vmsg("adjust %x to %x, length: %d", (int)oldp, (int)newp, len);
return newp;
}
/**
* split 'line' right before the character pos
*
* @return the latter line after splitting
*/
static textline_t *
split(textline_t * line, int pos)
{
if (pos <= line->len) {
register textline_t *p = alloc_line(WRAPMARGIN);
register char *ptr;
int spcs = indent_space();
curr_buf->totaln++;
p->len = line->len - pos + spcs;
line->len = pos;
memset(p->data, ' ', spcs);
p->data[spcs] = 0;
ptr = line->data + pos;
if (curr_buf->indent_mode)
ptr = next_non_space_char(ptr);
strcat(p->data + spcs, ptr);
ptr[0] = '\0';
if (line == curr_buf->currline && pos <= curr_buf->currpnt) {
line = adjustline(line, line->len);
insert_line(line, p);
// because p is allocated with fullsize, we can skip adjust.
// curr_buf->oldcurrline = line;
curr_buf->oldcurrline = curr_buf->currline = p;
if (pos == curr_buf->currpnt)
curr_buf->currpnt = spcs;
else
curr_buf->currpnt -= pos;
curr_buf->curr_window_line++;
curr_buf->currln++;
/* split may cause cursor hit bottom */
if (cursor_at_bottom_line())
window_scroll_up();
} else {
p = adjustline(p, p->len);
insert_line(line, p);
}
curr_buf->redraw_everything = YEA;
}
return line;
}
/**
* Insert a character ch to current line.
*
* The line will be split if the length is >= WRAPMARGIN. It'll be split
* from the last space if any, or start a new line after the last character.
*/
static void
insert_char(int ch)
{
register textline_t *p = curr_buf->currline;
register int i = p->len;
register char *s;
int wordwrap = YEA;
if (curr_buf->currpnt > i) {
indigestion(1);
return;
}
if (curr_buf->currpnt < i && !curr_buf->insert_mode) {
p->data[curr_buf->currpnt++] = ch;
/* Thor: ansi 編輯, 可以overwrite, 不蓋到 ansi code */
if (curr_buf->ansimode)
curr_buf->currpnt = ansi2n(n2ansi(curr_buf->currpnt, p), p);
} else {
raw_shift_right(p->data + curr_buf->currpnt, i - curr_buf->currpnt + 1);
p->data[curr_buf->currpnt++] = ch;
i = ++(p->len);
}
if (i < WRAPMARGIN)
return;
s = p->data + (i - 1);
while (s != p->data && *s == ' ')
s--;
while (s != p->data && *s != ' ')
s--;
if (s == p->data) {
wordwrap = NA;
s = p->data + (i - 2);
}
p = split(p, (s - p->data) + 1);
p = p->next;
i = p->len;
if (wordwrap && i >= 1) {
if (p->data[i - 1] != ' ') {
p->data[i] = ' ';
p->data[i + 1] = '\0';
p->len++;
}
}
}
/**
* insert_char twice.
*/
static void
insert_dchar(const char *dchar)
{
insert_char(*dchar);
insert_char(*(dchar+1));
}
static void
insert_tab(void)
{
do {
insert_char(' ');
} while (curr_buf->currpnt & 0x7);
}
/**
* Insert a string.
*
* All printable and ESC_CHR will be directly printed out.
* '\t' will be printed to align every 8 byte.
* '\n' will split the line.
* The other character will be ignore.
*/
static void
insert_string(const char *str)
{
char ch;
while ((ch = *str++)) {
if (isprint2(ch) || ch == ESC_CHR)
insert_char(ch);
else if (ch == '\t')
insert_tab();
else if (ch == '\n')
split(curr_buf->currline, curr_buf->currpnt);
}
}
/**
* undelete the deleted line.
*
* return NULL if there's no deleted_line, otherwise, return currline.
*/
static textline_t *
undelete_line(void)
{
editor_internal_t tmp;
if (!curr_buf->deleted_line)
return NULL;
tmp.top_of_win = curr_buf->top_of_win;
tmp.indent_mode = curr_buf->indent_mode;
tmp.curr_window_line = curr_buf->curr_window_line;
curr_buf->indent_mode = 0;
curr_buf->currpnt = 0;
curr_buf->currln++;
insert_string(curr_buf->deleted_line->data);
insert_string("\n");
curr_buf->top_of_win = tmp.top_of_win;
curr_buf->indent_mode = tmp.indent_mode;
curr_buf->curr_window_line = tmp.curr_window_line;
assert(curr_buf->currline->prev);
curr_buf->currline = adjustline(curr_buf->currline, curr_buf->currline->len);
curr_buf->currline = curr_buf->currline->prev;
curr_buf->currline = adjustline(curr_buf->currline, WRAPMARGIN);
curr_buf->oldcurrline = curr_buf->currline;
if (curr_buf->currline->prev == NULL) {
curr_buf->top_of_win = curr_buf->currline;
curr_buf->currln = 0;
}
return curr_buf->currline;
}
/*
* join $line and $line->next
*
* line: A1 A2
* next: B1 B2
* ....: C1 C2
*
* case B=empty:
* return YEA
*
* case A+B < WRAPMARGIN:
* line: A1 A2 B1 B2
* next: C1 C2
* return YEA
* NOTE It assumes $line has allocated WRAPMARGIN length of data buffer.
*
* case A+B1+B2 > WRAPMARGIN, A+B1<WRAPMARGIN
* line: A1 A2 B1
* next: B2 " "
* call join($next)
*/
static int
join(textline_t * line)
{
register textline_t *n;
register int ovfl;
if (!(n = line->next))
return YEA;
if (!*next_non_space_char(n->data))
return YEA;
ovfl = line->len + n->len - WRAPMARGIN;
if (ovfl < 0) {
strcat(line->data, n->data);
line->len += n->len;
delete_line(n, 0);
return YEA;
} else {
register char *s; /* the split point */
s = n->data + n->len - ovfl - 1;
while (s != n->data && *s == ' ')
s--;
while (s != n->data && *s != ' ')
s--;
if (s == n->data)
return YEA;
split(n, (s - n->data) + 1);
if (line->len + line->next->len >= WRAPMARGIN) {
indigestion(0);
return YEA;
}
join(line);
n = line->next;
ovfl = n->len - 1;
if (ovfl >= 0 && ovfl < WRAPMARGIN - 2) {
s = &(n->data[ovfl]);
if (*s != ' ') {
strcpy(s, " ");
n->len++;
}
}
line->next=adjustline(line->next, WRAPMARGIN);
join(line->next);
line->next=adjustline(line->next, line->next->len);
return NA;
}
}
static void
delete_char(void)
{
register int len;
if ((len = curr_buf->currline->len)) {
if (unlikely(curr_buf->currpnt >= len)) {
indigestion(1);
return;
}
raw_shift_left(curr_buf->currline->data + curr_buf->currpnt, curr_buf->currline->len - curr_buf->currpnt + 1);
curr_buf->currline->len--;
}
}
static void
load_file(FILE * fp, off_t offSig)
{
char buf[WRAPMARGIN + 2];
int indent_mode0 = curr_buf->indent_mode;
size_t szread = 0;
assert(fp);
curr_buf->indent_mode = 0;
while (fgets(buf, sizeof(buf), fp))
{
szread += strlen(buf);
if (offSig < 0 || szread <= offSig)
{
insert_string(buf);
}
else
{
// this is the site sig
break;
}
}
curr_buf->indent_mode = indent_mode0;
}
/* 暫存檔 */
char *
ask_tmpbuf(int y)
{
static char fp_buf[10] = "buf.0";
static char msg[] = "請選擇暫存檔 (0-9)[0]: ";
msg[19] = fp_buf[4];
do {
if (!getdata(y, 0, msg, fp_buf + 4, 4, DOECHO))
fp_buf[4] = msg[19];
} while (fp_buf[4] < '0' || fp_buf[4] > '9');
return fp_buf;
}
static void
read_tmpbuf(int n)
{
FILE *fp;
char fp_tmpbuf[80];
char tmpfname[] = "buf.0";
char *tmpf;
char ans[4] = "y";
if (curr_buf->totaln >= EDIT_LINE_LIMIT)
{
vmsg("檔案已超過最大限制,無法再讀入暫存檔。");
return;
}
if (0 <= n && n <= 9) {
tmpfname[4] = '0' + n;
tmpf = tmpfname;
} else {
tmpf = ask_tmpbuf(3);
n = tmpf[4] - '0';
}
setuserfile(fp_tmpbuf, tmpf);
if (n != 0 && n != 5 && more(fp_tmpbuf, NA) != -1)
getdata(b_lines - 1, 0, "確定讀入嗎(Y/N)?[Y]", ans, sizeof(ans), LCECHO);
if (*ans != 'n' && (fp = fopen(fp_tmpbuf, "r"))) {
load_file(fp, -1);
fclose(fp);
while (curr_buf->curr_window_line >= b_lines) {
curr_buf->curr_window_line--;
curr_buf->top_of_win = curr_buf->top_of_win->next;
}
}
}
static void
write_tmpbuf(void)
{
FILE *fp;
char fp_tmpbuf[80], ans[4];
textline_t *p;
off_t sz = 0;
setuserfile(fp_tmpbuf, ask_tmpbuf(3));
if (dashf(fp_tmpbuf)) {
more(fp_tmpbuf, NA);
getdata(b_lines - 1, 0, "暫存檔已有資料 (A)附加 (W)覆寫 (Q)取消?[A] ",
ans, sizeof(ans), LCECHO);
if (ans[0] == 'q')
return;
}
if (ans[0] != 'w') // 'a'
{
sz = dashs(fp_tmpbuf);
if (sz > EDIT_SIZE_LIMIT)
{
vmsg("暫存檔已超過大小限制,無法再附加。");
return;
}
}
if ((fp = fopen(fp_tmpbuf, (ans[0] == 'w' ? "w" : "a+")))) {
for (p = curr_buf->firstline; p; p = p->next) {
if (p->next || p->data[0])
fprintf(fp, "%s\n", p->data);
}
fclose(fp);
}
}
static void
erase_tmpbuf(void)
{
char fp_tmpbuf[80];
char ans[4] = "n";
setuserfile(fp_tmpbuf, ask_tmpbuf(3));
if (more(fp_tmpbuf, NA) != -1)
getdata(b_lines - 1, 0, "確定刪除嗎(Y/N)?[N]",
ans, sizeof(ans), LCECHO);
if (*ans == 'y')
unlink(fp_tmpbuf);
}
/**
* 編輯器自動備份
*(最多備份 512 行 (?))
*/
void
auto_backup(void)
{
if (curr_buf == NULL)
return;
if (curr_buf->currline) {
FILE *fp;
textline_t *p, *v;
char bakfile[PATHLEN];
int count = 0;
setuserfile(bakfile, fp_bak);
if ((fp = fopen(bakfile, "w"))) {
for (p = curr_buf->firstline; p != NULL && count < 512; p = v, count++) {
v = p->next;
fprintf(fp, "%s\n", p->data);
free_line(p);
}
fclose(fp);
}
curr_buf->currline = NULL;
}
}
/**
* 取回編輯器備份
*/
void
restore_backup(void)
{
char bakfile[80], buf[80];
setuserfile(bakfile, fp_bak);
if (dashf(bakfile)) {
stand_title("編輯器自動復原");
getdata(1, 0, "您有一篇文章尚未完成,(S)寫入暫存檔 (Q)算了?[S] ",
buf, 4, LCECHO);
if (buf[0] != 'q') {
setuserfile(buf, ask_tmpbuf(3));
Rename(bakfile, buf);
} else
unlink(bakfile);
}
}
/* 引用文章 */
static int
garbage_line(const char *str)
{
int qlevel = 0;
while (*str == ':' || *str == '>') {
if (*(++str) == ' ')
str++;
if (qlevel++ >= 1)
return 1;
}
while (*str == ' ' || *str == '\t')
str++;
if (qlevel >= 1) {
if (!strncmp(str, "※ ", 3) || !strncmp(str, "==>", 3) ||
strstr(str, ") 提到:\n"))
return 1;
}
return (*str == '\n');
}
static void
quote_strip_ansi_inline(unsigned char *is)
{
unsigned char *os = is;
while (*is)
{
if(*is != ESC_CHR)
*os++ = *is;
else
{
is ++;
if(*is == '*')
{
/* ptt prints, keep it as normal */
*os++ = '*';
*os++ = '*';
}
else
{
/* normal ansi, strip them out. */
while (*is && ANSI_IN_ESCAPE(*is))
is++;
}
}
is++;
}
*os = 0;
}
static void
do_quote(void)
{
int op;
char buf[512];
getdata(b_lines - 1, 0, "請問要引用原文嗎(Y/N/All/Repost)?[Y] ",
buf, 3, LCECHO);
op = buf[0];
if (op != 'n') {
FILE *inf;
if ((inf = fopen(quote_file, "r"))) {
char *ptr;
int indent_mode0 = curr_buf->indent_mode;
fgets(buf, sizeof(buf), inf);
if ((ptr = strrchr(buf, ')')))
ptr[1] = '\0';
else if ((ptr = strrchr(buf, '\n')))
ptr[0] = '\0';
if ((ptr = strchr(buf, ':'))) {
char *str;
while (*(++ptr) == ' ');
/* 順手牽羊,取得 author's address */
if ((curredit & EDIT_BOTH) && (str = strchr(quote_user, '.'))) {
strcpy(++str, ptr);
str = strchr(str, ' ');
assert(str);
str[0] = '\0';
}
} else
ptr = quote_user;
curr_buf->indent_mode = 0;
insert_string("※ 引述《");
insert_string(ptr);
insert_string("》之銘言:\n");
if (op != 'a') /* 去掉 header */
while (fgets(buf, sizeof(buf), inf) && buf[0] != '\n');
/* FIXME by MH:
如果 header 到內文中間沒有空行分隔,會造成 All 以外的模式
都引不到內文。
*/
if (op == 'a')
while (fgets(buf, sizeof(buf), inf)) {
insert_char(':');
insert_char(' ');
quote_strip_ansi_inline((unsigned char *)buf);
insert_string(buf);
}
else if (op == 'r')
while (fgets(buf, sizeof(buf), inf)) {
/* repost, keep anything */
// quote_strip_ansi_inline((unsigned char *)buf);
insert_string(buf);
}
else {
if (curredit & EDIT_LIST) /* 去掉 mail list 之 header */
while (fgets(buf, sizeof(buf), inf) && (!strncmp(buf, "※ ", 3)));
while (fgets(buf, sizeof(buf), inf)) {
if (!strcmp(buf, "--\n"))
break;
if (!garbage_line(buf)) {
insert_char(':');
insert_char(' ');
quote_strip_ansi_inline((unsigned char *)buf);
insert_string(buf);
}
}
}
curr_buf->indent_mode = indent_mode0;
fclose(inf);
}
}
}
/**
* 審查 user 引言的使用
*/
static int
check_quote(void)
{
register textline_t *p = curr_buf->firstline;
register char *str;
int post_line;
int included_line;
post_line = included_line = 0;
while (p) {
if (!strcmp(str = p->data, "--"))
break;
if (str[1] == ' ' && ((str[0] == ':') || (str[0] == '>')))
included_line++;
else {
while (*str == ' ' || *str == '\t')
str++;
if (*str)
post_line++;
}
p = p->next;
}
if ((included_line >> 2) > post_line) {
move(4, 0);
outs("本篇文章的引言比例超過 80%,請您做些微的修正:\n\n"
ANSI_COLOR(1;33) "1) 增加一些文章 或 2) 刪除不必要之引言" ANSI_RESET);
{
char ans[4];
getdata(12, 12, "(E)繼續編輯 (W)強制寫入?[E] ",
ans, sizeof(ans), LCECHO);
if (ans[0] == 'w')
return 0;
}
return 1;
}
return 0;
}
/* 檔案處理:讀檔、存檔、標題、簽名檔 */
off_t loadsitesig(const char *fname);
static void
read_file(const char *fpath, int splitSig)
{
FILE *fp;
off_t offSig = -1;
if (splitSig)
offSig = loadsitesig(fpath);
if ((fp = fopen(fpath, "r")) == NULL) {
int fd;
if ((fd = creat(fpath, 0600)) >= 0) {
close(fd);
return;
}
indigestion(4);
abort_bbs(0);
}
load_file(fp, offSig);
fclose(fp);
}
void
write_header(FILE * fp, char *mytitle) // FIXME unused
{
if (curredit & EDIT_MAIL || curredit & EDIT_LIST) {
fprintf(fp, "%s %s (%s)\n", str_author1, cuser.userid,
cuser.nickname
);
} else {
char *ptr = mytitle;
struct {
char author[IDLEN + 1];
char board[IDLEN + 1];
char title[66];
time4_t date; /* last post's date */
int number; /* post number */
} postlog;
memset(&postlog, 0, sizeof(postlog));
strlcpy(postlog.author, cuser.userid, sizeof(postlog.author));
if (curr_buf)
curr_buf->ifuseanony = 0;
#ifdef HAVE_ANONYMOUS
if (currbrdattr & BRD_ANONYMOUS) {
int defanony = (currbrdattr & BRD_DEFAULTANONYMOUS);
if (defanony)
getdata(3, 0, "請輸入你想用的ID,也可直接按[Enter],"
"或是按[r]用真名:", real_name, sizeof(real_name), DOECHO);
else
getdata(3, 0, "請輸入你想用的ID,也可直接按[Enter]使用原ID:",
real_name, sizeof(real_name), DOECHO);
if (!real_name[0] && defanony) {
strlcpy(real_name, "Anonymous", sizeof(real_name));
strlcpy(postlog.author, real_name, sizeof(postlog.author));
if (curr_buf)
curr_buf->ifuseanony = 1;
} else {
if (!strcmp("r", real_name) || (!defanony && !real_name[0]))
strlcpy(postlog.author, cuser.userid, sizeof(postlog.author));
else {
snprintf(postlog.author, sizeof(postlog.author),
"%s.", real_name);
if (curr_buf)
curr_buf->ifuseanony = 1;
}
}
}
#endif
strlcpy(postlog.board, currboard, sizeof(postlog.board));
if (!strncmp(ptr, str_reply, 4))
ptr += 4;
strlcpy(postlog.title, ptr, sizeof(postlog.title));
postlog.date = now;
postlog.number = 1;
append_record(".post", (fileheader_t *) & postlog, sizeof(postlog));
#ifdef HAVE_ANONYMOUS
if (currbrdattr & BRD_ANONYMOUS) {
int defanony = (currbrdattr & BRD_DEFAULTANONYMOUS);
fprintf(fp, "%s %s (%s) %s %s\n", str_author1, postlog.author,
(((!strcmp(real_name, "r") && defanony) ||
(!real_name[0] && (!defanony))) ? cuser.nickname :
"猜猜我是誰 ? ^o^"),
local_article ? str_post2 : str_post1, currboard);
} else {
fprintf(fp, "%s %s (%s) %s %s\n", str_author1, cuser.userid,
cuser.nickname,
local_article ? str_post2 : str_post1, currboard);
}
#else /* HAVE_ANONYMOUS */
fprintf(fp, "%s %s (%s) %s %s\n", str_author1, cuser.userid,
cuser.nickname,
local_article ? str_post2 : str_post1, currboard);
#endif /* HAVE_ANONYMOUS */
}
mytitle[72] = '\0';
fprintf(fp, "標題: %s\n時間: %s\n", mytitle, ctime4(&now));
}
off_t
loadsitesig(const char *fname)
{
int fd = 0;
off_t sz = 0, ret = -1;
char *start, *sp;
sz = dashs(fname);
if (sz < 1)
return -1;
fd = open(fname, O_RDONLY);
if (fd < 0)
return -1;
start = (char*)mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0);
if (start)
{
sp = start + sz - 4 - 1; // 4 = \n--\n
while (sp > start)
{
if ((*sp == '\n' && strncmp(sp, "\n--\n", 4) == 0) ||
(*sp == '\r' && strncmp(sp, "\r--\r", 4) == 0) )
{
size_t szSig = sz - (sp-start+1);
ret = sp - start + 1;
// allocate string
curr_buf->sitesig_string = (char*) malloc (szSig + 1);
if (curr_buf->sitesig_string)
{
memcpy(curr_buf->sitesig_string, sp+1, szSig);
curr_buf->sitesig_string[szSig] = 0;
}
break;
}
sp --;
}
munmap(start, sz);
}
close(fd);
return ret;
}
void
addsignature(FILE * fp, int ifuseanony)
{
FILE *fs;
int i;
char buf[WRAPMARGIN + 1];
char fpath[STRLEN];
char ch;
if (!strcmp(cuser.userid, STR_GUEST)) {
fprintf(fp, "\n--\n※ 發信站 :" BBSNAME "(" MYHOSTNAME
") \n◆ From: %s\n", fromhost);
return;
}
if (!ifuseanony) {
int browsing = 0;
SigInfo si;
memset(&si, 0, sizeof(si));
browse_sigs:
showsignature(fpath, &i, &si);
if (si.total > 0){
char msg[64];
ch = isdigit(cuser.signature) ? cuser.signature : 'x';
sprintf(msg,
(browsing || (si.max > si.show_max)) ?
"請選擇簽名檔 (1-9, 0=不加 n=翻頁 x=隨機)[%c]: ":
"請選擇簽名檔 (1-9, 0=不加 x=隨機)[%c]: ",
ch);
getdata(0, 0, msg, buf, 4, LCECHO);
if(buf[0] == 'n')
{
si.show_start = si.show_max + 1;
if(si.show_start > si.max)
si.show_start = 0;
browsing = 1;
goto browse_sigs;
}
if (!buf[0])
buf[0] = ch;
if (isdigit((int)buf[0]))
ch = buf[0];
else
ch = '1' + random() % (si.max+1);
cuser.signature = buf[0];
if (ch != '0') {
fpath[i] = ch;
do
{
if ((fs = fopen(fpath, "r"))) {
fputs("\n--\n", fp);
for (i = 0; i < MAX_SIGLINES &&
fgets(buf, sizeof(buf), fs); i++)
fputs(buf, fp);
fclose(fs);
fpath[i] = ch;
}
else
fpath[i] = '1' + (fpath[i] - '1' + 1) % (si.max+1);
} while (!isdigit((int)buf[0]) && si.max > 0 && ch != fpath[i]);
}
}
}
#ifdef HAVE_ORIGIN
#ifdef HAVE_ANONYMOUS
if (ifuseanony)
fprintf(fp, "\n--\n※ 發信站: " BBSNAME "(" MYHOSTNAME
") \n◆ From: %s\n", "匿名天使的家");
else
#endif
{
char temp[33];
strlcpy(temp, fromhost, sizeof(temp));
fprintf(fp, "\n--\n※ 發信站: " BBSNAME "(" MYHOSTNAME
") \n◆ From: %s\n", temp);
}
#endif
}
#ifdef EXP_EDIT_UPLOAD
static void upload_file(void);
#endif // EXP_EDIT_UPLOAD
static int
write_file(char *fpath, int saveheader, int *islocal, char *mytitle, int upload)
{
struct tm *ptime;
FILE *fp = NULL;
textline_t *p, *v;
char ans[TTLEN], *msg;
int aborted = 0, line = 0, checksum[3], sum = 0, po = 1;
stand_title("檔案處理");
move(1,0);
#ifdef EDIT_UPLOAD_ALLOWALL
upload = 1;
#endif // EDIT_UPLOAD_ALLOWALL
// common trail
if (currstat == SMAIL)
outs("[S]儲存");
else if (local_article)
outs("[L]站內信件 (S)儲存");
else
outs("[S]儲存 (L)站內信件");
#ifdef EXP_EDIT_UPLOAD
if (upload)
outs(" (U)上傳資料");
#endif // EXP_EDIT_UPLOAD
outs(" (A)放棄 (T)改標題 (E)繼續 (R/W/D)讀寫刪暫存檔");
getdata(2, 0, "確定要儲存檔案嗎? ", ans, 2, LCECHO);
// avoid lots pots
sleep(1);
switch (ans[0]) {
case 'a':
outs("文章" ANSI_COLOR(1) " 沒有 " ANSI_RESET "存入");
aborted = -1;
break;
case 'e':
return KEEP_EDITING;
#ifdef EXP_EDIT_UPLOAD
case 'u':
if (upload)
upload_file();
return KEEP_EDITING;
#endif // EXP_EDIT_UPLOAD
case 'r':
read_tmpbuf(-1);
return KEEP_EDITING;
case 'w':
write_tmpbuf();
return KEEP_EDITING;
case 'd':
erase_tmpbuf();
return KEEP_EDITING;
case 't':
move(3, 0);
prints("舊標題:%s", mytitle);
strlcpy(ans, mytitle, sizeof(ans));
if (getdata_buf(4, 0, "新標題:", ans, sizeof(ans), DOECHO))
strlcpy(mytitle, ans, STRLEN);
return KEEP_EDITING;
case 's':
if (!HasUserPerm(PERM_LOGINOK)) {
local_article = 1;
move(2, 0);
outs("您尚未通過身份確認,只能 Local Save。\n");
pressanykey();
} else
local_article = 0;
break;
case 'l':
local_article = 1;
}
if (!aborted) {
if (saveheader && !(curredit & EDIT_MAIL) && check_quote())
return KEEP_EDITING;
if (!(*fpath))
setuserfile(fpath, "ve_XXXXXX");
if ((fp = fopen(fpath, "w")) == NULL) {
indigestion(5);
abort_bbs(0);
}
if (saveheader)
write_header(fp, mytitle);
}
for (p = curr_buf->firstline; p; p = v) {
v = p->next;
if (!aborted) {
assert(fp);
msg = p->data;
if (v || msg[0]) {
trim(msg);
line++;
/* check crosspost */
if (currstat == POSTING && po ) {
int msgsum = StringHash(msg);
if (msgsum) {
if (postrecord.last_bid != currbid &&
postrecord.checksum[po] == msgsum) {
po++;
if (po > 3) {
postrecord.times++;
postrecord.last_bid = currbid;
po = 0;
}
} else
po = 1;
if (line >= curr_buf->totaln / 2 && sum < 3) {
checksum[sum++] = msgsum;
}
}
}
fprintf(fp, "%s\n", msg);
}
}
free_line(p);
}
curr_buf->currline = NULL;
if (postrecord.times > MAX_CROSSNUM-1 && !is_hidden_board_friend(currbid, currutmp->uid))
anticrosspost();
if (po && sum == 3) {
memcpy(&postrecord.checksum[1], checksum, sizeof(int) * 3);
if(postrecord.last_bid != currbid)
postrecord.times = 0;
}
if (!aborted) {
if (islocal)
*islocal = local_article;
if (curr_buf->sitesig_string)
{
fprintf(fp, curr_buf->sitesig_string);
}
if (currstat == POSTING || currstat == SMAIL)
{
addsignature(fp, curr_buf->ifuseanony);
}
else if (currstat == REEDIT
#ifndef ALL_REEDIT_LOG
&& strcmp(currboard, GLOBAL_SYSOP) == 0
#endif
) {
ptime = localtime4(&now);
fprintf(fp,
"※ 編輯: %-15s 來自: %-20s (%02d/%02d %02d:%02d)\n",
cuser.userid, fromhost,
ptime->tm_mon + 1, ptime->tm_mday, ptime->tm_hour, ptime->tm_min);
}
fclose(fp);
}
return aborted;
}
static inline int
has_block_selection(void)
{
return curr_buf->blockln >= 0;
}
/**
* a block is continual lines of the article.
*/
/**
* stop the block selection.
*/
static void
block_cancel(void)
{
if (has_block_selection()) {
curr_buf->blockln = -1;
curr_buf->redraw_everything = YEA;
}
}
static inline void
setup_block_begin_end(textline_t **begin, textline_t **end)
{
if (curr_buf->currln >= curr_buf->blockln) {
*begin = curr_buf->blockline;
*end = curr_buf->currline;
} else {
*begin = curr_buf->currline;
*end = curr_buf->blockline;
}
}
#define BLOCK_TRUNCATE 0
#define BLOCK_APPEND 1
/**
* save the selected block to file 'fname.'
* mode: BLOCK_TRUNCATE truncate mode
* BLOCK_APPEND append mode
*/
static void
block_save_to_file(const char *fname, int mode)
{
textline_t *begin, *end;
char fp_tmpbuf[80];
FILE *fp;
if (!has_block_selection())
return;
setup_block_begin_end(&begin, &end);
setuserfile(fp_tmpbuf, fname);
if ((fp = fopen(fp_tmpbuf, mode == BLOCK_APPEND ? "a+" : "w+"))) {
textline_t *p;
for (p = begin; p != end; p = p->next)
fprintf(fp, "%s\n", p->data);
fprintf(fp, "%s\n", end->data);
fclose(fp);
}
}
/**
* delete selected block
*/
static void
block_delete(void)
{
textline_t *begin, *end;
textline_t *p;
if (!has_block_selection())
return;
setup_block_begin_end(&begin, &end);
// the block region is (currln, block) or (blockln, currln).
if (curr_buf->currln > curr_buf->blockln) {
// case (blockln, currln)
// piaip 2007/1201 在這裡原有 offset-by-one issue
// 如果又遇到,請檢查這附近。
curr_buf->curr_window_line -= (curr_buf->currln - curr_buf->blockln);
if (curr_buf->curr_window_line <= 0) {
curr_buf->curr_window_line = 0;
if (end->next)
(curr_buf->top_of_win = end->next)->prev = begin->prev;
else
curr_buf->top_of_win = (curr_buf->lastline = begin->prev);
}
curr_buf->currln -= (curr_buf->currln - curr_buf->blockln);
} else {
// case (currln, blockln)
}
// adjust buffer after delete
if (begin->prev)
begin->prev->next = end->next;
else if (end->next)
curr_buf->top_of_win = curr_buf->firstline = end->next;
else {
curr_buf->currline = curr_buf->top_of_win = curr_buf->firstline = curr_buf->lastline = alloc_line(WRAPMARGIN);
curr_buf->currln = curr_buf->curr_window_line = curr_buf->edit_margin = 0;
}
// adjust current line
if (end->next) {
curr_buf->currline = end->next;
curr_buf->currline->prev = begin->prev;
}
else if (begin->prev) {
curr_buf->currline = (curr_buf->lastline = begin->prev);
curr_buf->currln--;
if (curr_buf->curr_window_line > 0)
curr_buf->curr_window_line--;
}
// remove buffer
for (p = begin; p != end; curr_buf->totaln--)
free_line((p = p->next)->prev);
free_line(end);
curr_buf->totaln--;
curr_buf->currpnt = 0;
}
static void
block_cut(void)
{
if (!has_block_selection())
return;
block_save_to_file("buf.0", BLOCK_TRUNCATE);
block_delete();
curr_buf->blockln = -1;
curr_buf->redraw_everything = YEA;
}
static void
block_copy(void)
{
if (!has_block_selection())
return;
block_save_to_file("buf.0", BLOCK_TRUNCATE);
curr_buf->blockln = -1;
curr_buf->redraw_everything = YEA;
}
static void
block_prompt(void)
{
char fp_tmpbuf[80];
char tmpfname[] = "buf.0";
char mode[2];
move(b_lines - 1, 0);
clrtoeol();
if (!getdata(b_lines - 1, 0, "把區塊移至暫存檔 (0:Cut, 5:Copy, 6-9, q: Cancel)[0] ", tmpfname + 4, 4, LCECHO))
tmpfname[4] = '0';
if (tmpfname[4] < '0' || tmpfname[4] > '9')
goto cancel_block;
if (tmpfname[4] == '0') {
block_cut();
return;
}
else if (tmpfname[4] == '5') {
block_copy();
return;
}
setuserfile(fp_tmpbuf, tmpfname);
if (dashf(fp_tmpbuf)) {
more(fp_tmpbuf, NA);
getdata(b_lines - 1, 0, "暫存檔已有資料 (A)附加 (W)覆寫 (Q)取消?[W] ", mode, sizeof(mode), LCECHO);
if (mode[0] == 'q')
goto cancel_block;
else if (mode[0] != 'a')
mode[0] = 'w';
}
if (getans("刪除區塊(Y/N)?[N] ") != 'y')
goto cancel_block;
block_save_to_file(tmpfname, mode[0] == 'a' ? BLOCK_APPEND : BLOCK_TRUNCATE);
cancel_block:
curr_buf->blockln = -1;
curr_buf->redraw_everything = YEA;
}
static void
block_select(void)
{
curr_buf->blockln = curr_buf->currln;
curr_buf->blockline = curr_buf->currline;
}
enum {
EOATTR_NORMAL = 0x00,
EOATTR_SELECTED = 0x01, // selected (reverse)
EOATTR_MOVIECODE= 0x02, // pmore movie
};
/**
* Just like outs, but print out '*' instead of 27(decimal) in the given string.
*
* FIXME column could not start from 0
*/
static void
edit_outs_attr_n(const char *text, int n, int attr)
{
int column = 0;
register unsigned char inAnsi = 0;
register unsigned char ch;
int doReset = 0;
const char *reset = ANSI_RESET;
#ifdef COLORED_SELECTION
if (attr & EOATTR_SELECTED & EOATTR_MOVIECODE)
{
reset = ANSI_COLOR(0;7;36);
doReset = 1;
outs(reset);
}
else
#endif // if not defined, color by priority - selection first
if (attr & EOATTR_SELECTED)
{
reset = ANSI_COLOR(0;7);
doReset = 1;
outs(reset);
}
else if (attr & EOATTR_MOVIECODE)
{
reset = ANSI_COLOR(0;36);
doReset = 1;
outs(reset);
}
#ifdef DBCSAWARE
/* 0 = N/A, 1 = leading byte printed, 2 = ansi in middle */
register unsigned char isDBCS = 0;
#endif
while ((ch = *text++) && (++column < t_columns) && n-- > 0)
{
if(inAnsi == 1)
{
if(ch == ESC_CHR)
outc('*');
else
{
outc(ch);
if(!ANSI_IN_ESCAPE(ch))
{
inAnsi = 0;
outs(reset);
}
}
}
else if(ch == ESC_CHR)
{
inAnsi = 1;
#ifdef DBCSAWARE
if(isDBCS == 1)
{
isDBCS = 2;
outs(ANSI_COLOR(1;33) "?");
outs(reset);
}
#endif
outs(ANSI_COLOR(1) "*");
}
else
{
#ifdef DBCSAWARE
if(isDBCS == 1)
isDBCS = 0;
else if (isDBCS == 2)
{
/* ansi in middle. */
outs(ANSI_COLOR(0;33) "?");
outs(reset);
isDBCS = 0;
continue;
}
else
if(IS_BIG5_HI(ch))
{
isDBCS = 1;
// peak next char
if(n > 0 && *text == ESC_CHR)
continue;
}
#endif
outc(ch);
}
}
// this must be ANSI_RESET, not "reset".
if(inAnsi || doReset)
outs(ANSI_RESET);
}
static void
edit_outs_attr(const char *text, int attr)
{
edit_outs_attr_n(text, scr_cols, attr);
}
static void
edit_ansi_outs_n(const char *str, int n, int attr)
{
char c;
while (n-- > 0 && (c = *str++)) {
if(c == ESC_CHR && *str == '*')
{
// ptt prints
/* Because moving within ptt_prints is too hard
* let's just display it as-is.
*/
outc('*');
} else {
outc(c);
}
}
}
static void
edit_ansi_outs(const char *str, int attr)
{
return edit_ansi_outs_n(str, strlen(str), attr);
}
// old compatible API
void
edit_outs(const char *text)
{
edit_outs_attr(text, 0);
}
void
edit_outs_n(const char *text, int n)
{
edit_outs_attr_n(text, n, 0);
}
#define PMORE_USE_ASCII_MOVIE // disable this if you don't enable ascii movie
#ifdef PMORE_USE_ASCII_MOVIE
// pmore movie header support
unsigned char *
mf_movieFrameHeader(unsigned char *p, unsigned char *end);
#endif // PMORE_USE_ASCII_MOVIE
static inline void
display_textline_internal(textline_t *p, int i)
{
short tmp;
void (*output)(const char *, int) = edit_outs_attr;
void (*output_n)(const char *, int, int)= edit_outs_attr_n;
int attr = EOATTR_NORMAL;
move(i, 0);
clrtoeol();
if (!p) {
outc('~');
outs(ANSI_CLRTOEND);
return;
}
if (curr_buf->ansimode) {
output = edit_ansi_outs;
output_n = edit_ansi_outs_n;
}
tmp = curr_buf->currln - curr_buf->curr_window_line + i;
// parse attribute of line
// selected attribute?
if (has_block_selection() &&
( (curr_buf->blockln <= curr_buf->currln &&
curr_buf->blockln <= tmp && tmp <= curr_buf->currln) ||
(curr_buf->currln <= tmp && tmp <= curr_buf->blockln)) )
{
// outs(ANSI_COLOR(7)); // remove me when EOATTR is ready...
attr |= EOATTR_SELECTED;
}
// movie attribute?
#ifdef PMORE_USE_ASCII_MOVIE
if (mf_movieFrameHeader(
(unsigned char*)p->data,
(unsigned char*)p->data + p->len))
attr |= EOATTR_MOVIECODE;
#endif // PMORE_USE_ASCII_MOVIE
#ifdef DBCSAWARE
if(mbcs_mode && curr_buf->edit_margin > 0)
{
if(curr_buf->edit_margin >= p->len)
{
(*output)("", attr);
} else {
int newpnt = curr_buf->edit_margin;
unsigned char *pdata = (unsigned char*)
(&p->data[0] + curr_buf->edit_margin);
if(mbcs_mode)
newpnt = fix_cursor(p->data, newpnt, FC_LEFT);
if(newpnt == curr_buf->edit_margin-1)
{
/* this should be always 'outs'? */
// (*output)(ANSI_COLOR(1) "<" ANSI_RESET);
outs(ANSI_COLOR(1) "<" ANSI_RESET);
pdata++;
}
(*output)((char*)pdata, attr);
}
} else
#endif
(*output)((curr_buf->edit_margin < p->len) ?
&p->data[curr_buf->edit_margin] : "", attr);
if (attr)
outs(ANSI_RESET);
// workaround poor terminal
outs(ANSI_CLRTOEND);
}
static void
refresh_window(void)
{
register textline_t *p;
register int i;
for (p = curr_buf->top_of_win, i = 0; i < b_lines; i++) {
display_textline_internal(p, i);
if (p)
p = p->next;
}
edit_msg();
}
static void
goto_line(int lino)
{
if (lino > 0 && lino <= curr_buf->totaln + 1) {
textline_t *p;
p = curr_buf->firstline;
curr_buf->currln = lino - 1;
while (--lino && p->next)
p = p->next;
if (p)
curr_buf->currline = p;
else {
curr_buf->currln = curr_buf->totaln;
curr_buf->currline = curr_buf->lastline;
}
curr_buf->currpnt = 0;
/* move window */
if (curr_buf->currln < middle_line()) {
curr_buf->top_of_win = curr_buf->firstline;
curr_buf->curr_window_line = curr_buf->currln;
} else {
int i;
curr_buf->curr_window_line = middle_line();
for (i = curr_buf->curr_window_line; i; i--)
p = p->prev;
curr_buf->top_of_win = p;
}
}
curr_buf->redraw_everything = YEA;
}
static void
prompt_goto_line(void)
{
char buf[10];
if (getdata(b_lines - 1, 0, "跳至第幾行:", buf, sizeof(buf), DOECHO))
goto_line(atoi(buf));
}
/**
* search string interactively.
* @param mode 0: prompt
* 1: forward
* -1: backward
*/
static void
search_str(int mode)
{
const int max_keyword = 65;
char *str;
char ans[4] = "n";
if (curr_buf->searched_string == NULL) {
if (mode != 0)
return;
curr_buf->searched_string = (char *)malloc(max_keyword * sizeof(char));
curr_buf->searched_string[0] = 0;
}
str = curr_buf->searched_string;
if (!mode) {
if (getdata_buf(b_lines - 1, 0, "[搜尋]關鍵字:",
str, max_keyword, DOECHO))
if (*str) {
if (getdata(b_lines - 1, 0, "區分大小寫(Y/N/Q)? [N] ",
ans, sizeof(ans), LCECHO) && *ans == 'y')
curr_buf->substr_fp = strstr;
else
curr_buf->substr_fp = strcasestr;
}
}
if (*str && *ans != 'q') {
textline_t *p;
char *pos = NULL;
int lino;
if (mode >= 0) {
for (lino = curr_buf->currln, p = curr_buf->currline; p; p = p->next, lino++)
if ((pos = (*curr_buf->substr_fp)(p->data + (lino == curr_buf->currln ? curr_buf->currpnt + 1 : 0),
str)) && (lino != curr_buf->currln ||
pos - p->data != curr_buf->currpnt))
break;
} else {
for (lino = curr_buf->currln, p = curr_buf->currline; p; p = p->prev, lino--)
if ((pos = (*curr_buf->substr_fp)(p->data, str)) &&
(lino != curr_buf->currln || pos - p->data != curr_buf->currpnt))
break;
}
if (pos) {
/* move window */
curr_buf->currline = p;
curr_buf->currln = lino;
curr_buf->currpnt = pos - p->data;
if (lino < middle_line()) {
curr_buf->top_of_win = curr_buf->firstline;
curr_buf->curr_window_line = curr_buf->currln;
} else {
int i;
curr_buf->curr_window_line = middle_line();
for (i = curr_buf->curr_window_line; i; i--)
p = p->prev;
curr_buf->top_of_win = p;
}
curr_buf->redraw_everything = YEA;
}
}
if (!mode)
curr_buf->redraw_everything = YEA;
}
/**
* move the cursor from bracket to corresponding bracket.
*/
static void
match_paren(void)
{
char *parens = "()[]{}";
int type;
int parenum = 0;
char *ptype;
textline_t *p;
int lino;
int c, i = 0;
if (!(ptype = strchr(parens, curr_buf->currline->data[curr_buf->currpnt])))
return;
type = (ptype - parens) / 2;
parenum = ((ptype - parens) % 2) ? -1 : 1;
/* FIXME CRASH */
/* FIXME refactoring */
if (parenum > 0) {
for (lino = curr_buf->currln, p = curr_buf->currline; p; p = p->next, lino++) {
int len = strlen(p->data);
for (i = (lino == curr_buf->currln) ? curr_buf->currpnt + 1 : 0; i < len; i++) {
if (p->data[i] == '/' && p->data[++i] == '*') {
++i;
while (1) {
while (i < len &&
!(p->data[i] == '*' && p->data[i + 1] == '/')) {
i++;
}
if (i >= len && p->next) {
p = p->next;
len = strlen(p->data);
++lino;
i = 0;
} else
break;
}
} else if ((c = p->data[i]) == '\'' || c == '"') {
while (1) {
while (i < len - 1) {
if (p->data[++i] == '\\' && (size_t)i < len - 2)
++i;
else if (p->data[i] == c)
goto end_quote;
}
if ((size_t)i >= len - 1 && p->next) {
p = p->next;
len = strlen(p->data);
++lino;
i = -1;
} else
break;
}
end_quote:
;
} else if ((ptype = strchr(parens, p->data[i])) &&
(ptype - parens) / 2 == type) {
if (!(parenum += ((ptype - parens) % 2) ? -1 : 1))
goto p_outscan;
}
}
}
} else {
for (lino = curr_buf->currln, p = curr_buf->currline; p; p = p->prev, lino--) {
int len = strlen(p->data);
for (i = ((lino == curr_buf->currln) ? curr_buf->currpnt - 1 : len - 1); i >= 0; i--) {
if (p->data[i] == '/' && p->data[--i] == '*' && i > 0) {
--i;
while (1) {
while (i > 0 &&
!(p->data[i] == '*' && p->data[i - 1] == '/')) {
i--;
}
if (i <= 0 && p->prev) {
p = p->prev;
len = strlen(p->data);
--lino;
i = len - 1;
} else
break;
}
} else if ((c = p->data[i]) == '\'' || c == '"') {
while (1) {
while (i > 0)
if (i > 1 && p->data[i - 2] == '\\')
i -= 2;
else if ((p->data[--i]) == c)
goto begin_quote;
if (i <= 0 && p->prev) {
p = p->prev;
len = strlen(p->data);
--lino;
i = len;
} else
break;
}
begin_quote:
;
} else if ((ptype = strchr(parens, p->data[i])) &&
(ptype - parens) / 2 == type) {
if (!(parenum += ((ptype - parens) % 2) ? -1 : 1))
goto p_outscan;
}
}
}
}
p_outscan:
if (!parenum) {
int top = curr_buf->currln - curr_buf->curr_window_line;
int bottom = curr_buf->currln - curr_buf->curr_window_line + b_lines - 1;
curr_buf->currpnt = i;
curr_buf->currline = p;
curr_buf->curr_window_line += lino - curr_buf->currln;
curr_buf->currln = lino;
if (lino < top || lino > bottom) {
if (lino < middle_line()) {
curr_buf->top_of_win = curr_buf->firstline;
curr_buf->curr_window_line = curr_buf->currln;
} else {
int i;
curr_buf->curr_window_line = middle_line();
for (i = curr_buf->curr_window_line; i; i--)
p = p->prev;
curr_buf->top_of_win = p;
}
curr_buf->redraw_everything = YEA;
}
}
}
static void
currline_shift_left(void)
{
int currpnt0;
if (curr_buf->currline->len <= 0)
return;
currpnt0 = curr_buf->currpnt;
curr_buf->currpnt = 0;
delete_char();
curr_buf->currpnt = (currpnt0 <= curr_buf->currline->len) ? currpnt0 : currpnt0 - 1;
if (curr_buf->ansimode)
curr_buf->currpnt = ansi2n(n2ansi(curr_buf->currpnt, curr_buf->currline), curr_buf->currline);
}
static void
currline_shift_right(void)
{
int currpnt0;
if (curr_buf->currline->len >= WRAPMARGIN - 1)
return;
currpnt0 = curr_buf->currpnt;
curr_buf->currpnt = 0;
insert_char(' ');
curr_buf->currpnt = currpnt0;
}
static void
cursor_to_next_word(void)
{
while (curr_buf->currpnt < curr_buf->currline->len &&
isalnum((int)curr_buf->currline->data[++curr_buf->currpnt]));
while (curr_buf->currpnt < curr_buf->currline->len &&
isspace((int)curr_buf->currline->data[++curr_buf->currpnt]));
}
static void
cursor_to_prev_word(void)
{
while (curr_buf->currpnt && isspace((int)curr_buf->currline->data[--curr_buf->currpnt]));
while (curr_buf->currpnt && isalnum((int)curr_buf->currline->data[--curr_buf->currpnt]));
if (curr_buf->currpnt > 0)
curr_buf->currpnt++;
}
static void
delete_current_word(void)
{
while (curr_buf->currpnt < curr_buf->currline->len) {
delete_char();
if (!isalnum((int)curr_buf->currline->data[curr_buf->currpnt]))
break;
}
while (curr_buf->currpnt < curr_buf->currline->len) {
delete_char();
if (!isspace((int)curr_buf->currline->data[curr_buf->currpnt]))
break;
}
}
/**
* transform every "*[" in given string to KEY_ESC "["
*/
static void
transform_to_color(char *line)
{
while (line[0] && line[1])
if (line[0] == '*' && line[1] == '[') {
line[0] = KEY_ESC;
line += 2;
} else
++line;
}
static void
block_color(void)
{
textline_t *begin, *end, *p;
setup_block_begin_end(&begin, &end);
p = begin;
while (1) {
// FIXME CRASH p will be NULL here.
assert(p);
transform_to_color(p->data);
if (p == end)
break;
else
p = p->next;
}
block_cancel();
}
/**
* insert ansi code
*/
static void
insert_ansi_code(void)
{
int ch = curr_buf->insert_mode;
curr_buf->insert_mode = curr_buf->redraw_everything = YEA;
if (!curr_buf->ansimode)
insert_string(reset_color);
else {
char ans[4];
move(b_lines - 2, 55);
outs(ANSI_COLOR(1;33;40) "B" ANSI_COLOR(41) "R" ANSI_COLOR(42) "G" ANSI_COLOR(43) "Y" ANSI_COLOR(44) "L"
ANSI_COLOR(45) "P" ANSI_COLOR(46) "C" ANSI_COLOR(47) "W" ANSI_RESET);
if (getdata(b_lines - 1, 0,
"請輸入 亮度/前景/背景[正常白字黑底][0wb]:",
ans, sizeof(ans), LCECHO))
{
const char t[] = "BRGYLPCW";
char color[15];
char *tmp, *apos = ans;
int fg, bg;
strcpy(color, ESC_STR "[");
if (isdigit((int)*apos)) {
sprintf(color,"%s%c", color, *(apos++));
if (*apos)
strcat(color, ";");
}
if (*apos) {
if ((tmp = strchr(t, toupper(*(apos++)))))
fg = tmp - t + 30;
else
fg = 37;
sprintf(color, "%s%d", color, fg);
}
if (*apos) {
if ((tmp = strchr(t, toupper(*(apos++)))))
bg = tmp - t + 40;
else
bg = 40;
sprintf(color, "%s;%d", color, bg);
}
strcat(color, "m");
insert_string(color);
} else
insert_string(reset_color);
}
curr_buf->insert_mode = ch;
}
static inline void
phone_mode_switch(void)
{
if (curr_buf->phone_mode)
curr_buf->phone_mode = 0;
else {
curr_buf->phone_mode = 1;
if (!curr_buf->last_phone_mode)
curr_buf->last_phone_mode = 2;
}
}
/**
* return coresponding phone char of given key c
*/
static const char*
phone_char(char c)
{
if (curr_buf->last_phone_mode > 0 && curr_buf->last_phone_mode < 20) {
if (tolower(c)<'a'||(tolower(c)-'a') >= strlen(BIG5[curr_buf->last_phone_mode - 1]) / 2)
return 0;
return BIG5[curr_buf->last_phone_mode - 1] + (tolower(c) - 'a') * 2;
}
else if (curr_buf->last_phone_mode >= 20) {
if (c == '.') c = '/';
if (c < '/' || c > '9')
return 0;
return table[curr_buf->last_phone_mode - 20] + (c - '/') * 2;
}
return 0;
}
/**
* When get the key for phone mode, handle it (e.g. edit_msg) and return the
* key. Otherwise return 0.
*/
static inline char
phone_mode_filter(char ch)
{
if (!curr_buf->phone_mode)
return 0;
switch (ch) {
case 'z':
case 'Z':
if (curr_buf->last_phone_mode < 20)
curr_buf->last_phone_mode = 20;
else
curr_buf->last_phone_mode = 2;
edit_msg();
return ch;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (curr_buf->last_phone_mode < 20) {
curr_buf->last_phone_mode = ch - '0' + 1;
curr_buf->redraw_everything = YEA;
return ch;
}
break;
case '-':
if (curr_buf->last_phone_mode < 20) {
curr_buf->last_phone_mode = 11;
curr_buf->redraw_everything = YEA;
return ch;
}
break;
case '=':
if (curr_buf->last_phone_mode < 20) {
curr_buf->last_phone_mode = 12;
curr_buf->redraw_everything = YEA;
return ch;
}
break;
case '`':
if (curr_buf->last_phone_mode < 20) {
curr_buf->last_phone_mode = 13;
curr_buf->redraw_everything = YEA;
return ch;
}
break;
case '/':
if (curr_buf->last_phone_mode >= 20) {
curr_buf->last_phone_mode += 4;
if (curr_buf->last_phone_mode > 27)
curr_buf->last_phone_mode -= 8;
curr_buf->redraw_everything = YEA;
return ch;
}
break;
case '*':
if (curr_buf->last_phone_mode >= 20) {
curr_buf->last_phone_mode++;
if ((curr_buf->last_phone_mode - 21) % 4 == 3)
curr_buf->last_phone_mode -= 4;
curr_buf->redraw_everything = YEA;
return ch;
}
break;
}
return 0;
}
#ifdef EXP_EDIT_UPLOAD
static void
upload_file(void)
{
size_t szdata = 0;
int c = 1;
char promptmsg = 0;
clear();
block_cancel();
stand_title("上傳文字檔案");
move(3,0);
outs("利用本服務您可以上傳較大的文字檔 (但不計入稿費)。\n"
"\n"
"上傳期間您打的字暫時不會出現在螢幕上,除了 Ctrl-U 會被轉換為 ANSI \n"
"控制碼的 ESC 外,其它特殊鍵一律沒有作用。\n"
"\n"
"請在您的電腦本機端複製好內容後貼上即可開始傳送。\n");
do {
if (!num_in_buf())
{
move(10, 0); clrtobot();
prints("\n\n資料接收中... %u 位元組。\n", (unsigned int)szdata);
outs(ANSI_COLOR(1)
"◆全部完成後按下 End 或 ^X/^Q/^C 即可回到編輯畫面。"
ANSI_RESET "\n");
promptmsg = 0;
}
c = igetch();
if (c < 0x100 && isprint2(c))
{
insert_char(c);
szdata ++;
}
else if (c == Ctrl('U') || c == ESC_CHR)
{
insert_char(ESC_CHR);
szdata ++;
}
else if (c == Ctrl('I'))
{
insert_tab();
szdata ++;
}
else if (c == '\r' || c == '\n')
{
split(curr_buf->currline, curr_buf->currpnt);
curr_buf->oldcurrline = curr_buf->currline;
szdata ++;
promptmsg = 1;
}
if (!promptmsg)
promptmsg = (szdata && szdata % 1024 == 0);
// all other keys are ignored.
} while (c != KEY_END && c != Ctrl('X') &&
c != Ctrl('C') && c != Ctrl('Q') &&
curr_buf->totaln <= EDIT_LINE_LIMIT &&
szdata <= EDIT_SIZE_LIMIT);
move(12, 0);
prints("傳送結束: 收到 %u 位元組。", (unsigned int)szdata);
vmsgf("回到編輯畫面");
}
#endif // EXP_EDIT_UPLOAD
/* 編輯處理:主程式、鍵盤處理 */
int
vedit2(char *fpath, int saveheader, int *islocal, int flags)
{
char last = 0; /* the last key you press */
int ch, tmp;
int mode0 = currutmp->mode;
int destuid0 = currutmp->destuid;
int money = 0;
int interval = 0;
time4_t th = now;
int count = 0, tin = 0, quoted = 0;
char trans_buffer[256];
char mytitle[STRLEN];
STATINC(STAT_VEDIT);
currutmp->mode = EDITING;
currutmp->destuid = currstat;
strlcpy(mytitle, save_title, sizeof(mytitle));
#ifdef DBCSAWARE
mbcs_mode = (cuser.uflag & DBCSAWARE_FLAG) ? 1 : 0;
#endif
enter_edit_buffer();
curr_buf->oldcurrline = curr_buf->currline = curr_buf->top_of_win =
curr_buf->firstline = curr_buf->lastline = alloc_line(WRAPMARGIN);
if (*fpath) {
read_file(fpath, (flags & EDITFLAG_TEXTONLY) ? 1 : 0);
}
if (*quote_file) {
do_quote();
*quote_file = '\0';
quoted = 1;
}
if( curr_buf->oldcurrline != curr_buf->firstline ||
curr_buf->currline != curr_buf->firstline) {
/* we must adjust because cursor (currentline) moved. */
curr_buf->oldcurrline = curr_buf->currline = curr_buf->top_of_win =
curr_buf->firstline= adjustline(curr_buf->firstline, WRAPMARGIN);
}
/* No matter you quote or not, just start the cursor from (0,0) */
curr_buf->currpnt = curr_buf->currln = curr_buf->curr_window_line =
curr_buf->edit_margin = curr_buf->last_margin = 0;
/* if quote, move to end of file. */
if(quoted)
{
/* maybe do this in future. */
}
while (1) {
if (curr_buf->redraw_everything || has_block_selection()) {
refresh_window();
curr_buf->redraw_everything = NA;
}
if( curr_buf->oldcurrline != curr_buf->currline ){
curr_buf->oldcurrline = adjustline(curr_buf->oldcurrline, curr_buf->oldcurrline->len);
curr_buf->oldcurrline = curr_buf->currline = adjustline(curr_buf->currline, WRAPMARGIN);
}
if (curr_buf->ansimode)
ch = n2ansi(curr_buf->currpnt, curr_buf->currline);
else
ch = curr_buf->currpnt - curr_buf->edit_margin;
move(curr_buf->curr_window_line, ch);
#if 0 // DEPRECATED, it's really not a well known expensive feature
if (!curr_buf->line_dirty && strcmp(editline, curr_buf->currline->data))
strcpy(editline, curr_buf->currline->data);
#endif
ch = igetch();
/* jochang debug */
if ((interval = (now - th))) {
th = now;
if ((char)ch != last) {
money++;
last = (char)ch;
}
}
if (interval && interval == tin)
{ // Ptt : +- 1 秒也算
count++;
if(count>60)
{
money = 0;
count = 0;
/*
log_file("etc/illegal_money", LOG_CREAT | LOG_VF,
ANSI_COLOR(1;33;46) "%s " ANSI_COLOR(37;45) " 用機器人發表文章 " ANSI_COLOR(37) " %s" ANSI_RESET "\n",
cuser.userid, ctime4(&now));
post_violatelaw(cuser.userid, BBSMNAME "系統警察",
"用機器人發表文章", "強制離站");
abort_bbs(0);
*/
}
}
else if(interval){
count = 0;
tin = interval;
}
#ifndef DBCSAWARE
/* this is almost useless! */
if (curr_buf->raw_mode) {
switch (ch) {
case Ctrl('S'):
case Ctrl('Q'):
case Ctrl('T'):
continue;
}
}
#endif
if (phone_mode_filter(ch))
continue;
if (ch < 0x100 && isprint2(ch)) {
const char *pstr;
if(curr_buf->phone_mode && (pstr=phone_char(ch)))
insert_dchar(pstr);
else
insert_char(ch);
curr_buf->lastindent = -1;
} else {
if (ch == KEY_UP || ch == KEY_DOWN ){
if (curr_buf->lastindent == -1)
curr_buf->lastindent = curr_buf->currpnt;
} else
curr_buf->lastindent = -1;
if (ch == KEY_ESC)
switch (KEY_ESC_arg) {
case ',':
ch = Ctrl(']');
break;
case '.':
ch = Ctrl('T');
break;
case 'v':
ch = KEY_PGUP;
break;
case 'a':
case 'A':
ch = Ctrl('V');
break;
case 'X':
ch = Ctrl('X');
break;
case 'q':
ch = Ctrl('Q');
break;
case 'o':
ch = Ctrl('O');
break;
#if 0 // DEPRECATED, it's really not a well known expensive feature
case '-':
ch = Ctrl('_');
break;
#endif
case 's':
ch = Ctrl('S');
break;
}
switch (ch) {
case KEY_F10:
case Ctrl('X'): /* Save and exit */
tmp = write_file(fpath, saveheader, islocal, mytitle,
(flags & EDITFLAG_UPLOAD) ? 1 : 0);
if (tmp != KEEP_EDITING) {
strlcpy(save_title, mytitle, sizeof(save_title));
save_title[STRLEN-1] = 0;
currutmp->mode = mode0;
currutmp->destuid = destuid0;
exit_edit_buffer();
if (!tmp)
return money;
else
return tmp;
}
curr_buf->oldcurrline = curr_buf->currline;
curr_buf->redraw_everything = YEA;
break;
case KEY_F5:
prompt_goto_line();
curr_buf->redraw_everything = YEA;
break;
case KEY_F8:
t_users();
curr_buf->redraw_everything = YEA;
break;
case Ctrl('W'):
block_cut();
// curr_buf->oldcurrline is freed in block_cut, and currline is
// well adjusted now. This will avoid re-adjusting later.
// It's not a good implementation, try to find a better
// solution!
curr_buf->oldcurrline = curr_buf->currline;
break;
case Ctrl('Q'): /* Quit without saving */
grayout(0, b_lines-1, GRAYOUT_DARK);
ch = vmsg("結束但不儲存 [y/N]? ");
if (ch == 'y' || ch == 'Y') {
currutmp->mode = mode0;
currutmp->destuid = destuid0;
exit_edit_buffer();
return -1;
}
curr_buf->redraw_everything = YEA;
break;
case Ctrl('C'):
insert_ansi_code();
break;
case KEY_ESC:
switch (KEY_ESC_arg) {
case 'U':
t_users();
curr_buf->redraw_everything = YEA;
break;
case 'i':
t_idle();
curr_buf->redraw_everything = YEA;
break;
case 'n':
search_str(1);
break;
case 'p':
search_str(-1);
break;
case 'L':
case 'J':
prompt_goto_line();
curr_buf->redraw_everything = YEA;
break;
case ']':
match_paren();
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
read_tmpbuf(KEY_ESC_arg - '0');
curr_buf->oldcurrline = curr_buf->currline;
curr_buf->redraw_everything = YEA;
break;
case 'l': /* block delete */
case ' ':
if (has_block_selection()) {
block_prompt();
// curr_buf->oldcurrline is freed in block_cut, and currline is
// well adjusted now. This will avoid re-adjusting later.
// It's not a good implementation, try to find a better
// solution!
curr_buf->oldcurrline = curr_buf->currline;
}
else
block_select();
break;
case 'u':
block_cancel();
break;
case 'c':
block_copy();
break;
case 'y':
curr_buf->oldcurrline = undelete_line();
if (curr_buf->oldcurrline == NULL)
curr_buf->oldcurrline = curr_buf->currline;
break;
case 'R':
#ifdef DBCSAWARE
case 'r':
mbcs_mode =! mbcs_mode;
#endif
curr_buf->raw_mode ^= 1;
break;
case 'I':
curr_buf->indent_mode ^= 1;
break;
case 'j':
currline_shift_left();
break;
case 'k':
currline_shift_right();
break;
case 'f':
cursor_to_next_word();
break;
case 'b':
cursor_to_prev_word();
break;
case 'd':
delete_current_word();
break;
}
break;
case Ctrl('S'):
case KEY_F3:
search_str(0);
break;
case Ctrl('U'):
insert_char(ESC_CHR);
break;
case Ctrl('V'): /* Toggle ANSI color */
curr_buf->ansimode ^= 1;
if (curr_buf->ansimode && has_block_selection())
block_color();
clear();
curr_buf->redraw_everything = YEA;
break;
case Ctrl('I'):
insert_tab();
break;
case '\r':
case '\n':
block_cancel();
if (curr_buf->totaln >= EDIT_LINE_LIMIT)
{
vmsg("檔案已超過最大限制,無法再增加行數。");
break;
}
#ifdef MAX_EDIT_LINE
if(curr_buf->totaln ==
((flags & EDITFLAG_ALLOWLARGE) ?
MAX_EDIT_LINE_LARGE : MAX_EDIT_LINE))
{
vmsg("已到達最大行數限制。");
break;
}
#endif
split(curr_buf->currline, curr_buf->currpnt);
curr_buf->oldcurrline = curr_buf->currline;
break;
case Ctrl('G'):
{
unsigned int currstat0 = currstat;
setutmpmode(EDITEXP);
a_menu("編輯輔助器", "etc/editexp",
(HasUserPerm(PERM_SYSOP) ? SYSOP : NOBODY),
0,
trans_buffer);
currstat = currstat0;
}
if (trans_buffer[0]) {
FILE *fp1;
if ((fp1 = fopen(trans_buffer, "r"))) {
int indent_mode0 = curr_buf->indent_mode;
char buf[WRAPMARGIN + 2];
curr_buf->indent_mode = 0;
while (fgets(buf, sizeof(buf), fp1)) {
if (!strncmp(buf, "作者:", 5) ||
!strncmp(buf, "標題:", 5) ||
!strncmp(buf, "時間:", 5))
continue;
insert_string(buf);
}
fclose(fp1);
curr_buf->indent_mode = indent_mode0;
while (curr_buf->curr_window_line >= b_lines) {
curr_buf->curr_window_line--;
curr_buf->top_of_win = curr_buf->top_of_win->next;
}
}
}
curr_buf->redraw_everything = YEA;
break;
case Ctrl('P'):
phone_mode_switch();
curr_buf->redraw_everything = YEA;
break;
case KEY_F1:
case Ctrl('Z'): /* Help */
more("etc/ve.hlp", YEA);
curr_buf->redraw_everything = YEA;
break;
case Ctrl('L'):
clear();
curr_buf->redraw_everything = YEA;
break;
case KEY_LEFT:
if (curr_buf->currpnt) {
if (curr_buf->ansimode)
curr_buf->currpnt = n2ansi(curr_buf->currpnt, curr_buf->currline);
curr_buf->currpnt--;
if (curr_buf->ansimode)
curr_buf->currpnt = ansi2n(curr_buf->currpnt, curr_buf->currline);
#ifdef DBCSAWARE
if(mbcs_mode)
curr_buf->currpnt = fix_cursor(curr_buf->currline->data, curr_buf->currpnt, FC_LEFT);
#endif
} else if (curr_buf->currline->prev) {
curr_buf->curr_window_line--;
curr_buf->currln--;
curr_buf->currline = curr_buf->currline->prev;
curr_buf->currpnt = curr_buf->currline->len;
}
break;
case KEY_RIGHT:
if (curr_buf->currline->len != curr_buf->currpnt) {
if (curr_buf->ansimode)
curr_buf->currpnt = n2ansi(curr_buf->currpnt, curr_buf->currline);
curr_buf->currpnt++;
if (curr_buf->ansimode)
curr_buf->currpnt = ansi2n(curr_buf->currpnt, curr_buf->currline);
#ifdef DBCSAWARE
if(mbcs_mode)
curr_buf->currpnt = fix_cursor(curr_buf->currline->data, curr_buf->currpnt, FC_RIGHT);
#endif
} else if (curr_buf->currline->next) {
curr_buf->currpnt = 0;
curr_buf->curr_window_line++;
curr_buf->currln++;
curr_buf->currline = curr_buf->currline->next;
}
break;
case KEY_UP:
cursor_to_prev_line();
break;
case KEY_DOWN:
cursor_to_next_line();
break;
case Ctrl('B'):
case KEY_PGUP: {
short tmp = curr_buf->currln;
curr_buf->top_of_win = back_line(curr_buf->top_of_win, t_lines - 2);
curr_buf->currln = tmp;
curr_buf->currline = back_line(curr_buf->currline, t_lines - 2);
curr_buf->curr_window_line = get_lineno_in_window();
if (curr_buf->currpnt > curr_buf->currline->len)
curr_buf->currpnt = curr_buf->currline->len;
curr_buf->redraw_everything = YEA;
break;
}
case Ctrl('F'):
case KEY_PGDN: {
short tmp = curr_buf->currln;
curr_buf->top_of_win = forward_line(curr_buf->top_of_win, t_lines - 2);
curr_buf->currln = tmp;
curr_buf->currline = forward_line(curr_buf->currline, t_lines - 2);
curr_buf->curr_window_line = get_lineno_in_window();
if (curr_buf->currpnt > curr_buf->currline->len)
curr_buf->currpnt = curr_buf->currline->len;
curr_buf->redraw_everything = YEA;
break;
}
case KEY_END:
case Ctrl('E'):
curr_buf->currpnt = curr_buf->currline->len;
break;
case Ctrl(']'): /* start of file */
curr_buf->currline = curr_buf->top_of_win = curr_buf->firstline;
curr_buf->currpnt = curr_buf->currln = curr_buf->curr_window_line = 0;
curr_buf->redraw_everything = YEA;
break;
case Ctrl('T'): /* tail of file */
curr_buf->top_of_win = back_line(curr_buf->lastline, t_lines - 1);
curr_buf->currline = curr_buf->lastline;
curr_buf->curr_window_line = get_lineno_in_window();
curr_buf->currln = curr_buf->totaln;
curr_buf->redraw_everything = YEA;
curr_buf->currpnt = 0;
break;
case KEY_HOME:
case Ctrl('A'):
curr_buf->currpnt = 0;
break;
case Ctrl('O'): // better not use ^O - UNIX not sending.
case KEY_INS: /* Toggle insert/overwrite */
if (has_block_selection() && curr_buf->insert_mode) {
char ans[4];
getdata(b_lines - 1, 0,
"區塊微調右移插入字元(預設為空白字元)",
ans, sizeof(ans), LCECHO);
curr_buf->insert_c = ans[0] ? ans[0] : ' ';
}
curr_buf->insert_mode ^= 1;
break;
case Ctrl('H'):
case '\177': /* backspace */
block_cancel();
if (curr_buf->ansimode) {
curr_buf->ansimode = 0;
clear();
curr_buf->redraw_everything = YEA;
} else {
if (curr_buf->currpnt == 0) {
if (!curr_buf->currline->prev)
break;
curr_buf->curr_window_line--;
curr_buf->currln--;
curr_buf->currline = adjustline(curr_buf->currline, curr_buf->currline->len);
curr_buf->currline = curr_buf->currline->prev;
curr_buf->currline = adjustline(curr_buf->currline, WRAPMARGIN);
curr_buf->oldcurrline = curr_buf->currline;
curr_buf->currpnt = curr_buf->currline->len;
curr_buf->redraw_everything = YEA;
if (curr_buf->currline->next == curr_buf->top_of_win) {
curr_buf->top_of_win = curr_buf->currline;
curr_buf->curr_window_line = 0;
}
if (*next_non_space_char(curr_buf->currline->next->data) == '\0') {
delete_line(curr_buf->currline->next, 0);
break;
}
join(curr_buf->currline);
break;
}
#ifndef DBCSAWARE
curr_buf->currpnt--;
delete_char();
#else
{
int newpnt = curr_buf->currpnt - 1;
if(mbcs_mode)
newpnt = fix_cursor(curr_buf->currline->data, newpnt, FC_LEFT);
for(; curr_buf->currpnt > newpnt;)
{
curr_buf->currpnt --;
delete_char();
}
}
#endif
}
break;
case Ctrl('D'):
case KEY_DEL: /* delete current character */
block_cancel();
if (curr_buf->currline->len == curr_buf->currpnt) {
join(curr_buf->currline);
curr_buf->redraw_everything = YEA;
} else {
#ifndef DBCSAWARE
delete_char();
#else
{
int w = 1;
if(mbcs_mode)
w = mchar_len((unsigned char*)(curr_buf->currline->data + curr_buf->currpnt));
for(; w > 0; w --)
delete_char();
}
#endif
if (curr_buf->ansimode)
curr_buf->currpnt = ansi2n(n2ansi(curr_buf->currpnt, curr_buf->currline), curr_buf->currline);
}
break;
case Ctrl('Y'): /* delete current line */
curr_buf->currline->len = curr_buf->currpnt = 0;
case Ctrl('K'): /* delete to end of line */
block_cancel();
if (curr_buf->currline->len == 0) {
textline_t *p = curr_buf->currline->next;
if (!p) {
p = curr_buf->currline->prev;
if (!p) {
curr_buf->currline->data[0] = 0;
break;
}
if (curr_buf->curr_window_line > 0) {
curr_buf->curr_window_line--;
}
curr_buf->currln--;
}
if (curr_buf->currline == curr_buf->top_of_win)
curr_buf->top_of_win = p;
delete_line(curr_buf->currline, 1);
curr_buf->currline = p;
curr_buf->redraw_everything = YEA;
curr_buf->oldcurrline = curr_buf->currline = adjustline(curr_buf->currline, WRAPMARGIN);
break;
}
else if (curr_buf->currline->len == curr_buf->currpnt) {
join(curr_buf->currline);
curr_buf->redraw_everything = YEA;
break;
}
curr_buf->currline->len = curr_buf->currpnt;
curr_buf->currline->data[curr_buf->currpnt] = '\0';
break;
}
if (curr_buf->currln < 0)
curr_buf->currln = 0;
if (curr_buf->curr_window_line < 0)
window_scroll_down();
else if (cursor_at_bottom_line())
window_scroll_up();
#ifdef DBCSAWARE
if(mbcs_mode)
curr_buf->currpnt = fix_cursor(curr_buf->currline->data, curr_buf->currpnt, FC_LEFT);
#endif
}
if (curr_buf->ansimode)
tmp = n2ansi(curr_buf->currpnt, curr_buf->currline);
else
tmp = curr_buf->currpnt;
if (tmp < t_columns - 1)
curr_buf->edit_margin = 0;
else
curr_buf->edit_margin = tmp / (t_columns - 8) * (t_columns - 8);
if (!curr_buf->redraw_everything) {
if (curr_buf->edit_margin != curr_buf->last_margin) {
curr_buf->last_margin = curr_buf->edit_margin;
curr_buf->redraw_everything = YEA;
} else {
move(curr_buf->curr_window_line, 0);
clrtoeol();
if (curr_buf->ansimode)
outs(curr_buf->currline->data);
else
{
int attr = EOATTR_NORMAL;
#ifdef PMORE_USE_ASCII_MOVIE
if (mf_movieFrameHeader(
(unsigned char*)curr_buf->currline->data,
(unsigned char*)curr_buf->currline->data + curr_buf->currline->len))
attr |= EOATTR_MOVIECODE;
#endif // PMORE_USE_ASCII_MOVIE
edit_outs_attr(&curr_buf->currline->data[curr_buf->edit_margin], attr);
}
outs(ANSI_RESET ANSI_CLRTOEND);
edit_msg();
}
} /* redraw */
} /* main event loop */
exit_edit_buffer();
}
int
vedit(char *fpath, int saveheader, int *islocal)
{
return vedit2(fpath, saveheader, islocal, 0);
}
/* vim:sw=4:nofoldenable
*/