/jEdit/branches/4.5.x/org/gjt/sp/jedit/Buffer.java

# · Java · 2123 lines · 1308 code · 237 blank · 578 comment · 259 complexity · 3888b640aa149cfacbde6a58a8fad29b MD5 · raw file

  1. /*
  2. * Buffer.java - jEdit buffer
  3. * :tabSize=8:indentSize=8:noTabs=false:
  4. * :folding=explicit:collapseFolds=1:
  5. *
  6. * Copyright (C) 1998, 2005 Slava Pestov
  7. * Portions copyright (C) 1999, 2000 mike dillon
  8. *
  9. * This program is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU General Public License
  11. * as published by the Free Software Foundation; either version 2
  12. * of the License, or any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program; if not, write to the Free Software
  21. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  22. */
  23. package org.gjt.sp.jedit;
  24. //{{{ Imports
  25. import java.io.File;
  26. import java.io.IOException;
  27. import java.net.Socket;
  28. import java.util.Arrays;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.Vector;
  32. import javax.swing.*;
  33. import javax.swing.text.Segment;
  34. import org.gjt.sp.jedit.browser.VFSBrowser;
  35. import org.gjt.sp.jedit.buffer.BufferUndoListener;
  36. import org.gjt.sp.jedit.buffer.FoldHandler;
  37. import org.gjt.sp.jedit.buffer.JEditBuffer;
  38. import org.gjt.sp.jedit.bufferio.BufferAutosaveRequest;
  39. import org.gjt.sp.jedit.bufferio.BufferIORequest;
  40. import org.gjt.sp.jedit.bufferio.MarkersSaveRequest;
  41. import org.gjt.sp.jedit.bufferset.BufferSet;
  42. import org.gjt.sp.jedit.io.FileVFS;
  43. import org.gjt.sp.jedit.io.VFS;
  44. import org.gjt.sp.jedit.io.VFSFile;
  45. import org.gjt.sp.jedit.io.VFSManager;
  46. import org.gjt.sp.jedit.msg.BufferUpdate;
  47. import org.gjt.sp.jedit.options.GeneralOptionPane;
  48. import org.gjt.sp.jedit.syntax.ModeProvider;
  49. import org.gjt.sp.jedit.syntax.ParserRuleSet;
  50. import org.gjt.sp.jedit.visitors.JEditVisitorAdapter;
  51. import org.gjt.sp.jedit.visitors.SaveCaretInfoVisitor;
  52. import org.gjt.sp.util.IntegerArray;
  53. import org.gjt.sp.util.Log;
  54. import org.gjt.sp.util.StandardUtilities;
  55. import org.gjt.sp.util.ThreadUtilities;
  56. //}}}
  57. /**
  58. * A <code>Buffer</code> represents the contents of an open text
  59. * file as it is maintained in the computer's memory (as opposed to
  60. * how it may be stored on a disk).<p>
  61. *
  62. * In a BeanShell script, you can obtain the current buffer instance from the
  63. * <code>buffer</code> variable.<p>
  64. *
  65. * This class does not have a public constructor.
  66. * Buffers can be opened and closed using methods in the <code>jEdit</code>
  67. * class.<p>
  68. *
  69. * This class is partially thread-safe, however you must pay attention to two
  70. * very important guidelines:
  71. * <ul>
  72. * <li>Operations such as insert() and remove(),
  73. * undo(), change Buffer data in a writeLock(), and must
  74. * be called from the AWT thread.
  75. * <li>When accessing the buffer from another thread, you must
  76. * call readLock() before and readUnLock() after, if you plan on performing
  77. * more than one read, to ensure that the buffer contents are not changed by
  78. * the AWT thread for the duration of the lock. Only methods whose descriptions
  79. * specify thread safety can be invoked from other threads.
  80. * </ul>
  81. *
  82. * @author Slava Pestov
  83. * @version $Id: Buffer.java 20007 2011-09-24 00:49:35Z Vampire0 $
  84. */
  85. public class Buffer extends JEditBuffer
  86. {
  87. //{{{ Some constants
  88. /**
  89. * Backed up property.
  90. * @since jEdit 3.2pre2
  91. */
  92. public static final String BACKED_UP = "Buffer__backedUp";
  93. /**
  94. * Caret info properties.
  95. * @since jEdit 3.2pre1
  96. */
  97. public static final String CARET = "Buffer__caret";
  98. public static final String CARET_POSITIONED = "Buffer__caretPositioned";
  99. /**
  100. * Stores a List of {@link org.gjt.sp.jedit.textarea.Selection} instances.
  101. */
  102. public static final String SELECTION = "Buffer__selection";
  103. /**
  104. * This should be a physical line number, so that the scroll
  105. * position is preserved correctly across reloads (which will
  106. * affect virtual line numbers, due to fold being reset)
  107. */
  108. public static final String SCROLL_VERT = "Buffer__scrollVert";
  109. public static final String SCROLL_HORIZ = "Buffer__scrollHoriz";
  110. /**
  111. * Should jEdit try to set the encoding based on a UTF8, UTF16 or
  112. * XML signature at the beginning of the file?
  113. */
  114. public static final String ENCODING_AUTODETECT = "encodingAutodetect";
  115. /**
  116. * This property is set to 'true' if the file has a trailing newline.
  117. * @since jEdit 4.0pre1
  118. */
  119. public static final String TRAILING_EOL = "trailingEOL";
  120. /**
  121. * This property is set to 'true' if the file should be GZipped.
  122. * @since jEdit 4.0pre4
  123. */
  124. public static final String GZIPPED = "gzipped";
  125. //}}}
  126. //{{{ Input/output methods
  127. //{{{ reload() method
  128. /**
  129. * Reloads the buffer from disk, asking for confirmation if the buffer
  130. * has unsaved changes.
  131. * @param view The view
  132. * @since jEdit 2.7pre2
  133. */
  134. public void reload(View view)
  135. {
  136. if (getFlag(UNTITLED))
  137. return;
  138. if(isDirty())
  139. {
  140. String[] args = { path };
  141. int result = GUIUtilities.confirm(view,"changedreload",
  142. args,JOptionPane.YES_NO_OPTION,
  143. JOptionPane.WARNING_MESSAGE);
  144. if(result != JOptionPane.YES_OPTION)
  145. return;
  146. }
  147. view.visit(new SaveCaretInfoVisitor());
  148. load(view,true);
  149. } //}}}
  150. //{{{ load() method
  151. /**
  152. * Loads the buffer from disk.
  153. * @param view The view
  154. * @param reload If true, user will not be asked to recover autosave
  155. * file, if any
  156. *
  157. * @since 2.5pre1
  158. */
  159. public boolean load(final View view, final boolean reload)
  160. {
  161. if(isPerformingIO())
  162. {
  163. GUIUtilities.error(view,"buffer-multiple-io",null);
  164. return false;
  165. }
  166. setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false);
  167. setLoading(true);
  168. // view text areas temporarily blank out while a buffer is
  169. // being loaded, to indicate to the user that there is no
  170. // data available yet.
  171. if(!getFlag(TEMPORARY))
  172. EditBus.send(new BufferUpdate(this,view,BufferUpdate.LOAD_STARTED));
  173. final boolean loadAutosave;
  174. if(reload || !getFlag(NEW_FILE))
  175. {
  176. if(file != null)
  177. modTime = file.lastModified();
  178. // Only on initial load
  179. if(!reload && autosaveFile != null && autosaveFile.exists())
  180. loadAutosave = recoverAutosave(view);
  181. else
  182. {
  183. if(autosaveFile != null)
  184. autosaveFile.delete();
  185. loadAutosave = false;
  186. }
  187. if(!loadAutosave)
  188. {
  189. VFS vfs = VFSManager.getVFSForPath(path);
  190. if(!checkFileForLoad(view,vfs,path))
  191. {
  192. setLoading(false);
  193. return false;
  194. }
  195. // have to check again since above might set
  196. // NEW_FILE flag
  197. if(reload || !getFlag(NEW_FILE))
  198. {
  199. if(!vfs.load(view,this,path))
  200. {
  201. setLoading(false);
  202. return false;
  203. }
  204. }
  205. }
  206. }
  207. else
  208. loadAutosave = false;
  209. //{{{ Do some stuff once loading is finished
  210. Runnable runnable = new Runnable()
  211. {
  212. public void run()
  213. {
  214. String newPath = getStringProperty(
  215. BufferIORequest.NEW_PATH);
  216. Segment seg = (Segment)getProperty(
  217. BufferIORequest.LOAD_DATA);
  218. IntegerArray endOffsets = (IntegerArray)
  219. getProperty(BufferIORequest.END_OFFSETS);
  220. loadText(seg,endOffsets);
  221. unsetProperty(BufferIORequest.LOAD_DATA);
  222. unsetProperty(BufferIORequest.END_OFFSETS);
  223. unsetProperty(BufferIORequest.NEW_PATH);
  224. undoMgr.clear();
  225. undoMgr.setLimit(jEdit.getIntegerProperty(
  226. "buffer.undoCount",100));
  227. // If the buffer is temporary, we don't need to
  228. // call finishLoading() because it sets the FoldHandler
  229. // and reload markers. But we always need to set the edit
  230. // mode that is necessary for HyperSearch on directories
  231. if (getFlag(TEMPORARY))
  232. setMode();
  233. else
  234. finishLoading();
  235. setLoading(false);
  236. // if reloading a file, clear dirty flag
  237. if(reload)
  238. setDirty(false);
  239. if(!loadAutosave && newPath != null)
  240. setPath(newPath);
  241. // if loadAutosave is false, we loaded an
  242. // autosave file, so we set 'dirty' to true
  243. // note that we don't use setDirty(),
  244. // because a) that would send an unnecessary
  245. // message, b) it would also set the
  246. // AUTOSAVE_DIRTY flag, which will make
  247. // the autosave thread write out a
  248. // redundant autosave file
  249. if(loadAutosave)
  250. Buffer.super.setDirty(true);
  251. // send some EditBus messages
  252. if(!getFlag(TEMPORARY))
  253. {
  254. fireBufferLoaded();
  255. EditBus.send(new BufferUpdate(Buffer.this,
  256. view,BufferUpdate.LOADED));
  257. //EditBus.send(new BufferUpdate(Buffer.this,
  258. // view,BufferUpdate.MARKERS_CHANGED));
  259. }
  260. }
  261. }; //}}}
  262. if(getFlag(TEMPORARY))
  263. runnable.run();
  264. else
  265. VFSManager.runInAWTThread(runnable);
  266. return true;
  267. } //}}}
  268. //{{{ insertFile() method
  269. /**
  270. * Loads a file from disk, and inserts it into this buffer.
  271. * @param view The view
  272. * @param path the path of the file to insert
  273. *
  274. * @since 4.0pre1
  275. */
  276. public boolean insertFile(View view, String path)
  277. {
  278. if(isPerformingIO())
  279. {
  280. GUIUtilities.error(view,"buffer-multiple-io",null);
  281. return false;
  282. }
  283. setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false);
  284. path = MiscUtilities.constructPath(this.path,path);
  285. Buffer buffer = jEdit.getBuffer(path);
  286. if(buffer != null)
  287. {
  288. view.getTextArea().setSelectedText(
  289. buffer.getText(0,buffer.getLength()));
  290. return true;
  291. }
  292. VFS vfs = VFSManager.getVFSForPath(path);
  293. // this returns false if initial sanity
  294. // checks (if the file is a directory, etc)
  295. // fail
  296. return vfs.insert(view,this,path);
  297. } //}}}
  298. //{{{ autosave() method
  299. /**
  300. * Autosaves this buffer.
  301. */
  302. public void autosave()
  303. {
  304. if(autosaveFile == null || !getFlag(AUTOSAVE_DIRTY)
  305. || !isDirty() || isPerformingIO() ||
  306. !autosaveFile.getParentFile().exists())
  307. return;
  308. setFlag(AUTOSAVE_DIRTY,false);
  309. VFSManager.runInWorkThread(new BufferAutosaveRequest(
  310. null,this,null,VFSManager.getFileVFS(),
  311. autosaveFile.getPath()));
  312. } //}}}
  313. //{{{ saveAs() method
  314. /**
  315. * Prompts the user for a file to save this buffer to.
  316. * @param view The view
  317. * @param rename True if the buffer's path should be changed, false
  318. * if only a copy should be saved to the specified filename
  319. * @return true if the buffer was successfully saved
  320. * @since jEdit 2.6pre5
  321. */
  322. public boolean saveAs(View view, boolean rename)
  323. {
  324. String[] files = GUIUtilities.showVFSFileDialog(view,path,
  325. VFSBrowser.SAVE_DIALOG,false);
  326. // files[] should have length 1, since the dialog type is
  327. // SAVE_DIALOG
  328. if(files == null)
  329. return false;
  330. boolean saved = save(view, files[0], rename);
  331. if (saved)
  332. setReadOnly(false);
  333. return saved;
  334. } //}}}
  335. //{{{ save() method
  336. /**
  337. * Saves this buffer to the specified path name, or the current path
  338. * name if it's null.
  339. * @param view The view
  340. * @param path The path name to save the buffer to, or null to use
  341. * @return true if the buffer was successfully saved
  342. * the existing path
  343. */
  344. public boolean save(View view, String path)
  345. {
  346. return save(view,path,true,false);
  347. } //}}}
  348. //{{{ save() method
  349. /**
  350. * Saves this buffer to the specified path name, or the current path
  351. * name if it's null.
  352. * @param view The view
  353. * @param path The path name to save the buffer to, or null to use
  354. * the existing path
  355. * @param rename True if the buffer's path should be changed, false
  356. * if only a copy should be saved to the specified filename
  357. * @return true if the buffer was successfully saved
  358. * @since jEdit 2.6pre5
  359. */
  360. public boolean save(View view, String path, boolean rename)
  361. {
  362. return save(view,path,rename,false);
  363. } //}}}
  364. //{{{ save() method
  365. /**
  366. * Saves this buffer to the specified path name, or the current path
  367. * name if it's null.
  368. * @param view The view
  369. * @param path The path name to save the buffer to, or null to use
  370. * the existing path
  371. * @param rename True if the buffer's path should be changed, false
  372. * if only a copy should be saved to the specified filename
  373. * @param disableFileStatusCheck Disables file status checking
  374. * regardless of the state of the checkFileStatus property
  375. * @return true if the buffer was successfully saved
  376. */
  377. public boolean save(final View view, String path, final boolean rename, boolean disableFileStatusCheck)
  378. {
  379. if(isPerformingIO())
  380. {
  381. GUIUtilities.error(view,"buffer-multiple-io",null);
  382. return false;
  383. }
  384. setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false);
  385. if(path == null && getFlag(NEW_FILE))
  386. return saveAs(view,rename);
  387. if(path == null && file != null)
  388. {
  389. long newModTime = file.lastModified();
  390. if(newModTime != modTime
  391. && jEdit.getBooleanProperty("view.checkModStatus"))
  392. {
  393. Object[] args = { this.path };
  394. int result = GUIUtilities.confirm(view,
  395. "filechanged-save",args,
  396. JOptionPane.YES_NO_OPTION,
  397. JOptionPane.WARNING_MESSAGE);
  398. if(result != JOptionPane.YES_OPTION)
  399. return false;
  400. }
  401. }
  402. EditBus.send(new BufferUpdate(this,view,BufferUpdate.SAVING));
  403. setPerformingIO(true);
  404. final String oldPath = this.path;
  405. final String oldSymlinkPath = symlinkPath;
  406. final String newPath = path == null ? this.path : path;
  407. VFS vfs = VFSManager.getVFSForPath(newPath);
  408. if(!checkFileForSave(view,vfs,newPath))
  409. {
  410. setPerformingIO(false);
  411. return false;
  412. }
  413. Object session = vfs.createVFSSession(newPath,view);
  414. if (session == null)
  415. {
  416. setPerformingIO(false);
  417. return false;
  418. }
  419. unsetProperty("overwriteReadonly");
  420. unsetProperty("forbidTwoStageSave");
  421. try
  422. {
  423. VFSFile file = vfs._getFile(session,newPath,view);
  424. if (file != null)
  425. {
  426. boolean vfsRenameCap = (vfs.getCapabilities() & VFS.RENAME_CAP) != 0;
  427. if (!file.isWriteable())
  428. {
  429. Log.log(Log.WARNING, this, "Buffer saving : File " + file + " is readOnly");
  430. if (vfsRenameCap)
  431. {
  432. Log.log(Log.DEBUG, this, "Buffer saving : VFS can rename files");
  433. String savePath = vfs._canonPath(session,newPath,view);
  434. if(!MiscUtilities.isURL(savePath))
  435. savePath = MiscUtilities.resolveSymlinks(savePath);
  436. savePath = vfs.getTwoStageSaveName(savePath);
  437. if (savePath == null)
  438. {
  439. Log.log(Log.DEBUG, this, "Buffer saving : two stage save impossible because path is null");
  440. VFSManager.error(view,
  441. newPath,
  442. "ioerror.save-readonly-twostagefail",
  443. null);
  444. setPerformingIO(false);
  445. return false;
  446. }
  447. else
  448. {
  449. int result = GUIUtilities.confirm(
  450. view, "vfs.overwrite-readonly",
  451. new Object[]{newPath},
  452. JOptionPane.YES_NO_OPTION,
  453. JOptionPane.WARNING_MESSAGE);
  454. if (result == JOptionPane.YES_OPTION)
  455. {
  456. Log.log(Log.WARNING, this, "Buffer saving : two stage save will be used to save buffer");
  457. setBooleanProperty("overwriteReadonly",true);
  458. }
  459. else
  460. {
  461. Log.log(Log.DEBUG,this, "Buffer not saved");
  462. setPerformingIO(false);
  463. return false;
  464. }
  465. }
  466. }
  467. else
  468. {
  469. Log.log(Log.WARNING, this, "Buffer saving : file is readonly and vfs cannot do two stage save");
  470. VFSManager.error(view,
  471. newPath,
  472. "ioerror.write-error-readonly",
  473. null);
  474. setPerformingIO(false);
  475. return false;
  476. }
  477. }
  478. else
  479. {
  480. String savePath = vfs._canonPath(session,newPath,view);
  481. if(!MiscUtilities.isURL(savePath))
  482. savePath = MiscUtilities.resolveSymlinks(savePath);
  483. savePath = vfs.getTwoStageSaveName(savePath);
  484. if (jEdit.getBooleanProperty("twoStageSave") && (!vfsRenameCap || savePath == null))
  485. {
  486. // the file is writeable but the vfs cannot do two stage. We must overwrite
  487. // readonly flag
  488. int result = GUIUtilities.confirm(
  489. view, "vfs.twostageimpossible",
  490. new Object[]{newPath},
  491. JOptionPane.YES_NO_OPTION,
  492. JOptionPane.WARNING_MESSAGE);
  493. if (result == JOptionPane.YES_OPTION)
  494. {
  495. Log.log(Log.WARNING, this, "Buffer saving : two stage save cannot be used");
  496. setBooleanProperty("forbidTwoStageSave",true);
  497. }
  498. else
  499. {
  500. Log.log(Log.DEBUG,this, "Buffer not saved");
  501. setPerformingIO(false);
  502. return false;
  503. }
  504. }
  505. }
  506. }
  507. }
  508. catch(IOException io)
  509. {
  510. VFSManager.error(view,newPath,"ioerror",
  511. new String[] { io.toString() });
  512. setPerformingIO(false);
  513. return false;
  514. }
  515. finally
  516. {
  517. try
  518. {
  519. vfs._endVFSSession(session,view);
  520. }
  521. catch(IOException io)
  522. {
  523. VFSManager.error(view,newPath,"ioerror",
  524. new String[] { io.toString() });
  525. setPerformingIO(false);
  526. return false;
  527. }
  528. }
  529. if(!vfs.save(view,this,newPath))
  530. {
  531. setPerformingIO(false);
  532. return false;
  533. }
  534. // Once save is complete, do a few other things
  535. VFSManager.runInAWTThread(new Runnable()
  536. {
  537. public void run()
  538. {
  539. setPerformingIO(false);
  540. setProperty("overwriteReadonly",null);
  541. finishSaving(view,oldPath,oldSymlinkPath,
  542. newPath,rename,getBooleanProperty(
  543. BufferIORequest.ERROR_OCCURRED));
  544. updateMarkersFile(view);
  545. }
  546. });
  547. int check = jEdit.getIntegerProperty("checkFileStatus");
  548. if(!disableFileStatusCheck && (check == GeneralOptionPane.checkFileStatus_all ||
  549. check == GeneralOptionPane.checkFileStatus_operations))
  550. jEdit.checkBufferStatus(view,false);
  551. return true;
  552. } //}}}
  553. //{{{ checkFileStatus() method
  554. public static final int FILE_NOT_CHANGED = 0;
  555. public static final int FILE_CHANGED = 1;
  556. public static final int FILE_DELETED = 2;
  557. /**
  558. * Check if the buffer has changed on disk.
  559. * @return One of <code>NOT_CHANGED</code>, <code>CHANGED</code>, or
  560. * <code>DELETED</code>.
  561. *
  562. * @since jEdit 4.2pre1
  563. */
  564. public int checkFileStatus(View view)
  565. {
  566. // - don't do these checks while a save is in progress,
  567. // because for a moment newModTime will be greater than
  568. // oldModTime, due to the multithreading
  569. // - only supported on local file system
  570. if(!isPerformingIO() && file != null && !getFlag(NEW_FILE))
  571. {
  572. boolean newReadOnly = file.exists() && !file.canWrite();
  573. if(newReadOnly != isFileReadOnly())
  574. {
  575. setFileReadOnly(newReadOnly);
  576. EditBus.send(new BufferUpdate(this,null,
  577. BufferUpdate.DIRTY_CHANGED));
  578. }
  579. long oldModTime = modTime;
  580. long newModTime = file.lastModified();
  581. if(newModTime != oldModTime)
  582. {
  583. modTime = newModTime;
  584. if(!file.exists())
  585. {
  586. setFlag(NEW_FILE,true);
  587. setDirty(true);
  588. return FILE_DELETED;
  589. }
  590. else
  591. {
  592. return FILE_CHANGED;
  593. }
  594. }
  595. }
  596. return FILE_NOT_CHANGED;
  597. } //}}}
  598. //}}}
  599. //{{{ Getters/setter methods for various buffer meta-data
  600. //{{{ getLastModified() method
  601. /**
  602. * Returns the last time jEdit modified the file on disk.
  603. * This method is thread-safe.
  604. */
  605. public long getLastModified()
  606. {
  607. return modTime;
  608. } //}}}
  609. //{{{ setLastModified() method
  610. /**
  611. * Sets the last time jEdit modified the file on disk.
  612. * @param modTime The new modification time
  613. */
  614. public void setLastModified(long modTime)
  615. {
  616. this.modTime = modTime;
  617. } //}}}
  618. //{{{ getAutoReload() method
  619. /**
  620. * Returns the status of the AUTORELOAD flag
  621. * If true, reload changed files automatically
  622. */
  623. public boolean getAutoReload()
  624. {
  625. return getFlag(AUTORELOAD);
  626. } //}}}
  627. //{{{ setAutoReload() method
  628. /**
  629. * Sets the status of the AUTORELOAD flag
  630. * @param value # If true, reload changed files automatically
  631. */
  632. public void setAutoReload(boolean value)
  633. {
  634. setFlag(AUTORELOAD, value);
  635. autoreloadOverridden = isAutoreloadPropertyOverriden();
  636. } //}}}
  637. //{{{ getAutoReloadDialog() method
  638. /**
  639. * Returns the status of the AUTORELOAD_DIALOG flag
  640. * If true, prompt for reloading or notify user
  641. * when the file has changed on disk
  642. */
  643. public boolean getAutoReloadDialog()
  644. {
  645. return getFlag(AUTORELOAD_DIALOG);
  646. } //}}}
  647. //{{{ setAutoReloadDialog() method
  648. /**
  649. * Sets the status of the AUTORELOAD_DIALOG flag
  650. * @param value # If true, prompt for reloading or notify user
  651. * when the file has changed on disk
  652. */
  653. public void setAutoReloadDialog(boolean value)
  654. {
  655. setFlag(AUTORELOAD_DIALOG, value);
  656. autoreloadOverridden = isAutoreloadPropertyOverriden();
  657. } //}}}
  658. //{{{ getVFS() method
  659. /**
  660. * Returns the virtual filesystem responsible for loading and
  661. * saving this buffer. This method is thread-safe.
  662. */
  663. public VFS getVFS()
  664. {
  665. return VFSManager.getVFSForPath(path);
  666. } //}}}
  667. //{{{ getAutosaveFile() method
  668. /**
  669. * Returns the autosave file for this buffer. This may be null if
  670. * the file is non-local.
  671. */
  672. public File getAutosaveFile()
  673. {
  674. return autosaveFile;
  675. } //}}}
  676. //{{{ removeAutosaveFile() method
  677. /**
  678. * Remove the autosave file.
  679. * @since jEdit 4.3pre12
  680. */
  681. public void removeAutosaveFile()
  682. {
  683. if (autosaveFile != null)
  684. {
  685. autosaveFile.delete();
  686. setFlag(AUTOSAVE_DIRTY,true);
  687. }
  688. } //}}}
  689. //{{{ getName() method
  690. /**
  691. * Returns the name of this buffer. This method is thread-safe.
  692. */
  693. public String getName()
  694. {
  695. return name;
  696. } //}}}
  697. //{{{ getPath() method
  698. /**
  699. * Returns the path name of this buffer. This method is thread-safe.
  700. */
  701. public String getPath()
  702. {
  703. return path;
  704. } //}}}
  705. //{{{ getPath() method
  706. /**
  707. * @param shortVersion if true, replaces home path with ~/ on unix
  708. */
  709. public String getPath(Boolean shortVersion)
  710. {
  711. return shortVersion ? MiscUtilities.abbreviate(path) : getPath();
  712. } //}}}
  713. //{{{ getSymlinkPath() method
  714. /**
  715. * If this file is a symbolic link, returns the link destination.
  716. * Otherwise returns the file's path. This method is thread-safe.
  717. * @since jEdit 4.2pre1
  718. */
  719. public String getSymlinkPath()
  720. {
  721. return symlinkPath;
  722. } //}}}
  723. //{{{ getDirectory() method
  724. /**
  725. * Returns the directory containing this buffer.
  726. * @since jEdit 4.1pre11
  727. */
  728. public String getDirectory()
  729. {
  730. return directory;
  731. } //}}}
  732. //{{{ isClosed() method
  733. /**
  734. * Returns true if this buffer has been closed with
  735. * {@link org.gjt.sp.jedit.jEdit#closeBuffer(View,Buffer)}.
  736. * This method is thread-safe.
  737. */
  738. public boolean isClosed()
  739. {
  740. return getFlag(CLOSED);
  741. } //}}}
  742. //{{{ isLoaded() method
  743. /**
  744. * Returns true if the buffer is loaded. This method is thread-safe.
  745. */
  746. public boolean isLoaded()
  747. {
  748. return !isLoading();
  749. } //}}}
  750. //{{{ isNewFile() method
  751. /**
  752. * Returns whether this buffer lacks a corresponding version on disk.
  753. * This method is thread-safe.
  754. */
  755. public boolean isNewFile()
  756. {
  757. return getFlag(NEW_FILE);
  758. } //}}}
  759. //{{{ setNewFile() method
  760. /**
  761. * Sets the new file flag.
  762. * @param newFile The new file flag
  763. */
  764. public void setNewFile(boolean newFile)
  765. {
  766. setFlag(NEW_FILE,newFile);
  767. if(!newFile)
  768. setFlag(UNTITLED,false);
  769. } //}}}
  770. //{{{ isUntitled() method
  771. /**
  772. * Returns true if this file is 'untitled'. This method is thread-safe.
  773. */
  774. public boolean isUntitled()
  775. {
  776. return getFlag(UNTITLED);
  777. } //}}}
  778. //{{{ setDirty() method
  779. /**
  780. * Sets the 'dirty' (changed since last save) flag of this buffer.
  781. */
  782. @Override
  783. public void setDirty(boolean d)
  784. {
  785. boolean old_d = isDirty();
  786. if (isUntitled() && jEdit.getBooleanProperty("suppressNotSavedConfirmUntitled"))
  787. d = false;
  788. if (d && getLength() == initialLength)
  789. {
  790. if (jEdit.getBooleanProperty("useMD5forDirtyCalculation"))
  791. d = !Arrays.equals(calculateHash(), md5hash);
  792. }
  793. super.setDirty(d);
  794. boolean editable = isEditable();
  795. if(d)
  796. {
  797. if(editable)
  798. setFlag(AUTOSAVE_DIRTY,true);
  799. }
  800. else
  801. {
  802. setFlag(AUTOSAVE_DIRTY,false);
  803. if(autosaveFile != null)
  804. autosaveFile.delete();
  805. }
  806. if(d != old_d && editable)
  807. {
  808. EditBus.send(new BufferUpdate(this,null,
  809. BufferUpdate.DIRTY_CHANGED));
  810. }
  811. } //}}}
  812. //{{{ isTemporary() method
  813. /**
  814. * Returns if this is a temporary buffer. This method is thread-safe.
  815. * @see jEdit#openTemporary(View,String,String,boolean)
  816. * @see jEdit#commitTemporary(Buffer)
  817. * @since jEdit 2.2pre7
  818. */
  819. public boolean isTemporary()
  820. {
  821. return getFlag(TEMPORARY);
  822. } //}}}
  823. //{{{ getIcon() method
  824. /**
  825. * Returns this buffer's icon.
  826. * @since jEdit 2.6pre6
  827. */
  828. public Icon getIcon()
  829. {
  830. if(isDirty())
  831. return GUIUtilities.loadIcon("dirty.gif");
  832. else if(isReadOnly())
  833. return GUIUtilities.loadIcon("readonly.gif");
  834. else if(getFlag(NEW_FILE))
  835. return GUIUtilities.loadIcon("new.gif");
  836. else
  837. return GUIUtilities.loadIcon("normal.gif");
  838. } //}}}
  839. //}}}
  840. //{{{ Property methods
  841. //{{{ propertiesChanged() method
  842. /**
  843. * Reloads settings from the properties. This should be called
  844. * after the <code>syntax</code> or <code>folding</code>
  845. * buffer-local properties are changed.
  846. */
  847. @Override
  848. public void propertiesChanged()
  849. {
  850. super.propertiesChanged();
  851. if (!autoreloadOverridden)
  852. {
  853. setAutoReloadDialog(jEdit.getBooleanProperty("autoReloadDialog"));
  854. setAutoReload(jEdit.getBooleanProperty("autoReload"));
  855. }
  856. if (!isTemporary())
  857. EditBus.send(new BufferUpdate(this,null,BufferUpdate.PROPERTIES_CHANGED));
  858. } //}}}
  859. //{{{ getDefaultProperty() method
  860. @Override
  861. public Object getDefaultProperty(String name)
  862. {
  863. Object retVal;
  864. if(mode != null)
  865. {
  866. retVal = mode.getProperty(name);
  867. if(retVal == null)
  868. return null;
  869. setDefaultProperty(name,retVal);
  870. return retVal;
  871. }
  872. // Now try buffer.<property>
  873. String value = jEdit.getProperty("buffer." + name);
  874. if(value == null)
  875. return null;
  876. // Try returning it as an integer first
  877. try
  878. {
  879. retVal = new Integer(value);
  880. }
  881. catch(NumberFormatException nf)
  882. {
  883. retVal = value;
  884. }
  885. return retVal;
  886. } //}}}
  887. //{{{ toggleWordWrap() method
  888. /**
  889. * Toggles word wrap between the three available modes. This is used
  890. * by the status bar.
  891. * @param view We show a message in the view's status bar
  892. * @since jEdit 4.1pre3
  893. */
  894. public void toggleWordWrap(View view)
  895. {
  896. String wrap = getStringProperty("wrap");
  897. if(wrap.equals("none"))
  898. wrap = "soft";
  899. else if(wrap.equals("soft"))
  900. wrap = "hard";
  901. else if(wrap.equals("hard"))
  902. wrap = "none";
  903. view.getStatus().setMessageAndClear(jEdit.getProperty(
  904. "view.status.wrap-changed",new String[] {
  905. wrap }));
  906. setProperty("wrap",wrap);
  907. propertiesChanged();
  908. } //}}}
  909. //{{{ toggleLineSeparator() method
  910. /**
  911. * Toggles the line separator between the three available settings.
  912. * This is used by the status bar.
  913. * @param view We show a message in the view's status bar
  914. * @since jEdit 4.1pre3
  915. */
  916. public void toggleLineSeparator(View view)
  917. {
  918. String status = null;
  919. String lineSep = getStringProperty(LINESEP);
  920. if("\n".equals(lineSep))
  921. {
  922. status = "windows";
  923. lineSep = "\r\n";
  924. }
  925. else if("\r\n".equals(lineSep))
  926. {
  927. status = "mac";
  928. lineSep = "\r";
  929. }
  930. else if("\r".equals(lineSep))
  931. {
  932. status = "unix";
  933. lineSep = "\n";
  934. }
  935. view.getStatus().setMessageAndClear(jEdit.getProperty(
  936. "view.status.linesep-changed",new String[] {
  937. jEdit.getProperty("lineSep." + status) }));
  938. setProperty(LINESEP, lineSep);
  939. setDirty(true);
  940. propertiesChanged();
  941. } //}}}
  942. //{{{ getContextSensitiveProperty() method
  943. /**
  944. * Some settings, like comment start and end strings, can
  945. * vary between different parts of a buffer (HTML text and inline
  946. * JavaScript, for example).
  947. * @param offset The offset
  948. * @param name The property name
  949. * @since jEdit 4.0pre3
  950. */
  951. @Override
  952. public String getContextSensitiveProperty(int offset, String name)
  953. {
  954. Object value = super.getContextSensitiveProperty(offset,name);
  955. if(value == null)
  956. {
  957. ParserRuleSet rules = getRuleSetAtOffset(offset);
  958. value = jEdit.getMode(rules.getModeName())
  959. .getProperty(name);
  960. if(value == null)
  961. value = mode.getProperty(name);
  962. }
  963. if(value == null)
  964. return null;
  965. else
  966. return String.valueOf(value);
  967. } //}}}
  968. //}}}
  969. //{{{ Edit modes, syntax highlighting
  970. //{{{ setMode() method
  971. /**
  972. * Sets this buffer's edit mode by calling the accept() method
  973. * of each registered edit mode.
  974. */
  975. public void setMode()
  976. {
  977. Mode mode = null;
  978. String userMode = getStringProperty("mode");
  979. if(userMode != null)
  980. {
  981. unsetProperty("mode");
  982. mode = ModeProvider.instance.getMode(userMode);
  983. }
  984. if (mode == null)
  985. {
  986. String firstLine = getLineText(0);
  987. mode = ModeProvider.instance.getModeForFile(getVFS().getFilePath(path), null, firstLine);
  988. }
  989. if (mode != null)
  990. {
  991. boolean forceInsensitive = false;
  992. if (!getFlag(TEMPORARY) && getLength() > jEdit.getIntegerProperty("largeBufferSize", 4000000))
  993. {
  994. boolean contextInsensitive = mode.getBooleanProperty("contextInsensitive");
  995. if (!contextInsensitive)
  996. {
  997. JTextPane tp = new JTextPane();
  998. tp.setEditable(false);
  999. tp.setText(jEdit.getProperty("largeBufferDialog.message"));
  1000. int i = JOptionPane.showOptionDialog(jEdit.getActiveView(),
  1001. tp,
  1002. jEdit.getProperty("largeBufferDialog.title", new String[]{name}),
  1003. JOptionPane.DEFAULT_OPTION,
  1004. JOptionPane.WARNING_MESSAGE,
  1005. null,
  1006. new String[]{
  1007. jEdit.getProperty("largeBufferDialog.fullSyntax"),
  1008. jEdit.getProperty("largeBufferDialog.contextInsensitive"),
  1009. jEdit.getProperty("largeBufferDialog.defaultMode")},
  1010. jEdit.getProperty("largeBufferDialog.fullSyntax"));
  1011. switch (i)
  1012. {
  1013. case 0:
  1014. // do nothing
  1015. break;
  1016. case 1:
  1017. forceInsensitive = true;
  1018. break;
  1019. case 2:
  1020. mode = getDefaultMode();
  1021. break;
  1022. }
  1023. }
  1024. }
  1025. setMode(mode, forceInsensitive);
  1026. return;
  1027. }
  1028. Mode defaultMode = getDefaultMode();
  1029. if (defaultMode != null)
  1030. setMode(defaultMode);
  1031. } //}}}
  1032. private static Mode getDefaultMode()
  1033. {
  1034. Mode defaultMode = jEdit.getMode(jEdit.getProperty("buffer.defaultMode"));
  1035. if(defaultMode == null)
  1036. defaultMode = jEdit.getMode("text");
  1037. return defaultMode;
  1038. }
  1039. //}}}
  1040. //{{{ Deprecated methods
  1041. //{{{ getFile() method
  1042. /**
  1043. * @deprecated Do not call this method, use {@link #getPath()}
  1044. * instead.
  1045. */
  1046. @Deprecated
  1047. public File getFile()
  1048. {
  1049. return file;
  1050. } //}}}
  1051. //}}}
  1052. //{{{ Marker methods
  1053. //{{{ getMarkers() method
  1054. /**
  1055. * Returns a vector of markers.
  1056. * @since jEdit 3.2pre1
  1057. */
  1058. public Vector<Marker> getMarkers()
  1059. {
  1060. return markers;
  1061. } //}}}
  1062. //{{{ getMarkerStatusPrompt() method
  1063. /**
  1064. * Returns the status prompt for the given marker action. Only
  1065. * intended to be called from <code>actions.xml</code>.
  1066. * @since jEdit 4.2pre2
  1067. */
  1068. public String getMarkerStatusPrompt(String action)
  1069. {
  1070. return jEdit.getProperty("view.status." + action,
  1071. new String[] { getMarkerNameString() });
  1072. } //}}}
  1073. //{{{ getMarkerNameString() method
  1074. /**
  1075. * Returns a string of all set markers, used by the status bar
  1076. * (eg, "a b $ % ^").
  1077. * @since jEdit 4.2pre2
  1078. */
  1079. public String getMarkerNameString()
  1080. {
  1081. StringBuilder buf = new StringBuilder();
  1082. for(int i = 0; i < markers.size(); i++)
  1083. {
  1084. Marker marker = markers.get(i);
  1085. if(marker.getShortcut() != '\0')
  1086. {
  1087. if(buf.length() != 0)
  1088. buf.append(' ');
  1089. buf.append(marker.getShortcut());
  1090. }
  1091. }
  1092. if(buf.length() == 0)
  1093. return jEdit.getProperty("view.status.no-markers");
  1094. else
  1095. return buf.toString();
  1096. } //}}}
  1097. //{{{ addOrRemoveMarker() method
  1098. /**
  1099. * If a marker is set on the line of the position, it is removed. Otherwise
  1100. * a new marker with the specified shortcut is added.
  1101. * @param pos The position of the marker
  1102. * @param shortcut The shortcut ('\0' if none)
  1103. * @since jEdit 3.2pre5
  1104. */
  1105. public void addOrRemoveMarker(char shortcut, int pos)
  1106. {
  1107. int line = getLineOfOffset(pos);
  1108. if(getMarkerAtLine(line) != null)
  1109. removeMarker(line);
  1110. else
  1111. addMarker(shortcut,pos);
  1112. } //}}}
  1113. //{{{ addMarker() method
  1114. /**
  1115. * Adds a marker to this buffer.
  1116. * @param pos The position of the marker
  1117. * @param shortcut The shortcut ('\0' if none)
  1118. * @since jEdit 3.2pre1
  1119. */
  1120. public void addMarker(char shortcut, int pos)
  1121. {
  1122. Marker markerN = new Marker(this,shortcut,pos);
  1123. boolean added = false;
  1124. // don't sort markers while buffer is being loaded
  1125. if(isLoaded())
  1126. {
  1127. setFlag(MARKERS_CHANGED,true);
  1128. markerN.createPosition();
  1129. for(int i = 0; i < markers.size(); i++)
  1130. {
  1131. Marker marker = markers.get(i);
  1132. if(shortcut != '\0' && marker.getShortcut() == shortcut)
  1133. marker.setShortcut('\0');
  1134. if(marker.getPosition() == pos)
  1135. {
  1136. markers.removeElementAt(i);
  1137. i--;
  1138. }
  1139. }
  1140. for(int i = 0; i < markers.size(); i++)
  1141. {
  1142. Marker marker = markers.get(i);
  1143. if(marker.getPosition() > pos)
  1144. {
  1145. markers.insertElementAt(markerN,i);
  1146. added = true;
  1147. break;
  1148. }
  1149. }
  1150. }
  1151. if(!added)
  1152. markers.addElement(markerN);
  1153. if(isLoaded() && !getFlag(TEMPORARY))
  1154. {
  1155. EditBus.send(new BufferUpdate(this,null,
  1156. BufferUpdate.MARKERS_CHANGED));
  1157. }
  1158. } //}}}
  1159. //{{{ getMarkerInRange() method
  1160. /**
  1161. * Returns the first marker within the specified range.
  1162. * @param start The start offset
  1163. * @param end The end offset
  1164. * @since jEdit 4.0pre4
  1165. */
  1166. public Marker getMarkerInRange(int start, int end)
  1167. {
  1168. for(int i = 0; i < markers.size(); i++)
  1169. {
  1170. Marker marker = markers.get(i);
  1171. int pos = marker.getPosition();
  1172. if(pos >= start && pos < end)
  1173. return marker;
  1174. }
  1175. return null;
  1176. } //}}}
  1177. //{{{ getMarkerAtLine() method
  1178. /**
  1179. * Returns the first marker at the specified line, or <code>null</code>
  1180. * if there is none.
  1181. * @param line The line number
  1182. * @since jEdit 3.2pre2
  1183. */
  1184. public Marker getMarkerAtLine(int line)
  1185. {
  1186. for(int i = 0; i < markers.size(); i++)
  1187. {
  1188. Marker marker = markers.get(i);
  1189. if(getLineOfOffset(marker.getPosition()) == line)
  1190. return marker;
  1191. }
  1192. return null;
  1193. } //}}}
  1194. //{{{ removeMarker() method
  1195. /**
  1196. * Removes all markers at the specified line.
  1197. * @param line The line number
  1198. * @since jEdit 3.2pre2
  1199. */
  1200. public void removeMarker(int line)
  1201. {
  1202. for(int i = 0; i < markers.size(); i++)
  1203. {
  1204. Marker marker = markers.get(i);
  1205. if(getLineOfOffset(marker.getPosition()) == line)
  1206. {
  1207. setFlag(MARKERS_CHANGED,true);
  1208. marker.removePosition();
  1209. markers.removeElementAt(i);
  1210. i--;
  1211. }
  1212. }
  1213. EditBus.send(new BufferUpdate(this,null,
  1214. BufferUpdate.MARKERS_CHANGED));
  1215. } //}}}
  1216. //{{{ removeAllMarkers() method
  1217. /**
  1218. * Removes all defined markers.
  1219. * @since jEdit 2.6pre1
  1220. */
  1221. public void removeAllMarkers()
  1222. {
  1223. setFlag(MARKERS_CHANGED,true);
  1224. for(int i = 0; i < markers.size(); i++)
  1225. markers.get(i).removePosition();
  1226. markers.removeAllElements();
  1227. if(isLoaded())
  1228. {
  1229. EditBus.send(new BufferUpdate(this,null,
  1230. BufferUpdate.MARKERS_CHANGED));
  1231. }
  1232. } //}}}
  1233. //{{{ getMarker() method
  1234. /**
  1235. * Returns the marker with the specified shortcut.
  1236. * @param shortcut The shortcut
  1237. * @since jEdit 3.2pre2
  1238. */
  1239. public Marker getMarker(char shortcut)
  1240. {
  1241. for (Marker marker : markers)
  1242. {
  1243. if(marker.getShortcut() == shortcut)
  1244. return marker;
  1245. }
  1246. return null;
  1247. } //}}}
  1248. //{{{ getMarkersPath() method
  1249. /**
  1250. * Returns the path for this buffer's markers file
  1251. * @param vfs The appropriate VFS
  1252. * @param path the path of the buffer, it can be different from the field
  1253. * when using save-as
  1254. * @since jEdit 4.3pre10
  1255. */
  1256. public static String getMarkersPath(VFS vfs, String path)
  1257. {
  1258. return vfs.getParentOfPath(path)
  1259. + '.' + vfs.getFileName(path)
  1260. + ".marks";
  1261. } //}}}
  1262. //{{{ updateMarkersFile() method
  1263. /**
  1264. * Save the markers file, or delete it when there are mo markers left
  1265. * Handling markers is now independent from saving the buffer.
  1266. * Changing markers will not set the buffer dirty any longer.
  1267. * @param view The current view
  1268. * @since jEdit 4.3pre7
  1269. */
  1270. public boolean updateMarkersFile(View view)
  1271. {
  1272. if(!markersChanged())
  1273. return true;
  1274. // adapted from VFS.save
  1275. VFS vfs = VFSManager.getVFSForPath(getPath());
  1276. if (((vfs.getCapabilities() & VFS.WRITE_CAP) == 0) ||
  1277. !vfs.isMarkersFileSupported())
  1278. {
  1279. VFSManager.error(view, path, "vfs.not-supported.save",
  1280. new String[] { "markers file" });
  1281. return false;
  1282. }
  1283. Object session = vfs.createVFSSession(path, view);
  1284. if(session == null)
  1285. return false;
  1286. ThreadUtilities.runInBackground(
  1287. new MarkersSaveRequest(
  1288. view, this, session, vfs, path));
  1289. return true;
  1290. } //}}}
  1291. //{{{ markersChanged() method
  1292. /**
  1293. * Return true when markers have changed and the markers file needs
  1294. * to be updated
  1295. * @since jEdit 4.3pre7
  1296. */
  1297. public boolean markersChanged()
  1298. {
  1299. return getFlag(MARKERS_CHANGED);
  1300. } //}}}
  1301. //{{{ setMarkersChanged() method
  1302. /**
  1303. * Sets/unsets the MARKERS_CHANGED flag
  1304. * @since jEdit 4.3pre7
  1305. */
  1306. public void setMarkersChanged(boolean changed)
  1307. {
  1308. setFlag(MARKERS_CHANGED, changed);
  1309. } //}}}
  1310. //}}}
  1311. //{{{ Miscellaneous methods
  1312. //{{{ setWaitSocket() method
  1313. /**
  1314. * This socket is closed when the buffer is closed.
  1315. */
  1316. public void setWaitSocket(Socket waitSocket)
  1317. {
  1318. this.waitSocket = waitSocket;
  1319. } //}}}
  1320. //{{{ getNext() method
  1321. /**
  1322. * Returns the next buffer in the list.
  1323. */
  1324. public Buffer getNext()
  1325. {
  1326. return next;
  1327. } //}}}
  1328. //{{{ getPrev() method
  1329. /**
  1330. * Returns the previous buffer in the list.
  1331. */
  1332. public Buffer getPrev()
  1333. {
  1334. return prev;
  1335. } //}}}
  1336. //{{{ getIndex() method
  1337. /**
  1338. * Returns the position of this buffer in the buffer list.
  1339. */
  1340. public int getIndex()
  1341. {
  1342. int count = 0;
  1343. Buffer buffer = prev;
  1344. while (true)
  1345. {
  1346. if(buffer == null)
  1347. break;
  1348. count++;
  1349. buffer = buffer.prev;
  1350. }
  1351. return count;
  1352. } //}}}
  1353. //{{{ toString() method
  1354. /**
  1355. * Returns a string representation of this buffer.
  1356. * This simply returns the path name.
  1357. */
  1358. @Override
  1359. public String toString()
  1360. {
  1361. return name + " (" + MiscUtilities.abbreviate(directory) + ')';
  1362. } //}}}
  1363. //{{{ addBufferUndoListener() method
  1364. /**
  1365. * Adds a buffer undo listener.
  1366. * @param listener The listener
  1367. * @since jEdit 4.3pre18
  1368. */
  1369. public void addBufferUndoListener(BufferUndoListener listener)
  1370. {
  1371. undoListeners.add(listener);
  1372. } //}}}
  1373. //{{{ removeBufferUndoListener() method
  1374. /**
  1375. * Removes a buffer undo listener.
  1376. * @param listener The listener
  1377. * @since jEdit 4.3pre18
  1378. */
  1379. public void removeBufferUndoListener(BufferUndoListener listener)
  1380. {
  1381. undoListeners.remove(listener);
  1382. } //}}}
  1383. //}}}
  1384. //{{{ Package-private members
  1385. /** The previous buffer in the list. */
  1386. Buffer prev;
  1387. /** The next buffer in the list. */
  1388. Buffer next;
  1389. //{{{ Buffer constructor
  1390. Buffer(String path, boolean newFile, boolean temp, Map props)
  1391. {
  1392. super(props);
  1393. markers = new Vector<Marker>();
  1394. setFlag(TEMPORARY,temp);
  1395. // this must be called before any EditBus messages are sent
  1396. setPath(path);
  1397. /* Magic: UNTITLED is only set if newFile param to
  1398. * constructor is set, NEW_FILE is also set if file
  1399. * doesn't exist on disk.
  1400. *
  1401. * This is so that we can tell apart files created
  1402. * with jEdit.newFile(), and those that just don't
  1403. * exist on disk.
  1404. *
  1405. * Why do we need to tell the difference between the
  1406. * two? jEdit.addBufferToList() checks if the only
  1407. * opened buffer is an untitled buffer, and if so,
  1408. * replaces it with the buffer to add. We don't want
  1409. * this behavior to occur with files that don't
  1410. * exist on disk; only untitled ones.
  1411. */
  1412. setFlag(UNTITLED,newFile);
  1413. setFlag(NEW_FILE,newFile);
  1414. setFlag(AUTORELOAD,jEdit.getBooleanProperty("autoReload"));
  1415. setFlag(AUTORELOAD_DIALOG,jEdit.getBooleanProperty("autoReloadDialog"));
  1416. undoListeners = new Vector<BufferUndoListener>();
  1417. } //}}}
  1418. //{{{ commitTemporary() method
  1419. void commitTemporary()
  1420. {
  1421. setFlag(TEMPORARY,false);
  1422. finishLoading();
  1423. } //}}}
  1424. //{{{ close() method
  1425. void close()
  1426. {
  1427. setFlag(CLOSED,true);
  1428. if(autosaveFile != null)
  1429. autosaveFile.delete();
  1430. // notify clients with -wait
  1431. if(waitSocket != null)
  1432. {
  1433. try
  1434. {
  1435. waitSocket.getOutputStream().write('\0');
  1436. waitSocket.getOutputStream().flush();
  1437. waitSocket.getInputStream().close();
  1438. waitSocket.getOutputStream().close();
  1439. waitSocket.close();
  1440. }
  1441. catch(IOException io)
  1442. {
  1443. //Log.log(Log.ERROR,this,io);
  1444. }
  1445. }
  1446. } //}}}
  1447. //}}}
  1448. //{{{ Protected members
  1449. //{{{ fireBeginUndo() method
  1450. protected void fireBeginUndo()
  1451. {
  1452. for (BufferUndoListener listener: undoListeners)
  1453. {
  1454. try
  1455. {
  1456. listener.beginUndo(this);
  1457. }
  1458. catch(Throwable t)
  1459. {
  1460. Log.log(Log.ERROR,this,"Exception while sending buffer undo event to "+ listener +" :");
  1461. Log.log(Log.ERROR,this,t);
  1462. }
  1463. }
  1464. } //}}}
  1465. //{{{ fireEndUndo() method
  1466. protected void fireEndUndo()
  1467. {
  1468. for (BufferUndoListener listener: undoListeners)
  1469. {
  1470. try
  1471. {
  1472. listener.endUndo(this);
  1473. }
  1474. catch(Throwable t)
  1475. {
  1476. Log.log(Log.ERROR,this,"Exception while sending buffer undo event to "+ listener +" :");
  1477. Log.log(Log.ERROR,this,t);
  1478. }
  1479. }
  1480. } //}}}
  1481. //{{{ fireBeginRedo() method
  1482. protected void fireBeginRedo()
  1483. {
  1484. for (BufferUndoListener listener: undoListeners)
  1485. {
  1486. try
  1487. {
  1488. listener.beginRedo(this);
  1489. }
  1490. catch(Throwable t)
  1491. {
  1492. Log.log(Log.ERROR,this,"Exception while sending buffer begin redo event to "+ listener +" :");
  1493. Log.log(Log.ERROR,this,t);
  1494. }
  1495. }
  1496. } //}}}
  1497. //{{{ fireEndRedo() method
  1498. protected void fireEndRedo()
  1499. {
  1500. for (BufferUndoListener listener: undoListeners)
  1501. {
  1502. try
  1503. {
  1504. listener.endRedo(this);
  1505. }
  1506. catch(Throwable t)
  1507. {
  1508. Log.log(Log.ERROR,this,"Exception while sending buffer end redo event to "+ listener +" :");
  1509. Log.log(Log.ERROR,this,t);
  1510. }
  1511. }
  1512. } //}}}
  1513. //}}}
  1514. //{{{ Private members
  1515. //{{{ Flags
  1516. //{{{ setFlag() method
  1517. private void setFlag(int flag, boolean value)
  1518. {
  1519. if(value)
  1520. flags |= 1 << flag;
  1521. else
  1522. flags &= ~(1 << flag);
  1523. } //}}}
  1524. //{{{ getFlag() method
  1525. private boolean getFlag(int flag)
  1526. {
  1527. int mask = 1 << flag;
  1528. return (flags & mask) == mask;
  1529. } //}}}
  1530. //{{{ getFlag() method
  1531. private boolean isAutoreloadPropertyOverriden()
  1532. {
  1533. return getFlag(AUTORELOAD) != jEdit.getBooleanProperty("autoReload") ||
  1534. getFlag(AUTORELOAD_DIALOG) != jEdit.getBooleanProperty("autoReloadDialog");
  1535. } //}}}
  1536. //{{{ Flag values
  1537. private static final int CLOSED = 0;
  1538. private static final int NEW_FILE = 3;
  1539. private static final int UNTITLED = 4;
  1540. private static final int AUTOSAVE_DIRTY = 5;
  1541. private static final int AUTORELOAD = 6;
  1542. private static final int AUTORELOAD_DIALOG = 7;
  1543. private static final int TEMPORARY = 10;
  1544. private static final int MARKERS_CHANGED = 12;
  1545. //}}}
  1546. private int flags;
  1547. //}}}
  1548. //{{{ Instance variables
  1549. /** Indicate if the autoreload property was overridden */
  1550. private boolean autoreloadOverridden;
  1551. private String path;
  1552. private String symlinkPath;
  1553. private String name;
  1554. private String directory;
  1555. private File file;
  1556. private File autosaveFile;
  1557. private long modTime;
  1558. private byte[] md5hash;
  1559. private int initialLength;
  1560. /**
  1561. * The longBufferMode is an option
  1562. */
  1563. private int longBufferMode;
  1564. private final Vector<Marker> markers;
  1565. private Socket waitSocket;
  1566. private List<BufferUndoListener> undoListeners;
  1567. //}}}
  1568. //{{{ setPath() method
  1569. private void setPath(final String path)
  1570. {
  1571. jEdit.visit(new JEditVisitorAdapter()
  1572. {
  1573. @Override
  1574. public void visit(EditPane editPane)
  1575. {
  1576. editPane.bufferRenamed(Buffer.this.path, path);
  1577. }
  1578. });
  1579. this.path = path;
  1580. VFS vfs = VFSManager.getVFSForPath(path);
  1581. if((vfs.getCapabilities() & VFS.WRITE_CAP) == 0)
  1582. setFileReadOnly(true);
  1583. name = vfs.getFileName(path);
  1584. directory = vfs.getParentOfPath(path);
  1585. if(vfs instanceof FileVFS)
  1586. {
  1587. file = new File(path);
  1588. symlinkPath = MiscUtilities.resolveSymlinks(path);
  1589. // if we don't do this, the autosave file won't be
  1590. // deleted after a save as
  1591. if(autosaveFile != null)
  1592. autosaveFile.delete();
  1593. autosaveFile = new File(file.getParent(),'#' + name + '#');
  1594. }
  1595. else
  1596. {
  1597. // I wonder if the lack of this broke anything in the
  1598. // past?
  1599. file = null;
  1600. autosaveFile = null;
  1601. symlinkPath = path;
  1602. }
  1603. } //}}}
  1604. //{{{ recoverAutosave() method
  1605. private boolean recoverAutosave(final View view)
  1606. {
  1607. if(!autosaveFile.canRead())
  1608. return false;
  1609. // this method might get called at startup
  1610. GUIUtilities.hideSplashScreen();
  1611. final Object[] args = { autosaveFile.getPath() };
  1612. int result = GUIUtilities.confirm(view,"autosave-found",args,
  1613. JOptionPane.YES_NO_OPTION,JOptionPane.WARNING_MESSAGE);
  1614. if(result == JOptionPane.YES_OPTION)
  1615. {
  1616. VFSManager.getFileVFS().load(view,this,autosaveFile.getPath());
  1617. // show this message when all I/O requests are
  1618. // complete
  1619. VFSManager.runInAWTThread(new Runnable()
  1620. {
  1621. public void run()
  1622. {
  1623. GUIUtilities.message(view,"autosave-loaded",args);
  1624. }
  1625. });
  1626. return true;
  1627. }
  1628. else
  1629. return false;
  1630. } //}}}
  1631. //{{{ checkFileForLoad() method
  1632. private boolean checkFileForLoad(View view, VFS vfs, String path)
  1633. {
  1634. if((vfs.getCapabilities() & VFS.LOW_LATENCY_CAP) != 0)
  1635. {
  1636. Object session = vfs.createVFSSession(path, view);
  1637. if(session == null)
  1638. return false;
  1639. try
  1640. {
  1641. VFSFile file = vfs._getFile(session,path,view);
  1642. if(file == null)
  1643. {
  1644. setNewFile(true);
  1645. return true;
  1646. }
  1647. if(!file.isReadable())
  1648. {
  1649. VFSManager.error(view,path,"ioerror.no-read",null);
  1650. setNewFile(false);
  1651. return false;
  1652. }
  1653. setFileReadOnly(!file.isWriteable());
  1654. if(file.getType() != VFSFile.FILE)
  1655. {
  1656. VFSManager.error(view,path,
  1657. "ioerror.open-directory",null);
  1658. setNewFile(false);
  1659. return false;
  1660. }
  1661. }
  1662. catch(IOException io)
  1663. {
  1664. VFSManager.error(view,path,"ioerror",
  1665. new String[] { io.toString() });
  1666. return false;
  1667. }
  1668. finally
  1669. {
  1670. try
  1671. {
  1672. vfs._endVFSSession(session,view);
  1673. }
  1674. catch(IOException io)
  1675. {
  1676. VFSManager.error(view,path,"ioerror",
  1677. new String[] { io.toString() });
  1678. return false;
  1679. }
  1680. }
  1681. }
  1682. return true;
  1683. } //}}}
  1684. //{{{ checkFileForSave() method
  1685. private static boolean checkFileForSave(View view, VFS vfs, String path)
  1686. {
  1687. if((vfs.getCapabilities() & VFS.LOW_LATENCY_CAP) != 0)
  1688. {
  1689. Object session = vfs.createVFSSession(path,view);
  1690. if(session == null)
  1691. return false;
  1692. try
  1693. {
  1694. VFSFile file = vfs._getFile(session,path,view);
  1695. if(file == null)
  1696. return true;
  1697. if(file.getType() != VFSFile.FILE)
  1698. {
  1699. VFSManager.error(view,path,
  1700. "ioerror.save-directory",null);
  1701. return false;
  1702. }
  1703. }
  1704. catch(IOException io)
  1705. {
  1706. VFSManager.error(view,path,"ioerror",
  1707. new String[] { io.toString() });
  1708. return false;
  1709. }
  1710. finally
  1711. {
  1712. try
  1713. {
  1714. vfs._endVFSSession(session,view);
  1715. }
  1716. catch(IOException io)
  1717. {
  1718. VFSManager.error(view,path,"ioerror",
  1719. new String[] { io.toString() });
  1720. return false;
  1721. }
  1722. }
  1723. }
  1724. return true;
  1725. } //}}}
  1726. /** @return an MD5 hash of the contents of the buffer */
  1727. private byte[] calculateHash()
  1728. {
  1729. final byte[] dummy = new byte[1];
  1730. if (!jEdit.getBooleanProperty("useMD5forDirtyCalculation"))
  1731. return dummy;
  1732. return StandardUtilities.md5(getText());
  1733. }
  1734. /** Update the buffer's members with the current hash and length,
  1735. * for later comparison.
  1736. */
  1737. private void updateHash()
  1738. {
  1739. initialLength = getLength();
  1740. md5hash = calculateHash();
  1741. }
  1742. //{{{ finishLoading() method
  1743. private void finishLoading()
  1744. {
  1745. updateHash();
  1746. parseBufferLocalProperties();
  1747. // AHA!
  1748. // this is probably the only way to fix this
  1749. FoldHandler oldFoldHandler = getFoldHandler();
  1750. setMode();
  1751. if(getFoldHandler() == oldFoldHandler)
  1752. {
  1753. // on a reload, the fold handler doesn't change, but
  1754. // we still need to re-collapse folds.
  1755. // don't do this on initial fold handler creation
  1756. invalidateFoldLevels();
  1757. fireFoldHandlerChanged();
  1758. }
  1759. // Create marker positions
  1760. for(int i = 0; i < markers.size(); i++)
  1761. {
  1762. Marker marker = markers.get(i);
  1763. marker.removePosition();
  1764. int pos = marker.getPosition();
  1765. if(pos > getLength())
  1766. marker.setPosition(getLength());
  1767. else if(pos < 0)
  1768. marker.setPosition(0);
  1769. marker.createPosition();
  1770. }
  1771. } //}}}
  1772. //{{{ finishSaving() method
  1773. private void finishSaving(View view, String oldPath,
  1774. String oldSymlinkPath, String path,
  1775. boolean rename, boolean error)
  1776. {
  1777. //{{{ Set the buffer's path
  1778. // Caveat: won't work if save() called with a relative path.
  1779. // But I don't think anyone calls it like that anyway.
  1780. if(!error && !path.equals(oldPath))
  1781. {
  1782. Buffer buffer = jEdit.getBuffer(path);
  1783. if(rename)
  1784. {
  1785. /* if we save a file with the same name as one
  1786. * that's already open, we presume that we can
  1787. * close the existing file, since the user
  1788. * would have confirmed the overwrite in the
  1789. * 'save as' dialog box anyway */
  1790. if(buffer != null && /* can't happen? */
  1791. !buffer.getPath().equals(oldPath))
  1792. {
  1793. buffer.setDirty(false);
  1794. jEdit.closeBuffer(view,buffer);
  1795. }
  1796. setPath(path);
  1797. jEdit.visit(new JEditVisitorAdapter()
  1798. {
  1799. @Override
  1800. public void visit(EditPane editPane)
  1801. {
  1802. BufferSet bufferSet = editPane.getBufferSet();
  1803. if (bufferSet.indexOf(Buffer.this) != -1)
  1804. {
  1805. bufferSet.sort();
  1806. }
  1807. }
  1808. });
  1809. }
  1810. else
  1811. {
  1812. /* if we saved over an already open file using
  1813. * 'save a copy as', then reload the existing
  1814. * buffer */
  1815. if(buffer != null && /* can't happen? */
  1816. !buffer.getPath().equals(oldPath))
  1817. {
  1818. buffer.load(view,true);
  1819. }
  1820. }
  1821. } //}}}
  1822. //{{{ Update this buffer for the new path
  1823. if(rename)
  1824. {
  1825. if(file != null)
  1826. modTime = file.lastModified();
  1827. if(!error)
  1828. {
  1829. // we do a write lock so that the
  1830. // autosave, which grabs a read lock,
  1831. // is not executed between the
  1832. // deletion of the autosave file
  1833. // and clearing of the dirty flag
  1834. try
  1835. {
  1836. writeLock();
  1837. if(autosaveFile != null)
  1838. autosaveFile.delete();
  1839. setFlag(AUTOSAVE_DIRTY,false);
  1840. setFileReadOnly(false);
  1841. setFlag(NEW_FILE,false);
  1842. setFlag(UNTITLED,false);
  1843. super.setDirty(false);
  1844. if(jEdit.getBooleanProperty("resetUndoOnSave"))
  1845. {
  1846. undoMgr.clear();
  1847. }
  1848. }
  1849. finally
  1850. {
  1851. writeUnlock();
  1852. }
  1853. parseBufferLocalProperties();
  1854. if(!getPath().equals(oldPath))
  1855. {
  1856. if (!isTemporary())
  1857. jEdit.updatePosition(oldSymlinkPath,this);
  1858. setMode();
  1859. }
  1860. else
  1861. {
  1862. // if user adds mode buffer-local property
  1863. String newMode = getStringProperty("mode");
  1864. if(newMode != null &&
  1865. !newMode.equals(getMode()
  1866. .getName()))
  1867. setMode();
  1868. else
  1869. propertiesChanged();
  1870. }
  1871. updateHash();
  1872. if (!isTemporary())
  1873. {
  1874. EditBus.send(new BufferUpdate(this,
  1875. view,BufferUpdate.DIRTY_CHANGED));
  1876. // new message type introduced in 4.0pre4
  1877. EditBus.send(new BufferUpdate(this,
  1878. view,BufferUpdate.SAVED));
  1879. }
  1880. }
  1881. } //}}}
  1882. } //}}}
  1883. //}}}
  1884. }