PageRenderTime 402ms CodeModel.GetById 365ms app.highlight 28ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-1-pre5/org/gjt/sp/jedit/buffer/OffsetManager.java

#
Java | 705 lines | 466 code | 94 blank | 145 comment | 113 complexity | 35bcd8b7caeb471676ace0a8aecdaa70 MD5 | raw file
  1/*
  2 * OffsetManager.java - Manages line info, line start offsets, positions
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 2001, 2002 Slava Pestov
  7 *
  8 * This program is free software; you can redistribute it and/or
  9 * modify it under the terms of the GNU General Public License
 10 * as published by the Free Software Foundation; either version 2
 11 * of the License, or any later version.
 12 *
 13 * This program is distributed in the hope that it will be useful,
 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16 * GNU General Public License for more details.
 17 *
 18 * You should have received a copy of the GNU General Public License
 19 * along with this program; if not, write to the Free Software
 20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 21 */
 22
 23package org.gjt.sp.jedit.buffer;
 24
 25//{{{ Imports
 26import javax.swing.text.*;
 27import org.gjt.sp.jedit.syntax.*;
 28import org.gjt.sp.jedit.Buffer;
 29import org.gjt.sp.util.IntegerArray;
 30import org.gjt.sp.util.Log;
 31//}}}
 32
 33/**
 34 * A class internal to jEdit's document model. You should not use it
 35 * directly. To improve performance, none of the methods in this class
 36 * check for out of bounds access, nor are they thread-safe. The
 37 * <code>Buffer</code> class, through which these methods must be
 38 * called through, implements such protection.
 39 *
 40 * @author Slava Pestov
 41 * @version $Id: OffsetManager.java 4266 2002-06-18 09:21:24Z spestov $
 42 * @since jEdit 4.0pre1
 43 */
 44public class OffsetManager
 45{
 46	//{{{ OffsetManager constructor
 47	public OffsetManager(Buffer buffer)
 48	{
 49		this.buffer = buffer;
 50
 51		lineInfo = new long[1];
 52		// make first line visible by default
 53		lineInfo[0] = 1L | (0xffL << VISIBLE_SHIFT);
 54		lineContext = new TokenMarker.LineContext[1];
 55		lineCount = 1;
 56
 57		positions = new PosBottomHalf[100];
 58
 59		virtualLineCounts = new int[8];
 60		for(int i = 0; i < 8; i++)
 61			virtualLineCounts[i] = 1;
 62
 63		gapLine = -1;
 64	} //}}}
 65
 66	//{{{ getLineCount() method
 67	public final int getLineCount()
 68	{
 69		return lineCount;
 70	} //}}}
 71
 72	//{{{ getVirtualLineCount() method
 73	public final int getVirtualLineCount(int index)
 74	{
 75		return virtualLineCounts[index];
 76	} //}}}
 77
 78	//{{{ setVirtualLineCount() method
 79	public final void setVirtualLineCount(int index, int lineCount)
 80	{
 81		virtualLineCounts[index] = lineCount;
 82	} //}}}
 83
 84	//{{{ getLineOfOffset() method
 85	public int getLineOfOffset(int offset)
 86	{
 87		int start = 0;
 88		int end = lineCount - 1;
 89
 90		for(;;)
 91		{
 92			switch(end - start)
 93			{
 94			case 0:
 95				if(getLineEndOffset(start) <= offset)
 96					return start + 1;
 97				else
 98					return start;
 99			case 1:
100				if(getLineEndOffset(start) <= offset)
101				{
102					if(getLineEndOffset(end) <= offset)
103						return end + 1;
104					else
105						return end;
106				}
107				else
108					return start;
109			default:
110				int pivot = (end + start) / 2;
111				int value = getLineEndOffset(pivot);
112				if(value == offset)
113					return pivot + 1;
114				else if(value < offset)
115					start = pivot + 1;
116				else
117					end = pivot - 1;
118				break;
119			}
120		}
121	} //}}}
122
123	//{{{ getLineEndOffset() method
124	public final int getLineEndOffset(int line)
125	{
126		int end = (int)(lineInfo[line] & END_MASK);
127		if(gapLine != -1 && line >= gapLine)
128			return end + gapWidth;
129		else
130			return end;
131	} //}}}
132
133	//{{{ isFoldLevelValid() method
134	public final boolean isFoldLevelValid(int line)
135	{
136		if(gapLine != -1 && line >= gapLine)
137			return false;
138
139		return (lineInfo[line] & FOLD_LEVEL_VALID_MASK) != 0;
140	} //}}}
141
142	//{{{ getFoldLevel() method
143	public final int getFoldLevel(int line)
144	{
145		return (int)((lineInfo[line] & FOLD_LEVEL_MASK)
146			>> FOLD_LEVEL_SHIFT);
147	} //}}}
148
149	//{{{ setFoldLevel() method
150	// Also sets 'fold level valid' flag
151	public final void setFoldLevel(int line, int level)
152	{
153		if(gapLine != -1 && line >= gapLine)
154			moveGap(line + 1,0);
155
156		lineInfo[line] = ((lineInfo[line] & ~FOLD_LEVEL_MASK)
157			| ((long)level << FOLD_LEVEL_SHIFT)
158			| FOLD_LEVEL_VALID_MASK);
159	} //}}}
160
161	//{{{ isLineVisible() method
162	public final boolean isLineVisible(int line, int index)
163	{
164		long mask = 1L << (index + VISIBLE_SHIFT);
165		return (lineInfo[line] & mask) != 0;
166	} //}}}
167
168	//{{{ setLineVisible() method
169	public final void setLineVisible(int line, int index, boolean visible)
170	{
171		long mask = 1L << (index + VISIBLE_SHIFT);
172		if(visible)
173			lineInfo[line] = (lineInfo[line] | mask);
174		else
175			lineInfo[line] = (lineInfo[line] & ~mask);
176	} //}}}
177
178	/* the next two methods are not used!
179
180	//{{{ getScreenLineCount() method
181	public final int getScreenLineCount(int line)
182	{
183		return (int)((lineInfo[line] & SCREEN_LINES_MASK)
184			>> SCREEN_LINES_SHIFT);
185	} //}}}
186
187	//{{{ setScreenLineCount() method
188	public final void setScreenLineCount(int line, int count)
189	{
190		lineInfo[line] = ((lineInfo[line] & ~SCREEN_LINES_MASK)
191			| ((long)count << SCREEN_LINES_SHIFT));
192	} //}}}
193
194	*/
195
196	//{{{ isLineContextValid() method
197	public final boolean isLineContextValid(int line)
198	{
199		if(gapLine != -1 && line >= gapLine)
200			return false;
201
202		return (lineInfo[line] & CONTEXT_VALID_MASK) != 0;
203	} //}}}
204
205	//{{{ getLineContext() method
206	public final TokenMarker.LineContext getLineContext(int line)
207	{
208		return lineContext[line];
209	} //}}}
210
211	//{{{ setLineContext() method
212	// Also sets 'context valid' to true
213	public final void setLineContext(int line, TokenMarker.LineContext context)
214	{
215		if(gapLine != -1 && line >= gapLine)
216			moveGap(line + 1,0);
217
218		lineContext[line] = context;
219		lineInfo[line] |= CONTEXT_VALID_MASK;
220	} //}}}
221
222	//{{{ createPosition() method
223
224	// note: Buffer.createPosition() grabs a read lock, so the buffer
225	// will not change during this method. however, if two stops call
226	// it, there can be contention issues unless this method is
227	// synchronized.
228
229	// I could make Buffer.createPosition() grab a write lock, but then
230	// it would be necessary to implement grabbing write locks within
231	// read locks, since HyperSearch for example does everything inside
232	// a read lock.
233	public synchronized Position createPosition(int offset)
234	{
235		PosBottomHalf bh = null;
236
237		for(int i = 0; i < positionCount; i++)
238		{
239			PosBottomHalf _bh = positions[i];
240			if(_bh.offset == offset)
241			{
242				bh = _bh;
243				break;
244			}
245			else if(_bh.offset > offset)
246			{
247				bh = new PosBottomHalf(offset);
248				growPositionArray();
249				System.arraycopy(positions,i,positions,i+1,
250					positionCount - i);
251				positionCount++;
252				positions[i] = bh;
253				break;
254			}
255		}
256
257		if(bh == null)
258		{
259			bh = new PosBottomHalf(offset);
260			growPositionArray();
261			positions[positionCount++] = bh;
262		}
263
264		return new PosTopHalf(bh);
265	} //}}}
266
267	//{{{ expandFolds() method
268	/**
269	 * Like <code>FoldVisibilityManager.expandFolds()</code>, but does
270	 * it for all fold visibility managers viewing this buffer. Should
271	 * only be called after loading.
272	 */
273	public void expandFolds(int foldLevel)
274	{
275		int newVirtualLineCount = 0;
276
277		if(foldLevel == 0)
278		{
279			newVirtualLineCount = lineCount;
280
281			for(int i = 0; i < lineCount; i++)
282				lineInfo[i] |= VISIBLE_MASK;
283		}
284		else
285		{
286			foldLevel = (foldLevel - 1) * buffer.getIndentSize() + 1;
287
288			/* this ensures that the first line is always visible */
289			boolean seenVisibleLine = false;
290
291			for(int i = 0; i < lineCount; i++)
292			{
293				if(!seenVisibleLine || buffer.getFoldLevel(i) < foldLevel)
294				{
295					seenVisibleLine = true;
296					lineInfo[i] |= VISIBLE_MASK;
297					newVirtualLineCount++;
298				}
299				else
300					lineInfo[i] &= ~VISIBLE_MASK;
301			}
302		}
303
304		for(int i = 0; i < virtualLineCounts.length; i++)
305		{
306			virtualLineCounts[i] = newVirtualLineCount;
307		}
308	} //}}}
309
310	//{{{ contentInserted() method
311	public void contentInserted(int startLine, int offset,
312		int numLines, int length, IntegerArray endOffsets)
313	{
314		int endLine = startLine + numLines;
315
316		//{{{ Update line info and line context arrays
317		if(numLines > 0)
318		{
319			moveGap(-1,0);
320
321			lineCount += numLines;
322
323			if(lineInfo.length <= lineCount)
324			{
325				long[] lineInfoN = new long[(lineCount + 1) * 2];
326				System.arraycopy(lineInfo,0,lineInfoN,0,
327						 lineInfo.length);
328				lineInfo = lineInfoN;
329
330				TokenMarker.LineContext[] lineContextN
331					= new TokenMarker.LineContext[(lineCount + 1) * 2];
332				System.arraycopy(lineContext,0,lineContextN,0,
333						 lineContext.length);
334				lineContext = lineContextN;
335			}
336
337			System.arraycopy(lineInfo,startLine,lineInfo,
338				endLine,lineCount - endLine);
339			System.arraycopy(lineContext,startLine,lineContext,
340				endLine,lineCount - endLine);
341
342			//{{{ Find fold start of this line
343			int foldLevel = buffer.getFoldLevel(startLine);
344			long visible = (0xffL << VISIBLE_SHIFT);
345			if(startLine != 0)
346			{
347				for(int i = startLine; i > 0; i--)
348				{
349					if(/* buffer.isFoldStart(i - 1)
350						&& */ buffer.getFoldLevel(i) <= foldLevel)
351					{
352						visible = (lineInfo[i] & VISIBLE_MASK);
353						break;
354					}
355				}
356			} //}}}
357
358			for(int i = 0; i < numLines; i++)
359			{
360				// need the line end offset to be in place
361				// for following fold level calculations
362				lineInfo[startLine + i] = (offset
363					+ endOffsets.get(i) + 1)
364					| visible;
365			}
366
367			//{{{ Unrolled
368			if((visible & (1L << (VISIBLE_SHIFT + 0))) != 0)
369				virtualLineCounts[0] += numLines;
370			if((visible & (1L << (VISIBLE_SHIFT + 1))) != 0)
371				virtualLineCounts[1] += numLines;
372			if((visible & (1L << (VISIBLE_SHIFT + 2))) != 0)
373				virtualLineCounts[2] += numLines;
374			if((visible & (1L << (VISIBLE_SHIFT + 3))) != 0)
375				virtualLineCounts[3] += numLines;
376			if((visible & (1L << (VISIBLE_SHIFT + 4))) != 0)
377				virtualLineCounts[4] += numLines;
378			if((visible & (1L << (VISIBLE_SHIFT + 5))) != 0)
379				virtualLineCounts[5] += numLines;
380			if((visible & (1L << (VISIBLE_SHIFT + 6))) != 0)
381				virtualLineCounts[6] += numLines;
382			if((visible & (1L << (VISIBLE_SHIFT + 7))) != 0)
383				virtualLineCounts[7] += numLines;
384			//}}}
385		} //}}}
386
387		moveGap(endLine,length);
388
389		updatePositionsForInsert(offset,length);
390	} //}}}
391
392	//{{{ contentRemoved() method
393	public void contentRemoved(int startLine, int offset,
394		int numLines, int length)
395	{
396		//{{{ Update virtual line counts
397		for(int i = 0; i < numLines; i++)
398		{
399			long info = lineInfo[startLine + i];
400
401			// Unrolled for max efficency
402			if((info & (1L << (VISIBLE_SHIFT + 0))) != 0)
403				virtualLineCounts[0]--;
404			if((info & (1L << (VISIBLE_SHIFT + 1))) != 0)
405				virtualLineCounts[1]--;
406			if((info & (1L << (VISIBLE_SHIFT + 2))) != 0)
407				virtualLineCounts[2]--;
408			if((info & (1L << (VISIBLE_SHIFT + 3))) != 0)
409				virtualLineCounts[3]--;
410			if((info & (1L << (VISIBLE_SHIFT + 4))) != 0)
411				virtualLineCounts[4]--;
412			if((info & (1L << (VISIBLE_SHIFT + 5))) != 0)
413				virtualLineCounts[5]--;
414			if((info & (1L << (VISIBLE_SHIFT + 6))) != 0)
415				virtualLineCounts[6]--;
416			if((info & (1L << (VISIBLE_SHIFT + 7))) != 0)
417				virtualLineCounts[7]--;
418		} //}}}
419
420		//{{{ Update line info and line context arrays
421		if(numLines > 0)
422		{
423			moveGap(-1,0);
424
425			lineCount -= numLines;
426			System.arraycopy(lineInfo,startLine + numLines,lineInfo,
427				startLine,lineCount - startLine);
428			System.arraycopy(lineContext,startLine + numLines,lineContext,
429				startLine,lineCount - startLine);
430		} //}}}
431
432		moveGap(startLine,-length);
433
434		updatePositionsForRemove(offset,length);
435	} //}}}
436
437	//{{{ lineInfoChangedFrom() method
438	public void lineInfoChangedFrom(int startLine)
439	{
440		moveGap(startLine,0);
441	} //}}}
442
443	//{{{ Private members
444
445	/* {{{ Format of entires in line info array:
446	 * 0-31: end offset
447	 * 32-47: fold level
448	 * 48-55: visibility bit flags
449	 * 56: fold level valid flag
450	 * 57: context valid flag
451	 * 58-62: number of screen lines (currently unused, reserved for jEdit 4.1)
452	 * 63: reserved
453	 *
454	 * Having all the info packed into a long is not very OO and makes the
455	 * code somewhat more complicated, but it saves a lot of memory.
456	 *
457	 * The new document model has just 12 bytes of overhead per line.
458	 * LineContext instances are now internalized, so only a few should
459	 * actually be in the heap.
460	 *
461	 * In the old document model there were 5 objects per line, for a
462	 * total of about 100 bytes, plus a cached token list, which used
463	 * another 100 or so bytes.
464	 * }}}*/
465	private static final long END_MASK = 0x00000000ffffffffL;
466	private static final long FOLD_LEVEL_MASK = 0x0000ffff00000000L;
467	private static final int FOLD_LEVEL_SHIFT = 32;
468	private static final long VISIBLE_MASK = 0x00ff000000000000L;
469	private static final int VISIBLE_SHIFT = 48;
470	private static final long FOLD_LEVEL_VALID_MASK = (1L<<56);
471	private static final long CONTEXT_VALID_MASK = (1L<<57);
472	private static final long SCREEN_LINES_MASK = 0x7c00000000000000L;
473	private static final long SCREEN_LINES_SHIFT = 58;
474
475	//{{{ Instance variables
476	private Buffer buffer;
477	private long[] lineInfo;
478	private TokenMarker.LineContext[] lineContext;
479
480	private int lineCount;
481
482	private PosBottomHalf[] positions;
483	private int positionCount;
484
485	private int[] virtualLineCounts;
486
487	/**
488	 * If -1, then there is no gap.
489	 * Otherwise, all lines from this line onwards need to have gapWidth
490	 * added to their end offsets.
491	 */
492	private int gapLine;
493	private int gapWidth;
494	//}}}
495
496	//{{{ setLineEndOffset() method
497	private final void setLineEndOffset(int line, int end)
498	{
499		lineInfo[line] = ((lineInfo[line] & ~(END_MASK
500			| FOLD_LEVEL_VALID_MASK | CONTEXT_VALID_MASK)) | end);
501		lineContext[line] = null;
502	} //}}}
503
504	//{{{ moveGap() method
505	private final void moveGap(int newGapLine, int newGapWidth)
506	{
507		//System.err.println(buffer.getName() + ": Moving gap from "
508		//	+ gapLine + " to " + newGapLine);
509
510		if(gapLine == -1)
511			gapWidth = newGapWidth;
512		// this handles the newGapLine == -1 case correctly!
513		else if(newGapLine < gapLine)
514		{
515			for(int i = gapLine; i < lineCount; i++)
516				setLineEndOffset(i,getLineEndOffset(i));
517
518			gapWidth = newGapWidth;
519		}
520		else //if(newGapLine >= gapLine)
521		{
522			for(int i = gapLine; i < newGapLine; i++)
523				setLineEndOffset(i,getLineEndOffset(i));
524
525			gapWidth += newGapWidth;
526		}
527
528		if(newGapLine == lineCount)
529			gapLine = -1;
530		else
531			gapLine = newGapLine;
532	} //}}}
533
534	//{{{ growPositionArray() method
535	private void growPositionArray()
536	{
537		if(positions.length < positionCount + 1)
538		{
539			PosBottomHalf[] newPositions = new PosBottomHalf[
540				(positionCount + 1) * 2];
541			System.arraycopy(positions,0,newPositions,0,positionCount);
542			positions = newPositions;
543		}
544	} //}}}
545
546	//{{{ removePosition() method
547	private synchronized void removePosition(PosBottomHalf bh)
548	{
549		int index = -1;
550
551		for(int i = 0; i < positionCount; i++)
552		{
553			if(positions[i] == bh)
554			{
555				index = i;
556				break;
557			}
558		}
559
560		System.arraycopy(positions,index + 1,positions,index,
561			positionCount - index - 1);
562		positions[--positionCount] = null;
563	} //}}}
564
565	//{{{ updatePositionsForInsert() method
566	private void updatePositionsForInsert(int offset, int length)
567	{
568		if(positionCount == 0)
569			return;
570
571		int start = getPositionAtOffset(offset);
572
573		for(int i = start; i < positionCount; i++)
574		{
575			PosBottomHalf bh = positions[i];
576			if(bh.offset < offset)
577				Log.log(Log.ERROR,this,"Screwed up: " + bh.offset);
578			else
579				bh.offset += length;
580		}
581	} //}}}
582
583	//{{{ updatePositionsForRemove() method
584	private void updatePositionsForRemove(int offset, int length)
585	{
586		if(positionCount == 0)
587			return;
588
589		int start = getPositionAtOffset(offset);
590
591		for(int i = start; i < positionCount; i++)
592		{
593			PosBottomHalf bh = positions[i];
594			if(bh.offset < offset)
595				Log.log(Log.ERROR,this,"Screwed up: " + bh.offset);
596			else if(bh.offset < offset + length)
597				bh.offset = offset;
598			else
599				bh.offset -= length;
600		}
601	} //}}}
602
603	//{{{ getPositionAtOffset() method
604	private int getPositionAtOffset(int offset)
605	{
606		int start = 0;
607		int end = positionCount - 1;
608
609		PosBottomHalf bh;
610
611loop:		for(;;)
612		{
613			switch(end - start)
614			{
615			case 0:
616				bh = positions[start];
617				if(bh.offset < offset)
618					start++;
619				break loop;
620			case 1:
621				bh = positions[end];
622				if(bh.offset < offset)
623				{
624					start = end + 1;
625				}
626				else
627				{
628					bh = positions[start];
629					if(bh.offset < offset)
630					{
631						start++;
632					}
633				}
634				break loop;
635			default:
636				int pivot = (start + end) / 2;
637				bh = positions[pivot];
638				if(bh.offset > offset)
639					end = pivot - 1;
640				else
641					start = pivot + 1;
642				break;
643			}
644		}
645
646		return start;
647	} //}}}
648
649	//}}}
650
651	//{{{ Inner classes
652
653	//{{{ PosTopHalf class
654	static class PosTopHalf implements Position
655	{
656		PosBottomHalf bh;
657
658		//{{{ PosTopHalf constructor
659		PosTopHalf(PosBottomHalf bh)
660		{
661			this.bh = bh;
662			bh.ref();
663		} //}}}
664
665		//{{{ getOffset() method
666		public int getOffset()
667		{
668			return bh.offset;
669		} //}}}
670
671		//{{{ finalize() method
672		public void finalize()
673		{
674			bh.unref();
675		} //}}}
676	} //}}}
677
678	//{{{ PosBottomHalf class
679	class PosBottomHalf
680	{
681		int offset;
682		int ref;
683
684		//{{{ PosBottomHalf constructor
685		PosBottomHalf(int offset)
686		{
687			this.offset = offset;
688		} //}}}
689
690		//{{{ ref() method
691		void ref()
692		{
693			ref++;
694		} //}}}
695
696		//{{{ unref() method
697		void unref()
698		{
699			if(--ref == 0)
700				removePosition(this);
701		} //}}}
702	} //}}}
703
704	//}}}
705}