PageRenderTime 48ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 1ms

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

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