/jEdit/tags/jedit-4-3-pre18/org/gjt/sp/jedit/buffer/JEditBuffer.java

# · Java · 2850 lines · 1737 code · 334 blank · 779 comment · 281 complexity · a1aeb040f5456fff5d15bb58a340e9ea MD5 · raw file

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