PageRenderTime 882ms CodeModel.GetById 478ms app.highlight 17ms RepoModel.GetById 384ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/textarea/ElasticTabStopBufferListener.java

#
Java | 542 lines | 406 code | 23 blank | 113 comment | 60 complexity | 6086ec87597607e900a0638e192b5d95 MD5 | raw file
  1/*
  2 * jEdit - Programmer's Text Editor
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright Š 2010 jEdit contributors
  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 * This program is distributed in the hope that it will be useful,
 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15 * GNU General Public License for more details.
 16 *
 17 * You should have received a copy of the GNU General Public License
 18 * along with this program; if not, write to the Free Software
 19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 20 */
 21package org.gjt.sp.jedit.textarea;
 22
 23import java.util.Vector;
 24
 25import javax.swing.text.Segment;
 26
 27import org.gjt.sp.jedit.buffer.BufferAdapter;
 28import org.gjt.sp.jedit.buffer.JEditBuffer;
 29import org.gjt.sp.jedit.textarea.TextArea;
 30
 31public class ElasticTabStopBufferListener extends BufferAdapter
 32{
 33	TextArea textArea;
 34	
 35	private boolean handledInsertion = true;
 36	private boolean handledDeletion = true;
 37	private boolean singleTabDeleted = false;
 38	
 39	//{{{ ElasticTabStopBufferListener() method
 40	public ElasticTabStopBufferListener(TextArea textArea)
 41	{
 42		this.textArea = textArea;
 43	}//}}}
 44	
 45	//{{{ fullyUpdateColumnBlocks() method
 46	private void fullyUpdateColumnBlocks(JEditBuffer buffer)
 47	{
 48		buffer.indentUsingElasticTabstops();
 49		handledInsertion = true;
 50		handledDeletion = true;
 51		buffer.getColumnBlock().setDirtyStatus(false);
 52		textArea.chunkCache.invalidateChunksFromPhys(0);
 53		textArea.invalidateLineRange(0, buffer.getLineCount()-1);
 54		//textArea.getPainter().repaint();
 55	}//}}}
 56	
 57	//{{{ contentInserted() method
 58	public void contentInserted(JEditBuffer buffer, int startLine, int offset,
 59			int numLines, int length) 
 60	{
 61		if(!buffer.getBooleanProperty("elasticTabstops"))
 62		{
 63			return;
 64		}
 65		String charDeleted;
 66		boolean isASimpleChar = false;
 67		boolean singleTabInserted = false;
 68		if((numLines==0)&(length==1))
 69		{
 70			isASimpleChar = true;
 71			charDeleted = buffer.getText(offset, length);
 72			if(charDeleted.equals("\t"))
 73			{
 74				singleTabInserted  = true;
 75			}
 76		}
 77		ColumnBlock rootBlock = buffer.getColumnBlock();
 78		if(rootBlock==null)
 79		{
 80			return;
 81		}
 82		//System.out.println("BEFORE UPDATING COLUMN BLOCKS-----");
 83		//System.out.println(rootBlock);
 84		int indexofBlockAbove = -1;
 85		ColumnBlock block = rootBlock.searchChildren(startLine);
 86		ColumnBlock blockjustAbove = null;
 87		boolean liesWithinBlock = false;
 88		int startIndex =-1;
 89		if(block!=null)
 90		{	
 91			startIndex =  rootBlock.getChildren().indexOf(block);
 92			indexofBlockAbove=startIndex-1;
 93			if(block.isLineWithinThisBlock(startLine)==0)
 94			{
 95				//if the line lies within this block we need to redraw it
 96				startIndex++;
 97				liesWithinBlock = true;
 98			}
 99		}
100		else
101		{
102			startIndex = rootBlock.getChildren().size();
103			indexofBlockAbove = startIndex-1;
104		}
105		
106		if(indexofBlockAbove>=0&&((ColumnBlock)(rootBlock.getChildren().get(indexofBlockAbove))).endLine+1==startLine)
107		{
108			blockjustAbove = (ColumnBlock)(rootBlock.getChildren().get(indexofBlockAbove));
109		}
110		if(numLines>0)
111		{	
112			rootBlock.endLine +=  numLines;
113			for(int i=startIndex;i<rootBlock.getChildren().size();i++)
114			{
115				((ColumnBlock)(rootBlock.getChildren().get(i))).updateLineNo(numLines);
116			}
117		}
118		int startingLine =-1;
119		int endLine = -1;
120		if(liesWithinBlock)
121		{
122			ColumnBlock innerContainingBlock = block.getContainingBlock(startLine, offset);
123			if((isASimpleChar)&&!(innerContainingBlock==null&&singleTabInserted))
124			{
125				//a simple char has been entered (no newline )
126				//if this lies inside a column block update the startIndex and endIndex of this blocks corresponding ColumnBlockLine
127				//and all subsequent ColumnBlock Lines after this one
128				//check whether columnBlockWidth is valid 
129				
130				//do nothing if this char does not lie inside a column block
131				if(innerContainingBlock!=null)
132				{
133					if(!singleTabInserted)
134					{	
135						innerContainingBlock.updateColumnBlockLineOffset(startLine, length, false);
136						ColumnBlockLine containingLine = innerContainingBlock.getLines().elementAt(startLine-innerContainingBlock.startLine);
137						startingLine = innerContainingBlock.startLine;
138						innerContainingBlock.setTabSizeDirtyStatus(true,false);
139						endLine = innerContainingBlock.endLine;
140					}
141					else
142					{
143						//no need to update line offset as ColumnBlock would be rebuilt
144						ColumnBlock innerParent = (ColumnBlock)innerContainingBlock.getParent();
145						//startingLine = innerContainingBlock.startLine;
146						//endLine = innerContainingBlock.endLine;
147						//innerParent.getChildren().remove(innerContainingBlock);
148						startingLine =  innerParent.startLine;
149						endLine =  innerParent.endLine;
150						innerParent.getChildren().removeAllElements();
151						buffer.updateColumnBlocks(startingLine, endLine,(int)innerParent.columnBlockWidth , innerParent);
152					}
153					
154				}
155				/*else if(innerContainingBlock==null&&singleTabInserted)
156				{
157					//TODO handle this case when tab has been inserted in FRONT and not OUTSIDE of a column block
158					//currently whole column block gets repainted in this case
159				}*/
160				else
161				{
162					//this line must have been  retokenized and repainted by the BufferHandler so repaint it again here after column blocks dirty status is updated 
163					startingLine = startLine;
164					endLine = startLine;
165				}
166			}
167			if((!isASimpleChar)||(innerContainingBlock==null&&singleTabInserted))
168			{	
169				startingLine = block.getStartLine();
170				endLine = block.getEndLine()+numLines;
171				rootBlock.getChildren().remove(block);
172				buffer.updateColumnBlocks(startingLine, endLine, 0, rootBlock);
173			}	
174		}
175		else
176		{
177			Segment seg  = new Segment();
178			buffer.getText(offset, length, seg);
179			if(buffer.getTabStopPosition(seg)>=0)
180			{
181				if(blockjustAbove!=null)
182				{
183					rootBlock.getChildren().remove(blockjustAbove);
184					startingLine=blockjustAbove.startLine;
185				}
186				else
187				{
188					startingLine=startLine;
189				}
190				
191				if((block!=null)&&(block.startLine==startLine+numLines+1))
192				{
193					rootBlock.getChildren().remove(block);
194					endLine = block.endLine;
195				}
196				else
197				{
198					endLine = startLine + numLines;
199				}
200				buffer.updateColumnBlocks(startingLine, endLine, 0, rootBlock);
201			}
202			
203		}
204		handledInsertion = true;
205		rootBlock.setDirtyStatus(false);
206		//System.out.println("AFTER UPDATING COLUMN BLOCKS-----");
207		//System.out.println(rootBlock);
208		if(startingLine!=-1&&endLine!=-1&&handledDeletion)
209		{	
210			textArea.chunkCache.invalidateChunksFromPhys(startingLine);
211			textArea.invalidateLineRange(startingLine, endLine);
212		}
213	}//}}}
214
215	//{{{ contentRemoved() method
216	/**
217	 * Called when text is removed from the buffer.
218	 * @param buffer The buffer in question
219	 * @param startLine The first line
220	 * @param offset The start offset, from the beginning of the buffer
221	 * @param numLines The number of lines removed
222	 * @param length The number of characters removed
223	 * @since jEdit 4.3pre3
224	 */
225	public void contentRemoved(JEditBuffer buffer, int startLine, int offset,
226		int numLines, int length)
227	{
228		if(!buffer.getBooleanProperty("elasticTabstops"))
229		{
230			return;
231		}
232		String charDeleted;
233		boolean isASimpleChar = false;
234		ColumnBlock rootBlock = buffer.getColumnBlock();
235		if(rootBlock==null)
236		{
237			return;
238		}
239		if((numLines==0)&(length==1))
240		{
241			isASimpleChar = true;
242		}
243		if((!isASimpleChar))
244		{
245			//we need to remove column blocks
246			//find the column block lying just below the first line deleted
247			ColumnBlock firstBlockEffected = rootBlock.searchChildren(startLine);
248			//info we need to determine inside this if block
249			int startLineToBuild = -1;
250			int endLineToBuild = -1;
251			ColumnBlock firstBlockToBeUpdated = null;
252			ColumnBlock firstBlockToBeRemoved = null;
253			ColumnBlock lastBlockToBeRemoved = null;
254			if(firstBlockEffected!=null)
255			{
256				int indexFirstBlockEffected =rootBlock.getChildren().indexOf(firstBlockEffected);
257				ColumnBlock blockAboveFirstEffected = null;
258				boolean justBelowBlock = false;
259				if(indexFirstBlockEffected>0)
260				{
261					blockAboveFirstEffected = (ColumnBlock)rootBlock.getChildren().get(indexFirstBlockEffected-1);
262					if(blockAboveFirstEffected.endLine==startLine-1 )
263					{
264						justBelowBlock  = true;
265					}
266				}
267				int posFirstLine =  firstBlockEffected.isLineWithinThisBlock(startLine);
268				boolean firstLineLiesInside =posFirstLine==0;
269				boolean firstLineLiesAbove =posFirstLine<0;
270				int posLastLine =  firstBlockEffected.isLineWithinThisBlock(startLine+numLines);
271				boolean lastLineLiesInside =posLastLine==0;
272				boolean lastLineLiesAbove = posLastLine<0;
273				boolean lastLineLiesBelow = posLastLine>0;
274				//deletion above block
275				if(lastLineLiesAbove )
276				{
277					//if last line lies above this block cannot be connected to a block above in this deletion without touching the block above
278					/*if(justBelowBlock&&startLine+numLines+1==firstBlockEffected.startLine)
279					{
280						startLineToBuild=blockAboveFirstEffected.startLine;
281						endLineToBuild= firstBlockEffected.endLine;
282						firstBlockToBeRemoved = blockAboveFirstEffected;
283						lastBlockToBeRemoved = firstBlockEffected;
284					}*/
285					firstBlockToBeUpdated = firstBlockEffected;
286					//else
287					//{	
288						firstBlockToBeRemoved =lastBlockToBeRemoved= null;
289						startLineToBuild=endLineToBuild=-1;
290					//}	
291				}
292				//deletion inside block
293				else if((firstLineLiesInside||firstLineLiesAbove)&&lastLineLiesInside)
294				{
295					startLineToBuild = Math.min( firstBlockEffected.startLine,startLine);
296					endLineToBuild = firstBlockEffected.endLine-numLines;
297					//if(indexFirstBlockEffected<rootBlock.getChildren().size()-1)
298					//{	
299						//firstBlockToBeUpdated =(ColumnBlock)rootBlock.getChildren().get(indexFirstBlockEffected+1) ;
300					//}	
301					firstBlockToBeRemoved =lastBlockToBeRemoved= firstBlockEffected;
302					if(justBelowBlock)
303					{
304						startLineToBuild =blockAboveFirstEffected.startLine ;
305						firstBlockToBeRemoved = blockAboveFirstEffected;
306					}
307				}
308				//deletion might cover other blocks as well
309				else if(((firstLineLiesInside)||(firstLineLiesAbove))&&lastLineLiesBelow)
310				{
311					startLineToBuild = Math.min(startLine, firstBlockEffected.startLine);
312					firstBlockToBeRemoved = firstBlockEffected;
313					ColumnBlock blockBelow = rootBlock.searchChildren(startLine+numLines);
314					int indexLastBlock = rootBlock.getChildren().indexOf(blockBelow);
315					if(blockBelow!=null)
316					{
317						//deletion partially overlaps this block
318						if(blockBelow.isLineWithinThisBlock(startLine+numLines)==0)
319						{
320							if(justBelowBlock)
321							{
322								startLineToBuild =blockAboveFirstEffected.startLine ;
323								firstBlockToBeRemoved = blockAboveFirstEffected;
324							}
325							lastBlockToBeRemoved = blockBelow;
326							endLineToBuild = blockBelow.endLine-numLines;
327							//if(indexLastBlock<rootBlock.getChildren().size()-1)
328							//{
329								//firstBlockToBeUpdated = (ColumnBlock)rootBlock.getChildren().get(indexLastBlock+1);
330							//}
331						}
332						
333						//deletion lies above this block
334						else 
335						{
336							//do not need to consider blockJustAbove here as we cannot connect two column blocks without 
337							//ending on one of the lines of either
338							//firstBlockToBeUpdated = blockBelow;
339							//if we have reached here there is surely a block above this one 
340							lastBlockToBeRemoved = (ColumnBlock)rootBlock.getChildren().get(indexLastBlock-1);
341							//if the first Block is wholly covered then all column blocks are being deleted completely and there is nothing to build
342							endLineToBuild = firstLineLiesAbove?-1:startLine;
343							//consider the case where last line deleted is just above the column block block below
344							if((blockBelow.startLine==startLine+numLines+1)&&(endLineToBuild!=-1))
345							{
346								endLineToBuild = blockBelow.endLine-numLines;
347								lastBlockToBeRemoved = blockBelow;
348							}
349							if(endLineToBuild==-1)
350							{
351								startLineToBuild = -1;
352							}
353						}
354					}
355					//no block below last line
356					else
357					{
358						lastBlockToBeRemoved = (ColumnBlock)rootBlock.getChildren().get(rootBlock.getChildren().size()-1);
359						//firstBlockToBeUpdated = null;
360						if(firstLineLiesInside)
361						{	
362							endLineToBuild = startLine;
363						}
364						else
365						{
366							startLineToBuild = -1;
367							endLineToBuild= -1;
368						}
369					} 
370				}
371			}
372			//deletion lies below all column blocks
373			else
374			{
375				 startLineToBuild = -1;
376				 endLineToBuild = -1;
377				 //firstBlockToBeUpdated = null;
378				 firstBlockToBeRemoved = null;
379				 lastBlockToBeRemoved = null;
380			}
381			//once we reach here we have three things to do
382			//1)delete columnBlocks using firstBlockToBeDeleted and lastBlockToBeDeleted
383			Vector blocksToBeRemoved =null;
384			if(firstBlockToBeRemoved!=null)
385			{
386				int startIndex = rootBlock.getChildren().indexOf(firstBlockToBeRemoved);
387				blocksToBeRemoved = new Vector();
388				if(lastBlockToBeRemoved==null)
389				{
390					throw new IllegalArgumentException("Deletion not handled properly");
391				}
392				int endIndex = rootBlock.getChildren().indexOf(lastBlockToBeRemoved);
393				for(int i=startIndex;i<=endIndex;i++)
394				{
395					blocksToBeRemoved.add(rootBlock.getChildren().get(i));
396				}
397					
398			}	
399			//2)update startLine/endLine in column blocks using firstBlockToBeUpdated
400			if(numLines>0)
401			{	
402				rootBlock.endLine-=numLines;
403				if((lastBlockToBeRemoved!=null)||(firstBlockToBeUpdated!=null))
404				{
405					int startIndex=-1;
406					if(lastBlockToBeRemoved!=null)
407					{
408						startIndex = rootBlock.getChildren().indexOf(lastBlockToBeRemoved);
409						//start just after the last block to be removed
410						startIndex++;
411					}
412					else if(firstBlockToBeUpdated!=null)
413					{
414						startIndex = rootBlock.getChildren().indexOf(firstBlockToBeUpdated);
415					}
416					for(int i=startIndex;i<rootBlock.getChildren().size();i++)
417					{	
418						((ColumnBlock)rootBlock.getChildren().get(i)).updateLineNo(-1*numLines);
419					}	
420				}
421			}
422			//once we are done with (2) we can safely change rootBlock
423			if(blocksToBeRemoved!=null)
424			{	
425				rootBlock.getChildren().removeAll(blocksToBeRemoved);
426			}	
427			//3)rebuild column blocks using endLine and startLine
428			if(startLineToBuild!=-1&&endLineToBuild!=-1)
429			{	
430				buffer.updateColumnBlocks(startLineToBuild, endLineToBuild, 0, rootBlock);
431				rootBlock.setDirtyStatus(false);
432				textArea.chunkCache.invalidateChunksFromPhys(startLineToBuild);
433				textArea.invalidateLineRange(startLineToBuild, endLineToBuild);
434			}
435			rootBlock.setDirtyStatus(false);
436			handledDeletion = true;
437		}
438		else
439		{
440			int startingLine = -1;
441			int endLine = -1;
442			//a simple char has been entered 
443			//if this lies inside a column block update the startIndex and endIndex of this blocks corresponding ColumnBlockLine
444			//and all subsequent ColumnBlock Lines after this one
445			//check whether columnBlockWidth is valid 
446			ColumnBlock innerContainingBlock = rootBlock.getContainingBlock(startLine, offset);
447			//do nothing if this char does not lie inside a column block
448			if(innerContainingBlock!=null)
449			{
450				if(!singleTabDeleted)
451				{	
452					innerContainingBlock.updateColumnBlockLineOffset(startLine, -1*length, false);
453					ColumnBlockLine containingLine = innerContainingBlock.getLines().elementAt(startLine-innerContainingBlock.startLine);
454					startingLine = innerContainingBlock.startLine;
455					endLine = innerContainingBlock.endLine;
456					innerContainingBlock.setTabSizeDirtyStatus(true,false);
457				}
458				else
459				{
460					//no need to update line offset as ColumnBlock would be rebuilt
461					ColumnBlock innerParent = (ColumnBlock)innerContainingBlock.getParent();
462					startingLine = innerContainingBlock.startLine;
463					endLine = innerContainingBlock.endLine;
464					innerParent.getChildren().remove(innerContainingBlock);
465					//startingLine =  innerParent.startLine;
466					//endLine =  innerParent.endLine;
467					//innerParent.getChildren().removeAllElements();
468					buffer.updateColumnBlocks(startingLine, endLine,(int)innerParent.columnBlockWidth , innerParent);
469				}
470					
471					
472			}
473			else
474			{
475				//this line must have been  retokenized and repainted by the BufferHandler so repaint it again here after column blocks dirty status is updated 
476				startingLine = startLine;
477				endLine = startLine;
478			}
479			handledDeletion = true;
480			rootBlock.setDirtyStatus(false);
481			if(startingLine!=-1&&endLine!=-1)
482			{	
483				
484				textArea.chunkCache.invalidateChunksFromPhys(startingLine);
485				textArea.invalidateLineRange(startingLine, endLine);
486			}
487		}
488	}//}}}
489		
490	//{{{ transactionComplete() method
491	public void transactionComplete(JEditBuffer buffer) 
492	{
493		if(!buffer.getBooleanProperty("elasticTabstops"))
494		{
495			return;
496		}
497		if((buffer.getBooleanProperty("elasticTabstops"))&&((handledInsertion==false)||(handledDeletion==false)))
498		{	
499			//if we reach here use brute force as a last resolve
500			fullyUpdateColumnBlocks(buffer);
501		}	
502			
503	}
504	//}}}
505		
506	//{{{ preContentInserted() method
507	public void preContentInserted(JEditBuffer buffer, int startLine, int offset, int numLines, int length)
508	{
509		if(!buffer.getBooleanProperty("elasticTabstops"))
510		{
511			return;
512		}
513		handledInsertion = false;
514		if(buffer.getColumnBlock()!=null)
515		buffer.getColumnBlock().setDirtyStatus(true);
516	} //}}}
517		
518	//{{{ preContentRemoved() method
519	public void preContentRemoved(JEditBuffer buffer, int startLine, int offset,
520		int numLines, int length) 
521	{
522		if(!buffer.getBooleanProperty("elasticTabstops"))
523		{
524			return;
525		}
526		handledDeletion = false;
527		singleTabDeleted = false;
528		if(buffer.getColumnBlock()!=null)
529		{	
530			buffer.getColumnBlock().setDirtyStatus(true);
531			if((numLines==0)&(length==1))
532			{
533				String str = buffer.getText(offset, length);
534				if(str.equals("\t"))
535				{
536					singleTabDeleted = true;
537				}
538			}
539		}
540	}
541	//}}}
542}