/jEdit/branches/concurrency/org/gjt/sp/jedit/buffer/JEditBuffer.java

# · Java · 2669 lines · 1599 code · 328 blank · 742 comment · 261 complexity · a7b833eccc9b2acbfeb198e588cc356b MD5 · raw file

  1. /*
  2. * JEditBuffer.java - jEdit buffer
  3. * :tabSize=8:indentSize=8:noTabs=false:
  4. * :folding=explicit:collapseFolds=1:
  5. *
  6. * Copyright (C) 1998, 2005 Slava Pestov
  7. * Portions copyright (C) 1999, 2000 mike dillon
  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.buffer;
  24. //{{{ Imports
  25. import org.gjt.sp.jedit.Debug;
  26. import org.gjt.sp.jedit.Mode;
  27. import org.gjt.sp.jedit.TextUtilities;
  28. import org.gjt.sp.jedit.indent.IndentAction;
  29. import org.gjt.sp.jedit.indent.IndentRule;
  30. import org.gjt.sp.jedit.syntax.*;
  31. import org.gjt.sp.jedit.textarea.TextArea;
  32. import org.gjt.sp.util.IntegerArray;
  33. import org.gjt.sp.util.Log;
  34. import org.gjt.sp.util.StandardUtilities;
  35. import javax.swing.*;
  36. import javax.swing.text.Position;
  37. import javax.swing.text.Segment;
  38. import java.awt.*;
  39. import java.util.*;
  40. import java.util.List;
  41. import java.util.concurrent.locks.ReentrantReadWriteLock;
  42. import java.util.regex.Pattern;
  43. //}}}
  44. /**
  45. * A <code>JEditBuffer</code> represents the contents of an open text
  46. * file as it is maintained in the computer's memory (as opposed to
  47. * how it may be stored on a disk).<p>
  48. *
  49. * This class is partially thread-safe, however you must pay attention to two
  50. * very important guidelines:
  51. * <ul>
  52. * <li>Changes to a buffer can only be made from the AWT thread.
  53. * <li>When accessing the buffer from another thread, you must
  54. * grab a read lock if you plan on performing more than one call, to ensure that
  55. * the buffer contents are not changed by the AWT thread for the duration of the
  56. * lock. Only methods whose descriptions specify thread safety can be invoked
  57. * from other threads.
  58. * </ul>
  59. *
  60. * @author Slava Pestov
  61. * @version $Id: JEditBuffer.java 14304 2009-01-05 13:21:11Z kpouer $
  62. *
  63. * @since jEdit 4.3pre3
  64. */
  65. public class JEditBuffer
  66. {
  67. /**
  68. * Line separator property.
  69. */
  70. public static final String LINESEP = "lineSeparator";
  71. /**
  72. * Character encoding used when loading and saving.
  73. * @since jEdit 3.2pre4
  74. */
  75. public static final String ENCODING = "encoding";
  76. //{{{ JEditBuffer constructor
  77. public JEditBuffer(Map props)
  78. {
  79. bufferListeners = new Vector<Listener>();
  80. lock = new ReentrantReadWriteLock();
  81. contentMgr = new ContentManager();
  82. lineMgr = new LineManager();
  83. positionMgr = new PositionManager(this);
  84. undoMgr = new UndoManager(this);
  85. seg = new Segment();
  86. integerArray = new IntegerArray();
  87. propertyLock = new Object();
  88. properties = new HashMap<Object, PropValue>();
  89. //{{{ need to convert entries of 'props' to PropValue instances
  90. Set<Map.Entry> set = props.entrySet();
  91. for (Map.Entry entry : set)
  92. {
  93. properties.put(entry.getKey(),new PropValue(entry.getValue(),false));
  94. } //}}}
  95. // fill in defaults for these from system properties if the
  96. // corresponding buffer.XXX properties not set
  97. if(getProperty(ENCODING) == null)
  98. properties.put(ENCODING,new PropValue(System.getProperty("file.encoding"),false));
  99. if(getProperty(LINESEP) == null)
  100. properties.put(LINESEP,new PropValue(System.getProperty("line.separator"),false));
  101. } //}}}
  102. //{{{ JEditBuffer constructor
  103. /**
  104. * Create a new JEditBuffer.
  105. * It is used by independent textarea only
  106. */
  107. public JEditBuffer()
  108. {
  109. bufferListeners = new Vector<Listener>();
  110. lock = new ReentrantReadWriteLock();
  111. contentMgr = new ContentManager();
  112. lineMgr = new LineManager();
  113. positionMgr = new PositionManager(this);
  114. undoMgr = new UndoManager(this);
  115. seg = new Segment();
  116. integerArray = new IntegerArray();
  117. propertyLock = new Object();
  118. properties = new HashMap<Object, PropValue>();
  119. properties.put("wrap",new PropValue("none",false));
  120. properties.put("folding",new PropValue("none",false));
  121. tokenMarker = new TokenMarker();
  122. tokenMarker.addRuleSet(new ParserRuleSet("text","MAIN"));
  123. setTokenMarker(tokenMarker);
  124. loadText(null,null);
  125. // corresponding buffer.XXX properties not set
  126. if(getProperty(ENCODING) == null)
  127. properties.put(ENCODING,new PropValue(System.getProperty("file.encoding"),false));
  128. if(getProperty(LINESEP) == null)
  129. properties.put(LINESEP,new PropValue(System.getProperty("line.separator"),false));
  130. setFoldHandler(new DummyFoldHandler());
  131. } //}}}
  132. //{{{ Flags
  133. //{{{ isDirty() method
  134. /**
  135. * Returns whether there have been unsaved changes to this buffer.
  136. * This method is thread-safe.
  137. */
  138. public boolean isDirty()
  139. {
  140. return dirty;
  141. } //}}}
  142. //{{{ isLoading() method
  143. public boolean isLoading()
  144. {
  145. return loading;
  146. } //}}}
  147. //{{{ setLoading() method
  148. public void setLoading(boolean loading)
  149. {
  150. this.loading = loading;
  151. } //}}}
  152. //{{{ isPerformingIO() method
  153. /**
  154. * Returns true if the buffer is currently performing I/O.
  155. * This method is thread-safe.
  156. * @since jEdit 2.7pre1
  157. */
  158. public boolean isPerformingIO()
  159. {
  160. return isLoading() || io;
  161. } //}}}
  162. //{{{ setPerformingIO() method
  163. /**
  164. * Returns true if the buffer is currently performing I/O.
  165. * This method is thread-safe.
  166. * @since jEdit 2.7pre1
  167. */
  168. public void setPerformingIO(boolean io)
  169. {
  170. this.io = io;
  171. } //}}}
  172. //{{{ isEditable() method
  173. /**
  174. * Returns true if this file is editable, false otherwise. A file may
  175. * become uneditable if it is read only, or if I/O is in progress.
  176. * This method is thread-safe.
  177. * @since jEdit 2.7pre1
  178. */
  179. public boolean isEditable()
  180. {
  181. return !(isReadOnly() || isPerformingIO());
  182. } //}}}
  183. //{{{ isReadOnly() method
  184. /**
  185. * Returns true if this file is read only, false otherwise.
  186. * This method is thread-safe.
  187. */
  188. public boolean isReadOnly()
  189. {
  190. return readOnly || readOnlyOverride;
  191. } //}}}
  192. //{{{ setReadOnly() method
  193. /**
  194. * Sets the read only flag.
  195. * @param readOnly The read only flag
  196. */
  197. public void setReadOnly(boolean readOnly)
  198. {
  199. readOnlyOverride = readOnly;
  200. } //}}}
  201. //{{{ setDirty() method
  202. /**
  203. * Sets the 'dirty' (changed since last save) flag of this buffer.
  204. */
  205. public void setDirty(boolean d)
  206. {
  207. boolean editable = isEditable();
  208. if(d)
  209. {
  210. if(editable)
  211. dirty = true;
  212. }
  213. else
  214. {
  215. dirty = false;
  216. // fixes dirty flag not being reset on
  217. // save/insert/undo/redo/undo
  218. if(!isUndoInProgress())
  219. {
  220. // this ensures that undo can clear the dirty flag properly
  221. // when all edits up to a save are undone
  222. undoMgr.resetClearDirty();
  223. }
  224. }
  225. } //}}}
  226. //}}}
  227. //{{{ Thread safety
  228. //{{{ readLock() method
  229. /**
  230. * The buffer is guaranteed not to change between calls to
  231. * {@link #readLock()} and {@link #readUnlock()}.
  232. */
  233. public void readLock()
  234. {
  235. lock.readLock().lock();
  236. } //}}}
  237. //{{{ readUnlock() method
  238. /**
  239. * The buffer is guaranteed not to change between calls to
  240. * {@link #readLock()} and {@link #readUnlock()}.
  241. */
  242. public void readUnlock()
  243. {
  244. lock.readLock().unlock();
  245. } //}}}
  246. //{{{ writeLock() method
  247. /**
  248. * Attempting to obtain read lock will block between calls to
  249. * {@link #writeLock()} and {@link #writeUnlock()}.
  250. */
  251. public void writeLock()
  252. {
  253. lock.writeLock().lock();
  254. } //}}}
  255. //{{{ writeUnlock() method
  256. /**
  257. * Attempting to obtain read lock will block between calls to
  258. * {@link #writeLock()} and {@link #writeUnlock()}.
  259. */
  260. public void writeUnlock()
  261. {
  262. lock.writeLock().unlock();
  263. } //}}}
  264. //}}}
  265. //{{{ Line offset methods
  266. //{{{ getLength() method
  267. /**
  268. * Returns the number of characters in the buffer. This method is thread-safe.
  269. */
  270. public int getLength()
  271. {
  272. // no need to lock since this just returns a value and that's it
  273. return contentMgr.getLength();
  274. } //}}}
  275. //{{{ getLineCount() method
  276. /**
  277. * Returns the number of physical lines in the buffer.
  278. * This method is thread-safe.
  279. * @since jEdit 3.1pre1
  280. */
  281. public int getLineCount()
  282. {
  283. // no need to lock since this just returns a value and that's it
  284. return lineMgr.getLineCount();
  285. } //}}}
  286. //{{{ getLineOfOffset() method
  287. /**
  288. * Returns the line containing the specified offset.
  289. * This method is thread-safe.
  290. * @param offset The offset
  291. * @since jEdit 4.0pre1
  292. */
  293. public int getLineOfOffset(int offset)
  294. {
  295. try
  296. {
  297. readLock();
  298. if(offset < 0 || offset > getLength())
  299. throw new ArrayIndexOutOfBoundsException(offset);
  300. return lineMgr.getLineOfOffset(offset);
  301. }
  302. finally
  303. {
  304. readUnlock();
  305. }
  306. } //}}}
  307. //{{{ getLineStartOffset() method
  308. /**
  309. * Returns the start offset of the specified line.
  310. * This method is thread-safe.
  311. * @param line The line
  312. * @return The start offset of the specified line
  313. * @since jEdit 4.0pre1
  314. */
  315. public int getLineStartOffset(int line)
  316. {
  317. try
  318. {
  319. readLock();
  320. if(line < 0 || line >= lineMgr.getLineCount())
  321. throw new ArrayIndexOutOfBoundsException(line);
  322. else if(line == 0)
  323. return 0;
  324. return lineMgr.getLineEndOffset(line - 1);
  325. }
  326. finally
  327. {
  328. readUnlock();
  329. }
  330. } //}}}
  331. //{{{ getLineEndOffset() method
  332. /**
  333. * Returns the end offset of the specified line.
  334. * This method is thread-safe.
  335. * @param line The line
  336. * @return The end offset of the specified line
  337. * invalid.
  338. * @since jEdit 4.0pre1
  339. */
  340. public int getLineEndOffset(int line)
  341. {
  342. try
  343. {
  344. readLock();
  345. if(line < 0 || line >= lineMgr.getLineCount())
  346. throw new ArrayIndexOutOfBoundsException(line);
  347. return lineMgr.getLineEndOffset(line);
  348. }
  349. finally
  350. {
  351. readUnlock();
  352. }
  353. } //}}}
  354. //{{{ getLineLength() method
  355. /**
  356. * Returns the length of the specified line.
  357. * This method is thread-safe.
  358. * @param line The line
  359. * @since jEdit 4.0pre1
  360. */
  361. public int getLineLength(int line)
  362. {
  363. try
  364. {
  365. readLock();
  366. return getLineEndOffset(line)
  367. - getLineStartOffset(line) - 1;
  368. }
  369. finally
  370. {
  371. readUnlock();
  372. }
  373. } //}}}
  374. //{{{ getPriorNonEmptyLine() method
  375. /**
  376. * Auto indent needs this.
  377. */
  378. public int getPriorNonEmptyLine(int lineIndex)
  379. {
  380. int returnValue = -1;
  381. if (!mode.getIgnoreWhitespace())
  382. {
  383. return lineIndex - 1;
  384. }
  385. for(int i = lineIndex - 1; i >= 0; i--)
  386. {
  387. getLineText(i,seg);
  388. if(seg.count != 0)
  389. returnValue = i;
  390. for(int j = 0; j < seg.count; j++)
  391. {
  392. char ch = seg.array[seg.offset + j];
  393. if(!Character.isWhitespace(ch))
  394. return i;
  395. }
  396. }
  397. // didn't find a line that contains non-whitespace chars
  398. // so return index of prior whitespace line
  399. return returnValue;
  400. } //}}}
  401. //}}}
  402. //{{{ Text getters and setters
  403. //{{{ getLineText() method
  404. /**
  405. * Returns the text on the specified line.
  406. * This method is thread-safe.
  407. * @param line The line
  408. * @return The text, or null if the line is invalid
  409. * @since jEdit 4.0pre1
  410. */
  411. public String getLineText(int line)
  412. {
  413. if(line < 0 || line >= lineMgr.getLineCount())
  414. throw new ArrayIndexOutOfBoundsException(line);
  415. try
  416. {
  417. readLock();
  418. int start = line == 0 ? 0 : lineMgr.getLineEndOffset(line - 1);
  419. int end = lineMgr.getLineEndOffset(line);
  420. return getText(start,end - start - 1);
  421. }
  422. finally
  423. {
  424. readUnlock();
  425. }
  426. } //}}}
  427. //{{{ getLineText() method
  428. /**
  429. * Returns the specified line in a <code>Segment</code>.<p>
  430. *
  431. * Using a <classname>Segment</classname> is generally more
  432. * efficient than using a <classname>String</classname> because it
  433. * results in less memory allocation and array copying.<p>
  434. *
  435. * This method is thread-safe.
  436. *
  437. * @param line The line
  438. * @since jEdit 4.0pre1
  439. */
  440. public void getLineText(int line, Segment segment)
  441. {
  442. if(line < 0 || line >= lineMgr.getLineCount())
  443. throw new ArrayIndexOutOfBoundsException(line);
  444. try
  445. {
  446. readLock();
  447. int start = line == 0 ? 0 : lineMgr.getLineEndOffset(line - 1);
  448. int end = lineMgr.getLineEndOffset(line);
  449. getText(start,end - start - 1,segment);
  450. }
  451. finally
  452. {
  453. readUnlock();
  454. }
  455. } //}}}
  456. //{{{ getLineSegment() method
  457. /**
  458. * Returns the text on the specified line.
  459. * This method is thread-safe.
  460. *
  461. * @param line The line index.
  462. * @return The text, or null if the line is invalid
  463. *
  464. * @since jEdit 4.3pre15
  465. */
  466. public CharSequence getLineSegment(int line)
  467. {
  468. if(line < 0 || line >= lineMgr.getLineCount())
  469. throw new ArrayIndexOutOfBoundsException(line);
  470. try
  471. {
  472. readLock();
  473. int start = line == 0 ? 0 : lineMgr.getLineEndOffset(line - 1);
  474. int end = lineMgr.getLineEndOffset(line);
  475. return getSegment(start,end - start - 1);
  476. }
  477. finally
  478. {
  479. readUnlock();
  480. }
  481. } //}}}
  482. //{{{ getText() method
  483. /**
  484. * Returns the specified text range. This method is thread-safe.
  485. * @param start The start offset
  486. * @param length The number of characters to get
  487. */
  488. public String getText(int start, int length)
  489. {
  490. try
  491. {
  492. readLock();
  493. if(start < 0 || length < 0
  494. || start + length > contentMgr.getLength())
  495. throw new ArrayIndexOutOfBoundsException(start + ":" + length);
  496. return contentMgr.getText(start,length);
  497. }
  498. finally
  499. {
  500. readUnlock();
  501. }
  502. } //}}}
  503. //{{{ getText() method
  504. /**
  505. * Returns the specified text range in a <code>Segment</code>.<p>
  506. *
  507. * Using a <classname>Segment</classname> is generally more
  508. * efficient than using a <classname>String</classname> because it
  509. * results in less memory allocation and array copying.<p>
  510. *
  511. * This method is thread-safe.
  512. *
  513. * @param start The start offset
  514. * @param length The number of characters to get
  515. * @param seg The segment to copy the text to
  516. */
  517. public void getText(int start, int length, Segment seg)
  518. {
  519. try
  520. {
  521. readLock();
  522. if(start < 0 || length < 0
  523. || start + length > contentMgr.getLength())
  524. throw new ArrayIndexOutOfBoundsException(start + ":" + length);
  525. contentMgr.getText(start,length,seg);
  526. }
  527. finally
  528. {
  529. readUnlock();
  530. }
  531. } //}}}
  532. //{{{ getSegment() method
  533. /**
  534. * Returns the specified text range. This method is thread-safe.
  535. *
  536. * @param start The start offset
  537. * @param length The number of characters to get
  538. *
  539. * @since jEdit 4.3pre15
  540. */
  541. public CharSequence getSegment(int start, int length)
  542. {
  543. try
  544. {
  545. readLock();
  546. if(start < 0 || length < 0
  547. || start + length > contentMgr.getLength())
  548. throw new ArrayIndexOutOfBoundsException(start + ":" + length);
  549. return contentMgr.getSegment(start,length);
  550. }
  551. finally
  552. {
  553. readUnlock();
  554. }
  555. } //}}}
  556. //{{{ insert() method
  557. /**
  558. * Inserts a string into the buffer.
  559. * @param offset The offset
  560. * @param str The string
  561. * @since jEdit 4.0pre1
  562. */
  563. public void insert(int offset, String str)
  564. {
  565. if(str == null)
  566. return;
  567. int len = str.length();
  568. if(len == 0)
  569. return;
  570. if(isReadOnly())
  571. throw new RuntimeException("buffer read-only");
  572. try
  573. {
  574. writeLock();
  575. if(offset < 0 || offset > contentMgr.getLength())
  576. throw new ArrayIndexOutOfBoundsException(offset);
  577. contentMgr.insert(offset,str);
  578. integerArray.clear();
  579. for(int i = 0; i < len; i++)
  580. {
  581. if(str.charAt(i) == '\n')
  582. integerArray.add(i + 1);
  583. }
  584. if(!undoInProgress)
  585. {
  586. undoMgr.contentInserted(offset,len,str,!dirty);
  587. }
  588. contentInserted(offset,len,integerArray);
  589. }
  590. finally
  591. {
  592. writeUnlock();
  593. }
  594. } //}}}
  595. //{{{ insert() method
  596. /**
  597. * Inserts a string into the buffer.
  598. * @param offset The offset
  599. * @param seg The segment
  600. * @since jEdit 4.0pre1
  601. */
  602. public void insert(int offset, Segment seg)
  603. {
  604. if(seg.count == 0)
  605. return;
  606. if(isReadOnly())
  607. throw new RuntimeException("buffer read-only");
  608. try
  609. {
  610. writeLock();
  611. if(offset < 0 || offset > contentMgr.getLength())
  612. throw new ArrayIndexOutOfBoundsException(offset);
  613. contentMgr.insert(offset,seg);
  614. integerArray.clear();
  615. for(int i = 0; i < seg.count; i++)
  616. {
  617. if(seg.array[seg.offset + i] == '\n')
  618. integerArray.add(i + 1);
  619. }
  620. if(!undoInProgress)
  621. {
  622. undoMgr.contentInserted(offset,seg.count,
  623. seg.toString(),!dirty);
  624. }
  625. contentInserted(offset,seg.count,integerArray);
  626. }
  627. finally
  628. {
  629. writeUnlock();
  630. }
  631. } //}}}
  632. //{{{ remove() method
  633. /**
  634. * Removes the specified rang efrom the buffer.
  635. * @param offset The start offset
  636. * @param length The number of characters to remove
  637. */
  638. public void remove(int offset, int length)
  639. {
  640. if(length == 0)
  641. return;
  642. if(isReadOnly())
  643. throw new RuntimeException("buffer read-only");
  644. try
  645. {
  646. transaction = true;
  647. writeLock();
  648. if(offset < 0 || length < 0
  649. || offset + length > contentMgr.getLength())
  650. throw new ArrayIndexOutOfBoundsException(offset + ":" + length);
  651. int startLine = lineMgr.getLineOfOffset(offset);
  652. int endLine = lineMgr.getLineOfOffset(offset + length);
  653. int numLines = endLine - startLine;
  654. if(!undoInProgress && !loading)
  655. {
  656. undoMgr.contentRemoved(offset,length,
  657. getText(offset,length),
  658. !dirty);
  659. }
  660. firePreContentRemoved(startLine,offset,numLines,length);
  661. contentMgr.remove(offset,length);
  662. lineMgr.contentRemoved(startLine,offset,numLines,length);
  663. positionMgr.contentRemoved(offset,length);
  664. fireContentRemoved(startLine,offset,numLines,length);
  665. /* otherwise it will be delivered later */
  666. if(!undoInProgress && !insideCompoundEdit())
  667. fireTransactionComplete();
  668. setDirty(true);
  669. }
  670. finally
  671. {
  672. transaction = false;
  673. writeUnlock();
  674. }
  675. } //}}}
  676. //}}}
  677. //{{{ Indentation
  678. //{{{ removeTrailingWhiteSpace() method
  679. /**
  680. * Removes trailing whitespace from all lines in the specified list.
  681. * @param lines The line numbers
  682. * @since jEdit 3.2pre1
  683. */
  684. public void removeTrailingWhiteSpace(int[] lines)
  685. {
  686. try
  687. {
  688. beginCompoundEdit();
  689. for(int i = 0; i < lines.length; i++)
  690. {
  691. int pos, lineStart, lineEnd, tail;
  692. getLineText(lines[i],seg);
  693. // blank line
  694. if (seg.count == 0) continue;
  695. lineStart = seg.offset;
  696. lineEnd = seg.offset + seg.count - 1;
  697. for (pos = lineEnd; pos >= lineStart; pos--)
  698. {
  699. if (!Character.isWhitespace(seg.array[pos]))
  700. break;
  701. }
  702. tail = lineEnd - pos;
  703. // no whitespace
  704. if (tail == 0) continue;
  705. remove(getLineEndOffset(lines[i]) - 1 - tail,tail);
  706. }
  707. }
  708. finally
  709. {
  710. endCompoundEdit();
  711. }
  712. } //}}}
  713. //{{{ shiftIndentLeft() method
  714. /**
  715. * Shifts the indent of each line in the specified list to the left.
  716. * @param lines The line numbers
  717. * @since jEdit 3.2pre1
  718. */
  719. public void shiftIndentLeft(int[] lines)
  720. {
  721. int tabSize = getTabSize();
  722. int indentSize = getIndentSize();
  723. boolean noTabs = getBooleanProperty("noTabs");
  724. try
  725. {
  726. beginCompoundEdit();
  727. for(int i = 0; i < lines.length; i++)
  728. {
  729. int lineStart = getLineStartOffset(lines[i]);
  730. CharSequence line = getLineSegment(lines[i]);
  731. int whiteSpace = StandardUtilities
  732. .getLeadingWhiteSpace(line);
  733. if(whiteSpace == 0)
  734. continue;
  735. int whiteSpaceWidth = Math.max(0,StandardUtilities
  736. .getLeadingWhiteSpaceWidth(line,tabSize)
  737. - indentSize);
  738. insert(lineStart + whiteSpace,StandardUtilities
  739. .createWhiteSpace(whiteSpaceWidth,
  740. noTabs ? 0 : tabSize));
  741. remove(lineStart,whiteSpace);
  742. }
  743. }
  744. finally
  745. {
  746. endCompoundEdit();
  747. }
  748. } //}}}
  749. //{{{ shiftIndentRight() method
  750. /**
  751. * Shifts the indent of each line in the specified list to the right.
  752. * @param lines The line numbers
  753. * @since jEdit 3.2pre1
  754. */
  755. public void shiftIndentRight(int[] lines)
  756. {
  757. try
  758. {
  759. beginCompoundEdit();
  760. int tabSize = getTabSize();
  761. int indentSize = getIndentSize();
  762. boolean noTabs = getBooleanProperty("noTabs");
  763. for(int i = 0; i < lines.length; i++)
  764. {
  765. int lineStart = getLineStartOffset(lines[i]);
  766. CharSequence line = getLineSegment(lines[i]);
  767. int whiteSpace = StandardUtilities
  768. .getLeadingWhiteSpace(line);
  769. // silly usability hack
  770. //if(lines.length != 1 && whiteSpace == 0)
  771. // continue;
  772. int whiteSpaceWidth = StandardUtilities
  773. .getLeadingWhiteSpaceWidth(
  774. line,tabSize) + indentSize;
  775. insert(lineStart + whiteSpace,StandardUtilities
  776. .createWhiteSpace(whiteSpaceWidth,
  777. noTabs ? 0 : tabSize));
  778. remove(lineStart,whiteSpace);
  779. }
  780. }
  781. finally
  782. {
  783. endCompoundEdit();
  784. }
  785. } //}}}
  786. //{{{ indentLines() method
  787. /**
  788. * Indents all specified lines.
  789. * @param start The first line to indent
  790. * @param end The last line to indent
  791. * @since jEdit 3.1pre3
  792. */
  793. public void indentLines(int start, int end)
  794. {
  795. try
  796. {
  797. beginCompoundEdit();
  798. for(int i = start; i <= end; i++)
  799. indentLine(i,true);
  800. }
  801. finally
  802. {
  803. endCompoundEdit();
  804. }
  805. } //}}}
  806. //{{{ indentLines() method
  807. /**
  808. * Indents all specified lines.
  809. * @param lines The line numbers
  810. * @since jEdit 3.2pre1
  811. */
  812. public void indentLines(int[] lines)
  813. {
  814. try
  815. {
  816. beginCompoundEdit();
  817. for(int i = 0; i < lines.length; i++)
  818. indentLine(lines[i],true);
  819. }
  820. finally
  821. {
  822. endCompoundEdit();
  823. }
  824. } //}}}
  825. //{{{ indentLine() method
  826. /**
  827. * @deprecated Use {@link #indentLine(int,boolean)} instead.
  828. */
  829. @Deprecated
  830. public boolean indentLine(int lineIndex, boolean canIncreaseIndent,
  831. boolean canDecreaseIndent)
  832. {
  833. return indentLine(lineIndex,canDecreaseIndent);
  834. } //}}}
  835. //{{{ indentLine() method
  836. /**
  837. * Indents the specified line.
  838. * @param lineIndex The line number to indent
  839. * @param canDecreaseIndent If true, the indent can be decreased as a
  840. * result of this. Set this to false for Tab key.
  841. * @return true If indentation took place, false otherwise.
  842. * @since jEdit 4.2pre2
  843. */
  844. public boolean indentLine(int lineIndex, boolean canDecreaseIndent)
  845. {
  846. int[] whitespaceChars = new int[1];
  847. int currentIndent = getCurrentIndentForLine(lineIndex,
  848. whitespaceChars);
  849. int idealIndent = getIdealIndentForLine(lineIndex);
  850. if(idealIndent == -1 || idealIndent == currentIndent
  851. || (!canDecreaseIndent && idealIndent < currentIndent))
  852. return false;
  853. // Do it
  854. try
  855. {
  856. beginCompoundEdit();
  857. int start = getLineStartOffset(lineIndex);
  858. remove(start,whitespaceChars[0]);
  859. insert(start,StandardUtilities.createWhiteSpace(
  860. idealIndent, getBooleanProperty("noTabs") ? 0 : getTabSize()));
  861. }
  862. finally
  863. {
  864. endCompoundEdit();
  865. }
  866. return true;
  867. } //}}}
  868. //{{{ getCurrentIndentForLine() method
  869. /**
  870. * Returns the line's current leading indent.
  871. * @param lineIndex The line number
  872. * @param whitespaceChars If this is non-null, the number of whitespace
  873. * characters is stored at the 0 index
  874. * @since jEdit 4.2pre2
  875. */
  876. public int getCurrentIndentForLine(int lineIndex, int[] whitespaceChars)
  877. {
  878. getLineText(lineIndex,seg);
  879. int tabSize = getTabSize();
  880. int currentIndent = 0;
  881. loop: for(int i = 0; i < seg.count; i++)
  882. {
  883. char c = seg.array[seg.offset + i];
  884. switch(c)
  885. {
  886. case ' ':
  887. currentIndent++;
  888. if(whitespaceChars != null)
  889. whitespaceChars[0]++;
  890. break;
  891. case '\t':
  892. currentIndent += tabSize - (currentIndent
  893. % tabSize);
  894. if(whitespaceChars != null)
  895. whitespaceChars[0]++;
  896. break;
  897. default:
  898. break loop;
  899. }
  900. }
  901. return currentIndent;
  902. } //}}}
  903. //{{{ getIdealIndentForLine() method
  904. /**
  905. * Returns the ideal leading indent for the specified line.
  906. * This will apply the various auto-indent rules.
  907. * @param lineIndex The line number
  908. */
  909. public int getIdealIndentForLine(int lineIndex)
  910. {
  911. int prevLineIndex = getPriorNonEmptyLine(lineIndex);
  912. int prevPrevLineIndex = prevLineIndex < 0 ? -1
  913. : getPriorNonEmptyLine(prevLineIndex);
  914. int oldIndent = prevLineIndex == -1 ? 0 :
  915. StandardUtilities.getLeadingWhiteSpaceWidth(
  916. getLineSegment(prevLineIndex),
  917. getTabSize());
  918. int newIndent = oldIndent;
  919. List<IndentRule> indentRules = getIndentRules(lineIndex);
  920. List<IndentAction> actions = new LinkedList<IndentAction>();
  921. for (int i = 0;i<indentRules.size();i++)
  922. {
  923. IndentRule rule = indentRules.get(i);
  924. rule.apply(this,lineIndex,prevLineIndex,
  925. prevPrevLineIndex,actions);
  926. }
  927. for (IndentAction action : actions)
  928. {
  929. newIndent = action.calculateIndent(this, lineIndex,
  930. oldIndent, newIndent);
  931. if (!action.keepChecking())
  932. break;
  933. }
  934. if (newIndent < 0)
  935. newIndent = 0;
  936. return newIndent;
  937. } //}}}
  938. //{{{ getVirtualWidth() method
  939. /**
  940. * Returns the virtual column number (taking tabs into account) of the
  941. * specified position.
  942. *
  943. * @param line The line number
  944. * @param column The column number
  945. * @since jEdit 4.1pre1
  946. */
  947. public int getVirtualWidth(int line, int column)
  948. {
  949. try
  950. {
  951. readLock();
  952. int start = getLineStartOffset(line);
  953. getText(start,column,seg);
  954. return StandardUtilities.getVirtualWidth(seg,getTabSize());
  955. }
  956. finally
  957. {
  958. readUnlock();
  959. }
  960. } //}}}
  961. //{{{ getOffsetOfVirtualColumn() method
  962. /**
  963. * Returns the offset of a virtual column number (taking tabs
  964. * into account) relative to the start of the line in question.
  965. *
  966. * @param line The line number
  967. * @param column The virtual column number
  968. * @param totalVirtualWidth If this array is non-null, the total
  969. * virtual width will be stored in its first location if this method
  970. * returns -1.
  971. *
  972. * @return -1 if the column is out of bounds
  973. *
  974. * @since jEdit 4.1pre1
  975. */
  976. public int getOffsetOfVirtualColumn(int line, int column,
  977. int[] totalVirtualWidth)
  978. {
  979. try
  980. {
  981. readLock();
  982. getLineText(line,seg);
  983. return StandardUtilities.getOffsetOfVirtualColumn(seg,
  984. getTabSize(),column,totalVirtualWidth);
  985. }
  986. finally
  987. {
  988. readUnlock();
  989. }
  990. } //}}}
  991. //{{{ insertAtColumn() method
  992. /**
  993. * Like the {@link #insert(int,String)} method, but inserts the string at
  994. * the specified virtual column. Inserts spaces as appropriate if
  995. * the line is shorter than the column.
  996. * @param line The line number
  997. * @param col The virtual column number
  998. * @param str The string
  999. */
  1000. public void insertAtColumn(int line, int col, String str)
  1001. {
  1002. try
  1003. {
  1004. writeLock();
  1005. int[] total = new int[1];
  1006. int offset = getOffsetOfVirtualColumn(line,col,total);
  1007. if(offset == -1)
  1008. {
  1009. offset = getLineEndOffset(line) - 1;
  1010. str = StandardUtilities.createWhiteSpace(col - total[0],0) + str;
  1011. }
  1012. else
  1013. offset += getLineStartOffset(line);
  1014. insert(offset,str);
  1015. }
  1016. finally
  1017. {
  1018. writeUnlock();
  1019. }
  1020. } //}}}
  1021. //{{{ insertIndented() method
  1022. /**
  1023. * Inserts a string into the buffer, indenting each line of the string
  1024. * to match the indent of the first line.
  1025. *
  1026. * @param offset The offset
  1027. * @param text The text
  1028. *
  1029. * @return The number of characters of indent inserted on each new
  1030. * line. This is used by the abbreviations code.
  1031. *
  1032. * @since jEdit 4.2pre14
  1033. */
  1034. public int insertIndented(int offset, String text)
  1035. {
  1036. try
  1037. {
  1038. beginCompoundEdit();
  1039. // obtain the leading indent for later use
  1040. int firstLine = getLineOfOffset(offset);
  1041. CharSequence lineText = getLineSegment(firstLine);
  1042. int leadingIndent
  1043. = StandardUtilities.getLeadingWhiteSpaceWidth(
  1044. lineText,getTabSize());
  1045. String whiteSpace = StandardUtilities.createWhiteSpace(
  1046. leadingIndent,getBooleanProperty("noTabs")
  1047. ? 0 : getTabSize());
  1048. insert(offset,text);
  1049. int lastLine = getLineOfOffset(offset + text.length());
  1050. // note that if firstLine == lastLine, loop does not
  1051. // execute
  1052. for(int i = firstLine + 1; i <= lastLine; i++)
  1053. {
  1054. insert(getLineStartOffset(i),whiteSpace);
  1055. }
  1056. return whiteSpace.length();
  1057. }
  1058. finally
  1059. {
  1060. endCompoundEdit();
  1061. }
  1062. } //}}}
  1063. //{{{ isElectricKey() method
  1064. /**
  1065. * Should inserting this character trigger a re-indent of
  1066. * the current line?
  1067. * @since jEdit 4.3pre2
  1068. * @deprecated Use #isElectricKey(char,int)
  1069. */
  1070. public boolean isElectricKey(char ch)
  1071. {
  1072. return mode.isElectricKey(ch);
  1073. } //}}}
  1074. //{{{ isElectricKey() method
  1075. /**
  1076. * Should inserting this character trigger a re-indent of
  1077. * the current line?
  1078. * @since jEdit 4.3pre9
  1079. */
  1080. public boolean isElectricKey(char ch, int line)
  1081. {
  1082. TokenMarker.LineContext ctx = lineMgr.getLineContext(line);
  1083. Mode mode = ModeProvider.instance.getMode(ctx.rules.getModeName());
  1084. // mode can be null, though that's probably an error "further up":
  1085. if (mode == null)
  1086. return false;
  1087. return mode.isElectricKey(ch);
  1088. } //}}}
  1089. //}}}
  1090. //{{{ Syntax highlighting
  1091. //{{{ markTokens() method
  1092. /**
  1093. * Returns the syntax tokens for the specified line.
  1094. * @param lineIndex The line number
  1095. * @param tokenHandler The token handler that will receive the syntax
  1096. * tokens
  1097. * @since jEdit 4.1pre1
  1098. */
  1099. public void markTokens(int lineIndex, TokenHandler tokenHandler)
  1100. {
  1101. Segment seg;
  1102. if(SwingUtilities.isEventDispatchThread())
  1103. seg = this.seg;
  1104. else
  1105. seg = new Segment();
  1106. if(lineIndex < 0 || lineIndex >= lineMgr.getLineCount())
  1107. throw new ArrayIndexOutOfBoundsException(lineIndex);
  1108. int firstInvalidLineContext = lineMgr.getFirstInvalidLineContext();
  1109. int start;
  1110. if(textMode || firstInvalidLineContext == -1)
  1111. {
  1112. start = lineIndex;
  1113. }
  1114. else
  1115. {
  1116. start = Math.min(firstInvalidLineContext,
  1117. lineIndex);
  1118. }
  1119. if(Debug.TOKEN_MARKER_DEBUG)
  1120. Log.log(Log.DEBUG,this,"tokenize from " + start + " to " + lineIndex);
  1121. TokenMarker.LineContext oldContext = null;
  1122. TokenMarker.LineContext context = null;
  1123. for(int i = start; i <= lineIndex; i++)
  1124. {
  1125. getLineText(i,seg);
  1126. oldContext = lineMgr.getLineContext(i);
  1127. TokenMarker.LineContext prevContext = (
  1128. (i == 0 || textMode) ? null
  1129. : lineMgr.getLineContext(i - 1)
  1130. );
  1131. context = tokenMarker.markTokens(prevContext,
  1132. (i == lineIndex ? tokenHandler
  1133. : DummyTokenHandler.INSTANCE), seg);
  1134. lineMgr.setLineContext(i,context);
  1135. }
  1136. int lineCount = lineMgr.getLineCount();
  1137. if(lineCount - 1 == lineIndex)
  1138. lineMgr.setFirstInvalidLineContext(-1);
  1139. else if(oldContext != context)
  1140. lineMgr.setFirstInvalidLineContext(lineIndex + 1);
  1141. else if(firstInvalidLineContext == -1)
  1142. /* do nothing */;
  1143. else
  1144. {
  1145. lineMgr.setFirstInvalidLineContext(Math.max(
  1146. firstInvalidLineContext,lineIndex + 1));
  1147. }
  1148. } //}}}
  1149. //{{{ getTokenMarker() method
  1150. public TokenMarker getTokenMarker()
  1151. {
  1152. return tokenMarker;
  1153. } //}}}
  1154. //{{{ setTokenMarker() method
  1155. public void setTokenMarker(TokenMarker tokenMarker)
  1156. {
  1157. TokenMarker oldTokenMarker = this.tokenMarker;
  1158. this.tokenMarker = tokenMarker;
  1159. // don't do this on initial token marker
  1160. if(oldTokenMarker != null && tokenMarker != oldTokenMarker)
  1161. {
  1162. lineMgr.setFirstInvalidLineContext(0);
  1163. }
  1164. } //}}}
  1165. //{{{ createPosition() method
  1166. /**
  1167. * Creates a floating position.
  1168. * @param offset The offset
  1169. */
  1170. public Position createPosition(int offset)
  1171. {
  1172. try
  1173. {
  1174. readLock();
  1175. if(offset < 0 || offset > contentMgr.getLength())
  1176. throw new ArrayIndexOutOfBoundsException(offset);
  1177. return positionMgr.createPosition(offset);
  1178. }
  1179. finally
  1180. {
  1181. readUnlock();
  1182. }
  1183. } //}}}
  1184. //}}}
  1185. //{{{ Property methods
  1186. //{{{ propertiesChanged() method
  1187. /**
  1188. * Reloads settings from the properties. This should be called
  1189. * after the <code>syntax</code> or <code>folding</code>
  1190. * buffer-local properties are changed.
  1191. */
  1192. public void propertiesChanged()
  1193. {
  1194. String folding = getStringProperty("folding");
  1195. FoldHandler handler = FoldHandler.getFoldHandler(folding);
  1196. if(handler != null)
  1197. {
  1198. setFoldHandler(handler);
  1199. }
  1200. else
  1201. {
  1202. if (folding != null)
  1203. Log.log(Log.WARNING, this, "invalid 'folding' property: " + folding);
  1204. setFoldHandler(new DummyFoldHandler());
  1205. }
  1206. } //}}}
  1207. //{{{ getTabSize() method
  1208. /**
  1209. * Returns the tab size used in this buffer. This is equivalent
  1210. * to calling <code>getProperty("tabSize")</code>.
  1211. * This method is thread-safe.
  1212. */
  1213. public int getTabSize()
  1214. {
  1215. int tabSize = getIntegerProperty("tabSize",8);
  1216. if(tabSize <= 0)
  1217. return 8;
  1218. else
  1219. return tabSize;
  1220. } //}}}
  1221. //{{{ getIndentSize() method
  1222. /**
  1223. * Returns the indent size used in this buffer. This is equivalent
  1224. * to calling <code>getProperty("indentSize")</code>.
  1225. * This method is thread-safe.
  1226. * @since jEdit 2.7pre1
  1227. */
  1228. public int getIndentSize()
  1229. {
  1230. int indentSize = getIntegerProperty("indentSize",8);
  1231. if(indentSize <= 0)
  1232. return 8;
  1233. else
  1234. return indentSize;
  1235. } //}}}
  1236. //{{{ getProperty() method
  1237. /**
  1238. * Returns the value of a buffer-local property.<p>
  1239. *
  1240. * Using this method is generally discouraged, because it returns an
  1241. * <code>Object</code> which must be cast to another type
  1242. * in order to be useful, and this can cause problems if the object
  1243. * is of a different type than what the caller expects.<p>
  1244. *
  1245. * The following methods should be used instead:
  1246. * <ul>
  1247. * <li>{@link #getStringProperty(String)}</li>
  1248. * <li>{@link #getBooleanProperty(String)}</li>
  1249. * <li>{@link #getIntegerProperty(String,int)}</li>
  1250. * </ul>
  1251. *
  1252. * This method is thread-safe.
  1253. *
  1254. * @param name The property name. For backwards compatibility, this
  1255. * is an <code>Object</code>, not a <code>String</code>.
  1256. */
  1257. public Object getProperty(Object name)
  1258. {
  1259. synchronized(propertyLock)
  1260. {
  1261. // First try the buffer-local properties
  1262. PropValue o = properties.get(name);
  1263. if(o != null)
  1264. return o.value;
  1265. // For backwards compatibility
  1266. if(!(name instanceof String))
  1267. return null;
  1268. Object retVal = getDefaultProperty((String)name);
  1269. if(retVal == null)
  1270. return null;
  1271. else
  1272. {
  1273. properties.put(name,new PropValue(retVal,true));
  1274. return retVal;
  1275. }
  1276. }
  1277. } //}}}
  1278. //{{{ getDefaultProperty() method
  1279. public Object getDefaultProperty(String key)
  1280. {
  1281. return null;
  1282. } //}}}
  1283. //{{{ setProperty() method
  1284. /**
  1285. * Sets the value of a buffer-local property.
  1286. * @param name The property name
  1287. * @param value The property value
  1288. * @since jEdit 4.0pre1
  1289. */
  1290. public void setProperty(String name, Object value)
  1291. {
  1292. if(value == null)
  1293. properties.remove(name);
  1294. else
  1295. {
  1296. PropValue test = properties.get(name);
  1297. if(test == null)
  1298. properties.put(name,new PropValue(value,false));
  1299. else if(test.value.equals(value))
  1300. {
  1301. // do nothing
  1302. }
  1303. else
  1304. {
  1305. test.value = value;
  1306. test.defaultValue = false;
  1307. }
  1308. }
  1309. } //}}}
  1310. //{{{ setDefaultProperty() method
  1311. public void setDefaultProperty(String name, Object value)
  1312. {
  1313. properties.put(name,new PropValue(value,true));
  1314. } //}}}
  1315. //{{{ unsetProperty() method
  1316. /**
  1317. * Clears the value of a buffer-local property.
  1318. * @param name The property name
  1319. * @since jEdit 4.0pre1
  1320. */
  1321. public void unsetProperty(String name)
  1322. {
  1323. properties.remove(name);
  1324. } //}}}
  1325. //{{{ resetCachedProperties() method
  1326. public void resetCachedProperties()
  1327. {
  1328. // Need to reset properties that were cached defaults,
  1329. // since the defaults might have changed.
  1330. Iterator<PropValue> iter = properties.values().iterator();
  1331. while(iter.hasNext())
  1332. {
  1333. PropValue value = iter.next();
  1334. if(value.defaultValue)
  1335. iter.remove();
  1336. }
  1337. } //}}}
  1338. //{{{ getStringProperty() method
  1339. /**
  1340. * Returns the value of a string property. This method is thread-safe.
  1341. * @param name The property name
  1342. * @since jEdit 4.0pre1
  1343. */
  1344. public String getStringProperty(String name)
  1345. {
  1346. Object obj = getProperty(name);
  1347. if(obj != null)
  1348. return obj.toString();
  1349. else
  1350. return null;
  1351. } //}}}
  1352. //{{{ setStringProperty() method
  1353. /**
  1354. * Sets a string property.
  1355. * @param name The property name
  1356. * @param value The value
  1357. * @since jEdit 4.0pre1
  1358. */
  1359. public void setStringProperty(String name, String value)
  1360. {
  1361. setProperty(name,value);
  1362. } //}}}
  1363. //{{{ getBooleanProperty() method
  1364. /**
  1365. * Returns the value of a boolean property. This method is thread-safe.
  1366. * @param name The property name
  1367. * @since jEdit 4.0pre1
  1368. */
  1369. public boolean getBooleanProperty(String name)
  1370. {
  1371. return getBooleanProperty(name, false);
  1372. } //}}}
  1373. //{{{ getBooleanProperty() method
  1374. /**
  1375. * Returns the value of a boolean property. This method is thread-safe.
  1376. * @param name The property name
  1377. * @param def The default value
  1378. * @since jEdit 4.3pre17
  1379. */
  1380. public boolean getBooleanProperty(String name, boolean def)
  1381. {
  1382. Object obj = getProperty(name);
  1383. return StandardUtilities.getBoolean(obj, def);
  1384. } //}}}
  1385. //{{{ setBooleanProperty() method
  1386. /**
  1387. * Sets a boolean property.
  1388. * @param name The property name
  1389. * @param value The value
  1390. * @since jEdit 4.0pre1
  1391. */
  1392. public void setBooleanProperty(String name, boolean value)
  1393. {
  1394. setProperty(name,value ? Boolean.TRUE : Boolean.FALSE);
  1395. } //}}}
  1396. //{{{ getIntegerProperty() method
  1397. /**
  1398. * Returns the value of an integer property. This method is thread-safe.
  1399. * @param name The property name
  1400. * @since jEdit 4.0pre1
  1401. */
  1402. public int getIntegerProperty(String name, int defaultValue)
  1403. {
  1404. boolean defaultValueFlag;
  1405. Object obj;
  1406. PropValue value = properties.get(name);
  1407. if(value != null)
  1408. {
  1409. obj = value.value;
  1410. defaultValueFlag = value.defaultValue;
  1411. }
  1412. else
  1413. {
  1414. obj = getProperty(name);
  1415. // will be cached from now on...
  1416. defaultValueFlag = true;
  1417. }
  1418. if(obj == null)
  1419. return defaultValue;
  1420. else if(obj instanceof Number)
  1421. return ((Number)obj).intValue();
  1422. else
  1423. {
  1424. try
  1425. {
  1426. int returnValue = Integer.parseInt(
  1427. obj.toString().trim());
  1428. properties.put(name,new PropValue(
  1429. returnValue,
  1430. defaultValueFlag));
  1431. return returnValue;
  1432. }
  1433. catch(Exception e)
  1434. {
  1435. return defaultValue;
  1436. }
  1437. }
  1438. } //}}}
  1439. //{{{ setIntegerProperty() method
  1440. /**
  1441. * Sets an integer property.
  1442. * @param name The property name
  1443. * @param value The value
  1444. * @since jEdit 4.0pre1
  1445. */
  1446. public void setIntegerProperty(String name, int value)
  1447. {
  1448. setProperty(name,value);
  1449. } //}}}
  1450. //{{{ getPatternProperty()
  1451. /**
  1452. * Returns the value of a property as a regular expression.
  1453. * This method is thread-safe.
  1454. * @param name The property name
  1455. * @param flags Regular expression compilation flags
  1456. * @since jEdit 4.3pre5
  1457. */
  1458. public Pattern getPatternProperty(String name, int flags)
  1459. {
  1460. synchronized(propertyLock)
  1461. {
  1462. boolean defaultValueFlag;
  1463. Object obj;
  1464. PropValue value = properties.get(name);
  1465. if(value != null)
  1466. {
  1467. obj = value.value;
  1468. defaultValueFlag = value.defaultValue;
  1469. }
  1470. else
  1471. {
  1472. obj = getProperty(name);
  1473. // will be cached from now on...
  1474. defaultValueFlag = true;
  1475. }
  1476. if(obj == null)
  1477. return null;
  1478. else if (obj instanceof Pattern)
  1479. return (Pattern) obj;
  1480. else
  1481. {
  1482. Pattern re = Pattern.compile(obj.toString(),flags);
  1483. properties.put(name,new PropValue(re,
  1484. defaultValueFlag));
  1485. return re;
  1486. }
  1487. }
  1488. } //}}}
  1489. //{{{ getRuleSetAtOffset() method
  1490. /**
  1491. * Returns the syntax highlighting ruleset at the specified offset.
  1492. * @since jEdit 4.1pre1
  1493. */
  1494. public ParserRuleSet getRuleSetAtOffset(int offset)
  1495. {
  1496. int line = getLineOfOffset(offset);
  1497. offset -= getLineStartOffset(line);
  1498. if(offset != 0)
  1499. offset--;
  1500. DefaultTokenHandler tokens = new DefaultTokenHandler();
  1501. markTokens(line,tokens);
  1502. Token token = TextUtilities.getTokenAtOffset(tokens.getTokens(),offset);
  1503. return token.rules;
  1504. } //}}}
  1505. //{{{ getKeywordMapAtOffset() method
  1506. /**
  1507. * Returns the syntax highlighting keyword map in effect at the
  1508. * specified offset. Used by the <b>Complete Word</b> command to
  1509. * complete keywords.
  1510. * @param offset The offset
  1511. * @since jEdit 4.0pre3
  1512. */
  1513. public KeywordMap getKeywordMapAtOffset(int offset)
  1514. {
  1515. return getRuleSetAtOffset(offset).getKeywords();
  1516. } //}}}
  1517. //{{{ getContextSensitiveProperty() method
  1518. /**
  1519. * Some settings, like comment start and end strings, can
  1520. * vary between different parts of a buffer (HTML text and inline
  1521. * JavaScript, for example).
  1522. * @param offset The offset
  1523. * @param name The property name
  1524. * @since jEdit 4.0pre3
  1525. */
  1526. public String getContextSensitiveProperty(int offset, String name)
  1527. {
  1528. ParserRuleSet rules = getRuleSetAtOffset(offset);
  1529. Object value = null;
  1530. Map<String, String> rulesetProps = rules.getProperties();
  1531. if(rulesetProps != null)
  1532. value = rulesetProps.get(name);
  1533. if(value == null)
  1534. return null;
  1535. else
  1536. return String.valueOf(value);
  1537. } //}}}
  1538. //{{{ getMode() method
  1539. /**
  1540. * Returns this buffer's edit mode. This method is thread-safe.
  1541. */
  1542. public Mode getMode()
  1543. {
  1544. return mode;
  1545. } //}}}
  1546. //{{{ setMode() method
  1547. /**
  1548. * Sets this buffer's edit mode. Note that calling this before a buffer
  1549. * is loaded will have no effect; in that case, set the "mode" property
  1550. * to the name of the mode. A bit inelegant, I know...
  1551. * @param mode The mode name
  1552. * @since jEdit 4.2pre1
  1553. */
  1554. public void setMode(String mode)
  1555. {
  1556. setMode(ModeProvider.instance.getMode(mode));
  1557. } //}}}
  1558. //{{{ setMode() method
  1559. /**
  1560. * Sets this buffer's edit mode. Note that calling this before a buffer
  1561. * is loaded will have no effect; in that case, set the "mode" property
  1562. * to the name of the mode. A bit inelegant, I know...
  1563. * @param mode The mode
  1564. */
  1565. public void setMode(Mode mode)
  1566. {
  1567. /* This protects against stupid people (like me)
  1568. * doing stuff like buffer.setMode(jEdit.getMode(...)); */
  1569. if(mode == null)
  1570. throw new NullPointerException("Mode must be non-null");
  1571. this.mode = mode;
  1572. textMode = "text".equals(mode.getName());
  1573. setTokenMarker(mode.getTokenMarker());
  1574. resetCachedProperties();
  1575. propertiesChanged();
  1576. } //}}}
  1577. //}}}
  1578. //{{{ Folding methods
  1579. //{{{ isFoldStart() method
  1580. /**
  1581. * Returns if the specified line begins a fold.
  1582. * @since jEdit 3.1pre1
  1583. */
  1584. public boolean isFoldStart(int line)
  1585. {
  1586. return line != getLineCount() - 1
  1587. && getFoldLevel(line) < getFoldLevel(line + 1);
  1588. } //}}}
  1589. //{{{ isFoldEnd() method
  1590. /**
  1591. * Returns if the specified line ends a fold.
  1592. * @since jEdit 4.2pre5
  1593. */
  1594. public boolean isFoldEnd(int line)
  1595. {
  1596. return line != getLineCount() - 1
  1597. && getFoldLevel(line) > getFoldLevel(line + 1);
  1598. } //}}}
  1599. //{{{ invalidateCachedFoldLevels() method
  1600. /**
  1601. * Invalidates all cached fold level information.
  1602. * @since jEdit 4.1pre11
  1603. */
  1604. public void invalidateCachedFoldLevels()
  1605. {
  1606. lineMgr.setFirstInvalidFoldLevel(0);
  1607. fireFoldLevelChanged(0,getLineCount());
  1608. } //}}}
  1609. //{{{ getFoldLevel() method
  1610. /**
  1611. * Returns the fold level of the specified line.
  1612. * @param line A physical line index
  1613. * @since jEdit 3.1pre1
  1614. */
  1615. public int getFoldLevel(int line)
  1616. {
  1617. if(line < 0 || line >= lineMgr.getLineCount())
  1618. throw new ArrayIndexOutOfBoundsException(line);
  1619. if(foldHandler instanceof DummyFoldHandler)
  1620. return 0;
  1621. int firstInvalidFoldLevel = lineMgr.getFirstInvalidFoldLevel();
  1622. if(firstInvalidFoldLevel == -1 || line < firstInvalidFoldLevel)
  1623. {
  1624. return lineMgr.getFoldLevel(line);
  1625. }
  1626. else
  1627. {
  1628. if(Debug.FOLD_DEBUG)
  1629. Log.log(Log.DEBUG,this,"Invalid fold levels from " + firstInvalidFoldLevel + " to " + line);
  1630. int newFoldLevel = 0;
  1631. boolean changed = false;
  1632. for(int i = firstInvalidFoldLevel; i <= line; i++)
  1633. {
  1634. newFoldLevel = foldHandler.getFoldLevel(this,i,seg);
  1635. if(newFoldLevel != lineMgr.getFoldLevel(i))
  1636. {
  1637. if(Debug.FOLD_DEBUG)
  1638. Log.log(Log.DEBUG,this,i + " fold level changed");
  1639. changed = true;
  1640. }
  1641. lineMgr.setFoldLevel(i,newFoldLevel);
  1642. }
  1643. if(line == lineMgr.getLineCount() - 1)
  1644. lineMgr.setFirstInvalidFoldLevel(-1);
  1645. else
  1646. lineMgr.setFirstInvalidFoldLevel(line + 1);
  1647. if(changed)
  1648. {
  1649. if(Debug.FOLD_DEBUG)
  1650. Log.log(Log.DEBUG,this,"fold level changed: " + firstInvalidFoldLevel + ',' + line);
  1651. fireFoldLevelChanged(firstInvalidFoldLevel,line);
  1652. }
  1653. return newFoldLevel;
  1654. }
  1655. } //}}}
  1656. //{{{ getFoldAtLine() method
  1657. /**
  1658. * Returns an array. The first element is the start line, the
  1659. * second element is the end line, of the fold containing the
  1660. * specified line number.
  1661. * @param line The line number
  1662. * @since jEdit 4.0pre3
  1663. */
  1664. public int[] getFoldAtLine(int line)
  1665. {
  1666. int start, end;
  1667. if(isFoldStart(line))
  1668. {
  1669. start = line;
  1670. int foldLevel = getFoldLevel(line);
  1671. line++;
  1672. while(getFoldLevel(line) > foldLevel)
  1673. {
  1674. line++;
  1675. if(line == getLineCount())
  1676. break;
  1677. }
  1678. end = line - 1;
  1679. }
  1680. else
  1681. {
  1682. start = line;
  1683. int foldLevel = getFoldLevel(line);
  1684. while(getFoldLevel(start) >= foldLevel)
  1685. {
  1686. if(start == 0)
  1687. break;
  1688. else
  1689. start--;
  1690. }
  1691. end = line;
  1692. while(getFoldLevel(end) >= foldLevel)
  1693. {
  1694. end++;
  1695. if(end == getLineCount())
  1696. break;
  1697. }
  1698. end--;
  1699. }
  1700. while(getLineLength(end) == 0 && end > start)
  1701. end--;
  1702. return new int[] { start, end };
  1703. } //}}}
  1704. //{{{ getFoldHandler() method
  1705. /**
  1706. * Returns the current buffer's fold handler.
  1707. * @since jEdit 4.2pre1
  1708. */
  1709. public FoldHandler getFoldHandler()
  1710. {
  1711. return foldHandler;
  1712. } //}}}
  1713. //{{{ setFoldHandler() method
  1714. /**
  1715. * Sets the buffer's fold handler.
  1716. * @since jEdit 4.2pre2
  1717. */
  1718. public void setFoldHandler(FoldHandler foldHandler)
  1719. {
  1720. FoldHandler oldFoldHandler = this.foldHandler;
  1721. if(foldHandler.equals(oldFoldHandler))
  1722. return;
  1723. this.foldHandler = foldHandler;
  1724. lineMgr.setFirstInvalidFoldLevel(0);
  1725. fireFoldHandlerChanged();
  1726. } //}}}
  1727. //}}}
  1728. //{{{ Undo
  1729. //{{{ undo() method
  1730. /**
  1731. * Undoes the most recent edit.
  1732. *
  1733. * @since jEdit 4.0pre1
  1734. */
  1735. public void undo(TextArea textArea)
  1736. {
  1737. if(undoMgr == null)
  1738. return;
  1739. if(!isEditable())
  1740. {
  1741. textArea.getToolkit().beep();
  1742. return;
  1743. }
  1744. try
  1745. {
  1746. writeLock();
  1747. undoInProgress = true;
  1748. int caret = undoMgr.undo();
  1749. if(caret == -1)
  1750. textArea.getToolkit().beep();
  1751. else
  1752. textArea.setCaretPosition(caret);
  1753. fireTransactionComplete();
  1754. }
  1755. finally
  1756. {
  1757. undoInProgress = false;
  1758. writeUnlock();
  1759. }
  1760. } //}}}
  1761. //{{{ redo() method
  1762. /**
  1763. * Redoes the most recently undone edit.
  1764. *
  1765. * @since jEdit 2.7pre2
  1766. */
  1767. public void redo(TextArea textArea)
  1768. {
  1769. if(undoMgr == null)
  1770. return;
  1771. if(!isEditable())
  1772. {
  1773. Toolkit.getDefaultToolkit().beep();
  1774. return;
  1775. }
  1776. try
  1777. {
  1778. writeLock();
  1779. undoInProgress = true;
  1780. int caret = undoMgr.redo();
  1781. if(caret == -1)
  1782. textArea.getToolkit().beep();
  1783. else
  1784. textArea.setCaretPosition(caret);
  1785. fireTransactionComplete();
  1786. }
  1787. finally
  1788. {
  1789. undoInProgress = false;
  1790. writeUnlock();
  1791. }
  1792. } //}}}
  1793. //{{{ isTransactionInProgress() method
  1794. /**
  1795. * Returns if an undo or compound edit is currently in progress. If this
  1796. * method returns true, then eventually a
  1797. * {@link org.gjt.sp.jedit.buffer.BufferListener#transactionComplete(JEditBuffer)}
  1798. * buffer event will get fired.
  1799. * @since jEdit 4.0pre6
  1800. */
  1801. public boolean isTransactionInProgress()
  1802. {
  1803. return transaction || undoInProgress || insideCompoundEdit();
  1804. } //}}}
  1805. //{{{ beginCompoundEdit() method
  1806. /**
  1807. * Starts a compound edit. All edits from now on until
  1808. * {@link #endCompoundEdit()} are called will be merged
  1809. * into one. This can be used to make a complex operation
  1810. * undoable in one step. Nested calls to
  1811. * {@link #beginCompoundEdit()} behave as expected,
  1812. * requiring the same number of {@link #endCompoundEdit()}
  1813. * calls to end the edit.
  1814. * @see #endCompoundEdit()
  1815. */
  1816. public void beginCompoundEdit()
  1817. {
  1818. try
  1819. {
  1820. writeLock();
  1821. undoMgr.beginCompoundEdit();
  1822. }
  1823. finally
  1824. {
  1825. writeUnlock();
  1826. }
  1827. } //}}}
  1828. //{{{ endCompoundEdit() method
  1829. /**
  1830. * Ends a compound edit. All edits performed since
  1831. * {@link #beginCompoundEdit()} was called can now
  1832. * be undone in one step by calling {@link #undo(TextArea)}.
  1833. * @see #beginCompoundEdit()
  1834. */
  1835. public void endCompoundEdit()
  1836. {
  1837. try
  1838. {
  1839. writeLock();
  1840. undoMgr.endCompoundEdit();
  1841. if(!insideCompoundEdit())
  1842. fireTransactionComplete();
  1843. }
  1844. finally
  1845. {
  1846. writeUnlock();
  1847. }
  1848. }//}}}
  1849. //{{{ insideCompoundEdit() method
  1850. /**
  1851. * Returns if a compound edit is currently active.
  1852. * @since jEdit 3.1pre1
  1853. */
  1854. public boolean insideCompoundEdit()
  1855. {
  1856. return undoMgr.insideCompoundEdit();
  1857. } //}}}
  1858. //{{{ isUndoInProgress() method
  1859. /**
  1860. * Returns if an undo or redo is currently being performed.
  1861. * @since jEdit 4.3pre3
  1862. */
  1863. public boolean isUndoInProgress()
  1864. {
  1865. return undoInProgress;
  1866. } //}}}
  1867. //}}}
  1868. //{{{ Buffer events
  1869. public static final int NORMAL_PRIORITY = 0;
  1870. public static final int HIGH_PRIORITY = 1;
  1871. static class Listener
  1872. {
  1873. BufferListener listener;
  1874. int priority;
  1875. Listener(BufferListener listener, int priority)
  1876. {
  1877. this.listener = listener;
  1878. this.priority = priority;
  1879. }
  1880. }
  1881. //{{{ addBufferListener() method
  1882. /**
  1883. * Adds a buffer change listener.
  1884. * @param listener The listener
  1885. * @param priority Listeners with HIGH_PRIORITY get the event before
  1886. * listeners with NORMAL_PRIORITY
  1887. * @since jEdit 4.3pre3
  1888. */
  1889. public void addBufferListener(BufferListener listener,
  1890. int priority)
  1891. {
  1892. Listener l = new Listener(listener,priority);
  1893. for(int i = 0; i < bufferListeners.size(); i++)
  1894. {
  1895. Listener _l = bufferListeners.get(i);
  1896. if(_l.priority < priority)
  1897. {
  1898. bufferListeners.add(i,l);
  1899. return;
  1900. }
  1901. }
  1902. bufferListeners.add(l);
  1903. } //}}}
  1904. //{{{ addBufferListener() method
  1905. /**
  1906. * Adds a buffer change listener.
  1907. * @param listener The listener
  1908. * @since jEdit 4.3pre3
  1909. */
  1910. public void addBufferListener(BufferListener listener)
  1911. {
  1912. addBufferListener(listener,NORMAL_PRIORITY);
  1913. } //}}}
  1914. //{{{ removeBufferListener() method
  1915. /**
  1916. * Removes a buffer change listener.
  1917. * @param listener The listener
  1918. * @since jEdit 4.3pre3
  1919. */
  1920. public void removeBufferListener(BufferListener listener)
  1921. {
  1922. for(int i = 0; i < bufferListeners.size(); i++)
  1923. {
  1924. if(bufferListeners.get(i).listener == listener)
  1925. {
  1926. bufferListeners.remove(i);
  1927. return;
  1928. }
  1929. }
  1930. } //}}}
  1931. //{{{ getBufferListeners() method
  1932. /**
  1933. * Returns an array of registered buffer change listeners.
  1934. * @since jEdit 4.3pre3
  1935. */
  1936. public BufferListener[] getBufferListeners()
  1937. {
  1938. BufferListener[] returnValue
  1939. = new BufferListener[
  1940. bufferListeners.size()];
  1941. for(int i = 0; i < returnValue.length; i++)
  1942. {
  1943. returnValue[i] = bufferListeners.get(i).listener;
  1944. }
  1945. return returnValue;
  1946. } //}}}
  1947. //{{{ setUndoLimit() method
  1948. /**
  1949. * Set the undo limit of the Undo Manager.
  1950. *
  1951. * @param limit the new limit
  1952. * @since jEdit 4.3pre16
  1953. */
  1954. public void setUndoLimit(int limit)
  1955. {
  1956. if (undoMgr != null)
  1957. undoMgr.setLimit(limit);
  1958. } //}}}
  1959. //}}}
  1960. //{{{ Protected members
  1961. protected Mode mode;
  1962. protected Segment seg;
  1963. protected boolean textMode;
  1964. protected UndoManager undoMgr;
  1965. //{{{ Event firing methods
  1966. //{{{ fireFoldLevelChanged() method
  1967. protected void fireFoldLevelChanged(int start, int end)
  1968. {
  1969. for(int i = 0; i < bufferListeners.size(); i++)
  1970. {
  1971. BufferListener listener = getListener(i);
  1972. try
  1973. {
  1974. listener.foldLevelChanged(this,start,end);
  1975. }
  1976. catch(Throwable t)
  1977. {
  1978. Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
  1979. Log.log(Log.ERROR,this,t);
  1980. }
  1981. }
  1982. } //}}}
  1983. //{{{ fireContentInserted() method
  1984. protected void fireContentInserted(int startLine, int offset,
  1985. int numLines, int length)
  1986. {
  1987. for(int i = 0; i < bufferListeners.size(); i++)
  1988. {
  1989. BufferListener listener = getListener(i);
  1990. try
  1991. {
  1992. listener.contentInserted(this,startLine,
  1993. offset,numLines,length);
  1994. }
  1995. catch(Throwable t)
  1996. {
  1997. Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
  1998. Log.log(Log.ERROR,this,t);
  1999. }
  2000. }
  2001. } //}}}
  2002. //{{{ fireContentRemoved() method
  2003. protected void fireContentRemoved(int startLine, int offset,
  2004. int numLines, int length)
  2005. {
  2006. for(int i = 0; i < bufferListeners.size(); i++)
  2007. {
  2008. BufferListener listener = getListener(i);
  2009. try
  2010. {
  2011. listener.contentRemoved(this,startLine,
  2012. offset,numLines,length);
  2013. }
  2014. catch(Throwable t)
  2015. {
  2016. Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
  2017. Log.log(Log.ERROR,this,t);
  2018. }
  2019. }
  2020. } //}}}
  2021. //{{{ firePreContentInserted() method
  2022. protected void firePreContentInserted(int startLine, int offset,
  2023. int numLines, int length)
  2024. {
  2025. for(int i = 0; i < bufferListeners.size(); i++)
  2026. {
  2027. BufferListener listener = getListener(i);
  2028. try
  2029. {
  2030. listener.preContentInserted(this,startLine,
  2031. offset,numLines,length);
  2032. }
  2033. catch(Throwable t)
  2034. {
  2035. Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
  2036. Log.log(Log.ERROR,this,t);
  2037. }
  2038. }
  2039. } //}}}
  2040. //{{{ firePreContentRemoved() method
  2041. protected void firePreContentRemoved(int startLine, int offset,
  2042. int numLines, int length)
  2043. {
  2044. for(int i = 0; i < bufferListeners.size(); i++)
  2045. {
  2046. BufferListener listener = getListener(i);
  2047. try
  2048. {
  2049. listener.preContentRemoved(this,startLine,
  2050. offset,numLines,length);
  2051. }
  2052. catch(Throwable t)
  2053. {
  2054. Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
  2055. Log.log(Log.ERROR,this,t);
  2056. }
  2057. }
  2058. } //}}}
  2059. //{{{ fireTransactionComplete() method
  2060. protected void fireTransactionComplete()
  2061. {
  2062. for(int i = 0; i < bufferListeners.size(); i++)
  2063. {
  2064. BufferListener listener = getListener(i);
  2065. try
  2066. {
  2067. listener.transactionComplete(this);
  2068. }
  2069. catch(Throwable t)
  2070. {
  2071. Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
  2072. Log.log(Log.ERROR,this,t);
  2073. }
  2074. }
  2075. } //}}}
  2076. //{{{ fireFoldHandlerChanged() method
  2077. protected void fireFoldHandlerChanged()
  2078. {
  2079. for(int i = 0; i < bufferListeners.size(); i++)
  2080. {
  2081. BufferListener listener = getListener(i);
  2082. try
  2083. {
  2084. listener.foldHandlerChanged(this);
  2085. }
  2086. catch(Throwable t)
  2087. {
  2088. Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
  2089. Log.log(Log.ERROR,this,t);
  2090. }
  2091. }
  2092. } //}}}
  2093. //{{{ fireBufferLoaded() method
  2094. protected void fireBufferLoaded()
  2095. {
  2096. for(int i = 0; i < bufferListeners.size(); i++)
  2097. {
  2098. BufferListener listener = getListener(i);
  2099. try
  2100. {
  2101. listener.bufferLoaded(this);
  2102. }
  2103. catch(Throwable t)
  2104. {
  2105. Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
  2106. Log.log(Log.ERROR,this,t);
  2107. }
  2108. }
  2109. } //}}}
  2110. //}}}
  2111. //{{{ isFileReadOnly() method
  2112. protected boolean isFileReadOnly()
  2113. {
  2114. return readOnly;
  2115. } //}}}
  2116. //{{{ setFileReadOnly() method
  2117. protected void setFileReadOnly(boolean readOnly)
  2118. {
  2119. this.readOnly = readOnly;
  2120. } //}}}
  2121. //{{{ loadText() method
  2122. protected void loadText(Segment seg, IntegerArray endOffsets)
  2123. {
  2124. if(seg == null)
  2125. seg = new Segment(new char[1024],0,0);
  2126. if(endOffsets == null)
  2127. {
  2128. endOffsets = new IntegerArray();
  2129. endOffsets.add(1);
  2130. }
  2131. try
  2132. {
  2133. writeLock();
  2134. // For `reload' command
  2135. // contentMgr.remove() changes this!
  2136. int length = getLength();
  2137. firePreContentRemoved(0,0,getLineCount()
  2138. - 1,length);
  2139. contentMgr.remove(0,length);
  2140. lineMgr.contentRemoved(0,0,getLineCount()
  2141. - 1,length);
  2142. positionMgr.contentRemoved(0,length);
  2143. fireContentRemoved(0,0,getLineCount()
  2144. - 1,length);
  2145. firePreContentInserted(0, 0, endOffsets.getSize() - 1, seg.count - 1);
  2146. // theoretically a segment could
  2147. // have seg.offset != 0 but
  2148. // SegmentBuffer never does that
  2149. contentMgr._setContent(seg.array,seg.count);
  2150. lineMgr._contentInserted(endOffsets);
  2151. positionMgr.contentInserted(0,seg.count);
  2152. fireContentInserted(0,0,
  2153. endOffsets.getSize() - 1,
  2154. seg.count - 1);
  2155. }
  2156. finally
  2157. {
  2158. writeUnlock();
  2159. }
  2160. } //}}}
  2161. //{{{ invalidateFoldLevels() method
  2162. protected void invalidateFoldLevels()
  2163. {
  2164. lineMgr.setFirstInvalidFoldLevel(0);
  2165. } //}}}
  2166. //{{{ parseBufferLocalProperties() method
  2167. protected void parseBufferLocalProperties()
  2168. {
  2169. int lastLine = Math.min(9,getLineCount() - 1);
  2170. parseBufferLocalProperties(getSegment(0,getLineEndOffset(lastLine) - 1));
  2171. // first line for last 10 lines, make sure not to overlap
  2172. // with the first 10
  2173. int firstLine = Math.max(lastLine + 1, getLineCount() - 10);
  2174. if(firstLine < getLineCount())
  2175. {
  2176. int length = getLineEndOffset(getLineCount() - 1)
  2177. - (getLineStartOffset(firstLine) + 1);
  2178. parseBufferLocalProperties(getSegment(getLineStartOffset(firstLine),length));
  2179. }
  2180. } //}}}
  2181. //{{{ Used to store property values
  2182. protected static class PropValue
  2183. {
  2184. PropValue(Object value, boolean defaultValue)
  2185. {
  2186. if(value == null)
  2187. throw new NullPointerException();
  2188. this.value = value;
  2189. this.defaultValue = defaultValue;
  2190. }
  2191. Object value;
  2192. /**
  2193. * If this is true, then this value is cached from the mode
  2194. * or global defaults, so when the defaults change this property
  2195. * value must be reset.
  2196. */
  2197. boolean defaultValue;
  2198. /**
  2199. * For debugging purposes.
  2200. */
  2201. public String toString()
  2202. {
  2203. return value.toString();
  2204. }
  2205. } //}}}
  2206. //}}}
  2207. //{{{ Private members
  2208. private List<Listener> bufferListeners;
  2209. private final ReentrantReadWriteLock lock;
  2210. private ContentManager contentMgr;
  2211. private LineManager lineMgr;
  2212. private PositionManager positionMgr;
  2213. private FoldHandler foldHandler;
  2214. private IntegerArray integerArray;
  2215. private TokenMarker tokenMarker;
  2216. private boolean undoInProgress;
  2217. private boolean dirty;
  2218. private boolean readOnly;
  2219. private boolean readOnlyOverride;
  2220. private boolean transaction;
  2221. private boolean loading;
  2222. private boolean io;
  2223. private final Map<Object, PropValue> properties;
  2224. private final Object propertyLock;
  2225. //{{{ getListener() method
  2226. private BufferListener getListener(int index)
  2227. {
  2228. return bufferListeners.get(index).listener;
  2229. } //}}}
  2230. //{{{ contentInserted() method
  2231. private void contentInserted(int offset, int length,
  2232. IntegerArray endOffsets)
  2233. {
  2234. try
  2235. {
  2236. transaction = true;
  2237. int startLine = lineMgr.getLineOfOffset(offset);
  2238. int numLines = endOffsets.getSize();
  2239. if (!loading)
  2240. {
  2241. firePreContentInserted(startLine, offset, numLines, length);
  2242. }
  2243. lineMgr.contentInserted(startLine,offset,numLines,length,
  2244. endOffsets);
  2245. positionMgr.contentInserted(offset,length);
  2246. setDirty(true);
  2247. if(!loading)
  2248. {
  2249. fireContentInserted(startLine,offset,numLines,length);
  2250. if(!undoInProgress && !insideCompoundEdit())
  2251. fireTransactionComplete();
  2252. }
  2253. }
  2254. finally
  2255. {
  2256. transaction = false;
  2257. }
  2258. } //}}}
  2259. //{{{ parseBufferLocalProperties() method
  2260. private void parseBufferLocalProperties(CharSequence prop)
  2261. {
  2262. StringBuilder buf = new StringBuilder();
  2263. String name = null;
  2264. boolean escape = false;
  2265. for(int i = 0; i < prop.length(); i++)
  2266. {
  2267. char c = prop.charAt(i);
  2268. switch(c)
  2269. {
  2270. case ':':
  2271. if(escape)
  2272. {
  2273. escape = false;
  2274. buf.append(':');
  2275. break;
  2276. }
  2277. if(name != null)
  2278. {
  2279. // use the low-level property setting code
  2280. // so that if we have a buffer-local
  2281. // property with the same value as a default,
  2282. // later changes in the default don't affect
  2283. // the buffer-local property
  2284. properties.put(name,new PropValue(buf.toString(),false));
  2285. name = null;
  2286. }
  2287. buf.setLength(0);
  2288. break;
  2289. case '=':
  2290. if(escape)
  2291. {
  2292. escape = false;
  2293. buf.append('=');
  2294. break;
  2295. }
  2296. name = buf.toString();
  2297. buf.setLength(0);
  2298. break;
  2299. case '\\':
  2300. if(escape)
  2301. buf.append('\\');
  2302. escape = !escape;
  2303. break;
  2304. case 'n':
  2305. if(escape)
  2306. { buf.append('\n');
  2307. escape = false;
  2308. break;
  2309. }
  2310. case 'r':
  2311. if(escape)
  2312. { buf.append('\r');
  2313. escape = false;
  2314. break;
  2315. }
  2316. case 't':
  2317. if(escape)
  2318. {
  2319. buf.append('\t');
  2320. escape = false;
  2321. break;
  2322. }
  2323. default:
  2324. buf.append(c);
  2325. break;
  2326. }
  2327. }
  2328. } //}}}
  2329. //{{{ getIndentRules() method
  2330. private List<IndentRule> getIndentRules(int line)
  2331. {
  2332. String modeName = null;
  2333. TokenMarker.LineContext ctx = lineMgr.getLineContext(line);
  2334. if (ctx != null && ctx.rules != null)
  2335. modeName = ctx.rules.getModeName();
  2336. if (modeName == null)
  2337. modeName = tokenMarker.getMainRuleSet().getModeName();
  2338. return ModeProvider.instance.getMode(modeName).getIndentRules();
  2339. } //}}}
  2340. //}}}
  2341. }