Mercurial > hg > xemacs-beta
diff src/line-number.c @ 211:78478c60bfcd r20-4b4
Import from CVS: tag r20-4b4
author | cvs |
---|---|
date | Mon, 13 Aug 2007 10:05:51 +0200 |
parents | |
children | 262b8bb4a523 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/line-number.c Mon Aug 13 10:05:51 2007 +0200 @@ -0,0 +1,300 @@ +/* Line number cache routines. + Copyright (C) 1997 Free Software Foundation, Inc. + +This file is part of XEmacs. + +XEmacs is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) any +later version. + +XEmacs is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with XEmacs; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + +/* Synched up with: Not in FSF. */ + +/* To calculate the line numbers, redisplay must count the newlines + from a known position. This used to be BUF_BEGV, but this made the + redisplay extremely slow for large buffers, because Emacs must + rescan the whole buffer at each redisplay, just to count the + newlines. + + To make line numbering efficient, we maintain a simple-minded + cache. Each buffer contains a small ring of known positions, each + element of the ring being a Lisp_Object -- either nil or a cons of + a buffer position and the line number (beginning with 0). + + When calculating the line numbers, this cache is consulted if it + would otherwise take too much time to count the newlines in the + buffer (see the comment to window_line_number.) + + Insertion and deletions that contain/delete newlines invalidate the + cached positions after the insertion point. This guarantees + relatively fast line numbers caching (even in buffers where point + moves a lot), and low memory usage. + + NOTE: line-number cache should not be confused with line-start + cache. Line-start cache (a part of redisplay) works with the + display lines, whereas this works with the buffer lines (literally + counting the newlines). */ + +#include <config.h> +#include "lisp.h" +#include "buffer.h" +#include "insdel.h" + +#include "line-number.h" + + +/* #### The following three values could use some tweaking, to get the + best performance. */ + +/* Size of the ring. The current code expects this to be a small + number. If you make it much bigger, you should probably tr yto + optimize the various routines to keep it sorted. */ +#define LINE_NUMBER_RING_SIZE 8 + +/* How much traversal has to be exceeded for two points to be + considered "far" from each other. When two points are far, cache + will be used. You can set this to a small value for debugging + purposes. */ +#define LINE_NUMBER_FAR 16384 + +/* How large a string has to be to give up searching it for newlines, + before change. */ +#define LINE_NUMBER_LARGE_STRING 256 + +/* To be used only when you *know* the cache has been allocated! */ +#define LINE_NUMBER_RING(b) (XCAR ((b)->line_number_cache)) +#define LINE_NUMBER_BEGV(b) (XCDR ((b)->line_number_cache)) + + +/* Initialize the cache. Cache is (in pseudo-BNF): + + CACHE = nil | INITIALIZED-CACHE + INITITIALIZED-CACHE = cons (RING, BEGV-LINE) + RING = vector (*RING-ELEMENT) + RING-ELEMENT = nil | RING-PAIR + RING-PAIR = cons (marker, integer) + BEGV-LINE = integer + + Line number cache should never, ever, be visible to Lisp (because + destructively modifying its elements can cause crashes.) Debug it + using debug_print (current_buffer->last_number_cache). */ +static void +allocate_line_number_cache (struct buffer *b) +{ + b->line_number_cache = Fcons (make_vector (LINE_NUMBER_RING_SIZE, Qnil), + Qzero); + narrow_line_number_cache (b); +} + +/* Update line_number_begv. Do it only if the line number cache is + already initialized. */ +void +narrow_line_number_cache (struct buffer *b) +{ + EMACS_INT lots = 999999999, shortage; + + if (NILP (b->line_number_cache)) + return; + /* Optimization: if BUF_BEG == BUF_BEGV (as is the case after Fwiden + and save_restriction_restore), don't bother calling scan_buffer. */ + if (BUF_BEG (b) == BUF_BEGV (b)) + { + LINE_NUMBER_BEGV (b) = Qzero; + return; + } + /* Count the newlines between beginning of buffer and beginning of + the visible portion of the buffer. */ + scan_buffer (b, '\n', BUF_BEG (b), BUF_BEGV (b), lots, + (int *)&shortage, 0); + LINE_NUMBER_BEGV (b) = make_int (lots - shortage); +} + +/* Invalidate the line number cache positions that lie after POS. */ +static void +invalidate_line_number_cache (struct buffer *b, Bufpos pos) +{ + EMACS_INT i, j; + Lisp_Object *ring = XVECTOR_DATA (LINE_NUMBER_RING (b)); + Lisp_Object lisp_buffer = make_buffer (b); + + for (i = 0; i < LINE_NUMBER_RING_SIZE; i++) + { + if (!CONSP (ring[i])) + break; + if (marker_position (XCAR (ring[i])) > pos) + { + /* Get the marker out of the way. */ + Fset_marker (XCAR (ring[i]), Qnil, lisp_buffer); + /* ...and shift the ring elements, up to the first nil. */ + for (j = i; !NILP (ring[j]) && j < LINE_NUMBER_RING_SIZE - 1; j++) + ring[j] = ring[j + 1]; + ring[j] = Qnil; + /* Must reevaluate the thing at position i. */ + i--; + } + } +} + +/* Invalidate the cache positions after POS, if the string to be + inserted contains a newline. If the string is too large (larger + than LINE_NUMBER_LARGE_STRING), invalidate the cache positions + after POS without prior search. + + This will do nothing, if cache is uninitialized. */ +void +insert_invalidate_line_number_cache (struct buffer *b, Bufpos pos, + CONST Bufbyte *nonreloc, Bytecount length) +{ + if (NILP (b->line_number_cache)) + return; + + if (length > LINE_NUMBER_LARGE_STRING + || + /* We could also count how many newlines are in the string, and + update the cache accordingly, but it would be too much work + for too little gain. */ + memchr ((void *)nonreloc, '\n', (size_t) length)) + invalidate_line_number_cache (b, pos); +} + +/* Invalidate the cache positions after FROM, if the region to be + deleted contains a newline. If the region is too large (larger + than LINE_NUMBER_LARGE_STRING), invalidate the cache positions + after FROM without prior search. + + This will do nothing, if cache is uninitialized. */ +void +delete_invalidate_line_number_cache (struct buffer *b, Bufpos from, Bufpos to) +{ + if (NILP (b->line_number_cache)) + return; + + if ((to - from) > LINE_NUMBER_LARGE_STRING) + invalidate_line_number_cache (b, from); + else + { + int shortage; + scan_buffer (b, '\n', from, to, 1, &shortage, 0); + if (!shortage) + /* The same as above applies. */ + invalidate_line_number_cache (b, from); + } +} + + +/* Get the nearest known position we know the line number of + (i.e. BUF_BEGV, and cached positions). The return position will be + either closer than BEG, or BEG. + + *LINE should be initialized to the line number of BEG (normally, + BEG will be BUF_BEGV, and *LINE will be XINT (LINE_NUMBER_BEGV). + This will initialize the cache, if necessary. */ +static void +get_nearest_line_number (struct buffer *b, Bufpos *beg, Bufpos pos, + EMACS_INT *line) +{ + Lisp_Object *ring = XVECTOR_DATA (LINE_NUMBER_RING (b)); + EMACS_INT i; + Charcount length, howfar; + Bufpos newpos; + + length = pos - *beg; + if (length < 0) + length = -length; + + /* Look for the nearest match. */ + for (i = 0; i < LINE_NUMBER_RING_SIZE; i++) + { + if (!CONSP (ring[i])) + break; + newpos = marker_position (XCAR (ring[i])); + howfar = newpos - pos; + if (howfar < 0) + howfar = -howfar; + if (howfar < length) + { + length = howfar; + *beg = newpos; + *line = XINT (XCDR (ring[i])); + } + } +} + +/* Add a (pos, line) pair to the ring, and rotate it. */ +static void +add_line_number (struct buffer *b, Bufpos pos, int line) +{ + Lisp_Object *ring = XVECTOR_DATA (LINE_NUMBER_RING (b)); + Lisp_Object marker; + int i; + + for (i = LINE_NUMBER_RING_SIZE - 1; i > 0; i--) + ring[i] = ring[i - 1]; + marker = Fmake_marker (); + Fset_marker (marker, make_int (pos), make_buffer (b)); + ring[0] = Fcons (marker, make_int (line)); +} + +/* Calculate the buffer line number. If CACHEP is non-zero, + initialize the line-number cache for future use. The line number + of the first line is 0. If narrowing is in effect, count the lines + from the beginning of the visible portion of the buffer. + + The cache works as follows: To calculate the line number, we need + two positions: position of point (POS) and the position from which + to count newlines (BEG). We start by setting BEG to BUF_BEGV. If + this would require too much searching (i.e. pos - BUF_BEGV > + LINE_NUMBER_FAR), try to find a closer position in the ring. If it + is found, use that position for BEG, and increment the line number + appropriately. + + If the calculation (with or without the cache lookup) required more + than LINE_NUMBER_FAR bytes of traversal, update the cache. */ +EMACS_INT +buffer_line_number (struct buffer *b, Bufpos pos, int cachep) +{ + Bufpos beg = BUF_BEGV (b); + EMACS_INT cached_lines = 0; + EMACS_INT lots = 999999999; + EMACS_INT shortage, line; + + if (cachep) + { + if (NILP (b->line_number_cache)) + allocate_line_number_cache (b); + cached_lines = XINT (LINE_NUMBER_BEGV (b)); + get_nearest_line_number (b, &beg, pos, &cached_lines); + } + + scan_buffer (b, '\n', beg, pos, pos > beg ? lots : -lots, + (int *)&shortage, 0); + + line = lots - shortage; + if (beg > pos) + line = -line; + line += cached_lines; + + if (cachep) + { + /* If too far, update the cache. */ + if ((pos > beg ? pos - beg : beg - pos) > LINE_NUMBER_FAR) + add_line_number (b, pos, line); + /* Account for narrowing. If CACHEP is nil, this is + unnecessary, because we counted from BUF_BEGV anyway. */ + if (cachep) + line -= XINT (LINE_NUMBER_BEGV (b)); + } + + return line; +} +