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

/jEdit/tags/jedit-4-1-pre5/org/gjt/sp/jedit/Buffer.java

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