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

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