This is ui.c in view mode; [Download] [Up]
/* vi:set ts=4 sw=4: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. */ /* * ui.c: functions that handle the user interface. * 1. Keyboard input stuff, and a bit of windowing stuff. These are called * before the machine specific stuff (mch_*) so that we can call the GUI * stuff instead if the GUI is running. * 2. Clipboard stuff. * 3. Input buffer stuff. */ #include "vim.h" #include "globals.h" #include "proto.h" #include "option.h" void ui_write(s, len) char_u *s; int len; { #ifdef USE_GUI if (gui.in_use && !gui.dying) { gui_write(s, len); if (p_wd) gui_wait_for_chars(p_wd); return; } #endif mch_write(s, len); } /* * ui_inchar(): low level input funcion. * Get a characters from the keyboard. * Return the number of characters that are available. * If wtime == 0 do not wait for characters. * If wtime == -1 wait forever for characters. * If wtime > 0 wait wtime milliseconds for a character. */ int ui_inchar(buf, maxlen, wtime) char_u *buf; int maxlen; long wtime; /* don't use "time", MIPS cannot handle it */ { #ifdef USE_GUI if (gui.in_use) { if (!gui_wait_for_chars(wtime)) return 0; return read_from_input_buf(buf, (long)maxlen); } #endif return mch_inchar(buf, maxlen, wtime); } /* * return non-zero if a character is available */ int ui_char_avail() { #ifdef USE_GUI if (gui.in_use) { gui_mch_update(); return !is_input_buf_empty(); } #endif return mch_char_avail(); } /* * Delay for the given number of milliseconds. If ignoreinput is FALSE then we * cancel the delay if a key is hit. */ void ui_delay(msec, ignoreinput) long msec; int ignoreinput; { #ifdef USE_GUI if (gui.in_use && !ignoreinput) gui_wait_for_chars(msec); else #endif mch_delay(msec, ignoreinput); } /* * If the machine has job control, use it to suspend the program, * otherwise fake it by starting a new shell. * When running the GUI iconify the window. */ void ui_suspend() { #ifdef USE_GUI if (gui.in_use) { gui_mch_iconify(); return; } #endif mch_suspend(); } /* * When the OS can't really suspend, call this function to start a shell. */ void suspend_shell() { MSG_PUTS("new shell started\n"); mch_call_shell(NULL, SHELL_COOKED); need_check_timestamps = TRUE; } int ui_can_restore_title() { #ifdef USE_GUI /* * If GUI is (going to be) used, we can always set the window title. * Saves a bit of time, because the X11 display server does not need to be * contacted. */ if (gui.starting || gui.in_use) return TRUE; #endif return mch_can_restore_title(); } int ui_can_restore_icon() { #ifdef USE_GUI /* * If GUI is (going to be) used, we can always set the icon name. * Saves a bit of time, because the X11 display server does not need to be * contacted. */ if (gui.starting || gui.in_use) return TRUE; #endif return mch_can_restore_icon(); } /* * Try to get the current window size. Put the result in Rows and Columns. */ int ui_get_winsize() { #ifdef USE_GUI if (gui.in_use) return gui_get_winsize(); #endif return mch_get_winsize(); } /* * Set the size of the window according to Rows and Columns, if possible. */ void ui_set_winsize() { #ifdef USE_GUI if (gui.in_use) gui_set_winsize(FALSE); else #endif mch_set_winsize(); } void ui_breakcheck() { #ifdef USE_GUI if (gui.in_use) gui_mch_update(); else #endif /* USE_GUI */ mch_breakcheck(); } /***************************************************************************** * Functions for copying and pasting text between applications. * This is always included in a GUI version, but may also be included when the * clipboard and mouse is available to a terminal version such as xterm. * Note: there are some more functions in ops.c that handle selection stuff. */ #ifdef USE_CLIPBOARD /* All the clipboard info */ VimClipboard clipboard; static void clip_own_selection __ARGS((void)); static void clip_invert_area __ARGS((int, int, int, int)); static void clip_yank_non_visual_selection __ARGS((int, int, int, int)); static void clip_get_word_boundaries __ARGS((VimClipboard *, int, int)); static int clip_get_line_end __ARGS((int)); static void clip_update_non_visual_selection __ARGS((VimClipboard *, int, int, int, int)); #define char_class(c) (c <= ' ' ? ' ' : iswordchar(c)) /* * Selection stuff using Visual mode, for cutting and pasting text to other * windows. */ /* * Call this to initialise the clipboard. Pass it FALSE if the clipboard code * is included, but the clipboard can not be used, or TRUE if the clipboard can * be used. Eg unix may call this with FALSE, then call it again with TRUE if * the GUI starts. */ void clip_init(can_use) int can_use; { clipboard.available = can_use; clipboard.owned = FALSE; clipboard.start.lnum = 0; clipboard.start.col = 0; clipboard.end.lnum = 0; clipboard.end.col = 0; clipboard.state = SELECT_CLEARED; } /* * Check whether the VIsual area has changed, and if so try to become the owner * of the selection, and free any old converted selection we may still have * lying around. If the VIsual mode has ended, make a copy of what was * selected so we can still give it to others. Will probably have to make sure * this is called whenever VIsual mode is ended. */ void clip_update_selection() { /* If visual mode is only due to a redo command ("."), then ignore it */ if (redo_VIsual_busy) return; if (!VIsual_active) { clip_clear_selection(); clipboard.start = clipboard.end = VIsual; } else if (lt(VIsual, curwin->w_cursor)) { if (!equal(clipboard.start, VIsual) || !equal(clipboard.end, curwin->w_cursor)) { clip_clear_selection(); clipboard.start = VIsual; clipboard.end = curwin->w_cursor; clip_free_selection(); clip_own_selection(); } } else { if (!equal(clipboard.start, curwin->w_cursor) || !equal(clipboard.end, VIsual)) { clip_clear_selection(); clipboard.start = curwin->w_cursor; clipboard.end = VIsual; clip_free_selection(); clip_own_selection(); } } } static void clip_own_selection() { /* * Also want to check somehow that we are reading from the keyboard rather * than a mapping etc. */ if (!clipboard.owned) { clipboard.owned = TRUE; if (clip_mch_own_selection()) /* May alter clipboard.owned */ clip_free_selection(); else clipboard.owned = FALSE; } } void clip_lose_selection() { clip_free_selection(); clipboard.owned = FALSE; clip_clear_selection(); clip_mch_lose_selection(); } void clip_copy_selection() { if (VIsual_active) { if (vim_strchr(p_guioptions, GO_ASEL) == NULL) clip_update_selection(); clip_own_selection(); if (clipboard.owned) clip_get_selection(); } } void clip_auto_select() { if (vim_strchr(p_guioptions, GO_ASEL) != NULL) clip_copy_selection(); } #ifdef USE_GUI /* * Stuff for general mouse selection, without using Visual mode. */ static int clip_compare_pos __ARGS((int row1, int col1, int row2, int col2)); /* * Compare two screen positions ala strcmp() */ static int clip_compare_pos(row1, col1, row2, col2) int row1; int col1; int row2; int col2; { if (row1 > row2) return( 1); if (row1 < row2) return(-1); if (col1 > col2) return( 1); if (col1 < col2) return(-1); return( 0); } /* * Start out the selection */ void clip_start_selection(button, x, y, repeated_click, modifiers) int button; int x; int y; int repeated_click; int_u modifiers; { VimClipboard *cb = &clipboard; if (cb->state == SELECT_DONE) clip_clear_selection(); cb->start.lnum = Y_2_ROW(y); cb->start.col = X_2_COL(x); cb->end = cb->start; cb->origin_row = (short_u)cb->start.lnum; cb->state = SELECT_IN_PROGRESS; if (repeated_click) { if (++(cb->mode) > SELECT_MODE_LINE) cb->mode = SELECT_MODE_CHAR; } else cb->mode = SELECT_MODE_CHAR; #ifdef USE_GUI /* clear the cursor until the selection is made */ gui_undraw_cursor(); #endif switch (cb->mode) { case SELECT_MODE_CHAR: cb->origin_start_col = cb->start.col; cb->word_end_col = clip_get_line_end(cb->start.lnum); break; case SELECT_MODE_WORD: clip_get_word_boundaries(cb, cb->start.lnum, cb->start.col); cb->origin_start_col = cb->word_start_col; cb->origin_end_col = cb->word_end_col; clip_invert_area(cb->start.lnum, cb->word_start_col, cb->end.lnum, cb->word_end_col); cb->start.col = cb->word_start_col; cb->end.col = cb->word_end_col; break; case SELECT_MODE_LINE: clip_invert_area(cb->start.lnum, 0, cb->start.lnum, Columns); cb->start.col = 0; cb->end.col = Columns; break; } cb->prev = cb->start; #ifdef DEBUG_SELECTION printf("Selection started at (%u,%u)\n", cb->start.lnum, cb->start.col); #endif } /* * Continue processing the selection */ void clip_process_selection(button, x, y, repeated_click, modifiers) int button; int x; int y; int repeated_click; int_u modifiers; { VimClipboard *cb = &clipboard; int row; int_u col; int diff; if (button == MOUSE_RELEASE) { /* Check to make sure we have something selected */ if (cb->start.lnum == cb->end.lnum && cb->start.col == cb->end.col) { #ifdef USE_GUI gui_update_cursor(FALSE); #endif cb->state = SELECT_CLEARED; return; } #ifdef DEBUG_SELECTION printf("Selection ended: (%u,%u) to (%u,%u)\n", cb->start.lnum, cb->start.col, cb->end.lnum, cb->end.col); #endif clip_free_selection(); clip_own_selection(); clip_yank_non_visual_selection(cb->start.lnum, cb->start.col, cb->end.lnum, cb->end.col); #ifdef USE_GUI gui_update_cursor(FALSE); #endif cb->state = SELECT_DONE; return; } row = Y_2_ROW(y); col = X_2_COL(x); row = check_row(row); col = check_col(col); if (col == cb->prev.col && row == cb->prev.lnum) return; /* * When extending the selection with the right mouse button, swap the * start and end if the position is before half the selection */ if (cb->state == SELECT_DONE && button == MOUSE_RIGHT) { /* * If the click is before the start, or the click is inside the * selection and the start is the closest side, set the origin to the * end of the selection. */ if (clip_compare_pos(row, col, cb->start.lnum, cb->start.col) < 0 || (clip_compare_pos(row, col, cb->end.lnum, cb->end.col) < 0 && (((cb->start.lnum == cb->end.lnum && cb->end.col - col > col - cb->start.col)) || ((diff = (cb->end.lnum - row) - (row - cb->start.lnum)) > 0 || (diff == 0 && col < (cb->start.col + cb->end.col) / 2))))) { cb->origin_row = (short_u)cb->end.lnum; cb->origin_start_col = cb->end.col - 1; cb->origin_end_col = cb->end.col; } else { cb->origin_row = (short_u)cb->start.lnum; cb->origin_start_col = cb->start.col; cb->origin_end_col = cb->start.col; } if (cb->mode == SELECT_MODE_WORD) { clip_get_word_boundaries(cb, cb->origin_row, cb->origin_start_col); cb->origin_start_col = cb->word_start_col; cb->origin_end_col = cb->word_end_col; } } /* set state, for when using the right mouse button */ cb->state = SELECT_IN_PROGRESS; #ifdef DEBUG_SELECTION printf("Selection extending to (%d,%d)\n", row, col); #endif switch (cb->mode) { case SELECT_MODE_CHAR: /* If we're on a different line, find where the line ends */ if (row != cb->prev.lnum) cb->word_end_col = clip_get_line_end(row); /* See if we are before or after the origin of the selection */ if (clip_compare_pos(row, col, cb->origin_row, cb->origin_start_col) >= 0) { if (col >= (int)cb->word_end_col) clip_update_non_visual_selection(cb, cb->origin_row, cb->origin_start_col, row, Columns); else clip_update_non_visual_selection(cb, cb->origin_row, cb->origin_start_col, row, col + 1); } else { if (col >= (int)cb->word_end_col) clip_update_non_visual_selection(cb, row, cb->word_end_col, cb->origin_row, cb->origin_start_col + 1); else clip_update_non_visual_selection(cb, row, col, cb->origin_row, cb->origin_start_col + 1); } break; case SELECT_MODE_WORD: /* If we are still within the same word, do nothing */ if (row == cb->prev.lnum && col >= (int)cb->word_start_col && col < (int)cb->word_end_col) return; /* Get new word boundaries */ clip_get_word_boundaries(cb, row, col); /* Handle being after the origin point of selection */ if (clip_compare_pos(row, col, cb->origin_row, cb->origin_start_col) >= 0) clip_update_non_visual_selection(cb, cb->origin_row, cb->origin_start_col, row, cb->word_end_col); else clip_update_non_visual_selection(cb, row, cb->word_start_col, cb->origin_row, cb->origin_end_col); break; case SELECT_MODE_LINE: if (row == cb->prev.lnum) return; if (clip_compare_pos(row, col, cb->origin_row, cb->origin_start_col) >= 0) clip_update_non_visual_selection(cb, cb->origin_row, 0, row, Columns); else clip_update_non_visual_selection(cb, row, 0, cb->origin_row, Columns); break; } cb->prev.lnum = row; cb->prev.col = col; #ifdef DEBUG_SELECTION printf("Selection is: (%u,%u) to (%u,%u)\n", cb->start.lnum, cb->start.col, cb->end.lnum, cb->end.col); #endif } /* * Called after an Expose event to redraw the selection */ void clip_redraw_selection(x, y, w, h) int x; int y; int w; int h; { VimClipboard *cb = &clipboard; int row1, col1, row2, col2; int row; int start; int end; if (cb->state == SELECT_CLEARED) return; #ifdef USE_GUI /* TODO: how do we invert for non-GUI versions? */ row1 = Y_2_ROW(y); col1 = X_2_COL(x); row2 = Y_2_ROW(y + h - 1); col2 = X_2_COL(x + w - 1); /* Limit the rows that need to be re-drawn */ if (cb->start.lnum > row1) row1 = cb->start.lnum; if (cb->end.lnum < row2) row2 = cb->end.lnum; /* Look at each row that might need to be re-drawn */ for (row = row1; row <= row2; row++) { /* For the first selection row, use the starting selection column */ if (row == cb->start.lnum) start = cb->start.col; else start = 0; /* For the last selection row, use the ending selection column */ if (row == cb->end.lnum) end = cb->end.col; else end = Columns; if (col1 > start) start = col1; if (col2 < end) end = col2 + 1; if (end > start) gui_mch_invert_rectangle(row, start, 1, end - start); } #endif } /* * Called from outside to clear selected region from the display */ void clip_clear_selection() { VimClipboard *cb = &clipboard; if (cb->state == SELECT_CLEARED) return; clip_invert_area(cb->start.lnum, cb->start.col, cb->end.lnum, cb->end.col); cb->state = SELECT_CLEARED; } /* * Invert a region of the display between a starting and ending row and column */ static void clip_invert_area(row1, col1, row2, col2) int row1; int col1; int row2; int col2; { #ifdef USE_GUI /* TODO: how do we invert for non-GUI versions? */ /* Swap the from and to positions so the from is always before */ if (clip_compare_pos(row1, col1, row2, col2) > 0) { int tmp_row, tmp_col; tmp_row = row1; tmp_col = col1; row1 = row2; col1 = col2; row2 = tmp_row; col2 = tmp_col; } /* If all on the same line, do it the easy way */ if (row1 == row2) { gui_mch_invert_rectangle(row1, col1, 1, col2 - col1); return; } /* Handle a piece of the first line */ if (col1 > 0) { gui_mch_invert_rectangle(row1, col1, 1, Columns - col1); row1++; } /* Handle a piece of the last line */ if (col2 < Columns - 1) { gui_mch_invert_rectangle(row2, 0, 1, col2); row2--; } /* Handle the rectangle thats left */ if (row2 >= row1) gui_mch_invert_rectangle(row1, 0, row2 - row1 + 1, Columns); #endif } /* * Yank the currently selected area into the special selection buffer so it * will be available for pasting. */ static void clip_yank_non_visual_selection(row1, col1, row2, col2) int row1; int col1; int row2; int col2; { char_u *buffer; char_u *bufp; int row; int start_col; int end_col; int line_end_col; int add_newline_flag = FALSE; /* * Make sure row1 <= row2, and if row1 == row2 that col1 <= col2. */ if (row1 > row2) { row = row1; row1 = row2; row2 = row; row = col1; col1 = col2; col2 = row; } else if (row1 == row2 && col1 > col2) { row = col1; col1 = col2; col2 = row; } /* Create a temporary buffer for storing the text */ buffer = lalloc((row2 - row1 + 1) * Columns + 1, TRUE); if (buffer == NULL) /* out of memory */ return; /* Process each row in the selection */ for (bufp = buffer, row = row1; row <= row2; row++) { if (row == row1) start_col = col1; else start_col = 0; if (row == row2) end_col = col2; else end_col = Columns; line_end_col = clip_get_line_end(row); /* See if we need to nuke some trailing whitespace */ if (end_col >= Columns && (row < row2 || end_col > line_end_col)) { /* Get rid of trailing whitespace */ end_col = line_end_col; if (end_col < start_col) end_col = start_col; /* If the last line extended to the end, add an extra newline */ if (row == row2) add_newline_flag = TRUE; } /* If after the first row, we need to always add a newline */ if (row > row1) *bufp++ = NL; if (row < screen_Rows && end_col <= screen_Columns) { STRNCPY(bufp, &LinePointers[row][start_col], end_col - start_col); bufp += end_col - start_col; } } /* Add a newline at the end if the selection ended there */ if (add_newline_flag) *bufp++ = NL; clip_yank_selection(MCHAR, buffer, bufp - buffer); vim_free(buffer); } /* * Find the starting and ending positions of the word at the given row and * column. */ static void clip_get_word_boundaries(cb, row, col) VimClipboard *cb; int row; int col; { char start_class; int temp_col; if (row >= screen_Rows || col >= screen_Columns) return; start_class = char_class(LinePointers[row][col]); temp_col = col; for ( ; temp_col > 0; temp_col--) if (char_class(LinePointers[row][temp_col - 1]) != start_class) break; cb->word_start_col = temp_col; temp_col = col; for ( ; temp_col < screen_Columns; temp_col++) if (char_class(LinePointers[row][temp_col]) != start_class) break; cb->word_end_col = temp_col; #ifdef DEBUG_SELECTION printf("Current word: col %u to %u\n", cb->word_start_col, cb->word_end_col); #endif } /* * Find the column position for the last non-whitespace character on the given * line. */ static int clip_get_line_end(row) int row; { int i; if (row >= screen_Rows) return 0; for (i = screen_Columns; i > 0; i--) if (LinePointers[row][i - 1] != ' ') break; return i; } /* * Update the currently selected region by adding and/or subtracting from the * beginning or end and inverting the changed area(s). */ static void clip_update_non_visual_selection(cb, row1, col1, row2, col2) VimClipboard *cb; int row1; int col1; int row2; int col2; { /* See if we changed at the beginning of the selection */ if (row1 != cb->start.lnum || col1 != (int)cb->start.col) { clip_invert_area(row1, col1, cb->start.lnum, cb->start.col); cb->start.lnum = row1; cb->start.col = col1; } /* See if we changed at the end of the selection */ if (row2 != cb->end.lnum || col2 != (int)cb->end.col) { clip_invert_area(row2, col2, cb->end.lnum, cb->end.col); cb->end.lnum = row2; cb->end.col = col2; } } #else /* If USE_GUI not defined */ /* * Called from outside to clear selected region from the display */ void clip_clear_selection() { /* * Dummy version for now... the point of this code is to set the selected * area back to "normal" colour if we are clearing the selection. As we * don't have GUI-style mouse selection, we can ignore this for now. * Eventually we could actually invert the area in a terminal by redrawing * in reverse mode, but we don't do that yet. */ clipboard.state = SELECT_CLEARED; } #endif /* USE_GUI */ #endif /* USE_CLIPBOARD */ /***************************************************************************** * Functions that handle the input buffer. * This is used for any GUI version, and the unix terminal version. * * For Unix, the input characters are buffered to be able to check for a * CTRL-C. This should be done with signals, but I don't know how to do that * in a portable way for a tty in RAW mode. */ #if defined(UNIX) || defined(USE_GUI) || defined(OS2) /* * Internal typeahead buffer. Includes extra space for long key code * descriptions which would otherwise overflow. The buffer is considered full * when only this extra space (or part of it) remains. */ #define INBUFLEN 250 static char_u inbuf[INBUFLEN + MAX_KEY_CODE_LEN]; static int inbufcount = 0; /* number of chars in inbuf[] */ /* * is_input_buf_full(), is_input_buf_empty(), add_to_input_buf(), and * trash_input_buf() are functions for manipulating the input buffer. These * are used by the gui_* calls when a GUI is used to handle keyboard input. */ int is_input_buf_full() { return (inbufcount >= INBUFLEN); } int is_input_buf_empty() { return (inbufcount == 0); } /* Add the given bytes to the input buffer */ void add_to_input_buf(s, len) char_u *s; int len; { if (inbufcount + len > INBUFLEN + MAX_KEY_CODE_LEN) return; /* Shouldn't ever happen! */ while (len--) inbuf[inbufcount++] = *s++; } /* Remove everything from the input buffer. Called when ^C is found */ void trash_input_buf() { inbufcount = 0; } /* * Read as much data from the input buffer as possible up to maxlen, and store * it in buf. * Note: this function used to be Read() in unix.c */ int read_from_input_buf(buf, maxlen) char_u *buf; long maxlen; { if (inbufcount == 0) /* if the buffer is empty, fill it */ fill_input_buf(TRUE); if (maxlen > inbufcount) maxlen = inbufcount; vim_memmove(buf, inbuf, (size_t)maxlen); inbufcount -= maxlen; if (inbufcount) vim_memmove(inbuf, inbuf + maxlen, (size_t)inbufcount); return (int)maxlen; } void fill_input_buf(exit_on_error) int exit_on_error; { #if defined(UNIX) || defined(OS2) int len; int try; static int did_read_something = FALSE; #endif #ifdef USE_GUI if (gui.in_use) { gui_mch_update(); return; } #endif #if defined(UNIX) || defined(OS2) if (is_input_buf_full()) return; /* * Fill_input_buf() is only called when we really need a character. * If we can't get any, but there is some in the buffer, just return. * If we can't get any, and there isn't any in the buffer, we give up and * exit Vim. */ for (try = 0; try < 100; ++try) { len = read(read_cmd_fd, (char *)inbuf + inbufcount, (size_t)(INBUFLEN - inbufcount)); if (len > 0) break; /* * If reading stdin results in an error, continue reading stderr. * This helps when using "foo | xargs vim". */ if (!did_read_something && !isatty(read_cmd_fd) && read_cmd_fd == 0) read_cmd_fd = 2; if (!exit_on_error) return; } if (len <= 0) { windgoto((int)Rows - 1, 0); fprintf(stderr, "Vim: Error reading input, exiting...\n"); ml_sync_all(FALSE, TRUE); /* preserve all swap files */ getout(1); } did_read_something = TRUE; while (len-- > 0) { /* * if a CTRL-C was typed, remove it from the buffer and set got_int */ if (inbuf[inbufcount] == 3) { /* remove everything typed before the CTRL-C */ vim_memmove(inbuf, inbuf + inbufcount, (size_t)(len + 1)); inbufcount = 0; got_int = TRUE; } ++inbufcount; } #endif /* UNIX or OS2 */ } #endif /* defined(UNIX) || defined(USE_GUI) || defined(OS2) */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.