PageRenderTime 57ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

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

#
Java | 2889 lines | 1673 code | 342 blank | 874 comment | 357 complexity | a21b6d3859f7ae55a1037ef43f7be631 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1. /*
  2. * Buffer.java - jEdit buffer
  3. * :tabSize=8:indentSize=8:noTabs=false:
  4. * :folding=explicit:collapseFolds=1:
  5. *
  6. * Copyright (C) 1998, 2003 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;
  24. //{{{ Imports
  25. import gnu.regexp.*;
  26. import javax.swing.*;
  27. import javax.swing.text.*;
  28. import java.awt.Toolkit;
  29. import java.io.File;
  30. import java.io.IOException;
  31. import java.net.Socket;
  32. import java.util.*;
  33. import org.gjt.sp.jedit.browser.VFSBrowser;
  34. import org.gjt.sp.jedit.buffer.*;
  35. import org.gjt.sp.jedit.io.*;
  36. import org.gjt.sp.jedit.msg.*;
  37. import org.gjt.sp.jedit.search.RESearchMatcher;
  38. import org.gjt.sp.jedit.syntax.*;
  39. import org.gjt.sp.jedit.textarea.*;
  40. import org.gjt.sp.util.*;
  41. //}}}
  42. /**
  43. * A <code>Buffer</code> represents the contents of an open text
  44. * file as it is maintained in the computer's memory (as opposed to
  45. * how it may be stored on a disk).<p>
  46. *
  47. * In a BeanShell script, you can obtain the current buffer instance from the
  48. * <code>buffer</code> variable.<p>
  49. *
  50. * This class does not have a public constructor.
  51. * Buffers can be opened and closed using methods in the <code>jEdit</code>
  52. * class.<p>
  53. *
  54. * This class is partially thread-safe, however you must pay attention to two
  55. * very important guidelines:
  56. * <ul>
  57. * <li>Changes to a buffer can only be made from the AWT thread.
  58. * <li>When accessing the buffer from another thread, you must
  59. * grab a read lock if you plan on performing more than one call, to ensure that
  60. * the buffer contents are not changed by the AWT thread for the duration of the
  61. * lock. Only methods whose descriptions specify thread safety can be invoked
  62. * from other threads.
  63. * </ul>
  64. *
  65. * @author Slava Pestov
  66. * @version $Id: Buffer.java 4843 2003-08-04 03:19:13Z spestov $
  67. */
  68. public class Buffer
  69. {
  70. //{{{ Some constants
  71. /**
  72. * Line separator property.
  73. */
  74. public static final String LINESEP = "lineSeparator";
  75. /**
  76. * Backed up property.
  77. * @since jEdit 3.2pre2
  78. */
  79. public static final String BACKED_UP = "Buffer__backedUp";
  80. /**
  81. * Caret info properties.
  82. * @since jEdit 3.2pre1
  83. */
  84. public static final String CARET = "Buffer__caret";
  85. public static final String SELECTION = "Buffer__selection";
  86. /**
  87. * This should be a physical line number, so that the scroll
  88. * position is preserved correctly across reloads (which will
  89. * affect virtual line numbers, due to fold being reset)
  90. */
  91. public static final String SCROLL_VERT = "Buffer__scrollVert";
  92. public static final String SCROLL_HORIZ = "Buffer__scrollHoriz";
  93. /**
  94. * Character encoding used when loading and saving.
  95. * @since jEdit 3.2pre4
  96. */
  97. public static final String ENCODING = "encoding";
  98. /**
  99. * This property is set to 'true' if the file has a trailing newline.
  100. * @since jEdit 4.0pre1
  101. */
  102. public static final String TRAILING_EOL = "trailingEOL";
  103. /**
  104. * This property is set to 'true' if the file should be GZipped.
  105. * @since jEdit 4.0pre4
  106. */
  107. public static final String GZIPPED = "gzipped";
  108. //}}}
  109. //{{{ Input/output methods
  110. //{{{ showInsertFileDialog() method
  111. /**
  112. * Displays the 'insert file' dialog box and inserts the selected file
  113. * into the buffer.
  114. * @param view The view
  115. * @since jEdit 2.7pre2
  116. */
  117. public void showInsertFileDialog(View view)
  118. {
  119. String[] files = GUIUtilities.showVFSFileDialog(view,null,
  120. VFSBrowser.OPEN_DIALOG,false);
  121. if(files != null)
  122. insertFile(view,files[0]);
  123. } //}}}
  124. //{{{ reload() method
  125. /**
  126. * Reloads the buffer from disk, asking for confirmation if the buffer
  127. * has unsaved changes.
  128. * @param view The view
  129. * @since jEdit 2.7pre2
  130. */
  131. public void reload(View view)
  132. {
  133. if(getFlag(DIRTY))
  134. {
  135. String[] args = { name };
  136. int result = GUIUtilities.confirm(view,"changedreload",
  137. args,JOptionPane.YES_NO_OPTION,
  138. JOptionPane.WARNING_MESSAGE);
  139. if(result != JOptionPane.YES_OPTION)
  140. return;
  141. }
  142. view.getEditPane().saveCaretInfo();
  143. load(view,true);
  144. } //}}}
  145. //{{{ load() method
  146. /**
  147. * Loads the buffer from disk, even if it is loaded already.
  148. * @param view The view
  149. * @param reload If true, user will not be asked to recover autosave
  150. * file, if any
  151. *
  152. * @since 2.5pre1
  153. */
  154. public boolean load(final View view, final boolean reload)
  155. {
  156. if(isPerformingIO())
  157. {
  158. GUIUtilities.error(view,"buffer-multiple-io",null);
  159. return false;
  160. }
  161. setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false);
  162. setFlag(LOADING,true);
  163. // view text areas temporarily blank out while a buffer is
  164. // being loaded, to indicate to the user that there is no
  165. // data available yet.
  166. if(!getFlag(TEMPORARY))
  167. EditBus.send(new BufferUpdate(this,view,BufferUpdate.LOAD_STARTED));
  168. final boolean loadAutosave;
  169. if(reload || !getFlag(NEW_FILE))
  170. {
  171. if(file != null)
  172. modTime = file.lastModified();
  173. // Only on initial load
  174. if(!reload && autosaveFile != null && autosaveFile.exists())
  175. loadAutosave = recoverAutosave(view);
  176. else
  177. {
  178. if(autosaveFile != null)
  179. autosaveFile.delete();
  180. loadAutosave = false;
  181. }
  182. if(!loadAutosave)
  183. {
  184. VFS vfs = VFSManager.getVFSForPath(path);
  185. if(!checkFileForLoad(view,vfs,path))
  186. {
  187. setFlag(LOADING,false);
  188. return false;
  189. }
  190. // have to check again since above might set
  191. // NEW_FILE flag
  192. if(reload || !getFlag(NEW_FILE))
  193. {
  194. if(!vfs.load(view,this,path))
  195. {
  196. setFlag(LOADING,false);
  197. return false;
  198. }
  199. }
  200. }
  201. }
  202. else
  203. loadAutosave = false;
  204. //{{{ Do some stuff once loading is finished
  205. Runnable runnable = new Runnable()
  206. {
  207. public void run()
  208. {
  209. String newPath = getStringProperty(
  210. BufferIORequest.NEW_PATH);
  211. Segment seg = (Segment)getProperty(
  212. BufferIORequest.LOAD_DATA);
  213. IntegerArray endOffsets = (IntegerArray)
  214. getProperty(BufferIORequest.END_OFFSETS);
  215. if(seg == null)
  216. seg = new Segment(new char[1024],0,0);
  217. if(endOffsets == null)
  218. {
  219. endOffsets = new IntegerArray();
  220. endOffsets.add(1);
  221. }
  222. try
  223. {
  224. writeLock();
  225. // For `reload' command
  226. firePreContentRemoved(0,0,getLineCount()
  227. - 1,getLength());
  228. contentMgr.remove(0,getLength());
  229. lineMgr.contentRemoved(0,0,getLineCount()
  230. - 1,getLength());
  231. positionMgr.contentRemoved(0,getLength());
  232. fireContentRemoved(0,0,getLineCount()
  233. - 1,getLength());
  234. // theoretically a segment could
  235. // have seg.offset != 0 but
  236. // SegmentBuffer never does that
  237. contentMgr._setContent(seg.array,seg.count);
  238. lineMgr._contentInserted(endOffsets);
  239. positionMgr.contentInserted(0,seg.count);
  240. fireContentInserted(0,0,
  241. endOffsets.getSize() - 1,
  242. seg.count - 1);
  243. }
  244. finally
  245. {
  246. writeUnlock();
  247. }
  248. unsetProperty(BufferIORequest.LOAD_DATA);
  249. unsetProperty(BufferIORequest.END_OFFSETS);
  250. unsetProperty(BufferIORequest.NEW_PATH);
  251. undoMgr.clear();
  252. undoMgr.setLimit(jEdit.getIntegerProperty(
  253. "buffer.undoCount",100));
  254. if(!getFlag(TEMPORARY))
  255. finishLoading();
  256. setFlag(LOADING,false);
  257. // if reloading a file, clear dirty flag
  258. if(reload)
  259. setDirty(false);
  260. if(!loadAutosave && newPath != null)
  261. setPath(newPath);
  262. // if loadAutosave is false, we loaded an
  263. // autosave file, so we set 'dirty' to true
  264. // note that we don't use setDirty(),
  265. // because a) that would send an unnecessary
  266. // message, b) it would also set the
  267. // AUTOSAVE_DIRTY flag, which will make
  268. // the autosave thread write out a
  269. // redundant autosave file
  270. if(loadAutosave)
  271. setFlag(DIRTY,true);
  272. // send some EditBus messages
  273. if(!getFlag(TEMPORARY))
  274. {
  275. EditBus.send(new BufferUpdate(Buffer.this,
  276. view,BufferUpdate.LOADED));
  277. //EditBus.send(new BufferUpdate(Buffer.this,
  278. // view,BufferUpdate.MARKERS_CHANGED));
  279. }
  280. }
  281. }; //}}}
  282. if(getFlag(TEMPORARY))
  283. runnable.run();
  284. else
  285. VFSManager.runInAWTThread(runnable);
  286. return true;
  287. } //}}}
  288. //{{{ insertFile() method
  289. /**
  290. * Loads a file from disk, and inserts it into this buffer.
  291. * @param view The view
  292. *
  293. * @since 4.0pre1
  294. */
  295. public boolean insertFile(final View view, String path)
  296. {
  297. if(isPerformingIO())
  298. {
  299. GUIUtilities.error(view,"buffer-multiple-io",null);
  300. return false;
  301. }
  302. setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false);
  303. path = MiscUtilities.constructPath(this.path,path);
  304. Buffer buffer = jEdit.getBuffer(path);
  305. if(buffer != null)
  306. {
  307. view.getTextArea().setSelectedText(
  308. buffer.getText(0,buffer.getLength()));
  309. return true;
  310. }
  311. VFS vfs = VFSManager.getVFSForPath(path);
  312. setFlag(IO,true);
  313. // this returns false if initial sanity
  314. // checks (if the file is a directory, etc)
  315. // fail
  316. if(!vfs.insert(view,this,path))
  317. {
  318. setFlag(IO,false);
  319. return false;
  320. }
  321. // Do some stuff once loading is finished
  322. VFSManager.runInAWTThread(new Runnable()
  323. {
  324. public void run()
  325. {
  326. setFlag(IO,false);
  327. SegmentBuffer sbuf = (SegmentBuffer)getProperty(
  328. BufferIORequest.LOAD_DATA);
  329. if(sbuf != null)
  330. {
  331. unsetProperty(BufferIORequest.LOAD_DATA);
  332. view.getTextArea().setSelectedText(sbuf.toString());
  333. }
  334. }
  335. });
  336. return true;
  337. } //}}}
  338. //{{{ autosave() method
  339. /**
  340. * Autosaves this buffer.
  341. */
  342. public void autosave()
  343. {
  344. if(autosaveFile == null || !getFlag(AUTOSAVE_DIRTY)
  345. || !getFlag(DIRTY)
  346. || getFlag(LOADING)
  347. || getFlag(IO))
  348. return;
  349. setFlag(AUTOSAVE_DIRTY,false);
  350. VFSManager.runInWorkThread(new BufferIORequest(
  351. BufferIORequest.AUTOSAVE,null,this,null,
  352. VFSManager.getFileVFS(),autosaveFile.getPath()));
  353. } //}}}
  354. //{{{ saveAs() method
  355. /**
  356. * Prompts the user for a file to save this buffer to.
  357. * @param view The view
  358. * @param rename True if the buffer's path should be changed, false
  359. * if only a copy should be saved to the specified filename
  360. * @since jEdit 2.6pre5
  361. */
  362. public boolean saveAs(View view, boolean rename)
  363. {
  364. String[] files = GUIUtilities.showVFSFileDialog(view,path,
  365. VFSBrowser.SAVE_DIALOG,false);
  366. // files[] should have length 1, since the dialog type is
  367. // SAVE_DIALOG
  368. if(files == null)
  369. return false;
  370. return save(view,files[0],rename);
  371. } //}}}
  372. //{{{ save() method
  373. /**
  374. * Saves this buffer to the specified path name, or the current path
  375. * name if it's null.
  376. * @param view The view
  377. * @param path The path name to save the buffer to, or null to use
  378. * the existing path
  379. */
  380. public boolean save(View view, String path)
  381. {
  382. return save(view,path,true);
  383. } //}}}
  384. //{{{ save() method
  385. /**
  386. * Saves this buffer to the specified path name, or the current path
  387. * name if it's null.
  388. * @param view The view
  389. * @param path The path name to save the buffer to, or null to use
  390. * the existing path
  391. * @param rename True if the buffer's path should be changed, false
  392. * if only a copy should be saved to the specified filename
  393. * @since jEdit 2.6pre5
  394. */
  395. public boolean save(final View view, String path, final boolean rename)
  396. {
  397. if(isPerformingIO())
  398. {
  399. GUIUtilities.error(view,"buffer-multiple-io",null);
  400. return false;
  401. }
  402. setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false);
  403. if(path == null && getFlag(NEW_FILE))
  404. return saveAs(view,rename);
  405. if(path == null && file != null)
  406. {
  407. long newModTime = file.lastModified();
  408. if(newModTime != modTime
  409. && jEdit.getBooleanProperty("view.checkModStatus"))
  410. {
  411. Object[] args = { this.path };
  412. int result = GUIUtilities.confirm(view,
  413. "filechanged-save",args,
  414. JOptionPane.YES_NO_OPTION,
  415. JOptionPane.WARNING_MESSAGE);
  416. if(result != JOptionPane.YES_OPTION)
  417. return false;
  418. }
  419. }
  420. setFlag(IO,true);
  421. EditBus.send(new BufferUpdate(this,view,BufferUpdate.SAVING));
  422. final String oldPath = this.path;
  423. final String oldSymlinkPath = this.symlinkPath;
  424. final String newPath = (path == null ? this.path : path);
  425. VFS vfs = VFSManager.getVFSForPath(newPath);
  426. if(!checkFileForSave(view,vfs,newPath))
  427. {
  428. setFlag(IO,false);
  429. return false;
  430. }
  431. if(!vfs.save(view,this,newPath))
  432. {
  433. setFlag(IO,false);
  434. return false;
  435. }
  436. // Once save is complete, do a few other things
  437. VFSManager.runInAWTThread(new Runnable()
  438. {
  439. public void run()
  440. {
  441. setFlag(IO,false);
  442. finishSaving(view,oldPath,oldSymlinkPath,
  443. newPath,rename,getBooleanProperty(
  444. BufferIORequest.ERROR_OCCURRED));
  445. }
  446. });
  447. return true;
  448. } //}}}
  449. //{{{ checkFileStatus() method
  450. public static final int FILE_NOT_CHANGED = 0;
  451. public static final int FILE_CHANGED = 1;
  452. public static final int FILE_DELETED = 2;
  453. /**
  454. * Check if the buffer has changed on disk.
  455. * @return One of <code>NOT_CHANGED</code>, <code>CHANGED</code>, or
  456. * <code>DELETED</code>.
  457. *
  458. * @since jEdit 4.2pre1
  459. */
  460. public int checkFileStatus(View view)
  461. {
  462. // - don't do these checks while a save is in progress,
  463. // because for a moment newModTime will be greater than
  464. // oldModTime, due to the multithreading
  465. // - only supported on local file system
  466. if(!getFlag(IO) && !getFlag(LOADING) && file != null
  467. && !getFlag(NEW_FILE))
  468. {
  469. boolean newReadOnly = (file.exists() && !file.canWrite());
  470. if(newReadOnly != getFlag(READ_ONLY))
  471. {
  472. setFlag(READ_ONLY,newReadOnly);
  473. EditBus.send(new BufferUpdate(this,null,
  474. BufferUpdate.DIRTY_CHANGED));
  475. }
  476. long oldModTime = modTime;
  477. long newModTime = file.lastModified();
  478. if(newModTime != oldModTime)
  479. {
  480. modTime = newModTime;
  481. if(!file.exists())
  482. {
  483. setFlag(NEW_FILE,true);
  484. setDirty(true);
  485. return FILE_DELETED;
  486. }
  487. else
  488. {
  489. return FILE_CHANGED;
  490. }
  491. }
  492. }
  493. return FILE_NOT_CHANGED;
  494. } //}}}
  495. //}}}
  496. //{{{ Getters/setter methods for various buffer meta-data
  497. //{{{ getLastModified() method
  498. /**
  499. * Returns the last time jEdit modified the file on disk.
  500. * This method is thread-safe.
  501. */
  502. public long getLastModified()
  503. {
  504. return modTime;
  505. } //}}}
  506. //{{{ setLastModified() method
  507. /**
  508. * Sets the last time jEdit modified the file on disk.
  509. * @param modTime The new modification time
  510. */
  511. public void setLastModified(long modTime)
  512. {
  513. this.modTime = modTime;
  514. } //}}}
  515. //{{{ getVFS() method
  516. /**
  517. * Returns the virtual filesystem responsible for loading and
  518. * saving this buffer. This method is thread-safe.
  519. */
  520. public VFS getVFS()
  521. {
  522. return VFSManager.getVFSForPath(path);
  523. } //}}}
  524. //{{{ getAutosaveFile() method
  525. /**
  526. * Returns the autosave file for this buffer. This may be null if
  527. * the file is non-local.
  528. */
  529. public File getAutosaveFile()
  530. {
  531. return autosaveFile;
  532. } //}}}
  533. //{{{ getName() method
  534. /**
  535. * Returns the name of this buffer. This method is thread-safe.
  536. */
  537. public String getName()
  538. {
  539. return name;
  540. } //}}}
  541. //{{{ getPath() method
  542. /**
  543. * Returns the path name of this buffer. This method is thread-safe.
  544. */
  545. public String getPath()
  546. {
  547. return path;
  548. } //}}}
  549. //{{{ getSymlinkPath() method
  550. /**
  551. * If this file is a symbolic link, returns the link destination.
  552. * Otherwise returns the file's path. This method is thread-safe.
  553. * @since jEdit 4.2pre1
  554. */
  555. public String getSymlinkPath()
  556. {
  557. return symlinkPath;
  558. } //}}}
  559. //{{{ getDirectory() method
  560. /**
  561. * Returns the directory containing this buffer.
  562. * @since jEdit 4.1pre11
  563. */
  564. public String getDirectory()
  565. {
  566. return directory;
  567. } //}}}
  568. //{{{ isClosed() method
  569. /**
  570. * Returns true if this buffer has been closed with
  571. * {@link org.gjt.sp.jedit.jEdit#closeBuffer(View,Buffer)}.
  572. * This method is thread-safe.
  573. */
  574. public boolean isClosed()
  575. {
  576. return getFlag(CLOSED);
  577. } //}}}
  578. //{{{ isLoaded() method
  579. /**
  580. * Returns true if the buffer is loaded. This method is thread-safe.
  581. */
  582. public boolean isLoaded()
  583. {
  584. return !getFlag(LOADING);
  585. } //}}}
  586. //{{{ isPerformingIO() method
  587. /**
  588. * Returns true if the buffer is currently performing I/O.
  589. * This method is thread-safe.
  590. * @since jEdit 2.7pre1
  591. */
  592. public boolean isPerformingIO()
  593. {
  594. return getFlag(LOADING) || getFlag(IO);
  595. } //}}}
  596. //{{{ isNewFile() method
  597. /**
  598. * Returns whether this buffer lacks a corresponding version on disk.
  599. * This method is thread-safe.
  600. */
  601. public boolean isNewFile()
  602. {
  603. return getFlag(NEW_FILE);
  604. } //}}}
  605. //{{{ setNewFile() method
  606. /**
  607. * Sets the new file flag.
  608. * @param newFile The new file flag
  609. */
  610. public void setNewFile(boolean newFile)
  611. {
  612. setFlag(NEW_FILE,newFile);
  613. if(!newFile)
  614. setFlag(UNTITLED,false);
  615. } //}}}
  616. //{{{ isUntitled() method
  617. /**
  618. * Returns true if this file is 'untitled'. This method is thread-safe.
  619. */
  620. public boolean isUntitled()
  621. {
  622. return getFlag(UNTITLED);
  623. } //}}}
  624. //{{{ isDirty() method
  625. /**
  626. * Returns whether there have been unsaved changes to this buffer.
  627. * This method is thread-safe.
  628. */
  629. public boolean isDirty()
  630. {
  631. return getFlag(DIRTY);
  632. } //}}}
  633. //{{{ isReadOnly() method
  634. /**
  635. * Returns true if this file is read only, false otherwise.
  636. * This method is thread-safe.
  637. */
  638. public boolean isReadOnly()
  639. {
  640. return getFlag(READ_ONLY) || getFlag(READ_ONLY_OVERRIDE);
  641. } //}}}
  642. //{{{ isEditable() method
  643. /**
  644. * Returns true if this file is editable, false otherwise. A file may
  645. * become uneditable if it is read only, or if I/O is in progress.
  646. * This method is thread-safe.
  647. * @since jEdit 2.7pre1
  648. */
  649. public boolean isEditable()
  650. {
  651. return !(getFlag(READ_ONLY) || getFlag(READ_ONLY_OVERRIDE)
  652. || getFlag(IO) || getFlag(LOADING));
  653. } //}}}
  654. //{{{ setReadOnly() method
  655. /**
  656. * Sets the read only flag.
  657. * @param readOnly The read only flag
  658. */
  659. public void setReadOnly(boolean readOnly)
  660. {
  661. setFlag(READ_ONLY_OVERRIDE,readOnly);
  662. } //}}}
  663. //{{{ setDirty() method
  664. /**
  665. * Sets the 'dirty' (changed since last save) flag of this buffer.
  666. */
  667. public void setDirty(boolean d)
  668. {
  669. boolean old_d = getFlag(DIRTY);
  670. boolean editable = isEditable();
  671. if(d)
  672. {
  673. if(editable)
  674. {
  675. setFlag(DIRTY,true);
  676. setFlag(AUTOSAVE_DIRTY,true);
  677. }
  678. }
  679. else
  680. {
  681. setFlag(DIRTY,false);
  682. setFlag(AUTOSAVE_DIRTY,false);
  683. if(autosaveFile != null)
  684. autosaveFile.delete();
  685. // fixes dirty flag not being reset on
  686. // save/insert/undo/redo/undo
  687. if(!getFlag(UNDO_IN_PROGRESS))
  688. {
  689. // this ensures that undo can clear the dirty flag properly
  690. // when all edits up to a save are undone
  691. undoMgr.bufferSaved();
  692. }
  693. }
  694. if(d != old_d && editable)
  695. {
  696. EditBus.send(new BufferUpdate(this,null,
  697. BufferUpdate.DIRTY_CHANGED));
  698. }
  699. } //}}}
  700. //{{{ isTemporary() method
  701. /**
  702. * Returns if this is a temporary buffer. This method is thread-safe.
  703. * @see jEdit#openTemporary(View,String,String,boolean)
  704. * @see jEdit#commitTemporary(Buffer)
  705. * @since jEdit 2.2pre7
  706. */
  707. public boolean isTemporary()
  708. {
  709. return getFlag(TEMPORARY);
  710. } //}}}
  711. //{{{ getIcon() method
  712. /**
  713. * Returns this buffer's icon.
  714. * @since jEdit 2.6pre6
  715. */
  716. public Icon getIcon()
  717. {
  718. if(getFlag(DIRTY))
  719. return GUIUtilities.DIRTY_BUFFER_ICON;
  720. else if(getFlag(READ_ONLY) || getFlag(READ_ONLY_OVERRIDE))
  721. return GUIUtilities.READ_ONLY_BUFFER_ICON;
  722. else if(getFlag(NEW_FILE))
  723. return GUIUtilities.NEW_BUFFER_ICON;
  724. else
  725. return GUIUtilities.NORMAL_BUFFER_ICON;
  726. } //}}}
  727. //}}}
  728. //{{{ Thread safety
  729. //{{{ readLock() method
  730. /**
  731. * The buffer is guaranteed not to change between calls to
  732. * {@link #readLock()} and {@link #readUnlock()}.
  733. */
  734. public void readLock()
  735. {
  736. lock.readLock();
  737. } //}}}
  738. //{{{ readUnlock() method
  739. /**
  740. * The buffer is guaranteed not to change between calls to
  741. * {@link #readLock()} and {@link #readUnlock()}.
  742. */
  743. public void readUnlock()
  744. {
  745. lock.readUnlock();
  746. } //}}}
  747. //{{{ writeLock() method
  748. /**
  749. * Attempting to obtain read lock will block between calls to
  750. * {@link #writeLock()} and {@link #writeUnlock()}.
  751. */
  752. public void writeLock()
  753. {
  754. lock.writeLock();
  755. } //}}}
  756. //{{{ writeUnlock() method
  757. /**
  758. * Attempting to obtain read lock will block between calls to
  759. * {@link #writeLock()} and {@link #writeUnlock()}.
  760. */
  761. public void writeUnlock()
  762. {
  763. lock.writeUnlock();
  764. } //}}}
  765. //}}}
  766. //{{{ Line offset methods
  767. //{{{ getLength() method
  768. /**
  769. * Returns the number of characters in the buffer. This method is thread-safe.
  770. */
  771. public int getLength()
  772. {
  773. // no need to lock since this just returns a value and that's it
  774. return contentMgr.getLength();
  775. } //}}}
  776. //{{{ getLineCount() method
  777. /**
  778. * Returns the number of physical lines in the buffer.
  779. * This method is thread-safe.
  780. * @since jEdit 3.1pre1
  781. */
  782. public int getLineCount()
  783. {
  784. // no need to lock since this just returns a value and that's it
  785. return lineMgr.getLineCount();
  786. } //}}}
  787. //{{{ getLineOfOffset() method
  788. /**
  789. * Returns the line containing the specified offset.
  790. * This method is thread-safe.
  791. * @param offset The offset
  792. * @since jEdit 4.0pre1
  793. */
  794. public int getLineOfOffset(int offset)
  795. {
  796. try
  797. {
  798. readLock();
  799. if(offset < 0 || offset > getLength())
  800. throw new ArrayIndexOutOfBoundsException(offset);
  801. return lineMgr.getLineOfOffset(offset);
  802. }
  803. finally
  804. {
  805. readUnlock();
  806. }
  807. } //}}}
  808. //{{{ getLineStartOffset() method
  809. /**
  810. * Returns the start offset of the specified line.
  811. * This method is thread-safe.
  812. * @param line The line
  813. * @return The start offset of the specified line
  814. * @since jEdit 4.0pre1
  815. */
  816. public int getLineStartOffset(int line)
  817. {
  818. try
  819. {
  820. readLock();
  821. if(line < 0 || line >= lineMgr.getLineCount())
  822. throw new ArrayIndexOutOfBoundsException(line);
  823. else if(line == 0)
  824. return 0;
  825. return lineMgr.getLineEndOffset(line - 1);
  826. }
  827. finally
  828. {
  829. readUnlock();
  830. }
  831. } //}}}
  832. //{{{ getLineEndOffset() method
  833. /**
  834. * Returns the end offset of the specified line.
  835. * This method is thread-safe.
  836. * @param line The line
  837. * @return The end offset of the specified line
  838. * invalid.
  839. * @since jEdit 4.0pre1
  840. */
  841. public int getLineEndOffset(int line)
  842. {
  843. try
  844. {
  845. readLock();
  846. if(line < 0 || line >= lineMgr.getLineCount())
  847. throw new ArrayIndexOutOfBoundsException(line);
  848. return lineMgr.getLineEndOffset(line);
  849. }
  850. finally
  851. {
  852. readUnlock();
  853. }
  854. } //}}}
  855. //{{{ getLineLength() method
  856. /**
  857. * Returns the length of the specified line.
  858. * This method is thread-safe.
  859. * @param line The line
  860. * @since jEdit 4.0pre1
  861. */
  862. public int getLineLength(int line)
  863. {
  864. try
  865. {
  866. readLock();
  867. return getLineEndOffset(line)
  868. - getLineStartOffset(line) - 1;
  869. }
  870. finally
  871. {
  872. readUnlock();
  873. }
  874. } //}}}
  875. //}}}
  876. //{{{ Text getters and setters
  877. //{{{ getLineText() method
  878. /**
  879. * Returns the text on the specified line.
  880. * This method is thread-safe.
  881. * @param line The line
  882. * @return The text, or null if the line is invalid
  883. * @since jEdit 4.0pre1
  884. */
  885. public String getLineText(int line)
  886. {
  887. if(line < 0 || line >= lineMgr.getLineCount())
  888. throw new ArrayIndexOutOfBoundsException(line);
  889. try
  890. {
  891. readLock();
  892. int start = (line == 0 ? 0
  893. : lineMgr.getLineEndOffset(line - 1));
  894. int end = lineMgr.getLineEndOffset(line);
  895. return getText(start,end - start - 1);
  896. }
  897. finally
  898. {
  899. readUnlock();
  900. }
  901. } //}}}
  902. //{{{ getLineText() method
  903. /**
  904. * Returns the specified line in a <code>Segment</code>.<p>
  905. *
  906. * Using a <classname>Segment</classname> is generally more
  907. * efficient than using a <classname>String</classname> because it
  908. * results in less memory allocation and array copying.<p>
  909. *
  910. * This method is thread-safe.
  911. *
  912. * @param line The line
  913. * @since jEdit 4.0pre1
  914. */
  915. public void getLineText(int line, Segment segment)
  916. {
  917. if(line < 0 || line >= lineMgr.getLineCount())
  918. throw new ArrayIndexOutOfBoundsException(line);
  919. try
  920. {
  921. readLock();
  922. int start = (line == 0 ? 0
  923. : lineMgr.getLineEndOffset(line - 1));
  924. int end = lineMgr.getLineEndOffset(line);
  925. getText(start,end - start - 1,segment);
  926. }
  927. finally
  928. {
  929. readUnlock();
  930. }
  931. } //}}}
  932. //{{{ getText() method
  933. /**
  934. * Returns the specified text range. This method is thread-safe.
  935. * @param start The start offset
  936. * @param length The number of characters to get
  937. */
  938. public String getText(int start, int length)
  939. {
  940. try
  941. {
  942. readLock();
  943. if(start < 0 || length < 0
  944. || start + length > contentMgr.getLength())
  945. throw new ArrayIndexOutOfBoundsException(start + ":" + length);
  946. return contentMgr.getText(start,length);
  947. }
  948. finally
  949. {
  950. readUnlock();
  951. }
  952. } //}}}
  953. //{{{ getText() method
  954. /**
  955. * Returns the specified text range in a <code>Segment</code>.<p>
  956. *
  957. * Using a <classname>Segment</classname> is generally more
  958. * efficient than using a <classname>String</classname> because it
  959. * results in less memory allocation and array copying.<p>
  960. *
  961. * This method is thread-safe.
  962. *
  963. * @param start The start offset
  964. * @param length The number of characters to get
  965. * @param seg The segment to copy the text to
  966. */
  967. public void getText(int start, int length, Segment seg)
  968. {
  969. try
  970. {
  971. readLock();
  972. if(start < 0 || length < 0
  973. || start + length > contentMgr.getLength())
  974. throw new ArrayIndexOutOfBoundsException(start + ":" + length);
  975. contentMgr.getText(start,length,seg);
  976. }
  977. finally
  978. {
  979. readUnlock();
  980. }
  981. } //}}}
  982. //{{{ insert() method
  983. /**
  984. * Inserts a string into the buffer.
  985. * @param offset The offset
  986. * @param str The string
  987. * @since jEdit 4.0pre1
  988. */
  989. public void insert(int offset, String str)
  990. {
  991. if(str == null)
  992. return;
  993. int len = str.length();
  994. if(len == 0)
  995. return;
  996. if(isReadOnly())
  997. throw new RuntimeException("buffer read-only");
  998. try
  999. {
  1000. writeLock();
  1001. if(offset < 0 || offset > contentMgr.getLength())
  1002. throw new ArrayIndexOutOfBoundsException(offset);
  1003. contentMgr.insert(offset,str);
  1004. integerArray.clear();
  1005. for(int i = 0; i < len; i++)
  1006. {
  1007. if(str.charAt(i) == '\n')
  1008. integerArray.add(i + 1);
  1009. }
  1010. if(!getFlag(UNDO_IN_PROGRESS))
  1011. {
  1012. undoMgr.contentInserted(offset,len,str,
  1013. !getFlag(DIRTY));
  1014. }
  1015. contentInserted(offset,len,integerArray);
  1016. }
  1017. finally
  1018. {
  1019. writeUnlock();
  1020. }
  1021. } //}}}
  1022. //{{{ insert() method
  1023. /**
  1024. * Inserts a string into the buffer.
  1025. * @param offset The offset
  1026. * @param seg The segment
  1027. * @since jEdit 4.0pre1
  1028. */
  1029. public void insert(int offset, Segment seg)
  1030. {
  1031. if(seg.count == 0)
  1032. return;
  1033. if(isReadOnly())
  1034. throw new RuntimeException("buffer read-only");
  1035. try
  1036. {
  1037. writeLock();
  1038. if(offset < 0 || offset > contentMgr.getLength())
  1039. throw new ArrayIndexOutOfBoundsException(offset);
  1040. contentMgr.insert(offset,seg);
  1041. integerArray.clear();
  1042. for(int i = 0; i < seg.count; i++)
  1043. {
  1044. if(seg.array[seg.offset + i] == '\n')
  1045. integerArray.add(i + 1);
  1046. }
  1047. if(!getFlag(UNDO_IN_PROGRESS))
  1048. {
  1049. undoMgr.contentInserted(offset,seg.count,
  1050. seg.toString(),!getFlag(DIRTY));
  1051. }
  1052. contentInserted(offset,seg.count,integerArray);
  1053. }
  1054. finally
  1055. {
  1056. writeUnlock();
  1057. }
  1058. } //}}}
  1059. //{{{ remove() method
  1060. /**
  1061. * Removes the specified rang efrom the buffer.
  1062. * @param offset The start offset
  1063. * @param length The number of characters to remove
  1064. */
  1065. public void remove(int offset, int length)
  1066. {
  1067. if(length == 0)
  1068. return;
  1069. if(isReadOnly())
  1070. throw new RuntimeException("buffer read-only");
  1071. try
  1072. {
  1073. writeLock();
  1074. if(offset < 0 || length < 0
  1075. || offset + length > contentMgr.getLength())
  1076. throw new ArrayIndexOutOfBoundsException(offset + ":" + length);
  1077. int startLine = lineMgr.getLineOfOffset(offset);
  1078. int endLine = lineMgr.getLineOfOffset(offset + length);
  1079. int numLines = endLine - startLine;
  1080. if(!getFlag(UNDO_IN_PROGRESS) && !getFlag(LOADING))
  1081. {
  1082. undoMgr.contentRemoved(offset,length,
  1083. getText(offset,length),
  1084. !getFlag(DIRTY));
  1085. }
  1086. firePreContentRemoved(startLine,offset,numLines,length);
  1087. contentMgr.remove(offset,length);
  1088. lineMgr.contentRemoved(startLine,offset,numLines,length);
  1089. positionMgr.contentRemoved(offset,length);
  1090. fireContentRemoved(startLine,offset,numLines,length);
  1091. setDirty(true);
  1092. }
  1093. finally
  1094. {
  1095. writeUnlock();
  1096. }
  1097. } //}}}
  1098. //}}}
  1099. //{{{ Undo
  1100. //{{{ undo() method
  1101. /**
  1102. * Undoes the most recent edit.
  1103. *
  1104. * @since jEdit 4.0pre1
  1105. */
  1106. public void undo(JEditTextArea textArea)
  1107. {
  1108. if(undoMgr == null)
  1109. return;
  1110. if(!isEditable())
  1111. {
  1112. textArea.getToolkit().beep();
  1113. return;
  1114. }
  1115. try
  1116. {
  1117. writeLock();
  1118. setFlag(UNDO_IN_PROGRESS,true);
  1119. int caret = undoMgr.undo();
  1120. if(caret == -1)
  1121. textArea.getToolkit().beep();
  1122. else
  1123. textArea.setCaretPosition(caret);
  1124. fireTransactionComplete();
  1125. }
  1126. finally
  1127. {
  1128. setFlag(UNDO_IN_PROGRESS,false);
  1129. writeUnlock();
  1130. }
  1131. } //}}}
  1132. //{{{ redo() method
  1133. /**
  1134. * Redoes the most recently undone edit.
  1135. *
  1136. * @since jEdit 2.7pre2
  1137. */
  1138. public void redo(JEditTextArea textArea)
  1139. {
  1140. if(undoMgr == null)
  1141. return;
  1142. if(!isEditable())
  1143. {
  1144. Toolkit.getDefaultToolkit().beep();
  1145. return;
  1146. }
  1147. try
  1148. {
  1149. writeLock();
  1150. setFlag(UNDO_IN_PROGRESS,true);
  1151. int caret = undoMgr.redo();
  1152. if(caret == -1)
  1153. textArea.getToolkit().beep();
  1154. else
  1155. textArea.setCaretPosition(caret);
  1156. fireTransactionComplete();
  1157. }
  1158. finally
  1159. {
  1160. setFlag(UNDO_IN_PROGRESS,false);
  1161. writeUnlock();
  1162. }
  1163. } //}}}
  1164. //{{{ isTransactionInProgress() method
  1165. /**
  1166. * Returns if an undo or compound edit is currently in progress. If this
  1167. * method returns true, then eventually a
  1168. * {@link org.gjt.sp.jedit.buffer.BufferChangeListener#transactionComplete(Buffer)}
  1169. * buffer event will get fired.
  1170. * @since jEdit 4.0pre6
  1171. */
  1172. public boolean isTransactionInProgress()
  1173. {
  1174. return getFlag(UNDO_IN_PROGRESS) || insideCompoundEdit();
  1175. } //}}}
  1176. //{{{ beginCompoundEdit() method
  1177. /**
  1178. * Starts a compound edit. All edits from now on until
  1179. * {@link #endCompoundEdit()} are called will be merged
  1180. * into one. This can be used to make a complex operation
  1181. * undoable in one step. Nested calls to
  1182. * {@link #beginCompoundEdit()} behave as expected,
  1183. * requiring the same number of {@link #endCompoundEdit()}
  1184. * calls to end the edit.
  1185. * @see #endCompoundEdit()
  1186. */
  1187. public void beginCompoundEdit()
  1188. {
  1189. // Why?
  1190. //if(getFlag(TEMPORARY))
  1191. // return;
  1192. try
  1193. {
  1194. writeLock();
  1195. undoMgr.beginCompoundEdit();
  1196. }
  1197. finally
  1198. {
  1199. writeUnlock();
  1200. }
  1201. } //}}}
  1202. //{{{ endCompoundEdit() method
  1203. /**
  1204. * Ends a compound edit. All edits performed since
  1205. * {@link #beginCompoundEdit()} was called can now
  1206. * be undone in one step by calling {@link #undo(JEditTextArea)}.
  1207. * @see #beginCompoundEdit()
  1208. */
  1209. public void endCompoundEdit()
  1210. {
  1211. // Why?
  1212. //if(getFlag(TEMPORARY))
  1213. // return;
  1214. try
  1215. {
  1216. writeLock();
  1217. undoMgr.endCompoundEdit();
  1218. if(!insideCompoundEdit())
  1219. fireTransactionComplete();
  1220. }
  1221. finally
  1222. {
  1223. writeUnlock();
  1224. }
  1225. }//}}}
  1226. //{{{ insideCompoundEdit() method
  1227. /**
  1228. * Returns if a compound edit is currently active.
  1229. * @since jEdit 3.1pre1
  1230. */
  1231. public boolean insideCompoundEdit()
  1232. {
  1233. return undoMgr.insideCompoundEdit();
  1234. } //}}}
  1235. //}}}
  1236. //{{{ Buffer events
  1237. public static final int NORMAL_PRIORITY = 0;
  1238. public static final int HIGH_PRIORITY = 1;
  1239. static class Listener
  1240. {
  1241. BufferChangeListener listener;
  1242. int priority;
  1243. Listener(BufferChangeListener listener, int priority)
  1244. {
  1245. this.listener = listener;
  1246. this.priority = priority;
  1247. }
  1248. }
  1249. //{{{ addBufferChangeListener() method
  1250. /**
  1251. * Adds a buffer change listener.
  1252. * @param listener The listener
  1253. * @param priority Listeners with HIGH_PRIORITY get the event before
  1254. * listeners with NORMAL_PRIORITY
  1255. * @since jEdit 4.2pre2
  1256. */
  1257. public void addBufferChangeListener(BufferChangeListener listener,
  1258. int priority)
  1259. {
  1260. Listener l = new Listener(listener,priority);
  1261. for(int i = 0; i < bufferListeners.size(); i++)
  1262. {
  1263. Listener _l = (Listener)bufferListeners.get(i);
  1264. if(_l.priority < priority)
  1265. {
  1266. bufferListeners.insertElementAt(l,i);
  1267. return;
  1268. }
  1269. }
  1270. bufferListeners.addElement(l);
  1271. } //}}}
  1272. //{{{ addBufferChangeListener() method
  1273. /**
  1274. * Adds a buffer change listener.
  1275. * @param listener The listener
  1276. * @since jEdit 4.0pre1
  1277. */
  1278. public void addBufferChangeListener(BufferChangeListener listener)
  1279. {
  1280. addBufferChangeListener(listener,NORMAL_PRIORITY);
  1281. } //}}}
  1282. //{{{ removeBufferChangeListener() method
  1283. /**
  1284. * Removes a buffer change listener.
  1285. * @param listener The listener
  1286. * @since jEdit 4.0pre1
  1287. */
  1288. public void removeBufferChangeListener(BufferChangeListener listener)
  1289. {
  1290. for(int i = 0; i < bufferListeners.size(); i++)
  1291. {
  1292. if(((Listener)bufferListeners.get(i)).listener == listener)
  1293. {
  1294. bufferListeners.removeElementAt(i);
  1295. return;
  1296. }
  1297. }
  1298. } //}}}
  1299. //{{{ getBufferChangeListeners() method
  1300. /**
  1301. * Returns an array of registered buffer change listeners.
  1302. * @param listener The listener
  1303. * @since jEdit 4.1pre3
  1304. */
  1305. public BufferChangeListener[] getBufferChangeListeners()
  1306. {
  1307. BufferChangeListener[] returnValue
  1308. = new BufferChangeListener[
  1309. bufferListeners.size()];
  1310. for(int i = 0; i < returnValue.length; i++)
  1311. {
  1312. returnValue[i] = ((Listener)bufferListeners.get(i))
  1313. .listener;
  1314. }
  1315. return returnValue;
  1316. } //}}}
  1317. //}}}
  1318. //{{{ Property methods
  1319. //{{{ propertiesChanged() method
  1320. /**
  1321. * Reloads settings from the properties. This should be called
  1322. * after the <code>syntax</code> or <code>folding</code>
  1323. * buffer-local properties are changed.
  1324. */
  1325. public void propertiesChanged()
  1326. {
  1327. String folding = getStringProperty("folding");
  1328. FoldHandler handler = FoldHandler.getFoldHandler(folding);
  1329. if(handler != null)
  1330. {
  1331. setFoldHandler(handler);
  1332. }
  1333. else
  1334. {
  1335. if (folding != null)
  1336. Log.log(Log.WARNING, this, path + ": invalid 'folding' property: " + folding);
  1337. setFoldHandler(new DummyFoldHandler());
  1338. }
  1339. EditBus.send(new BufferUpdate(this,null,BufferUpdate.PROPERTIES_CHANGED));
  1340. String newWrap = getStringProperty("wrap");
  1341. if(wrap != null && !newWrap.equals(wrap))
  1342. {
  1343. lineMgr.invalidateScreenLineCounts();
  1344. if(isLoaded())
  1345. fireWrapModeChanged();
  1346. }
  1347. this.wrap = newWrap;
  1348. } //}}}
  1349. //{{{ getTabSize() method
  1350. /**
  1351. * Returns the tab size used in this buffer. This is equivalent
  1352. * to calling <code>getProperty("tabSize")</code>.
  1353. * This method is thread-safe.
  1354. */
  1355. public int getTabSize()
  1356. {
  1357. return getIntegerProperty("tabSize",8);
  1358. } //}}}
  1359. //{{{ getIndentSize() method
  1360. /**
  1361. * Returns the indent size used in this buffer. This is equivalent
  1362. * to calling <code>getProperty("indentSize")</code>.
  1363. * This method is thread-safe.
  1364. * @since jEdit 2.7pre1
  1365. */
  1366. public int getIndentSize()
  1367. {
  1368. return getIntegerProperty("indentSize",8);
  1369. } //}}}
  1370. //{{{ getProperty() method
  1371. /**
  1372. * Returns the value of a buffer-local property.<p>
  1373. *
  1374. * Using this method is generally discouraged, because it returns an
  1375. * <code>Object</code> which must be cast to another type
  1376. * in order to be useful, and this can cause problems if the object
  1377. * is of a different type than what the caller expects.<p>
  1378. *
  1379. * The following methods should be used instead:
  1380. * <ul>
  1381. * <li>{@link #getStringProperty(String)}</li>
  1382. * <li>{@link #getBooleanProperty(String)}</li>
  1383. * <li>{@link #getIntegerProperty(String,int)}</li>
  1384. * <li>{@link #getRegexpProperty(String,int,gnu.regexp.RESyntax)}</li>
  1385. * </ul>
  1386. *
  1387. * This method is thread-safe.
  1388. *
  1389. * @param name The property name. For backwards compatibility, this
  1390. * is an <code>Object</code>, not a <code>String</code>.
  1391. */
  1392. public Object getProperty(Object name)
  1393. {
  1394. synchronized(propertyLock)
  1395. {
  1396. // First try the buffer-local properties
  1397. PropValue o = (PropValue)properties.get(name);
  1398. if(o != null)
  1399. return o.value;
  1400. // For backwards compatibility
  1401. if(!(name instanceof String))
  1402. return null;
  1403. // Now try mode.<mode>.<property>
  1404. if(mode != null)
  1405. {
  1406. Object retVal = mode.getProperty((String)name);
  1407. if(retVal == null)
  1408. return null;
  1409. properties.put(name,new PropValue(retVal,true));
  1410. return retVal;
  1411. }
  1412. else
  1413. {
  1414. // Now try buffer.<property>
  1415. String value = jEdit.getProperty("buffer." + name);
  1416. if(value == null)
  1417. return null;
  1418. // Try returning it as an integer first
  1419. Object retVal;
  1420. try
  1421. {
  1422. retVal = new Integer(value);
  1423. }
  1424. catch(NumberFormatException nf)
  1425. {
  1426. retVal = value;
  1427. }
  1428. properties.put(name,new PropValue(retVal,true));
  1429. return retVal;
  1430. }
  1431. }
  1432. } //}}}
  1433. //{{{ setProperty() method
  1434. /**
  1435. * Sets the value of a buffer-local property.
  1436. * @param name The property name
  1437. * @param value The property value
  1438. * @since jEdit 4.0pre1
  1439. */
  1440. public void setProperty(String name, Object value)
  1441. {
  1442. if(value == null)
  1443. properties.remove(name);
  1444. else
  1445. {
  1446. PropValue test = (PropValue)properties.get(name);
  1447. if(test == null)
  1448. properties.put(name,new PropValue(value,false));
  1449. else if(test.value.equals(value))
  1450. {
  1451. // do nothing
  1452. }
  1453. else
  1454. {
  1455. test.value = value;
  1456. test.defaultValue = false;
  1457. }
  1458. }
  1459. } //}}}
  1460. //{{{ unsetProperty() method
  1461. /**
  1462. * Clears the value of a buffer-local property.
  1463. * @param name The property name
  1464. * @since jEdit 4.0pre1
  1465. */
  1466. public void unsetProperty(String name)
  1467. {
  1468. properties.remove(name);
  1469. } //}}}
  1470. //{{{ getStringProperty() method
  1471. /**
  1472. * Returns the value of a string property. This method is thread-safe.
  1473. * @param name The property name
  1474. * @since jEdit 4.0pre1
  1475. */
  1476. public String getStringProperty(String name)
  1477. {
  1478. Object obj = getProperty(name);
  1479. if(obj != null)
  1480. return obj.toString();
  1481. else
  1482. return null;
  1483. } //}}}
  1484. //{{{ setStringProperty() method
  1485. /**
  1486. * Sets a string property.
  1487. * @param name The property name
  1488. * @param value The value
  1489. * @since jEdit 4.0pre1
  1490. */
  1491. public void setStringProperty(String name, String value)
  1492. {
  1493. setProperty(name,value);
  1494. } //}}}
  1495. //{{{ getBooleanProperty() method
  1496. /**
  1497. * Returns the value of a boolean property. This method is thread-safe.
  1498. * @param name The property name
  1499. * @since jEdit 4.0pre1
  1500. */
  1501. public boolean getBooleanProperty(String name)
  1502. {
  1503. Object obj = getProperty(name);
  1504. if(obj instanceof Boolean)
  1505. return ((Boolean)obj).booleanValue();
  1506. else if("true".equals(obj) || "on".equals(obj) || "yes".equals(obj))
  1507. return true;
  1508. else
  1509. return false;
  1510. } //}}}
  1511. //{{{ setBooleanProperty() method
  1512. /**
  1513. * Sets a boolean property.
  1514. * @param name The property name
  1515. * @param value The value
  1516. * @since jEdit 4.0pre1
  1517. */
  1518. public void setBooleanProperty(String name, boolean value)
  1519. {
  1520. setProperty(name,value ? Boolean.TRUE : Boolean.FALSE);
  1521. } //}}}
  1522. //{{{ getIntegerProperty() method
  1523. /**
  1524. * Returns the value of an integer property. This method is thread-safe.
  1525. * @param name The property name
  1526. * @since jEdit 4.0pre1
  1527. */
  1528. public int getIntegerProperty(String name, int defaultValue)
  1529. {
  1530. boolean defaultValueFlag;
  1531. Object obj;
  1532. PropValue value = (PropValue)properties.get(name);
  1533. if(value != null)
  1534. {
  1535. obj = value.value;
  1536. defaultValueFlag = value.defaultValue;
  1537. }
  1538. else
  1539. {
  1540. obj = getProperty(name);
  1541. // will be cached from now on...
  1542. defaultValueFlag = true;
  1543. }
  1544. if(obj == null)
  1545. return defaultValue;
  1546. else if(obj instanceof Number)
  1547. return ((Number)obj).intValue();
  1548. else
  1549. {
  1550. try
  1551. {
  1552. int returnValue = Integer.parseInt(
  1553. obj.toString().trim());
  1554. properties.put(name,new PropValue(
  1555. new Integer(returnValue),
  1556. defaultValueFlag));
  1557. return returnValue;
  1558. }
  1559. catch(Exception e)
  1560. {
  1561. return defaultValue;
  1562. }
  1563. }
  1564. } //}}}
  1565. //{{{ setIntegerProperty() method
  1566. /**
  1567. * Sets an integer property.
  1568. * @param name The property name
  1569. * @param value The value
  1570. * @since jEdit 4.0pre1
  1571. */
  1572. public void setIntegerProperty(String name, int value)
  1573. {
  1574. setProperty(name,new Integer(value));
  1575. } //}}}
  1576. //{{{ getRegexpProperty() method
  1577. /**
  1578. * Returns the value of a property as a regular expression.
  1579. * This method is thread-safe.
  1580. * @param name The property name
  1581. * @param cflags Regular expression compilation flags
  1582. * @param syntax Regular expression syntax
  1583. * @since jEdit 4.1pre9
  1584. */
  1585. public RE getRegexpProperty(String name, int cflags,
  1586. RESyntax syntax) throws REException
  1587. {
  1588. synchronized(propertyLock)
  1589. {
  1590. boolean defaultValueFlag;
  1591. Object obj;
  1592. PropValue value = (PropValue)properties.get(name);
  1593. if(value != null)
  1594. {
  1595. obj = value.value;
  1596. defaultValueFlag = value.defaultValue;
  1597. }
  1598. else
  1599. {
  1600. obj = getProperty(name);
  1601. // will be cached from now on...
  1602. defaultValueFlag = true;
  1603. }
  1604. if(obj == null)
  1605. return null;
  1606. else if(obj instanceof RE)
  1607. return (RE)obj;
  1608. else
  1609. {
  1610. RE re = new RE(obj.toString(),cflags,syntax);
  1611. properties.put(name,new PropValue(re,
  1612. defaultValueFlag));
  1613. return re;
  1614. }
  1615. }
  1616. } //}}}
  1617. //{{{ getRuleSetAtOffset() method
  1618. /**
  1619. * Returns the syntax highlighting ruleset at the specified offset.
  1620. * @since jEdit 4.1pre1
  1621. */
  1622. public ParserRuleSet getRuleSetAtOffset(int offset)
  1623. {
  1624. int line = getLineOfOffset(offset);
  1625. offset -= getLineStartOffset(line);
  1626. if(offset != 0)
  1627. offset--;
  1628. DefaultTokenHandler tokens = new DefaultTokenHandler();
  1629. markTokens(line,tokens);
  1630. Token token = TextUtilities.getTokenAtOffset(tokens.getTokens(),offset);
  1631. return token.rules;
  1632. } //}}}
  1633. //{{{ getKeywordMapAtOffset() method
  1634. /**
  1635. * Returns the syntax highlighting keyword map in effect at the
  1636. * specified offset. Used by the <b>Complete Word</b> command to
  1637. * complete keywords.
  1638. * @param offset The offset
  1639. * @since jEdit 4.0pre3
  1640. */
  1641. public KeywordMap getKeywordMapAtOffset(int offset)
  1642. {
  1643. return getRuleSetAtOffset(offset).getKeywords();
  1644. } //}}}
  1645. //{{{ getContextSensitiveProperty() method
  1646. /**
  1647. * Some settings, like comment start and end strings, can
  1648. * vary between different parts of a buffer (HTML text and inline
  1649. * JavaScript, for example).
  1650. * @param offset The offset
  1651. * @param name The property name
  1652. * @since jEdit 4.0pre3
  1653. */
  1654. public String getContextSensitiveProperty(int offset, String name)
  1655. {
  1656. ParserRuleSet rules = getRuleSetAtOffset(offset);
  1657. Object value = null;
  1658. Hashtable rulesetProps = rules.getProperties();
  1659. if(rulesetProps != null)
  1660. value = rulesetProps.get(name);
  1661. if(value == null)
  1662. {
  1663. value = jEdit.getMode(rules.getModeName())
  1664. .getProperty(name);
  1665. if(value == null)
  1666. value = mode.getProperty(name);
  1667. }
  1668. if(value == null)
  1669. return null;
  1670. else
  1671. return String.valueOf(value);
  1672. } //}}}
  1673. //{{{ Used to store property values
  1674. static class PropValue
  1675. {
  1676. PropValue(Object value, boolean defaultValue)
  1677. {
  1678. if(value == null)
  1679. throw new NullPointerException();
  1680. this.value = value;
  1681. this.defaultValue = defaultValue;
  1682. }
  1683. Object value;
  1684. /**
  1685. * If this is true, then this value is cached from the mode
  1686. * or global defaults, so when the defaults change this property
  1687. * value must be reset.
  1688. */
  1689. boolean defaultValue;
  1690. /**
  1691. * For debugging purposes.
  1692. */
  1693. public String toString()
  1694. {
  1695. return value.toString();
  1696. }
  1697. } //}}}
  1698. //{{{ toggleWordWrap() method
  1699. /**
  1700. * Toggles word wrap between the three available modes. This is used
  1701. * by the status bar.
  1702. * @param view We show a message in the view's status bar
  1703. * @since jEdit 4.1pre3
  1704. */
  1705. public void toggleWordWrap(View view)
  1706. {
  1707. String wrap = getStringProperty("wrap");
  1708. if(wrap.equals("none"))
  1709. wrap = "soft";
  1710. else if(wrap.equals("soft"))
  1711. wrap = "hard";
  1712. else if(wrap.equals("hard"))
  1713. wrap = "none";
  1714. view.getStatus().setMessageAndClear(jEdit.getProperty(
  1715. "view.status.wrap-changed",new String[] {
  1716. wrap }));
  1717. setProperty("wrap",wrap);
  1718. propertiesChanged();
  1719. } //}}}
  1720. //{{{ toggleLineSeparator() method
  1721. /**
  1722. * Toggles the line separator between the three available settings.
  1723. * This is used by the status bar.
  1724. * @param view We show a message in the view's status bar
  1725. * @since jEdit 4.1pre3
  1726. */
  1727. public void toggleLineSeparator(View view)
  1728. {
  1729. String status = null;
  1730. String lineSep = getStringProperty("lineSeparator");
  1731. if("\n".equals(lineSep))
  1732. {
  1733. status = "windows";
  1734. lineSep = "\r\n";
  1735. }
  1736. else if("\r\n".equals(lineSep))
  1737. {
  1738. status = "mac";
  1739. lineSep = "\r";
  1740. }
  1741. else if("\r".equals(lineSep))
  1742. {
  1743. status = "unix";
  1744. lineSep = "\n";
  1745. }
  1746. view.getStatus().setMessageAndClear(jEdit.getProperty(
  1747. "view.status.linesep-changed",new String[] {
  1748. jEdit.getProperty("lineSep." + status) }));
  1749. setProperty("lineSeparator",lineSep);
  1750. setDirty(true);
  1751. propertiesChanged();
  1752. } //}}}
  1753. //}}}
  1754. //{{{ Edit modes, syntax highlighting
  1755. //{{{ getMode() method
  1756. /**
  1757. * Returns this buffer's edit mode. This method is thread-safe.
  1758. */
  1759. public Mode getMode()
  1760. {
  1761. return mode;
  1762. } //}}}
  1763. //{{{ setMode() method
  1764. /**
  1765. * Sets this buffer's edit mode. Note that calling this before a buffer
  1766. * is loaded will have no effect; in that case, set the "mode" property
  1767. * to the name of the mode. A bit inelegant, I know...
  1768. * @param mode The mode name
  1769. * @since jEdit 4.2pre1
  1770. */
  1771. public void setMode(String mode)
  1772. {
  1773. setMode(jEdit.getMode(mode));
  1774. } //}}}
  1775. //{{{ setMode() method
  1776. /**
  1777. * Sets this buffer's edit mode. Note that calling this before a buffer
  1778. * is loaded will have no effect; in that case, set the "mode" property
  1779. * to the name of the mode. A bit inelegant, I know...
  1780. * @param mode The mode
  1781. */
  1782. public void setMode(Mode mode)
  1783. {
  1784. /* This protects against stupid people (like me)
  1785. * doing stuff like buffer.setMode(jEdit.getMode(...)); */
  1786. if(mode == null)
  1787. throw new NullPointerException("Mode must be non-null");
  1788. this.mode = mode;
  1789. textMode = "text".equals(mode.getName());
  1790. setTokenMarker(mode.getTokenMarker());
  1791. resetCachedProperties();
  1792. propertiesChanged();
  1793. } //}}}
  1794. //{{{ setMode() method
  1795. /**
  1796. * Sets this buffer's edit mode by calling the accept() method
  1797. * of each registered edit mode.
  1798. */
  1799. public void setMode()
  1800. {
  1801. String userMode = getStringProperty("mode");
  1802. if(userMode != null)
  1803. {
  1804. Mode m = jEdit.getMode(userMode);
  1805. if(m != null)
  1806. {
  1807. setMode(m);
  1808. return;
  1809. }
  1810. }
  1811. String nogzName = name.substring(0,name.length() -
  1812. (name.endsWith(".gz") ? 3 : 0));
  1813. Mode[] modes = jEdit.getModes();
  1814. String firstLine = getLineText(0);
  1815. // this must be in reverse order so that modes from the user
  1816. // catalog get checked first!
  1817. for(int i = modes.length - 1; i >= 0; i--)
  1818. {
  1819. if(modes[i].accept(nogzName,firstLine))
  1820. {
  1821. setMode(modes[i]);
  1822. return;
  1823. }
  1824. }
  1825. Mode defaultMode = jEdit.getMode(jEdit.getProperty("buffer.defaultMode"));
  1826. if(defaultMode == null)
  1827. defaultMode = jEdit.getMode("text");
  1828. setMode(defaultMode);
  1829. } //}}}
  1830. //{{{ markTokens() method
  1831. /**
  1832. * Returns the syntax tokens for the specified line.
  1833. * @param lineIndex The line number
  1834. * @param tokenHandler The token handler that will receive the syntax
  1835. * tokens
  1836. * @since jEdit 4.1pre1
  1837. */
  1838. public void markTokens(int lineIndex, TokenHandler tokenHandler)
  1839. {
  1840. Segment seg;
  1841. if(SwingUtilities.isEventDispatchThread())
  1842. seg = this.seg;
  1843. else
  1844. seg = new Segment();
  1845. if(lineIndex < 0 || lineIndex >= lineMgr.getLineCount())
  1846. throw new ArrayIndexOutOfBoundsException(lineIndex);
  1847. int firstInvalidLineContext = lineMgr.getFirstInvalidLineContext();
  1848. int start;
  1849. if(textMode || firstInvalidLineContext == -1)
  1850. {
  1851. start = lineIndex;
  1852. }
  1853. else
  1854. {
  1855. start = Math.min(firstInvalidLineContext,
  1856. lineIndex);
  1857. }
  1858. if(Debug.TOKEN_MARKER_DEBUG)
  1859. Log.log(Log.DEBUG,this,"tokenize from " + start + " to " + lineIndex);
  1860. for(int i = start; i <= lineIndex; i++)
  1861. {
  1862. getLineText(i,seg);
  1863. TokenMarker.LineContext context = lineMgr.getLineContext(i);
  1864. ParserRule oldRule;
  1865. ParserRuleSet oldRules;
  1866. char[] oldSpanEndSubst;
  1867. if(context == null)
  1868. {
  1869. //System.err.println(i + ": null context");
  1870. oldRule = null;
  1871. oldRules = null;
  1872. oldSpanEndSubst = null;
  1873. }
  1874. else
  1875. {
  1876. oldRule = context.inRule;
  1877. oldRules = context.rules;
  1878. oldSpanEndSubst = (context.parent != null
  1879. ? context.parent.spanEndSubst
  1880. : null);
  1881. }
  1882. TokenMarker.LineContext prevContext = (
  1883. (i == 0 || textMode) ? null
  1884. : lineMgr.getLineContext(i - 1)
  1885. );
  1886. context = tokenMarker.markTokens(prevContext,
  1887. (i == lineIndex ? tokenHandler
  1888. : DummyTokenHandler.INSTANCE),seg);
  1889. lineMgr.setLineContext(i,context);
  1890. // Could incorrectly be set to 'false' with
  1891. // recursive delegates, where the chaining might
  1892. // have changed but not the rule set in question (?)
  1893. if(oldRule != context.inRule)
  1894. {
  1895. nextLineRequested = true;
  1896. }
  1897. else if(oldRules != context.rules)
  1898. {
  1899. nextLineRequested = true;
  1900. }
  1901. else if(!MiscUtilities.objectsEqual(oldSpanEndSubst,
  1902. context.spanEndSubst))
  1903. {
  1904. nextLineRequested = true;
  1905. }
  1906. }
  1907. int lineCount = lineMgr.getLineCount();
  1908. if(lineCount - 1 == lineIndex)
  1909. lineMgr.setFirstInvalidLineContext(-1);
  1910. else if(nextLineRequested)
  1911. lineMgr.setFirstInvalidLineContext(lineIndex + 1);
  1912. else if(firstInvalidLineContext == -1)
  1913. /* do nothing */;
  1914. else
  1915. {
  1916. lineMgr.setFirstInvalidLineContext(Math.max(
  1917. firstInvalidLineContext,lineIndex + 1));
  1918. }
  1919. } //}}}
  1920. //{{{ isNextLineRequested() method
  1921. /**
  1922. * Returns true if the next line should be repainted. This
  1923. * will return true after a line has been tokenized that starts
  1924. * a multiline token that continues onto the next line.
  1925. */
  1926. public boolean isNextLineRequested()
  1927. {
  1928. boolean retVal = nextLineRequested;
  1929. nextLineRequested = false;
  1930. return retVal;
  1931. } //}}}
  1932. //}}}
  1933. //{{{ Indentation
  1934. //{{{ removeTrailingWhiteSpace() method
  1935. /**
  1936. * Removes trailing whitespace from all lines in the specified list.
  1937. * @param lines The line numbers
  1938. * @since jEdit 3.2pre1
  1939. */
  1940. public void removeTrailingWhiteSpace(int[] lines)
  1941. {
  1942. try
  1943. {
  1944. beginCompoundEdit();
  1945. for(int i = 0; i < lines.length; i++)
  1946. {
  1947. int pos, lineStart, lineEnd, tail;
  1948. getLineText(lines[i],seg);
  1949. // blank line
  1950. if (seg.count == 0) continue;
  1951. lineStart = seg.offset;
  1952. lineEnd = seg.offset + seg.count - 1;
  1953. for (pos = lineEnd; pos >= lineStart; pos--)
  1954. {
  1955. if (!Character.isWhitespace(seg.array[pos]))
  1956. break;
  1957. }
  1958. tail = lineEnd - pos;
  1959. // no whitespace
  1960. if (tail == 0) continue;
  1961. remove(getLineEndOffset(lines[i]) - 1 - tail,tail);
  1962. }
  1963. }
  1964. finally
  1965. {
  1966. endCompoundEdit();
  1967. }
  1968. } //}}}
  1969. //{{{ shiftIndentLeft() method
  1970. /**
  1971. * Shifts the indent of each line in the specified list to the left.
  1972. * @param lines The line numbers
  1973. * @since jEdit 3.2pre1
  1974. */
  1975. public void shiftIndentLeft(int[] lines)
  1976. {
  1977. int tabSize = getTabSize();
  1978. int indentSize = getIndentSize();
  1979. boolean noTabs = getBooleanProperty("noTabs");
  1980. try
  1981. {
  1982. beginCompoundEdit();
  1983. for(int i = 0; i < lines.length; i++)
  1984. {
  1985. int lineStart = getLineStartOffset(lines[i]);
  1986. String line = getLineText(lines[i]);
  1987. int whiteSpace = MiscUtilities
  1988. .getLeadingWhiteSpace(line);
  1989. if(whiteSpace == 0)
  1990. continue;
  1991. int whiteSpaceWidth = Math.max(0,MiscUtilities
  1992. .getLeadingWhiteSpaceWidth(line,tabSize)
  1993. - indentSize);
  1994. insert(lineStart + whiteSpace,MiscUtilities
  1995. .createWhiteSpace(whiteSpaceWidth,
  1996. (noTabs ? 0 : tabSize)));
  1997. remove(lineStart,whiteSpace);
  1998. }
  1999. }
  2000. finally
  2001. {
  2002. endCompoundEdit();
  2003. }
  2004. } //}}}
  2005. //{{{ shiftIndentRight() method
  2006. /**
  2007. * Shifts the indent of each line in the specified list to the right.
  2008. * @param lines The line numbers
  2009. * @since jEdit 3.2pre1
  2010. */
  2011. public void shiftIndentRight(int[] lines)
  2012. {
  2013. try
  2014. {
  2015. beginCompoundEdit();
  2016. int tabSize = getTabSize();
  2017. int indentSize = getIndentSize();
  2018. boolean noTabs = getBooleanProperty("noTabs");
  2019. for(int i = 0; i < lines.length; i++)
  2020. {
  2021. int lineStart = getLineStartOffset(lines[i]);
  2022. String line = getLineText(lines[i]);
  2023. int whiteSpace = MiscUtilities
  2024. .getLeadingWhiteSpace(line);
  2025. // silly usability hack
  2026. //if(lines.length != 1 && whiteSpace == 0)
  2027. // continue;
  2028. int whiteSpaceWidth = MiscUtilities
  2029. .getLeadingWhiteSpaceWidth(
  2030. line,tabSize) + indentSize;
  2031. insert(lineStart + whiteSpace,MiscUtilities
  2032. .createWhiteSpace(whiteSpaceWidth,
  2033. (noTabs ? 0 : tabSize)));
  2034. remove(lineStart,whiteSpace);
  2035. }
  2036. }
  2037. finally
  2038. {
  2039. endCompoundEdit();
  2040. }
  2041. } //}}}
  2042. //{{{ indentLines() method
  2043. /**
  2044. * Indents all specified lines.
  2045. * @param start The first line to indent
  2046. * @param end The last line to indent
  2047. * @since jEdit 3.1pre3
  2048. */
  2049. public void indentLines(int start, int end)
  2050. {
  2051. try
  2052. {
  2053. beginCompoundEdit();
  2054. for(int i = start; i <= end; i++)
  2055. indentLine(i,true);
  2056. }
  2057. finally
  2058. {
  2059. endCompoundEdit();
  2060. }
  2061. } //}}}
  2062. //{{{ indentLines() method
  2063. /**
  2064. * Indents all specified lines.
  2065. * @param lines The line numbers
  2066. * @since jEdit 3.2pre1
  2067. */
  2068. public void indentLines(int[] lines)
  2069. {
  2070. try
  2071. {
  2072. beginCompoundEdit();
  2073. for(int i = 0; i < lines.length; i++)
  2074. indentLine(lines[i],true);
  2075. }
  2076. finally
  2077. {
  2078. endCompoundEdit();
  2079. }
  2080. } //}}}
  2081. //{{{ indentLine() method
  2082. /**
  2083. * @deprecated Use {@link #indentLine(int,boolean)} instead.
  2084. */
  2085. public boolean indentLine(int lineIndex, boolean canIncreaseIndent,
  2086. boolean canDecreaseIndent)
  2087. {
  2088. return indentLine(lineIndex,canDecreaseIndent);
  2089. } //}}}
  2090. //{{{ indentLine() method
  2091. /**
  2092. * Indents the specified line.
  2093. * @param line The line number to indent
  2094. * @param canDecreaseIndent If true, the indent can be decreased as a
  2095. * result of this. Set this to false for Tab key.
  2096. * @return true If indentation took place, false otherwise.
  2097. * @since jEdit 4.2pre2
  2098. */
  2099. public boolean indentLine(int lineIndex, boolean canDecreaseIndent)
  2100. {
  2101. int[] whitespaceChars = new int[1];
  2102. int currentIndent = getCurrentIdentForLine(lineIndex,
  2103. whitespaceChars);
  2104. int idealIndent = getIdealIndentForLine(lineIndex);
  2105. if(idealIndent == -1 || idealIndent == currentIndent
  2106. || (!canDecreaseIndent && idealIndent < currentIndent))
  2107. return false;
  2108. // Do it
  2109. try
  2110. {
  2111. beginCompoundEdit();
  2112. int start = getLineStartOffset(lineIndex);
  2113. remove(start,whitespaceChars[0]);
  2114. insert(start,MiscUtilities.createWhiteSpace(
  2115. idealIndent,(getBooleanProperty("noTabs")
  2116. ? 0 : getTabSize())));
  2117. }
  2118. finally
  2119. {
  2120. endCompoundEdit();
  2121. }
  2122. return true;
  2123. } //}}}
  2124. //{{{ getCurrentIdentForLine() method
  2125. /**
  2126. * Returns the line's current leading indent.
  2127. * @param lineIndex The line number
  2128. * @param whitespaceChars If this is non-null, the number of whitespace
  2129. * characters is stored at the 0 index
  2130. * @since jEdit 4.2pre2
  2131. */
  2132. public int getCurrentIdentForLine(int lineIndex, int[] whitespaceChars)
  2133. {
  2134. getLineText(lineIndex,seg);
  2135. int tabSize = getTabSize();
  2136. int currentIndent = 0;
  2137. loop: for(int i = 0; i < seg.count; i++)
  2138. {
  2139. char c = seg.array[seg.offset + i];
  2140. switch(c)
  2141. {
  2142. case ' ':
  2143. currentIndent++;
  2144. if(whitespaceChars != null)
  2145. whitespaceChars[0]++;
  2146. break;
  2147. case '\t':
  2148. currentIndent += (tabSize - (currentIndent
  2149. % tabSize));
  2150. if(whitespaceChars != null)
  2151. whitespaceChars[0]++;
  2152. break;
  2153. default:
  2154. break loop;
  2155. }
  2156. }
  2157. return currentIndent;
  2158. } //}}}
  2159. //{{{ getIdealIndentForLine() method
  2160. /**
  2161. * Returns the ideal leading indent for the specified line.
  2162. * This will apply the various auto-indent rules.
  2163. * @param lineIndex The line number
  2164. */
  2165. public int getIdealIndentForLine(int lineIndex)
  2166. {
  2167. final String EXPLICIT_START = "{{{";
  2168. final String EXPLICIT_END = "}}}";
  2169. if(lineIndex == 0)
  2170. return -1;
  2171. //{{{ Get properties
  2172. String openBrackets = getStringProperty("indentOpenBrackets");
  2173. if(openBrackets == null)
  2174. openBrackets = "";
  2175. String closeBrackets = getStringProperty("indentCloseBrackets");
  2176. if(closeBrackets == null)
  2177. closeBrackets = "";
  2178. RE indentNextLineRE;
  2179. try
  2180. {
  2181. indentNextLineRE = getRegexpProperty("indentNextLine",
  2182. RE.REG_ICASE,RESearchMatcher.RE_SYNTAX_JEDIT);
  2183. }
  2184. catch(REException re)
  2185. {
  2186. indentNextLineRE = null;
  2187. Log.log(Log.ERROR,this,"Invalid indentNextLine regexp");
  2188. Log.log(Log.ERROR,this,re);
  2189. }
  2190. RE indentNextLinesRE;
  2191. try
  2192. {
  2193. indentNextLinesRE = getRegexpProperty("indentNextLines",
  2194. RE.REG_ICASE,RESearchMatcher.RE_SYNTAX_JEDIT);
  2195. }
  2196. catch(REException re)
  2197. {
  2198. indentNextLinesRE = null;
  2199. Log.log(Log.ERROR,this,"Invalid indentNextLines regexp");
  2200. Log.log(Log.ERROR,this,re);
  2201. }
  2202. boolean doubleBracketIndent = getBooleanProperty("doubleBracketIndent");
  2203. boolean lineUpClosingBracket = getBooleanProperty("lineUpClosingBracket");
  2204. int tabSize = getTabSize();
  2205. int indentSize = getIndentSize();
  2206. //}}}
  2207. //{{{ Get indent attributes of previous line
  2208. int prevLineIndex = getPriorNonEmptyLine(lineIndex);
  2209. if(prevLineIndex == -1)
  2210. return -1;
  2211. String prevLine = getLineText(prevLineIndex);
  2212. /*
  2213. * On the previous line,
  2214. * if(bob) { --> +1
  2215. * if(bob) { } --> 0
  2216. * } else if(bob) { --> +1
  2217. */
  2218. boolean prevLineStart = true; // False after initial indent
  2219. int indent = 0; // Indent width (tab expanded)
  2220. int prevLineBrackets = 0; // Additional bracket indent
  2221. int prevLineCloseBracketIndex = -1; // For finding whether we're in
  2222. // this kind of construct:
  2223. // if (cond1)
  2224. // while (cond2)
  2225. // if (cond3){
  2226. //
  2227. // }
  2228. // So we know to indent the next line under the 1st if.
  2229. int prevLineUnclosedParenIndex = -1; // Index of the last unclosed parenthesis
  2230. Stack openParens = new Stack();
  2231. for(int i = 0; i < prevLine.length(); i++)
  2232. {
  2233. char c = prevLine.charAt(i);
  2234. switch(c)
  2235. {
  2236. case ' ':
  2237. if(prevLineStart)
  2238. indent++;
  2239. break;
  2240. case '\t':
  2241. if(prevLineStart)
  2242. {
  2243. indent += (tabSize
  2244. - (indent
  2245. % tabSize));
  2246. }
  2247. break;
  2248. case '(':
  2249. openParens.push(new Integer(i));
  2250. break;
  2251. case ')':
  2252. if(openParens.size() > 0)
  2253. {
  2254. openParens.pop();
  2255. }
  2256. break;
  2257. default:
  2258. prevLineStart = false;
  2259. if(closeBrackets.indexOf(c) != -1)
  2260. {
  2261. if(prevLine.regionMatches(false,
  2262. i,EXPLICIT_END,0,3))
  2263. i += 2;
  2264. else
  2265. {
  2266. prevLineBrackets--;
  2267. if(prevLineBrackets < 0)
  2268. {
  2269. if(lineUpClosingBracket)
  2270. prevLineBrackets = 0;
  2271. prevLineCloseBracketIndex = i;
  2272. }
  2273. }
  2274. }
  2275. else if(openBrackets.indexOf(c) != -1)
  2276. {
  2277. if(prevLine.regionMatches(false,
  2278. i,EXPLICIT_START,0,3))
  2279. i += 2;
  2280. else
  2281. prevLineBrackets++;
  2282. }
  2283. break;
  2284. }
  2285. }
  2286. if(openParens.size() > 0)
  2287. {
  2288. prevLineUnclosedParenIndex = ((Integer) openParens.pop()).intValue();
  2289. } //}}}
  2290. //{{{ Get indent attributes for current line
  2291. String line = getLineText(lineIndex);
  2292. /*
  2293. * On the current line,
  2294. * } --> -1
  2295. * } else if(bob) { --> -1
  2296. * if(bob) { } --> 0
  2297. */
  2298. int lineBrackets = 0; // Additional bracket indent
  2299. int closeBracketIndex = -1; // For lining up closing
  2300. // and opening brackets
  2301. for(int i = 0; i < line.length(); i++)
  2302. {
  2303. char c = line.charAt(i);
  2304. if(closeBrackets.indexOf(c) != -1)
  2305. {
  2306. if(line.regionMatches(false,
  2307. i,EXPLICIT_END,0,3))
  2308. i += 2;
  2309. else
  2310. {
  2311. closeBracketIndex = i;
  2312. lineBrackets--;
  2313. }
  2314. }
  2315. else if(openBrackets.indexOf(c) != -1)
  2316. {
  2317. if(line.regionMatches(false,
  2318. i,EXPLICIT_START,0,3))
  2319. i += 2;
  2320. else if(lineBrackets >= 0)
  2321. lineBrackets++;
  2322. }
  2323. }
  2324. if(openParens.size() > 0)
  2325. {
  2326. prevLineUnclosedParenIndex = ((Integer) openParens.pop()).intValue();
  2327. } //}}}
  2328. //{{{ Deep indenting
  2329. if(getBooleanProperty("deepIndent"))
  2330. {
  2331. if(prevLineUnclosedParenIndex != -1)
  2332. {
  2333. indent = prevLineUnclosedParenIndex;
  2334. for(int i = 0; i < prevLine.length(); i++)
  2335. {
  2336. if(prevLine.charAt(i) == '\t')
  2337. {
  2338. indent += tabSize-1;
  2339. }
  2340. else
  2341. {
  2342. break;
  2343. }
  2344. }
  2345. indent++;
  2346. return indent;
  2347. }
  2348. }
  2349. //}}}
  2350. //{{{ Handle brackets
  2351. if(prevLineBrackets > 0)
  2352. indent += (indentSize * prevLineBrackets);
  2353. if(lineUpClosingBracket)
  2354. {
  2355. if(lineBrackets < 0)
  2356. {
  2357. int offset = TextUtilities.findMatchingBracket(
  2358. this,lineIndex,closeBracketIndex);
  2359. if(offset != -1)
  2360. {
  2361. String closeLine = getLineText(getLineOfOffset(offset));
  2362. indent = MiscUtilities.getLeadingWhiteSpaceWidth(
  2363. closeLine,tabSize);
  2364. }
  2365. else
  2366. return -1;
  2367. }
  2368. }
  2369. else
  2370. {
  2371. if(prevLineBrackets < 0)
  2372. {
  2373. int offset = TextUtilities.findMatchingBracket(
  2374. this,prevLineIndex,prevLineCloseBracketIndex);
  2375. if(offset != -1)
  2376. {
  2377. String closeLine = getLineText(getLineOfOffset(offset));
  2378. indent = MiscUtilities.getLeadingWhiteSpaceWidth(
  2379. closeLine,tabSize);
  2380. }
  2381. else
  2382. return -1;
  2383. }
  2384. }//}}}
  2385. //{{{ Handle regexps
  2386. if(lineBrackets >= 0)
  2387. {
  2388. // If the previous line matches indentNextLine or indentNextLines,
  2389. // add a level of indent
  2390. if((lineBrackets == 0 || doubleBracketIndent)
  2391. && indentNextLinesRE != null
  2392. && indentNextLinesRE.isMatch(prevLine))
  2393. {
  2394. indent += indentSize;
  2395. }
  2396. else if(indentNextLineRE != null)
  2397. {
  2398. if((lineBrackets == 0 || doubleBracketIndent)
  2399. && indentNextLineRE.isMatch(prevLine))
  2400. indent += indentSize;
  2401. // we don't want
  2402. // if(foo)
  2403. // {
  2404. // <--- decreased indent
  2405. else if(prevLineBrackets == 0)
  2406. {
  2407. // While prior lines match indentNextLine, remove a level of indent
  2408. // this correctly handles constructs like:
  2409. // if(foo)
  2410. // if(bar)
  2411. // if(baz)
  2412. // <--- put indent here
  2413. int prevPrevLineIndex;
  2414. /* if(prevLineCloseBracketIndex != -1)
  2415. {
  2416. int offset = TextUtilities.findMatchingBracket(
  2417. this,prevLineIndex,prevLineCloseBracketIndex);
  2418. if(offset == -1)
  2419. return -1;
  2420. prevPrevLineIndex = getLineOfOffset(offset);
  2421. }
  2422. else */
  2423. prevPrevLineIndex = getPriorNonEmptyLine(prevLineIndex);
  2424. while(prevPrevLineIndex != -1)
  2425. {
  2426. if(indentNextLineRE.isMatch(getLineText(prevPrevLineIndex)))
  2427. indent = getCurrentIdentForLine(prevPrevLineIndex,null);
  2428. else
  2429. break;
  2430. prevPrevLineIndex = getPriorNonEmptyLine(prevPrevLineIndex);
  2431. }
  2432. }
  2433. }
  2434. } //}}}
  2435. return indent;
  2436. } //}}}
  2437. //{{{ getVirtualWidth() method
  2438. /**
  2439. * Returns the virtual column number (taking tabs into account) of the
  2440. * specified position.
  2441. *
  2442. * @param line The line number
  2443. * @param column The column number
  2444. * @since jEdit 4.1pre1
  2445. */
  2446. public int getVirtualWidth(int line, int column)
  2447. {
  2448. try
  2449. {
  2450. readLock();
  2451. int start = getLineStartOffset(line);
  2452. getText(start,column,seg);
  2453. return MiscUtilities.getVirtualWidth(seg,getTabSize());
  2454. }
  2455. finally
  2456. {
  2457. readUnlock();
  2458. }
  2459. } //}}}
  2460. //{{{ getOffsetOfVirtualColumn() method
  2461. /**
  2462. * Returns the offset of a virtual column number (taking tabs
  2463. * into account) relative to the start of the line in question.
  2464. *
  2465. * @param line The line number
  2466. * @param column The virtual column number
  2467. * @param totalVirtualWidth If this array is non-null, the total
  2468. * virtual width will be stored in its first location if this method
  2469. * returns -1.
  2470. *
  2471. * @return -1 if the column is out of bounds
  2472. *
  2473. * @since jEdit 4.1pre1
  2474. */
  2475. public int getOffsetOfVirtualColumn(int line, int column,
  2476. int[] totalVirtualWidth)
  2477. {
  2478. try
  2479. {
  2480. readLock();
  2481. getLineText(line,seg);
  2482. return MiscUtilities.getOffsetOfVirtualColumn(seg,
  2483. getTabSize(),column,totalVirtualWidth);
  2484. }
  2485. finally
  2486. {
  2487. readUnlock();
  2488. }
  2489. } //}}}
  2490. //{{{ insertAtColumn()
  2491. /**
  2492. * Like the {@link #insert(int,String)} method, but inserts the string at
  2493. * the specified virtual column. Inserts spaces as appropriate if
  2494. * the line is shorter than the column.
  2495. * @param line The line number
  2496. * @param col The virtual column number
  2497. * @param str The string
  2498. */
  2499. public void insertAtColumn(int line, int col, String str)
  2500. {
  2501. try
  2502. {
  2503. writeLock();
  2504. int[] total = new int[1];
  2505. int offset = getOffsetOfVirtualColumn(line,col,total);
  2506. if(offset == -1)
  2507. {
  2508. offset = getLineEndOffset(line) - 1;
  2509. str = MiscUtilities.createWhiteSpace(col - total[0],0) + str;
  2510. }
  2511. else
  2512. offset += getLineStartOffset(line);
  2513. insert(offset,str);
  2514. }
  2515. finally
  2516. {
  2517. writeUnlock();
  2518. }
  2519. } //}}}
  2520. //}}}
  2521. //{{{ Deprecated methods
  2522. //{{{ putProperty() method
  2523. /**
  2524. * @deprecated Call <code>setProperty()</code> instead.
  2525. */
  2526. public void putProperty(Object name, Object value)
  2527. {
  2528. // for backwards compatibility
  2529. if(!(name instanceof String))
  2530. return;
  2531. setProperty((String)name,value);
  2532. } //}}}
  2533. //{{{ putBooleanProperty() method
  2534. /**
  2535. * @deprecated Call <code>setBooleanProperty()</code> instead
  2536. */
  2537. public void putBooleanProperty(String name, boolean value)
  2538. {
  2539. setBooleanProperty(name,value);
  2540. } //}}}
  2541. //{{{ markTokens() method
  2542. /**
  2543. * @deprecated Use org.gjt.sp.jedit.syntax.DefaultTokenHandler instead
  2544. */
  2545. public static class TokenList extends DefaultTokenHandler
  2546. {
  2547. public Token getF