diff options
| author | Eli Zaretskii | 2013-06-29 16:36:19 +0300 |
|---|---|---|
| committer | Eli Zaretskii | 2013-06-29 16:36:19 +0300 |
| commit | 4c672a0fec1d18cc1a445acf3e6935d681d4048f (patch) | |
| tree | c8fb2626c93a226bed5eaa0b92f95925734e893f /src | |
| parent | 73b1b3ad6196234984a29298bc66eabf1299de66 (diff) | |
| download | emacs-4c672a0fec1d18cc1a445acf3e6935d681d4048f.tar.gz emacs-4c672a0fec1d18cc1a445acf3e6935d681d4048f.zip | |
Implement visual-order cursor motion.
src/xdisp.c (Fmove_point_visually): New function.
lisp/bindings.el (visual-order-cursor-movement): New defcustom.
(right-char, left-char): Provide visual-order cursor motion by
calling move-point-visually. Update the doc strings.
doc/emacs/basic.texi (Moving Point): Document visual-order-cursor-movement
and its effect on right-char and left-char.
doc/lispref/display.texi (Bidirectional Display): Document move-point-visually.
etc/NEWS: Document the new feature.
Diffstat (limited to 'src')
| -rw-r--r-- | src/ChangeLog | 4 | ||||
| -rw-r--r-- | src/xdisp.c | 393 |
2 files changed, 397 insertions, 0 deletions
diff --git a/src/ChangeLog b/src/ChangeLog index b279f42e6b6..1e1b54a72d0 100644 --- a/src/ChangeLog +++ b/src/ChangeLog | |||
| @@ -1,3 +1,7 @@ | |||
| 1 | 2013-06-29 Eli Zaretskii <eliz@gnu.org> | ||
| 2 | |||
| 3 | * xdisp.c (Fmove_point_visually): New function. | ||
| 4 | |||
| 1 | 2013-06-28 Kenichi Handa <handa@gnu.org> | 5 | 2013-06-28 Kenichi Handa <handa@gnu.org> |
| 2 | 6 | ||
| 3 | * coding.h (define_coding_undecided_arg_index): New enum. | 7 | * coding.h (define_coding_undecided_arg_index): New enum. |
diff --git a/src/xdisp.c b/src/xdisp.c index 54ea325f642..420ff0c918b 100644 --- a/src/xdisp.c +++ b/src/xdisp.c | |||
| @@ -20051,6 +20051,398 @@ See also `bidi-paragraph-direction'. */) | |||
| 20051 | } | 20051 | } |
| 20052 | } | 20052 | } |
| 20053 | 20053 | ||
| 20054 | DEFUN ("move-point-visually", Fmove_point_visually, | ||
| 20055 | Smove_point_visually, 1, 1, 0, | ||
| 20056 | doc: /* Move point in the visual order in the specified DIRECTION. | ||
| 20057 | DIRECTION can be 1, meaning move to the right, or -1, which moves to the | ||
| 20058 | left. | ||
| 20059 | |||
| 20060 | Value is the new character position of point. */) | ||
| 20061 | (Lisp_Object direction) | ||
| 20062 | { | ||
| 20063 | struct window *w = XWINDOW (selected_window); | ||
| 20064 | struct buffer *b = NULL; | ||
| 20065 | struct glyph_row *row; | ||
| 20066 | int dir; | ||
| 20067 | Lisp_Object paragraph_dir; | ||
| 20068 | |||
| 20069 | #define ROW_GLYPH_NEWLINE_P(ROW,GLYPH) \ | ||
| 20070 | (!(ROW)->continued_p \ | ||
| 20071 | && INTEGERP ((GLYPH)->object) \ | ||
| 20072 | && (GLYPH)->type == CHAR_GLYPH \ | ||
| 20073 | && (GLYPH)->u.ch == ' ' \ | ||
| 20074 | && (GLYPH)->charpos >= 0 \ | ||
| 20075 | && !(GLYPH)->avoid_cursor_p) | ||
| 20076 | |||
| 20077 | CHECK_NUMBER (direction); | ||
| 20078 | dir = XINT (direction); | ||
| 20079 | if (dir > 0) | ||
| 20080 | dir = 1; | ||
| 20081 | else | ||
| 20082 | dir = -1; | ||
| 20083 | |||
| 20084 | if (BUFFERP (w->contents)) | ||
| 20085 | b = XBUFFER (w->contents); | ||
| 20086 | |||
| 20087 | /* If current matrix is up-to-date, we can use the information | ||
| 20088 | recorded in the glyphs, at least as long as the goal is on the | ||
| 20089 | screen. */ | ||
| 20090 | if (w->window_end_valid | ||
| 20091 | && !windows_or_buffers_changed | ||
| 20092 | && b | ||
| 20093 | && !b->clip_changed | ||
| 20094 | && !b->prevent_redisplay_optimizations_p | ||
| 20095 | && w->last_modified >= BUF_MODIFF (b) | ||
| 20096 | && w->last_overlay_modified >= BUF_OVERLAY_MODIFF (b) | ||
| 20097 | && w->cursor.vpos >= 0 | ||
| 20098 | && w->cursor.vpos < w->current_matrix->nrows | ||
| 20099 | && (row = MATRIX_ROW (w->current_matrix, w->cursor.vpos))->enabled_p) | ||
| 20100 | { | ||
| 20101 | struct glyph *g = row->glyphs[TEXT_AREA]; | ||
| 20102 | struct glyph *e = dir > 0 ? g + row->used[TEXT_AREA] : g - 1; | ||
| 20103 | struct glyph *gpt = g + w->cursor.hpos; | ||
| 20104 | |||
| 20105 | for (g = gpt + dir; (dir > 0 ? g < e : g > e); g += dir) | ||
| 20106 | { | ||
| 20107 | if (BUFFERP (g->object) && g->charpos != PT) | ||
| 20108 | { | ||
| 20109 | SET_PT (g->charpos); | ||
| 20110 | return make_number (PT); | ||
| 20111 | } | ||
| 20112 | else if (!INTEGERP (g->object) && g->object != gpt->object) | ||
| 20113 | { | ||
| 20114 | ptrdiff_t new_pos; | ||
| 20115 | |||
| 20116 | if (BUFFERP (gpt->object)) | ||
| 20117 | { | ||
| 20118 | new_pos = PT; | ||
| 20119 | if ((gpt->resolved_level - row->reversed_p) % 2 == 0) | ||
| 20120 | new_pos += (row->reversed_p ? -dir : dir); | ||
| 20121 | else | ||
| 20122 | new_pos -= (row->reversed_p ? -dir : dir);; | ||
| 20123 | } | ||
| 20124 | else if (BUFFERP (g->object)) | ||
| 20125 | new_pos = g->charpos; | ||
| 20126 | else | ||
| 20127 | break; | ||
| 20128 | SET_PT (new_pos); | ||
| 20129 | return make_number (PT); | ||
| 20130 | } | ||
| 20131 | else if (ROW_GLYPH_NEWLINE_P (row, g)) | ||
| 20132 | { | ||
| 20133 | /* Glyphs inserted at the end of a non-empty line for | ||
| 20134 | positioning the cursor have zero charpos, so we must | ||
| 20135 | deduce the value of point by other means. */ | ||
| 20136 | if (g->charpos > 0) | ||
| 20137 | SET_PT (g->charpos); | ||
| 20138 | else if (row->ends_at_zv_p && PT != ZV) | ||
| 20139 | SET_PT (ZV); | ||
| 20140 | else if (PT != MATRIX_ROW_END_CHARPOS (row) - 1) | ||
| 20141 | SET_PT (MATRIX_ROW_END_CHARPOS (row) - 1); | ||
| 20142 | else | ||
| 20143 | break; | ||
| 20144 | return make_number (PT); | ||
| 20145 | } | ||
| 20146 | } | ||
| 20147 | if (g == e || INTEGERP (g->object)) | ||
| 20148 | { | ||
| 20149 | if (row->truncated_on_left_p || row->truncated_on_right_p) | ||
| 20150 | goto simulate_display; | ||
| 20151 | if (!row->reversed_p) | ||
| 20152 | row += dir; | ||
| 20153 | else | ||
| 20154 | row -= dir; | ||
| 20155 | if (row < MATRIX_FIRST_TEXT_ROW (w->current_matrix) | ||
| 20156 | || row > MATRIX_BOTTOM_TEXT_ROW (w->current_matrix, w)) | ||
| 20157 | goto simulate_display; | ||
| 20158 | |||
| 20159 | if (dir > 0) | ||
| 20160 | { | ||
| 20161 | if (row->reversed_p && !row->continued_p) | ||
| 20162 | { | ||
| 20163 | SET_PT (MATRIX_ROW_END_CHARPOS (row) - 1); | ||
| 20164 | return make_number (PT); | ||
| 20165 | } | ||
| 20166 | g = row->glyphs[TEXT_AREA]; | ||
| 20167 | e = g + row->used[TEXT_AREA]; | ||
| 20168 | for ( ; g < e; g++) | ||
| 20169 | { | ||
| 20170 | if (BUFFERP (g->object) | ||
| 20171 | /* Empty lines have only one glyph, which stands | ||
| 20172 | for the newline, and whose charpos is the | ||
| 20173 | buffer position of the newline. */ | ||
| 20174 | || ROW_GLYPH_NEWLINE_P (row, g) | ||
| 20175 | /* When the buffer ends in a newline, the line at | ||
| 20176 | EOB also has one glyph, but its charpos is -1. */ | ||
| 20177 | || (row->ends_at_zv_p | ||
| 20178 | && !row->reversed_p | ||
| 20179 | && INTEGERP (g->object) | ||
| 20180 | && g->type == CHAR_GLYPH | ||
| 20181 | && g->u.ch == ' ')) | ||
| 20182 | { | ||
| 20183 | if (g->charpos > 0) | ||
| 20184 | SET_PT (g->charpos); | ||
| 20185 | else if (!row->reversed_p | ||
| 20186 | && row->ends_at_zv_p | ||
| 20187 | && PT != ZV) | ||
| 20188 | SET_PT (ZV); | ||
| 20189 | else | ||
| 20190 | continue; | ||
| 20191 | return make_number (PT); | ||
| 20192 | } | ||
| 20193 | } | ||
| 20194 | } | ||
| 20195 | else | ||
| 20196 | { | ||
| 20197 | if (!row->reversed_p && !row->continued_p) | ||
| 20198 | { | ||
| 20199 | SET_PT (MATRIX_ROW_END_CHARPOS (row) - 1); | ||
| 20200 | return make_number (PT); | ||
| 20201 | } | ||
| 20202 | e = row->glyphs[TEXT_AREA]; | ||
| 20203 | g = e + row->used[TEXT_AREA] - 1; | ||
| 20204 | for ( ; g >= e; g--) | ||
| 20205 | { | ||
| 20206 | if (BUFFERP (g->object) | ||
| 20207 | || (ROW_GLYPH_NEWLINE_P (row, g) | ||
| 20208 | && g->charpos > 0) | ||
| 20209 | /* Empty R2L lines on GUI frames have the buffer | ||
| 20210 | position of the newline stored in the stretch | ||
| 20211 | glyph. */ | ||
| 20212 | || g->type == STRETCH_GLYPH | ||
| 20213 | || (row->ends_at_zv_p | ||
| 20214 | && row->reversed_p | ||
| 20215 | && INTEGERP (g->object) | ||
| 20216 | && g->type == CHAR_GLYPH | ||
| 20217 | && g->u.ch == ' ')) | ||
| 20218 | { | ||
| 20219 | if (g->charpos > 0) | ||
| 20220 | SET_PT (g->charpos); | ||
| 20221 | else if (row->reversed_p | ||
| 20222 | && row->ends_at_zv_p | ||
| 20223 | && PT != ZV) | ||
| 20224 | SET_PT (ZV); | ||
| 20225 | else | ||
| 20226 | continue; | ||
| 20227 | return make_number (PT); | ||
| 20228 | } | ||
| 20229 | } | ||
| 20230 | } | ||
| 20231 | } | ||
| 20232 | } | ||
| 20233 | |||
| 20234 | simulate_display: | ||
| 20235 | |||
| 20236 | /* If we wind up here, we failed to move by using the glyphs, so we | ||
| 20237 | need to simulate display instead. */ | ||
| 20238 | |||
| 20239 | if (b) | ||
| 20240 | paragraph_dir = Fcurrent_bidi_paragraph_direction (w->contents); | ||
| 20241 | else | ||
| 20242 | paragraph_dir = Qleft_to_right; | ||
| 20243 | if (EQ (paragraph_dir, Qright_to_left)) | ||
| 20244 | dir = -dir; | ||
| 20245 | if (PT <= BEGV && dir < 0) | ||
| 20246 | xsignal0 (Qbeginning_of_buffer); | ||
| 20247 | else if (PT >= ZV && dir > 0) | ||
| 20248 | xsignal0 (Qend_of_buffer); | ||
| 20249 | else | ||
| 20250 | { | ||
| 20251 | struct text_pos pt; | ||
| 20252 | struct it it; | ||
| 20253 | int pt_x, target_x, pixel_width, pt_vpos; | ||
| 20254 | bool at_eol_p; | ||
| 20255 | bool disp_string_at_start_p = 0; | ||
| 20256 | bool overshoot_expected = false; | ||
| 20257 | bool target_is_eol_p = false; | ||
| 20258 | |||
| 20259 | /* Setup the arena. */ | ||
| 20260 | SET_TEXT_POS (pt, PT, PT_BYTE); | ||
| 20261 | start_display (&it, w, pt); | ||
| 20262 | |||
| 20263 | if (it.cmp_it.id < 0 | ||
| 20264 | && it.method == GET_FROM_STRING | ||
| 20265 | && it.area == TEXT_AREA | ||
| 20266 | && it.string_from_display_prop_p | ||
| 20267 | && (it.sp > 0 && it.stack[it.sp - 1].method == GET_FROM_BUFFER)) | ||
| 20268 | overshoot_expected = true; | ||
| 20269 | |||
| 20270 | /* Find the X coordinate of point. We start from the beginning | ||
| 20271 | of this or previous line to make sure we are before point in | ||
| 20272 | the logical order (since the move_it_* functions can only | ||
| 20273 | move forward). */ | ||
| 20274 | reseat_at_previous_visible_line_start (&it); | ||
| 20275 | it.current_x = it.hpos = it.current_y = it.vpos = 0; | ||
| 20276 | if (IT_CHARPOS (it) != PT) | ||
| 20277 | move_it_to (&it, overshoot_expected ? PT - 1 : PT, | ||
| 20278 | -1, -1, -1, MOVE_TO_POS); | ||
| 20279 | pt_x = it.current_x; | ||
| 20280 | pt_vpos = it.vpos; | ||
| 20281 | if (dir > 0 || overshoot_expected) | ||
| 20282 | { | ||
| 20283 | struct glyph_row *row = it.glyph_row; | ||
| 20284 | |||
| 20285 | /* When point is at beginning of line, we don't have | ||
| 20286 | information about the glyph there loaded into struct | ||
| 20287 | it. Calling get_next_display_element fixes that. */ | ||
| 20288 | if (pt_x == 0) | ||
| 20289 | get_next_display_element (&it); | ||
| 20290 | at_eol_p = ITERATOR_AT_END_OF_LINE_P (&it); | ||
| 20291 | it.glyph_row = NULL; | ||
| 20292 | PRODUCE_GLYPHS (&it); /* compute it.pixel_width */ | ||
| 20293 | it.glyph_row = row; | ||
| 20294 | /* PRODUCE_GLYPHS advances it.current_x, so we must restore | ||
| 20295 | it, lest it will become out of sync with it's buffer | ||
| 20296 | position. */ | ||
| 20297 | it.current_x = pt_x; | ||
| 20298 | } | ||
| 20299 | else | ||
| 20300 | at_eol_p = ITERATOR_AT_END_OF_LINE_P (&it); | ||
| 20301 | pixel_width = it.pixel_width; | ||
| 20302 | if (overshoot_expected && at_eol_p) | ||
| 20303 | pixel_width = 0; | ||
| 20304 | else if (pixel_width <= 0) | ||
| 20305 | pixel_width = 1; | ||
| 20306 | |||
| 20307 | /* If there's a display string at point, we are actually at the | ||
| 20308 | glyph to the left of point, so we need to correct the X | ||
| 20309 | coordinate. */ | ||
| 20310 | if (overshoot_expected) | ||
| 20311 | pt_x += pixel_width; | ||
| 20312 | |||
| 20313 | /* Compute target X coordinate, either to the left or to the | ||
| 20314 | right of point. On TTY frames, all characters have the same | ||
| 20315 | pixel width of 1, so we can use that. On GUI frames we don't | ||
| 20316 | have an easy way of getting at the pixel width of the | ||
| 20317 | character to the left of point, so we use a different method | ||
| 20318 | of getting to that place. */ | ||
| 20319 | if (dir > 0) | ||
| 20320 | target_x = pt_x + pixel_width; | ||
| 20321 | else | ||
| 20322 | target_x = pt_x - (!FRAME_WINDOW_P (it.f)) * pixel_width; | ||
| 20323 | |||
| 20324 | /* Target X coordinate could be one line above or below the line | ||
| 20325 | of point, in which case we need to adjust the target X | ||
| 20326 | coordinate. Also, if moving to the left, we need to begin at | ||
| 20327 | the left edge of the point's screen line. */ | ||
| 20328 | if (dir < 0) | ||
| 20329 | { | ||
| 20330 | if (pt_x > 0) | ||
| 20331 | { | ||
| 20332 | start_display (&it, w, pt); | ||
| 20333 | reseat_at_previous_visible_line_start (&it); | ||
| 20334 | it.current_x = it.current_y = it.hpos = 0; | ||
| 20335 | if (pt_vpos != 0) | ||
| 20336 | move_it_by_lines (&it, pt_vpos); | ||
| 20337 | } | ||
| 20338 | else | ||
| 20339 | { | ||
| 20340 | move_it_by_lines (&it, -1); | ||
| 20341 | target_x = it.last_visible_x - !FRAME_WINDOW_P (it.f); | ||
| 20342 | target_is_eol_p = true; | ||
| 20343 | } | ||
| 20344 | } | ||
| 20345 | else | ||
| 20346 | { | ||
| 20347 | if (at_eol_p | ||
| 20348 | || (target_x >= it.last_visible_x | ||
| 20349 | && it.line_wrap != TRUNCATE)) | ||
| 20350 | { | ||
| 20351 | if (pt_x > 0) | ||
| 20352 | move_it_by_lines (&it, 0); | ||
| 20353 | move_it_by_lines (&it, 1); | ||
| 20354 | target_x = 0; | ||
| 20355 | } | ||
| 20356 | } | ||
| 20357 | |||
| 20358 | /* Move to the target X coordinate. */ | ||
| 20359 | #ifdef HAVE_WINDOW_SYSTEM | ||
| 20360 | /* On GUI frames, as we don't know the X coordinate of the | ||
| 20361 | character to the left of point, moving point to the left | ||
| 20362 | requires walking, one grapheme cluster at a time, until we | ||
| 20363 | find ourself at a place immediately to the left of the | ||
| 20364 | character at point. */ | ||
| 20365 | if (FRAME_WINDOW_P (it.f) && dir < 0) | ||
| 20366 | { | ||
| 20367 | struct text_pos new_pos = it.current.pos; | ||
| 20368 | enum move_it_result rc = MOVE_X_REACHED; | ||
| 20369 | |||
| 20370 | while (it.current_x + it.pixel_width <= target_x | ||
| 20371 | && rc == MOVE_X_REACHED) | ||
| 20372 | { | ||
| 20373 | int new_x = it.current_x + it.pixel_width; | ||
| 20374 | |||
| 20375 | new_pos = it.current.pos; | ||
| 20376 | if (new_x == it.current_x) | ||
| 20377 | new_x++; | ||
| 20378 | rc = move_it_in_display_line_to (&it, ZV, new_x, | ||
| 20379 | MOVE_TO_POS | MOVE_TO_X); | ||
| 20380 | if (ITERATOR_AT_END_OF_LINE_P (&it) && !target_is_eol_p) | ||
| 20381 | break; | ||
| 20382 | } | ||
| 20383 | /* If we ended up on a composed character inside | ||
| 20384 | bidi-reordered text (e.g., Hebrew text with diacriticals), | ||
| 20385 | the iterator gives us the buffer position of the last (in | ||
| 20386 | logical order) character of the composed grapheme cluster, | ||
| 20387 | which is not what we want. So we cheat: we compute the | ||
| 20388 | character position of the character that follows (in the | ||
| 20389 | logical order) the one where the above loop stopped. That | ||
| 20390 | character will appear on display to the left of point. */ | ||
| 20391 | if (it.bidi_p | ||
| 20392 | && it.bidi_it.scan_dir == -1 | ||
| 20393 | && new_pos.charpos - IT_CHARPOS (it) > 1) | ||
| 20394 | { | ||
| 20395 | new_pos.charpos = IT_CHARPOS (it) + 1; | ||
| 20396 | new_pos.bytepos = CHAR_TO_BYTE (new_pos.charpos); | ||
| 20397 | } | ||
| 20398 | it.current.pos = new_pos; | ||
| 20399 | } | ||
| 20400 | else | ||
| 20401 | #endif | ||
| 20402 | if (it.current_x != target_x) | ||
| 20403 | move_it_in_display_line_to (&it, ZV, target_x, MOVE_TO_POS | MOVE_TO_X); | ||
| 20404 | |||
| 20405 | /* When lines are truncated, the above loop will stop at the | ||
| 20406 | window edge. But we want to get to the end of line, even if | ||
| 20407 | it is beyond the window edge; automatic hscroll will then | ||
| 20408 | scroll the window to show point as appropriate. */ | ||
| 20409 | if (target_is_eol_p && it.line_wrap == TRUNCATE | ||
| 20410 | && get_next_display_element (&it)) | ||
| 20411 | { | ||
| 20412 | struct text_pos new_pos = it.current.pos; | ||
| 20413 | |||
| 20414 | while (!ITERATOR_AT_END_OF_LINE_P (&it)) | ||
| 20415 | { | ||
| 20416 | set_iterator_to_next (&it, 0); | ||
| 20417 | if (it.method == GET_FROM_BUFFER) | ||
| 20418 | new_pos = it.current.pos; | ||
| 20419 | if (!get_next_display_element (&it)) | ||
| 20420 | break; | ||
| 20421 | } | ||
| 20422 | |||
| 20423 | it.current.pos = new_pos; | ||
| 20424 | } | ||
| 20425 | |||
| 20426 | /* If we ended up in a display string that covers point, move to | ||
| 20427 | buffer position to the right in the visual order. */ | ||
| 20428 | if (dir > 0) | ||
| 20429 | { | ||
| 20430 | while (IT_CHARPOS (it) == PT) | ||
| 20431 | { | ||
| 20432 | set_iterator_to_next (&it, 0); | ||
| 20433 | if (!get_next_display_element (&it)) | ||
| 20434 | break; | ||
| 20435 | } | ||
| 20436 | } | ||
| 20437 | |||
| 20438 | /* Move point to that position. */ | ||
| 20439 | SET_PT_BOTH (IT_CHARPOS (it), IT_BYTEPOS (it)); | ||
| 20440 | } | ||
| 20441 | |||
| 20442 | return make_number (PT); | ||
| 20443 | |||
| 20444 | #undef ROW_GLYPH_NEWLINE_P | ||
| 20445 | } | ||
| 20054 | 20446 | ||
| 20055 | 20447 | ||
| 20056 | /*********************************************************************** | 20448 | /*********************************************************************** |
| @@ -28713,6 +29105,7 @@ syms_of_xdisp (void) | |||
| 28713 | defsubr (&Sformat_mode_line); | 29105 | defsubr (&Sformat_mode_line); |
| 28714 | defsubr (&Sinvisible_p); | 29106 | defsubr (&Sinvisible_p); |
| 28715 | defsubr (&Scurrent_bidi_paragraph_direction); | 29107 | defsubr (&Scurrent_bidi_paragraph_direction); |
| 29108 | defsubr (&Smove_point_visually); | ||
| 28716 | 29109 | ||
| 28717 | DEFSYM (Qmenu_bar_update_hook, "menu-bar-update-hook"); | 29110 | DEFSYM (Qmenu_bar_update_hook, "menu-bar-update-hook"); |
| 28718 | DEFSYM (Qoverriding_terminal_local_map, "overriding-terminal-local-map"); | 29111 | DEFSYM (Qoverriding_terminal_local_map, "overriding-terminal-local-map"); |