PageRenderTime 45ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre4/org/gjt/sp/jedit/textarea/ChunkCache.java

#
Java | 749 lines | 498 code | 94 blank | 157 comment | 107 complexity | ec03ae686f2653232f9ceecb50d56181 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. * ChunkCache.java - Intermediate layer between token lists from a TokenMarker
  3. * and what you see on screen
  4. * :tabSize=8:indentSize=8:noTabs=false:
  5. * :folding=explicit:collapseFolds=1:
  6. *
  7. * Copyright (C) 2001, 2003 Slava Pestov
  8. *
  9. * This program is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU General Public License
  11. * as published by the Free Software Foundation; either version 2
  12. * of the License, or any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program; if not, write to the Free Software
  21. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  22. */
  23. package org.gjt.sp.jedit.textarea;
  24. //{{{ Imports
  25. import java.util.*;
  26. import org.gjt.sp.jedit.Buffer;
  27. import org.gjt.sp.jedit.Debug;
  28. import org.gjt.sp.jedit.syntax.*;
  29. import org.gjt.sp.util.Log;
  30. //}}}
  31. /**
  32. * Manages low-level text display tasks.
  33. *
  34. * @author Slava Pestov
  35. * @version $Id: ChunkCache.java 4765 2003-06-05 20:37:02Z spestov $
  36. */
  37. class ChunkCache
  38. {
  39. //{{{ ChunkCache constructor
  40. ChunkCache(JEditTextArea textArea)
  41. {
  42. this.textArea = textArea;
  43. out = new ArrayList();
  44. tokenHandler = new DisplayTokenHandler();
  45. } //}}}
  46. //{{{ getMaxHorizontalScrollWidth() method
  47. int getMaxHorizontalScrollWidth()
  48. {
  49. int max = 0;
  50. for(int i = 0; i < firstInvalidLine; i++)
  51. {
  52. LineInfo info = lineInfo[i];
  53. if(info.width > max)
  54. max = info.width;
  55. }
  56. return max;
  57. } //}}}
  58. //{{{ getScreenLineOfOffset() method
  59. int getScreenLineOfOffset(int line, int offset)
  60. {
  61. if(line < textArea.getFirstPhysicalLine())
  62. {
  63. return -1;
  64. }
  65. else if(line > textArea.getLastPhysicalLine())
  66. {
  67. return -1;
  68. }
  69. else
  70. {
  71. int screenLine;
  72. if(line == lastScreenLineP)
  73. {
  74. LineInfo last = getLineInfo(lastScreenLine);
  75. if(offset >= last.offset
  76. && offset < last.offset + last.length)
  77. {
  78. return lastScreenLine;
  79. }
  80. }
  81. screenLine = -1;
  82. // Find the screen line containing this offset
  83. for(int i = 0; i < textArea.getVisibleLines(); i++)
  84. {
  85. LineInfo info = getLineInfo(i);
  86. if(info.physicalLine > line)
  87. {
  88. // line is invisible?
  89. return -1;
  90. }
  91. else if(info.physicalLine == line)
  92. {
  93. if(offset >= info.offset
  94. && offset < info.offset + info.length)
  95. {
  96. screenLine = i;
  97. break;
  98. }
  99. }
  100. }
  101. if(screenLine == -1)
  102. return -1;
  103. else
  104. {
  105. lastScreenLineP = line;
  106. lastScreenLine = screenLine;
  107. return screenLine;
  108. }
  109. }
  110. } //}}}
  111. //{{{ recalculateVisibleLines() method
  112. void recalculateVisibleLines()
  113. {
  114. LineInfo[] newLineInfo = new LineInfo[textArea.getVisibleLines()];
  115. int start;
  116. if(lineInfo == null)
  117. start = 0;
  118. else
  119. {
  120. start = Math.min(lineInfo.length,newLineInfo.length);
  121. System.arraycopy(lineInfo,0,newLineInfo,0,start);
  122. }
  123. for(int i = start; i < newLineInfo.length; i++)
  124. newLineInfo[i] = new LineInfo();
  125. lineInfo = newLineInfo;
  126. lastScreenLine = lastScreenLineP = -1;
  127. } //}}}
  128. //{{{ setBuffer() method
  129. void setBuffer(Buffer buffer)
  130. {
  131. this.buffer = buffer;
  132. lastScreenLine = lastScreenLineP = -1;
  133. } //}}}
  134. //{{{ scrollDown() method
  135. void scrollDown(int amount)
  136. {
  137. int visibleLines = textArea.getVisibleLines();
  138. System.arraycopy(lineInfo,amount,lineInfo,0,visibleLines - amount);
  139. for(int i = visibleLines - amount; i < visibleLines; i++)
  140. {
  141. lineInfo[i] = new LineInfo();
  142. }
  143. firstInvalidLine -= amount;
  144. if(firstInvalidLine < 0)
  145. firstInvalidLine = 0;
  146. if(Debug.CHUNK_CACHE_DEBUG)
  147. {
  148. System.err.println("f > t.f: only " + amount
  149. + " need updates");
  150. }
  151. lastScreenLine = lastScreenLineP = -1;
  152. } //}}}
  153. //{{{ scrollUp() method
  154. void scrollUp(int amount)
  155. {
  156. System.arraycopy(lineInfo,0,lineInfo,amount,
  157. textArea.getVisibleLines() - amount);
  158. for(int i = 0; i < amount; i++)
  159. {
  160. lineInfo[i] = new LineInfo();
  161. }
  162. // don't try this at home
  163. int oldFirstInvalidLine = firstInvalidLine;
  164. firstInvalidLine = 0;
  165. updateChunksUpTo(amount);
  166. firstInvalidLine = oldFirstInvalidLine + amount;
  167. if(firstInvalidLine > textArea.getVisibleLines())
  168. firstInvalidLine = textArea.getVisibleLines();
  169. if(Debug.CHUNK_CACHE_DEBUG)
  170. {
  171. System.err.println("f > t.f: only " + amount
  172. + " need updates");
  173. }
  174. lastScreenLine = lastScreenLineP = -1;
  175. } //}}}
  176. //{{{ invalidateAll() method
  177. void invalidateAll()
  178. {
  179. firstInvalidLine = 0;
  180. lastScreenLine = lastScreenLineP = -1;
  181. } //}}}
  182. //{{{ invalidateChunksFrom() method
  183. void invalidateChunksFrom(int screenLine)
  184. {
  185. firstInvalidLine = Math.min(screenLine,firstInvalidLine);
  186. if(screenLine <= lastScreenLine)
  187. lastScreenLine = lastScreenLineP = -1;
  188. } //}}}
  189. //{{{ invalidateChunksFromPhys() method
  190. void invalidateChunksFromPhys(int physicalLine)
  191. {
  192. for(int i = 0; i < firstInvalidLine; i++)
  193. {
  194. LineInfo info = lineInfo[i];
  195. if(info.physicalLine == -1 || info.physicalLine >= physicalLine)
  196. {
  197. firstInvalidLine = i;
  198. break;
  199. }
  200. }
  201. } //}}}
  202. //{{{ getLineInfo() method
  203. LineInfo getLineInfo(int screenLine)
  204. {
  205. updateChunksUpTo(screenLine);
  206. return lineInfo[screenLine];
  207. } //}}}
  208. //{{{ getLineSubregionCount() method
  209. int getLineSubregionCount(int physicalLine)
  210. {
  211. if(!textArea.displayManager.softWrap)
  212. return 1;
  213. out.clear();
  214. lineToChunkList(physicalLine,out);
  215. int size = out.size();
  216. if(size == 0)
  217. return 1;
  218. else
  219. return size;
  220. } //}}}
  221. //{{{ getSubregionOfOffset() method
  222. /**
  223. * Returns the subregion containing the specified offset. A subregion
  224. * is a subset of a physical line. Each screen line corresponds to one
  225. * subregion. Unlike the {@link #getScreenLineOfOffset()} method,
  226. * this method works with non-visible lines too.
  227. */
  228. int getSubregionOfOffset(int offset, LineInfo[] lineInfos)
  229. {
  230. for(int i = 0; i < lineInfos.length; i++)
  231. {
  232. LineInfo info = lineInfos[i];
  233. if(offset >= info.offset && offset < info.offset + info.length)
  234. return i;
  235. }
  236. return -1;
  237. } //}}}
  238. //{{{ xToSubregionOffset() method
  239. /**
  240. * Converts an x co-ordinate within a subregion into an offset from the
  241. * start of that subregion.
  242. * @param physicalLine The physical line number
  243. * @param subregion The subregion; if -1, then this is the last
  244. * subregion.
  245. * @param x The x co-ordinate
  246. * @param round Round up to next character if x is past the middle of a
  247. * character?
  248. */
  249. int xToSubregionOffset(int physicalLine, int subregion, int x,
  250. boolean round)
  251. {
  252. LineInfo[] infos = getLineInfosForPhysicalLine(physicalLine);
  253. if(subregion == -1)
  254. subregion += infos.length;
  255. return xToSubregionOffset(infos[subregion],x,round);
  256. } //}}}
  257. //{{{ xToSubregionOffset() method
  258. /**
  259. * Converts an x co-ordinate within a subregion into an offset from the
  260. * start of that subregion.
  261. * @param info The line info object
  262. * @param x The x co-ordinate
  263. * @param round Round up to next character if x is past the middle of a
  264. * character?
  265. */
  266. int xToSubregionOffset(LineInfo info, int x,
  267. boolean round)
  268. {
  269. int offset = Chunk.xToOffset(info.chunks,
  270. x - textArea.getHorizontalOffset(),round);
  271. if(offset == -1 || offset == info.offset + info.length)
  272. offset = info.offset + info.length - 1;
  273. return offset;
  274. } //}}}
  275. //{{{ subregionOffsetToX() method
  276. /**
  277. * Converts an offset within a subregion into an x co-ordinate.
  278. * @param physicalLine The physical line
  279. * @param offset The offset
  280. */
  281. int subregionOffsetToX(int physicalLine, int offset)
  282. {
  283. LineInfo[] infos = getLineInfosForPhysicalLine(physicalLine);
  284. LineInfo info = infos[getSubregionOfOffset(offset,infos)];
  285. return subregionOffsetToX(info,offset);
  286. } //}}}
  287. //{{{ subregionOffsetToX() method
  288. /**
  289. * Converts an offset within a subregion into an x co-ordinate.
  290. * @param info The line info object
  291. * @param offset The offset
  292. */
  293. int subregionOffsetToX(LineInfo info, int offset)
  294. {
  295. return (int)(textArea.getHorizontalOffset() + Chunk.offsetToX(
  296. info.chunks,offset));
  297. } //}}}
  298. //{{{ getSubregionStartOffset() method
  299. /**
  300. * Returns the start offset of the specified subregion of the specified
  301. * physical line.
  302. * @param line The physical line number
  303. * @param offset An offset
  304. */
  305. int getSubregionStartOffset(int line, int offset)
  306. {
  307. LineInfo[] lineInfos = getLineInfosForPhysicalLine(line);
  308. LineInfo info = lineInfos[getSubregionOfOffset(offset,lineInfos)];
  309. return textArea.getLineStartOffset(info.physicalLine)
  310. + info.offset;
  311. } //}}}
  312. //{{{ getSubregionEndOffset() method
  313. /**
  314. * Returns the end offset of the specified subregion of the specified
  315. * physical line.
  316. * @param line The physical line number
  317. * @param offset An offset
  318. */
  319. int getSubregionEndOffset(int line, int offset)
  320. {
  321. LineInfo[] lineInfos = getLineInfosForPhysicalLine(line);
  322. LineInfo info = lineInfos[getSubregionOfOffset(offset,lineInfos)];
  323. return textArea.getLineStartOffset(info.physicalLine)
  324. + info.offset + info.length;
  325. } //}}}
  326. //{{{ getBelowPosition() method
  327. /**
  328. * @param physicalLine The physical line number
  329. * @param offset The offset
  330. * @param x The location
  331. * @param ignoreWrap If true, behave as if soft wrap is off even if it
  332. * is on
  333. */
  334. int getBelowPosition(int physicalLine, int offset, int x,
  335. boolean ignoreWrap)
  336. {
  337. LineInfo[] lineInfos = getLineInfosForPhysicalLine(physicalLine);
  338. int subregion = getSubregionOfOffset(offset,lineInfos);
  339. if(subregion != lineInfos.length - 1 && !ignoreWrap)
  340. {
  341. return textArea.getLineStartOffset(physicalLine)
  342. + xToSubregionOffset(lineInfos[subregion + 1],
  343. x,true);
  344. }
  345. else
  346. {
  347. int nextLine = textArea.displayManager
  348. .getNextVisibleLine(physicalLine);
  349. if(nextLine == -1)
  350. return -1;
  351. else
  352. {
  353. return textArea.getLineStartOffset(nextLine)
  354. + xToSubregionOffset(nextLine,0,
  355. x,true);
  356. }
  357. }
  358. } //}}}
  359. //{{{ getAbovePosition() method
  360. /**
  361. * @param physicalLine The physical line number
  362. * @param offset The offset
  363. * @param x The location
  364. * @param ignoreWrap If true, behave as if soft wrap is off even if it
  365. * is on
  366. */
  367. int getAbovePosition(int physicalLine, int offset, int x,
  368. boolean ignoreWrap)
  369. {
  370. LineInfo[] lineInfos = getLineInfosForPhysicalLine(physicalLine);
  371. int subregion = getSubregionOfOffset(offset,lineInfos);
  372. if(subregion != 0 && !ignoreWrap)
  373. {
  374. return textArea.getLineStartOffset(physicalLine)
  375. + xToSubregionOffset(lineInfos[subregion - 1],
  376. x,true);
  377. }
  378. else
  379. {
  380. int prevLine = textArea.displayManager
  381. .getPrevVisibleLine(physicalLine);
  382. if(prevLine == -1)
  383. return -1;
  384. else
  385. {
  386. return textArea.getLineStartOffset(prevLine)
  387. + xToSubregionOffset(prevLine,-1,
  388. x,true);
  389. }
  390. }
  391. } //}}}
  392. //{{{ needFullRepaint() method
  393. /**
  394. * The needFullRepaint variable becomes true when the number of screen
  395. * lines in a physical line changes.
  396. */
  397. boolean needFullRepaint()
  398. {
  399. boolean retVal = needFullRepaint;
  400. needFullRepaint = false;
  401. return retVal;
  402. } //}}}
  403. //{{{ getLineInfosForPhysicalLine() method
  404. LineInfo[] getLineInfosForPhysicalLine(int physicalLine)
  405. {
  406. out.clear();
  407. if(buffer.isLoaded())
  408. lineToChunkList(physicalLine,out);
  409. if(out.size() == 0)
  410. out.add(null);
  411. ArrayList returnValue = new ArrayList(out.size());
  412. getLineInfosForPhysicalLine(physicalLine,returnValue);
  413. return (LineInfo[])returnValue.toArray(new LineInfo[out.size()]);
  414. } //}}}
  415. //{{{ Private members
  416. //{{{ Instance variables
  417. private JEditTextArea textArea;
  418. private Buffer buffer;
  419. private LineInfo[] lineInfo;
  420. private ArrayList out;
  421. private int firstInvalidLine;
  422. private int lastScreenLineP;
  423. private int lastScreenLine;
  424. private boolean needFullRepaint;
  425. private DisplayTokenHandler tokenHandler;
  426. //}}}
  427. //{{{ getLineInfosForPhysicalLine() method
  428. private void getLineInfosForPhysicalLine(int physicalLine, List list)
  429. {
  430. for(int i = 0; i < out.size(); i++)
  431. {
  432. Chunk chunks = (Chunk)out.get(i);
  433. LineInfo info = new LineInfo();
  434. info.physicalLine = physicalLine;
  435. if(i == 0)
  436. {
  437. info.firstSubregion = true;
  438. info.offset = 0;
  439. }
  440. else
  441. info.offset = chunks.offset;
  442. if(i == out.size() - 1)
  443. {
  444. info.lastSubregion = true;
  445. info.length = textArea.getLineLength(physicalLine)
  446. - info.offset + 1;
  447. }
  448. else
  449. {
  450. info.length = ((Chunk)out.get(i + 1)).offset
  451. - info.offset;
  452. }
  453. info.chunks = chunks;
  454. list.add(info);
  455. }
  456. } //}}}
  457. //{{{ updateChunksUpTo() method
  458. private void updateChunksUpTo(int lastScreenLine)
  459. {
  460. // this method is a nightmare
  461. if(lastScreenLine >= lineInfo.length)
  462. {
  463. throw new ArrayIndexOutOfBoundsException(lastScreenLine);
  464. }
  465. // if one line's chunks are invalid, remaining lines are also
  466. // invalid
  467. if(lastScreenLine < firstInvalidLine)
  468. return;
  469. // find a valid line closest to the last screen line
  470. int firstScreenLine = 0;
  471. for(int i = firstInvalidLine - 1; i >= 0; i--)
  472. {
  473. if(lineInfo[i].lastSubregion)
  474. {
  475. firstScreenLine = i + 1;
  476. break;
  477. }
  478. }
  479. int physicalLine;
  480. // for the first line displayed, take its physical line to be
  481. // the text area's first physical line
  482. if(firstScreenLine == 0)
  483. {
  484. physicalLine = textArea.getFirstPhysicalLine();
  485. }
  486. // otherwise, determine the next visible line
  487. else
  488. {
  489. int prevPhysLine = lineInfo[
  490. firstScreenLine - 1]
  491. .physicalLine;
  492. // if -1, the empty space at the end of the text area
  493. // when the buffer has less lines than are visible
  494. if(prevPhysLine == -1)
  495. physicalLine = -1;
  496. else
  497. {
  498. physicalLine = textArea
  499. .displayManager
  500. .getNextVisibleLine(prevPhysLine);
  501. }
  502. }
  503. if(Debug.CHUNK_CACHE_DEBUG)
  504. {
  505. Log.log(Log.DEBUG,this,"Updating chunks from " + firstScreenLine
  506. + " to " + lastScreenLine);
  507. }
  508. // Note that we rely on the fact that when a physical line is
  509. // invalidated, all screen lines/subregions of that line are
  510. // invalidated as well. See below comment for code that tries
  511. // to uphold this assumption.
  512. out.clear();
  513. int offset = 0;
  514. int length = 0;
  515. for(int i = firstScreenLine; i <= lastScreenLine; i++)
  516. {
  517. LineInfo info = lineInfo[i];
  518. Chunk chunks;
  519. // get another line of chunks
  520. if(out.size() == 0)
  521. {
  522. // unless this is the first time, increment
  523. // the line number
  524. if(physicalLine != -1 && i != firstScreenLine)
  525. {
  526. physicalLine = textArea.displayManager
  527. .getNextVisibleLine(physicalLine);
  528. }
  529. // empty space
  530. if(physicalLine == -1)
  531. {
  532. info.chunks = null;
  533. info.physicalLine = -1;
  534. continue;
  535. }
  536. // chunk the line.
  537. lineToChunkList(physicalLine,out);
  538. info.firstSubregion = true;
  539. // if the line has no text, out.size() == 0
  540. if(out.size() == 0)
  541. {
  542. textArea.displayManager
  543. .setScreenLineCount(
  544. physicalLine,1);
  545. if(i == 0)
  546. {
  547. if(textArea.displayManager.firstLine.skew > 0)
  548. {
  549. Log.log(Log.ERROR,this,"BUG: skew=" + textArea.displayManager.firstLine.skew + ",out.size()=" + out.size());
  550. textArea.displayManager.firstLine.skew = 0;
  551. needFullRepaint = true;
  552. lastScreenLine = lineInfo.length - 1;
  553. }
  554. }
  555. chunks = null;
  556. offset = 0;
  557. length = 1;
  558. }
  559. // otherwise, the number of subregions
  560. else
  561. {
  562. textArea.displayManager
  563. .setScreenLineCount(
  564. physicalLine,out.size());
  565. if(i == 0)
  566. {
  567. int skew = textArea.displayManager.firstLine.skew;
  568. if(skew >= out.size())
  569. {
  570. Log.log(Log.ERROR,this,"BUG: skew=" + skew + ",out.size()=" + out.size());
  571. skew = 0;
  572. needFullRepaint = true;
  573. lastScreenLine = lineInfo.length - 1;
  574. }
  575. else if(skew > 0)
  576. {
  577. info.firstSubregion = false;
  578. for(int j = 0; j < skew; j++)
  579. out.remove(0);
  580. }
  581. }
  582. chunks = (Chunk)out.get(0);
  583. out.remove(0);
  584. offset = chunks.offset;
  585. if(out.size() != 0)
  586. length = ((Chunk)out.get(0)).offset - offset;
  587. else
  588. length = textArea.getLineLength(physicalLine) - offset + 1;
  589. }
  590. }
  591. else
  592. {
  593. info.firstSubregion = false;
  594. chunks = (Chunk)out.get(0);
  595. out.remove(0);
  596. offset = chunks.offset;
  597. if(out.size() != 0)
  598. length = ((Chunk)out.get(0)).offset - offset;
  599. else
  600. length = textArea.getLineLength(physicalLine) - offset + 1;
  601. }
  602. boolean lastSubregion = (out.size() == 0);
  603. if(i == lastScreenLine
  604. && lastScreenLine != lineInfo.length - 1)
  605. {
  606. /* If this line has become longer or shorter
  607. * (in which case the new physical line number
  608. * is different from the cached one) we need to:
  609. * - continue updating past the last line
  610. * - advise the text area to repaint
  611. * On the other hand, if the line wraps beyond
  612. * lastScreenLine, we need to keep updating the
  613. * chunk list to ensure proper alignment of
  614. * invalidation flags (see start of method) */
  615. if(info.physicalLine != physicalLine
  616. || info.lastSubregion != lastSubregion)
  617. {
  618. lastScreenLine++;
  619. needFullRepaint = true;
  620. }
  621. else if(out.size() != 0)
  622. lastScreenLine++;
  623. }
  624. info.physicalLine = physicalLine;
  625. info.lastSubregion = lastSubregion;
  626. info.offset = offset;
  627. info.length = length;
  628. info.chunks = chunks;
  629. }
  630. firstInvalidLine = Math.max(lastScreenLine + 1,firstInvalidLine);
  631. } //}}}
  632. //{{{ lineToChunkList() method
  633. private void lineToChunkList(int physicalLine, List out)
  634. {
  635. TextAreaPainter painter = textArea.getPainter();
  636. tokenHandler.init(painter.getStyles(),
  637. painter.getFontRenderContext(),
  638. painter,out,
  639. (textArea.displayManager.softWrap
  640. ? textArea.displayManager.wrapMargin : 0.0f));
  641. buffer.markTokens(physicalLine,tokenHandler);
  642. } //}}}
  643. //}}}
  644. //{{{ LineInfo class
  645. static class LineInfo
  646. {
  647. int physicalLine;
  648. int offset;
  649. int length;
  650. boolean firstSubregion;
  651. boolean lastSubregion;
  652. Chunk chunks;
  653. int width;
  654. } //}}}
  655. }