PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms 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
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  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 4266 2002-06-18 09:21:24Z 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);
  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);
  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);
  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);
  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);
  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);
  376. updatePositionsForRemove(offset,length);
  377. } //}}}
  378. //{{{ lineInfoChangedFrom() method
  379. public void lineInfoChangedFrom(int startLine)
  380. {
  381. moveGap(startLine,0);
  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)
  438. {
  439. //System.err.println(buffer.getName() + ": Moving gap from "
  440. // + gapLine + " to " + newGapLine);
  441. if(gapLine == -1)
  442. gapWidth = newGapWidth;
  443. // this handles the newGapLine == -1 case correctly!
  444. else if(newGapLine < gapLine)
  445. {
  446. for(int i = gapLine; i < lineCount; i++)
  447. setLineEndOffset(i,getLineEndOffset(i));
  448. gapWidth = newGapWidth;
  449. }
  450. else //if(newGapLine >= gapLine)
  451. {
  452. for(int i = gapLine; i < newGapLine; i++)
  453. setLineEndOffset(i,getLineEndOffset(i));
  454. gapWidth += newGapWidth;
  455. }
  456. if(newGapLine == lineCount)
  457. gapLine = -1;
  458. else
  459. gapLine = newGapLine;
  460. } //}}}
  461. //{{{ growPositionArray() method
  462. private void growPositionArray()
  463. {
  464. if(positions.length < positionCount + 1)
  465. {
  466. PosBottomHalf[] newPositions = new PosBottomHalf[
  467. (positionCount + 1) * 2];
  468. System.arraycopy(positions,0,newPositions,0,positionCount);
  469. positions = newPositions;
  470. }
  471. } //}}}
  472. //{{{ removePosition() method
  473. private synchronized void removePosition(PosBottomHalf bh)
  474. {
  475. int index = -1;
  476. for(int i = 0; i < positionCount; i++)
  477. {
  478. if(positions[i] == bh)
  479. {
  480. index = i;
  481. break;
  482. }
  483. }
  484. System.arraycopy(positions,index + 1,positions,index,
  485. positionCount - index - 1);
  486. positions[--positionCount] = null;
  487. } //}}}
  488. //{{{ updatePositionsForInsert() method
  489. private void updatePositionsForInsert(int offset, int length)
  490. {
  491. if(positionCount == 0)
  492. return;
  493. int start = getPositionAtOffset(offset);
  494. for(int i = start; i < positionCount; i++)
  495. {
  496. PosBottomHalf bh = positions[i];
  497. if(bh.offset < offset)
  498. Log.log(Log.ERROR,this,"Screwed up: " + bh.offset);
  499. else
  500. bh.offset += length;
  501. }
  502. } //}}}
  503. //{{{ updatePositionsForRemove() method
  504. private void updatePositionsForRemove(int offset, int length)
  505. {
  506. if(positionCount == 0)
  507. return;
  508. int start = getPositionAtOffset(offset);
  509. for(int i = start; i < positionCount; i++)
  510. {
  511. PosBottomHalf bh = positions[i];
  512. if(bh.offset < offset)
  513. Log.log(Log.ERROR,this,"Screwed up: " + bh.offset);
  514. else if(bh.offset < offset + length)
  515. bh.offset = offset;
  516. else
  517. bh.offset -= length;
  518. }
  519. } //}}}
  520. //{{{ getPositionAtOffset() method
  521. private int getPositionAtOffset(int offset)
  522. {
  523. int start = 0;
  524. int end = positionCount - 1;
  525. PosBottomHalf bh;
  526. loop: for(;;)
  527. {
  528. switch(end - start)
  529. {
  530. case 0:
  531. bh = positions[start];
  532. if(bh.offset < offset)
  533. start++;
  534. break loop;
  535. case 1:
  536. bh = positions[end];
  537. if(bh.offset < offset)
  538. {
  539. start = end + 1;
  540. }
  541. else
  542. {
  543. bh = positions[start];
  544. if(bh.offset < offset)
  545. {
  546. start++;
  547. }
  548. }
  549. break loop;
  550. default:
  551. int pivot = (start + end) / 2;
  552. bh = positions[pivot];
  553. if(bh.offset > offset)
  554. end = pivot - 1;
  555. else
  556. start = pivot + 1;
  557. break;
  558. }
  559. }
  560. return start;
  561. } //}}}
  562. //}}}
  563. //{{{ Inner classes
  564. //{{{ PosTopHalf class
  565. static class PosTopHalf implements Position
  566. {
  567. PosBottomHalf bh;
  568. //{{{ PosTopHalf constructor
  569. PosTopHalf(PosBottomHalf bh)
  570. {
  571. this.bh = bh;
  572. bh.ref();
  573. } //}}}
  574. //{{{ getOffset() method
  575. public int getOffset()
  576. {
  577. return bh.offset;
  578. } //}}}
  579. //{{{ finalize() method
  580. public void finalize()
  581. {
  582. bh.unref();
  583. } //}}}
  584. } //}}}
  585. //{{{ PosBottomHalf class
  586. class PosBottomHalf
  587. {
  588. int offset;
  589. int ref;
  590. //{{{ PosBottomHalf constructor
  591. PosBottomHalf(int offset)
  592. {
  593. this.offset = offset;
  594. } //}}}
  595. //{{{ ref() method
  596. void ref()
  597. {
  598. ref++;
  599. } //}}}
  600. //{{{ unref() method
  601. void unref()
  602. {
  603. if(--ref == 0)
  604. removePosition(this);
  605. } //}}}
  606. } //}}}
  607. //}}}
  608. }