PageRenderTime 42ms CodeModel.GetById 8ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-0-pre3/org/gjt/sp/jedit/Buffer.java

#
Java | 2963 lines | 1716 code | 394 blank | 853 comment | 336 complexity | 72b6a2e041a396c0049511142b975b71 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

Large files files are truncated, but you can click here to view the full file

  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 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.*;
  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. * Note that only very few methods in this class are thread safe; namely,
  44. * those that deal with obtaining buffer contents (<code>getText()</code>,
  45. * <code>getLineStartOffset()</code>, and so on).
  46. *
  47. * @author Slava Pestov
  48. * @version $Id: Buffer.java 3935 2001-12-11 06:32:54Z spestov $
  49. */
  50. public class Buffer implements EBComponent
  51. {
  52. //{{{ Some constants
  53. /**
  54. * Line separator property.
  55. */
  56. public static final String LINESEP = "lineSeparator";
  57. /**
  58. * Backed up property.
  59. * @since jEdit 3.2pre2
  60. */
  61. public static final String BACKED_UP = "Buffer__backedUp";
  62. /**
  63. * Caret info properties.
  64. * @since jEdit 3.2pre1
  65. */
  66. public static final String CARET = "Buffer__caret";
  67. public static final String SELECTION = "Buffer__selection";
  68. /**
  69. * This should be a physical line number, so that the scroll
  70. * position is preserved correctly across reloads (which will
  71. * affect virtual line numbers, due to fold being reset)
  72. */
  73. public static final String SCROLL_VERT = "Buffer__scrollVert";
  74. public static final String SCROLL_HORIZ = "Buffer__scrollHoriz";
  75. /**
  76. * Character encoding used when loading and saving.
  77. * @since jEdit 3.2pre4
  78. */
  79. public static final String ENCODING = "encoding";
  80. /**
  81. * This property is set to 'true' if the file has a trailing newline.
  82. * @since jEdit 4.0pre1
  83. */
  84. public static final String TRAILING_EOL = "trailingEOL";
  85. //}}}
  86. //{{{ Input/output methods
  87. //{{{ showInsertFileDialog() method
  88. /**
  89. * Displays the 'insert file' dialog box and inserts the selected file
  90. * into the buffer.
  91. * @param view The view
  92. * @since jEdit 2.7pre2
  93. */
  94. public void showInsertFileDialog(View view)
  95. {
  96. String[] files = GUIUtilities.showVFSFileDialog(view,null,
  97. VFSBrowser.OPEN_DIALOG,false);
  98. if(files != null)
  99. insertFile(view,files[0]);
  100. } //}}}
  101. //{{{ print() method
  102. /**
  103. * Prints the buffer.
  104. * @param view The view
  105. * @since jEdit 2.7pre2
  106. */
  107. public void print(View view)
  108. {
  109. PrintJob job = view.getToolkit().getPrintJob(view,name,null);
  110. if(job == null)
  111. return;
  112. view.showWaitCursor();
  113. int topMargin;
  114. int leftMargin;
  115. int bottomMargin;
  116. int rightMargin;
  117. int ppi = job.getPageResolution();
  118. try
  119. {
  120. topMargin = (int)(Float.valueOf(jEdit.getProperty(
  121. "print.margin.top")).floatValue() * ppi);
  122. }
  123. catch(NumberFormatException nf)
  124. {
  125. topMargin = ppi / 2;
  126. }
  127. try
  128. {
  129. leftMargin = (int)(Float.valueOf(jEdit.getProperty(
  130. "print.margin.left")).floatValue() * ppi);
  131. }
  132. catch(NumberFormatException nf)
  133. {
  134. leftMargin = ppi / 2;
  135. }
  136. try
  137. {
  138. bottomMargin = (int)(Float.valueOf(jEdit.getProperty(
  139. "print.margin.bottom")).floatValue() * ppi);
  140. }
  141. catch(NumberFormatException nf)
  142. {
  143. bottomMargin = topMargin;
  144. }
  145. try
  146. {
  147. rightMargin = (int)(Float.valueOf(jEdit.getProperty(
  148. "print.margin.right")).floatValue() * ppi);
  149. }
  150. catch(NumberFormatException nf)
  151. {
  152. rightMargin = leftMargin;
  153. }
  154. boolean printHeader = jEdit.getBooleanProperty("print.header");
  155. boolean printFooter = jEdit.getBooleanProperty("print.footer");
  156. boolean printLineNumbers = jEdit.getBooleanProperty("print.lineNumbers");
  157. boolean syntax = jEdit.getBooleanProperty("print.syntax");
  158. String header = path;
  159. String footer = new Date().toString();
  160. TabExpander expander = null;
  161. Graphics gfx = null;
  162. Font font = jEdit.getFontProperty("print.font");
  163. SyntaxStyle[] styles = GUIUtilities.loadStyles(
  164. jEdit.getProperty("print.font"),
  165. jEdit.getIntegerProperty("print.fontsize",10));
  166. boolean style = jEdit.getBooleanProperty("print.style");
  167. boolean color = jEdit.getBooleanProperty("print.color");
  168. FontMetrics fm = null;
  169. Dimension pageDimension = job.getPageDimension();
  170. int pageWidth = pageDimension.width;
  171. int pageHeight = pageDimension.height;
  172. int y = 0;
  173. int tabSize = 0;
  174. int lineHeight = 0;
  175. int page = 0;
  176. int lineNumberDigits = (int)Math.ceil(Math.log(
  177. getLineCount()) / Math.log(10));
  178. int lineNumberWidth = 0;
  179. TextRenderer renderer = new TextRenderer();
  180. renderer.configure(false,false);
  181. for(int i = 0; i < getLineCount(); i++)
  182. {
  183. if(gfx == null)
  184. {
  185. page++;
  186. gfx = job.getGraphics();
  187. renderer.setupGraphics(gfx);
  188. gfx.setFont(font);
  189. fm = gfx.getFontMetrics();
  190. if(printLineNumbers)
  191. {
  192. lineNumberWidth = fm.charWidth('0')
  193. * lineNumberDigits;
  194. }
  195. else
  196. lineNumberWidth = 0;
  197. lineHeight = fm.getHeight();
  198. tabSize = getTabSize() * fm.charWidth(' ');
  199. expander = new PrintTabExpander(leftMargin
  200. + lineNumberWidth,tabSize);
  201. y = topMargin + lineHeight - fm.getDescent()
  202. - fm.getLeading();
  203. if(printHeader)
  204. {
  205. gfx.setColor(Color.lightGray);
  206. gfx.fillRect(leftMargin,topMargin,pageWidth
  207. - leftMargin - rightMargin,lineHeight);
  208. gfx.setColor(Color.black);
  209. gfx.drawString(header,leftMargin,y);
  210. y += lineHeight;
  211. }
  212. }
  213. y += lineHeight;
  214. gfx.setColor(Color.black);
  215. gfx.setFont(font);
  216. int x = leftMargin;
  217. if(printLineNumbers)
  218. {
  219. String lineNumber = String.valueOf(i + 1);
  220. gfx.drawString(lineNumber,(leftMargin + lineNumberWidth)
  221. - fm.stringWidth(lineNumber),y);
  222. x += lineNumberWidth + fm.charWidth('0');
  223. }
  224. paintSyntaxLine(i,gfx,x,y,expander,style,color,
  225. font,Color.black,Color.white,styles,
  226. renderer);
  227. int bottomOfPage = pageHeight - bottomMargin - lineHeight;
  228. if(printFooter)
  229. bottomOfPage -= lineHeight * 2;
  230. if(y >= bottomOfPage || i == getLineCount() - 1)
  231. {
  232. if(printFooter)
  233. {
  234. y = pageHeight - bottomMargin;
  235. gfx.setColor(Color.lightGray);
  236. gfx.setFont(font);
  237. gfx.fillRect(leftMargin,y - lineHeight,pageWidth
  238. - leftMargin - rightMargin,lineHeight);
  239. gfx.setColor(Color.black);
  240. y -= (lineHeight - fm.getAscent());
  241. gfx.drawString(footer,leftMargin,y);
  242. Integer[] args = { new Integer(page) };
  243. String pageStr = jEdit.getProperty("print.page",args);
  244. int width = fm.stringWidth(pageStr);
  245. gfx.drawString(pageStr,pageWidth - rightMargin
  246. - width,y);
  247. }
  248. gfx.dispose();
  249. gfx = null;
  250. }
  251. }
  252. job.end();
  253. view.hideWaitCursor();
  254. } //}}}
  255. //{{{ reload() method
  256. /**
  257. * Reloads the buffer from disk, asking for confirmation if the buffer
  258. * is dirty.
  259. * @param view The view
  260. * @since jEdit 2.7pre2
  261. */
  262. public void reload(View view)
  263. {
  264. if(getFlag(DIRTY))
  265. {
  266. String[] args = { name };
  267. int result = GUIUtilities.confirm(view,"changedreload",
  268. args,JOptionPane.YES_NO_OPTION,
  269. JOptionPane.WARNING_MESSAGE);
  270. if(result != JOptionPane.YES_OPTION)
  271. return;
  272. }
  273. view.getEditPane().saveCaretInfo();
  274. load(view,true);
  275. } //}}}
  276. //{{{ load() method
  277. /**
  278. * Loads the buffer from disk, even if it is loaded already.
  279. * @param view The view
  280. * @param reload If true, user will not be asked to recover autosave
  281. * file, if any
  282. *
  283. * @since 2.5pre1
  284. */
  285. public boolean load(final View view, final boolean reload)
  286. {
  287. if(isPerformingIO())
  288. {
  289. GUIUtilities.error(view,"buffer-multiple-io",null);
  290. return false;
  291. }
  292. setFlag(LOADING,true);
  293. // view text areas temporarily blank out while a buffer is
  294. // being loaded, to indicate to the user that there is no
  295. // data available yet.
  296. if(!getFlag(TEMPORARY))
  297. EditBus.send(new BufferUpdate(this,view,BufferUpdate.LOAD_STARTED));
  298. final boolean loadAutosave;
  299. if(reload || !getFlag(NEW_FILE))
  300. {
  301. if(file != null)
  302. modTime = file.lastModified();
  303. // Only on initial load
  304. if(!reload && autosaveFile != null && autosaveFile.exists())
  305. loadAutosave = recoverAutosave(view);
  306. else
  307. {
  308. if(autosaveFile != null)
  309. autosaveFile.delete();
  310. loadAutosave = false;
  311. }
  312. if(!loadAutosave)
  313. {
  314. // this returns false if initial sanity
  315. // checks (if the file is a directory, etc)
  316. // fail
  317. if(!vfs.load(view,this,path))
  318. {
  319. setFlag(LOADING,false);
  320. return false;
  321. }
  322. }
  323. }
  324. else
  325. loadAutosave = false;
  326. //{{{ Do some stuff once loading is finished
  327. Runnable runnable = new Runnable()
  328. {
  329. public void run()
  330. {
  331. String newPath = getStringProperty(
  332. BufferIORequest.NEW_PATH);
  333. Segment seg = (Segment)getProperty(
  334. BufferIORequest.LOAD_DATA);
  335. IntegerArray endOffsets = (IntegerArray)
  336. getProperty(BufferIORequest.END_OFFSETS);
  337. // For `reload' command
  338. remove(0,getLength());
  339. if(seg != null && endOffsets != null)
  340. {
  341. // This is faster than Buffer.insert()
  342. try
  343. {
  344. writeLock();
  345. contentMgr.insert(0,seg.toString());
  346. int lineCount = endOffsets.getSize();
  347. if(lineCount != 0)
  348. {
  349. parseBufferLocalProperties(
  350. contentMgr.getText(0,
  351. endOffsets.get(Math.min(
  352. lineCount - 1,
  353. 10))));
  354. }
  355. setModeForFirstLine(contentMgr.getText(0,
  356. endOffsets.get(0)));
  357. contentInserted(0,seg.count,
  358. endOffsets);
  359. }
  360. finally
  361. {
  362. writeUnlock();
  363. }
  364. }
  365. else
  366. {
  367. // have to set mode according to path name only
  368. setMode();
  369. }
  370. unsetProperty(BufferIORequest.LOAD_DATA);
  371. unsetProperty(BufferIORequest.END_OFFSETS);
  372. unsetProperty(BufferIORequest.NEW_PATH);
  373. undoMgr.clear();
  374. undoMgr.setLimit(jEdit.getIntegerProperty(
  375. "buffer.undoCount",100));
  376. setFlag(LOADING,false);
  377. // if reloading a file, clear dirty flag
  378. if(reload)
  379. setDirty(false);
  380. if(!loadAutosave && newPath != null && !path.equals(newPath))
  381. setPath(newPath);
  382. // if loadAutosave is false, we loaded an
  383. // autosave file, so we set 'dirty' to true
  384. // note that we don't use setDirty(),
  385. // because a) that would send an unnecessary
  386. // message, b) it would also set the
  387. // AUTOSAVE_DIRTY flag, which will make
  388. // the autosave thread write out a
  389. // redundant autosave file
  390. if(loadAutosave)
  391. setFlag(DIRTY,true);
  392. if(jEdit.getBooleanProperty("parseFully"))
  393. {
  394. for(int i = 0; i < offsetMgr.getLineCount(); i++)
  395. markTokens(i);
  396. }
  397. // send some EditBus messages
  398. if(!getFlag(TEMPORARY))
  399. {
  400. EditBus.send(new BufferUpdate(Buffer.this,
  401. view,BufferUpdate.LOADED));
  402. EditBus.send(new BufferUpdate(Buffer.this,
  403. view,BufferUpdate.MARKERS_CHANGED));
  404. }
  405. }
  406. }; //}}}
  407. if(getFlag(TEMPORARY))
  408. runnable.run();
  409. else
  410. VFSManager.runInAWTThread(runnable);
  411. return true;
  412. } //}}}
  413. //{{{ insertFile() method
  414. /**
  415. * Loads a file from disk, and inserts it into this buffer.
  416. * @param view The view
  417. *
  418. * @since 4.0pre1
  419. */
  420. public boolean insertFile(final View view, String path)
  421. {
  422. if(isPerformingIO())
  423. {
  424. GUIUtilities.error(view,"buffer-multiple-io",null);
  425. return false;
  426. }
  427. path = MiscUtilities.constructPath(this.path,path);
  428. Buffer buffer = jEdit.getBuffer(path);
  429. if(buffer != null)
  430. {
  431. view.getTextArea().setSelectedText(
  432. buffer.getText(0,buffer.getLength()));
  433. return true;
  434. }
  435. VFS vfs = VFSManager.getVFSForPath(path);
  436. setFlag(IO,true);
  437. // this returns false if initial sanity
  438. // checks (if the file is a directory, etc)
  439. // fail
  440. if(!vfs.insert(view,this,path))
  441. {
  442. setFlag(IO,false);
  443. return false;
  444. }
  445. // Do some stuff once loading is finished
  446. VFSManager.runInAWTThread(new Runnable()
  447. {
  448. public void run()
  449. {
  450. setFlag(IO,false);
  451. StringBuffer sbuf = (StringBuffer)getProperty(
  452. BufferIORequest.LOAD_DATA);
  453. if(sbuf != null)
  454. {
  455. unsetProperty(BufferIORequest.LOAD_DATA);
  456. view.getTextArea().setSelectedText(sbuf.toString());
  457. }
  458. }
  459. });
  460. return true;
  461. } //}}}
  462. //{{{ autosave() method
  463. /**
  464. * Autosaves this buffer.
  465. */
  466. public void autosave()
  467. {
  468. if(autosaveFile == null || !getFlag(AUTOSAVE_DIRTY)
  469. || !getFlag(DIRTY)
  470. || getFlag(LOADING)
  471. || getFlag(IO))
  472. return;
  473. setFlag(AUTOSAVE_DIRTY,false);
  474. VFSManager.runInWorkThread(new BufferIORequest(
  475. BufferIORequest.AUTOSAVE,null,this,null,
  476. VFSManager.getFileVFS(),autosaveFile.getPath()));
  477. } //}}}
  478. //{{{ saveAs() method
  479. /**
  480. * Prompts the user for a file to save this buffer to.
  481. * @param view The view
  482. * @param rename True if the buffer's path should be changed, false
  483. * if only a copy should be saved to the specified filename
  484. * @since jEdit 2.6pre5
  485. */
  486. public boolean saveAs(View view, boolean rename)
  487. {
  488. String[] files = GUIUtilities.showVFSFileDialog(view,path,
  489. VFSBrowser.SAVE_DIALOG,false);
  490. // files[] should have length 1, since the dialog type is
  491. // SAVE_DIALOG
  492. if(files == null)
  493. return false;
  494. return save(view,files[0],rename);
  495. } //}}}
  496. //{{{ save() method
  497. /**
  498. * Saves this buffer to the specified path name, or the current path
  499. * name if it's null.
  500. * @param view The view
  501. * @param path The path name to save the buffer to, or null to use
  502. * the existing path
  503. */
  504. public boolean save(View view, String path)
  505. {
  506. return save(view,path,true);
  507. } //}}}
  508. //{{{ save() method
  509. /**
  510. * Saves this buffer to the specified path name, or the current path
  511. * name if it's null.
  512. * @param view The view
  513. * @param path The path name to save the buffer to, or null to use
  514. * the existing path
  515. * @param rename True if the buffer's path should be changed, false
  516. * if only a copy should be saved to the specified filename
  517. * @since jEdit 2.6pre5
  518. */
  519. public boolean save(final View view, String path, final boolean rename)
  520. {
  521. if(isPerformingIO())
  522. {
  523. GUIUtilities.error(view,"buffer-multiple-io",null);
  524. return false;
  525. }
  526. if(path == null && getFlag(NEW_FILE))
  527. return saveAs(view,rename);
  528. if(path == null && file != null)
  529. {
  530. long newModTime = file.lastModified();
  531. if(newModTime != modTime)
  532. {
  533. Object[] args = { this.path };
  534. int result = GUIUtilities.confirm(view,
  535. "filechanged-save",args,
  536. JOptionPane.YES_NO_OPTION,
  537. JOptionPane.WARNING_MESSAGE);
  538. if(result != JOptionPane.YES_OPTION)
  539. return false;
  540. }
  541. }
  542. setFlag(IO,true);
  543. EditBus.send(new BufferUpdate(this,view,BufferUpdate.SAVING));
  544. if(path == null)
  545. path = this.path;
  546. // can't call setPath() here because we don't want a failed
  547. // 'save as' to change the buffer's path, so obtain the VFS
  548. // instance 'manually'
  549. VFS vfs = VFSManager.getVFSForPath(path);
  550. if(!vfs.save(view,this,path))
  551. {
  552. setFlag(IO,false);
  553. return false;
  554. }
  555. final String oldPath = this.path;
  556. if(rename)
  557. setPath(path);
  558. // Once save is complete, do a few other things
  559. VFSManager.runInAWTThread(new Runnable()
  560. {
  561. public void run()
  562. {
  563. // Saving a NEW_FILE will create a file on
  564. // disk, thus file system browsers must reload
  565. if(getFlag(NEW_FILE) || !getPath().equals(oldPath))
  566. VFSManager.sendVFSUpdate(getVFS(),getPath(),true);
  567. setFlag(IO,false);
  568. if(rename)
  569. {
  570. // we do a write lock so that the
  571. // autosave, which grabs a read lock,
  572. // is not executed between the
  573. // deletion of the autosave file
  574. // and clearing of the dirty flag
  575. try
  576. {
  577. writeLock();
  578. if(autosaveFile != null)
  579. autosaveFile.delete();
  580. setFlag(AUTOSAVE_DIRTY,false);
  581. setFlag(READ_ONLY,false);
  582. setFlag(NEW_FILE,false);
  583. setFlag(UNTITLED,false);
  584. setFlag(DIRTY,false);
  585. // this ensures that undo can clear
  586. // the dirty flag properly when all
  587. // edits up to a save are undone
  588. undoMgr.bufferSaved();
  589. }
  590. finally
  591. {
  592. writeUnlock();
  593. }
  594. parseBufferLocalProperties();
  595. if(!getPath().equals(oldPath))
  596. {
  597. jEdit.updatePosition(Buffer.this);
  598. setMode();
  599. }
  600. else
  601. propertiesChanged();
  602. if(file != null)
  603. modTime = file.lastModified();
  604. EditBus.send(new BufferUpdate(Buffer.this,
  605. view,BufferUpdate.DIRTY_CHANGED));
  606. }
  607. }
  608. });
  609. return true;
  610. } //}}}
  611. //{{{ checkModTime() method
  612. /**
  613. * Check if the buffer has changed on disk.
  614. */
  615. public void checkModTime(View view)
  616. {
  617. // don't do these checks while a save is in progress,
  618. // because for a moment newModTime will be greater than
  619. // oldModTime, due to the multithreading
  620. if(file == null || getFlag(NEW_FILE) || getFlag(IO))
  621. return;
  622. boolean newReadOnly = (file.exists() && !file.canWrite());
  623. if(newReadOnly != getFlag(READ_ONLY))
  624. {
  625. setFlag(READ_ONLY,newReadOnly);
  626. EditBus.send(new BufferUpdate(this,
  627. view,BufferUpdate.DIRTY_CHANGED));
  628. }
  629. if(!jEdit.getBooleanProperty("view.checkModStatus"))
  630. return;
  631. long oldModTime = modTime;
  632. long newModTime = file.lastModified();
  633. if(newModTime != oldModTime)
  634. {
  635. modTime = newModTime;
  636. if(!file.exists())
  637. {
  638. setFlag(NEW_FILE,true);
  639. EditBus.send(new BufferUpdate(this,
  640. view,BufferUpdate.DIRTY_CHANGED));
  641. Object[] args = { path };
  642. GUIUtilities.message(view,"filedeleted",args);
  643. return;
  644. }
  645. String prop = (isDirty() ? "filechanged-dirty"
  646. : "filechanged-focus");
  647. Object[] args = { path };
  648. int result = GUIUtilities.confirm(view,
  649. prop,args,JOptionPane.YES_NO_OPTION,
  650. JOptionPane.WARNING_MESSAGE);
  651. if(result == JOptionPane.YES_OPTION)
  652. {
  653. view.getEditPane().saveCaretInfo();
  654. load(view,true);
  655. }
  656. }
  657. } //}}}
  658. //}}}
  659. //{{{ Getters/setter methods for various things
  660. //{{{ getLastModified() method
  661. /**
  662. * Returns the last time jEdit modified the file on disk.
  663. */
  664. public long getLastModified()
  665. {
  666. return modTime;
  667. } //}}}
  668. //{{{ setLastModified() method
  669. /**
  670. * Sets the last time jEdit modified the file on disk.
  671. * @param modTime The new modification time
  672. */
  673. public void setLastModified(long modTime)
  674. {
  675. this.modTime = modTime;
  676. } //}}}
  677. //{{{ getVFS() method
  678. /**
  679. * Returns the virtual filesystem responsible for loading and
  680. * saving this buffer.
  681. */
  682. public VFS getVFS()
  683. {
  684. return vfs;
  685. } //}}}
  686. //{{{ getFile() method
  687. /**
  688. * Returns the file for this buffer. This may be null if the buffer
  689. * is non-local.
  690. */
  691. public final File getFile()
  692. {
  693. return file;
  694. } //}}}
  695. //{{{ getAutosaveFile() method
  696. /**
  697. * Returns the autosave file for this buffer. This may be null if
  698. * the file is non-local.
  699. */
  700. public final File getAutosaveFile()
  701. {
  702. return autosaveFile;
  703. } //}}}
  704. //{{{ getName() method
  705. /**
  706. * Returns the name of this buffer.
  707. */
  708. public final String getName()
  709. {
  710. return name;
  711. } //}}}
  712. //{{{ getPath() method
  713. /**
  714. * Returns the path name of this buffer.
  715. */
  716. public final String getPath()
  717. {
  718. return path;
  719. } //}}}
  720. //{{{ isClosed() method
  721. /**
  722. * Returns true if this buffer has been closed with
  723. * <code>jEdit.closeBuffer()</code>.
  724. */
  725. public final boolean isClosed()
  726. {
  727. return getFlag(CLOSED);
  728. } //}}}
  729. //{{{ isLoaded() method
  730. /**
  731. * Returns true if the buffer is loaded.
  732. */
  733. public final boolean isLoaded()
  734. {
  735. return !getFlag(LOADING);
  736. } //}}}
  737. //{{{ isPerformingIO() method
  738. /**
  739. * Returns true if the buffer is currently performing I/O.
  740. * @since jEdit 2.7pre1
  741. */
  742. public final boolean isPerformingIO()
  743. {
  744. return getFlag(LOADING) || getFlag(IO);
  745. } //}}}
  746. //{{{ isNewFile() method
  747. /**
  748. * Returns true if this file doesn't exist on disk.
  749. */
  750. public final boolean isNewFile()
  751. {
  752. return getFlag(NEW_FILE);
  753. } //}}}
  754. //{{{ setNewFile() method
  755. /**
  756. * Sets the new file flag.
  757. * @param newFile The new file flag
  758. */
  759. public final void setNewFile(boolean newFile)
  760. {
  761. setFlag(NEW_FILE,newFile);
  762. if(!newFile)
  763. setFlag(UNTITLED,false);
  764. } //}}}
  765. //{{{ isUntitled() method
  766. /**
  767. * Returns true if this file is 'untitled'.
  768. */
  769. public final boolean isUntitled()
  770. {
  771. return getFlag(UNTITLED);
  772. } //}}}
  773. //{{{ isDirty() method
  774. /**
  775. * Returns true if this file has changed since last save, false
  776. * otherwise.
  777. */
  778. public final boolean isDirty()
  779. {
  780. return getFlag(DIRTY);
  781. } //}}}
  782. //{{{ isReadOnly() method
  783. /**
  784. * Returns true if this file is read only, false otherwise.
  785. */
  786. public final boolean isReadOnly()
  787. {
  788. return getFlag(READ_ONLY);
  789. } //}}}
  790. //{{{ isEditable() method
  791. /**
  792. * Returns true if this file is editable, false otherwise.
  793. * @since jEdit 2.7pre1
  794. */
  795. public final boolean isEditable()
  796. {
  797. return !(getFlag(READ_ONLY) || getFlag(IO) || getFlag(LOADING));
  798. } //}}}
  799. //{{{ isReadOnly() method
  800. /**
  801. * Sets the read only flag.
  802. * @param readOnly The read only flag
  803. */
  804. public final void setReadOnly(boolean readOnly)
  805. {
  806. setFlag(READ_ONLY,readOnly);
  807. } //}}}
  808. //{{{ setDirty() method
  809. /**
  810. * Sets the `dirty' (changed since last save) flag of this buffer.
  811. */
  812. public void setDirty(boolean d)
  813. {
  814. boolean old_d = getFlag(DIRTY);
  815. if(d)
  816. {
  817. if(getFlag(LOADING) || getFlag(READ_ONLY))
  818. return;
  819. if(getFlag(DIRTY) && getFlag(AUTOSAVE_DIRTY))
  820. return;
  821. setFlag(DIRTY,true);
  822. setFlag(AUTOSAVE_DIRTY,true);
  823. }
  824. else
  825. {
  826. setFlag(DIRTY,false);
  827. setFlag(AUTOSAVE_DIRTY,false);
  828. // this ensures that undo can clear the dirty flag properly
  829. // when all edits up to a save are undone
  830. undoMgr.bufferSaved();
  831. }
  832. if(d != old_d)
  833. {
  834. EditBus.send(new BufferUpdate(this,null,
  835. BufferUpdate.DIRTY_CHANGED));
  836. }
  837. } //}}}
  838. //{{{ isTemporary() method
  839. /**
  840. * Returns if this is a temporary buffer.
  841. * @see jEdit#openTemporary(View,String,String,boolean,boolean)
  842. * @see jEdit#commitTemporary(Buffer)
  843. * @since jEdit 2.2pre7
  844. */
  845. public boolean isTemporary()
  846. {
  847. return getFlag(TEMPORARY);
  848. } //}}}
  849. //{{{ getIcon() method
  850. /**
  851. * Returns this buffer's icon.
  852. * @since jEdit 2.6pre6
  853. */
  854. public Icon getIcon()
  855. {
  856. if(getFlag(DIRTY))
  857. return GUIUtilities.DIRTY_BUFFER_ICON;
  858. else if(getFlag(READ_ONLY))
  859. return GUIUtilities.READ_ONLY_BUFFER_ICON;
  860. else if(getFlag(NEW_FILE))
  861. return GUIUtilities.NEW_BUFFER_ICON;
  862. else
  863. return GUIUtilities.NORMAL_BUFFER_ICON;
  864. } //}}}
  865. //}}}
  866. //{{{ Thread safety
  867. //{{{ readLock() method
  868. /**
  869. * The buffer is guaranteed not to change between calls to
  870. * <code>readLock()</code> and <code>readUnlock()</code>.
  871. */
  872. public final void readLock()
  873. {
  874. lock.readLock();
  875. } //}}}
  876. //{{{ readUnlock() method
  877. /**
  878. * The buffer is guaranteed not to change between calls to
  879. * <code>readLock()</code> and <code>readUnlock()</code>.
  880. */
  881. public final void readUnlock()
  882. {
  883. lock.readUnlock();
  884. } //}}}
  885. //{{{ writeLock() method
  886. /**
  887. * The buffer cintents are guaranteed not to be read or written
  888. * by other threads between calls to <code>writeLock()</code>
  889. * and <code>writeUnlock()</code>.
  890. */
  891. public final void writeLock()
  892. {
  893. lock.writeLock();
  894. } //}}}
  895. //{{{ writeUnlock() method
  896. /**
  897. * The buffer cintents are guaranteed not to be read or written
  898. * by other threads between calls to <code>writeLock()</code>
  899. * and <code>writeUnlock()</code>.
  900. */
  901. public final void writeUnlock()
  902. {
  903. lock.writeUnlock();
  904. } //}}}
  905. //}}}
  906. //{{{ Text reading methods
  907. //{{{ getLength() method
  908. /**
  909. * Returns the number of characters in the buffer.
  910. */
  911. public int getLength()
  912. {
  913. // no need to lock since this just returns a value and that's it
  914. return contentMgr.getLength();
  915. } //}}}
  916. //{{{ getLineCount() method
  917. /**
  918. * Returns the number of physical lines in the buffer.
  919. * This method is thread-safe.
  920. * @since jEdit 3.1pre1
  921. */
  922. public int getLineCount()
  923. {
  924. // no need to lock since this just returns a value and that's it
  925. return offsetMgr.getLineCount();
  926. } //}}}
  927. //{{{ Debugging
  928. public void testOffsetManager()
  929. {
  930. java.util.Random random = new java.util.Random();
  931. for(int i = 0; i < 10000; i++)
  932. {
  933. int next = Math.abs(random.nextInt()) % getLength();
  934. int line = getLineOfOffset(next);
  935. int start = getLineStartOffset(line);
  936. int end = getLineEndOffset(line);
  937. if(next < start || next >= end)
  938. {
  939. System.err.println(next + ":" + line);
  940. break;
  941. }
  942. }
  943. for(int i = 0; i < getLineCount(); i++)
  944. {
  945. int start = getLineStartOffset(i);
  946. int end = getLineEndOffset(i);
  947. if(start >= end)
  948. System.err.println(i + ":" + start + ":" + end);
  949. }
  950. }
  951. public void testPositions(int positions, int inserts)
  952. {
  953. java.util.Vector v = new Vector();
  954. java.util.Random random = new java.util.Random();
  955. for(int i = 0; i < positions; i++)
  956. {
  957. v.addElement(createPosition(Math.abs(random.nextInt()) % getLength()));
  958. }
  959. long start = System.currentTimeMillis();
  960. for(int i = 0; i < inserts; i++)
  961. {
  962. int pos = Math.abs(random.nextInt()) % getLength();
  963. insert(pos,"a");
  964. }
  965. System.err.println(System.currentTimeMillis() - start);
  966. } //}}}
  967. //{{{ getLineOfOffset() method
  968. /**
  969. * Returns the line containing the specified offset.
  970. * This method is thread-safe.
  971. * @param offset The offset
  972. * @since jEdit 4.0pre1
  973. */
  974. public final int getLineOfOffset(int offset)
  975. {
  976. try
  977. {
  978. readLock();
  979. if(offset < 0 || offset > getLength())
  980. throw new ArrayIndexOutOfBoundsException(offset);
  981. return offsetMgr.getLineOfOffset(offset);
  982. }
  983. finally
  984. {
  985. readUnlock();
  986. }
  987. } //}}}
  988. //{{{ getLineStartOffset() method
  989. /**
  990. * Returns the start offset of the specified line.
  991. * This method is thread-safe.
  992. * @param line The line
  993. * @return The start offset of the specified line
  994. * @since jEdit 4.0pre1
  995. */
  996. public int getLineStartOffset(int line)
  997. {
  998. try
  999. {
  1000. readLock();
  1001. if(line < 0 || line >= offsetMgr.getLineCount())
  1002. throw new ArrayIndexOutOfBoundsException(line);
  1003. else if(line == 0)
  1004. return 0;
  1005. return offsetMgr.getLineEndOffset(line - 1);
  1006. }
  1007. finally
  1008. {
  1009. readUnlock();
  1010. }
  1011. } //}}}
  1012. //{{{ getLineEndOffset() method
  1013. /**
  1014. * Returns the end offset of the specified line.
  1015. * This method is thread-safe.
  1016. * @param line The line
  1017. * @return The end offset of the specified line
  1018. * invalid.
  1019. * @since jEdit 4.0pre1
  1020. */
  1021. public int getLineEndOffset(int line)
  1022. {
  1023. try
  1024. {
  1025. readLock();
  1026. if(line < 0 || line >= offsetMgr.getLineCount())
  1027. throw new ArrayIndexOutOfBoundsException(line);
  1028. return offsetMgr.getLineEndOffset(line);
  1029. }
  1030. finally
  1031. {
  1032. readUnlock();
  1033. }
  1034. } //}}}
  1035. //{{{ getLineLength() method
  1036. /**
  1037. * Returns the length of the specified line.
  1038. * This method is thread-safe.
  1039. * @param line The line
  1040. * @since jEdit 4.0pre1
  1041. */
  1042. public int getLineLength(int line)
  1043. {
  1044. try
  1045. {
  1046. readLock();
  1047. return getLineEndOffset(line)
  1048. - getLineStartOffset(line) - 1;
  1049. }
  1050. finally
  1051. {
  1052. readUnlock();
  1053. }
  1054. } //}}}
  1055. //{{{ getLineText() method
  1056. /**
  1057. * Returns the text on the specified line.
  1058. * This method is thread-safe.
  1059. * @param lineIndex The line
  1060. * @return The text, or null if the line is invalid
  1061. * @since jEdit 4.0pre1
  1062. */
  1063. public String getLineText(int lineIndex)
  1064. {
  1065. try
  1066. {
  1067. readLock();
  1068. return getText(getLineStartOffset(lineIndex),
  1069. getLineLength(lineIndex));
  1070. }
  1071. finally
  1072. {
  1073. readUnlock();
  1074. }
  1075. } //}}}
  1076. //{{{ getLineText() method
  1077. /**
  1078. * Copies the text on the specified line into a segment.
  1079. * This method is thread-safe.
  1080. * @param lineIndex The line
  1081. * @since jEdit 4.0pre1
  1082. */
  1083. public void getLineText(int lineIndex, Segment segment)
  1084. {
  1085. try
  1086. {
  1087. readLock();
  1088. getText(getLineStartOffset(lineIndex),
  1089. getLineLength(lineIndex),segment);
  1090. }
  1091. finally
  1092. {
  1093. readUnlock();
  1094. }
  1095. } //}}}
  1096. //{{{ getText() method
  1097. /**
  1098. * Returns the specified text range.
  1099. * @param start The start offset
  1100. * @param length The number of characters to get
  1101. */
  1102. public String getText(int start, int length)
  1103. {
  1104. try
  1105. {
  1106. readLock();
  1107. if(start < 0 || length < 0
  1108. || start + length > contentMgr.getLength())
  1109. throw new ArrayIndexOutOfBoundsException(start + ":" + length);
  1110. return contentMgr.getText(start,length);
  1111. }
  1112. finally
  1113. {
  1114. readUnlock();
  1115. }
  1116. } //}}}
  1117. //{{{ getText() method
  1118. /**
  1119. * Returns the specified text range.
  1120. * @param start The start offset
  1121. * @param length The number of characters to get
  1122. * @param seg The segment to copy the text to
  1123. */
  1124. public void getText(int start, int length, Segment seg)
  1125. {
  1126. try
  1127. {
  1128. readLock();
  1129. if(start < 0 || length < 0
  1130. || start + length > contentMgr.getLength())
  1131. throw new ArrayIndexOutOfBoundsException(start + ":" + length);
  1132. contentMgr.getText(start,length,seg);
  1133. }
  1134. finally
  1135. {
  1136. readUnlock();
  1137. }
  1138. } //}}}
  1139. //}}}
  1140. //{{{ Text writing methods
  1141. //{{{ insert() method
  1142. /**
  1143. * Inserts a string into the buffer.
  1144. * @param offset The offset
  1145. * @param str The string
  1146. * @since jEdit 4.0pre1
  1147. */
  1148. public void insert(int offset, String str)
  1149. {
  1150. if(str == null || str.length() == 0)
  1151. return;
  1152. if(isReadOnly())
  1153. throw new RuntimeException("buffer read-only");
  1154. try
  1155. {
  1156. writeLock();
  1157. if(offset < 0 || offset > contentMgr.getLength())
  1158. throw new ArrayIndexOutOfBoundsException(offset);
  1159. contentMgr.insert(offset,str);
  1160. integerArray.clear();
  1161. for(int i = 0; i < str.length(); i++)
  1162. {
  1163. if(str.charAt(i) == '\n')
  1164. integerArray.add(i);
  1165. }
  1166. if(!getFlag(UNDO_IN_PROGRESS))
  1167. {
  1168. undoMgr.contentInserted(offset,str.length(),str,
  1169. !getFlag(DIRTY));
  1170. }
  1171. contentInserted(offset,str.length(),integerArray);
  1172. }
  1173. finally
  1174. {
  1175. writeUnlock();
  1176. }
  1177. } //}}}
  1178. //{{{ insert() method
  1179. /**
  1180. * Inserts a string into the buffer.
  1181. * @param offset The offset
  1182. * @param seg The segment
  1183. * @since jEdit 4.0pre1
  1184. */
  1185. public void insert(int offset, Segment seg)
  1186. {
  1187. if(seg.count == 0)
  1188. return;
  1189. if(isReadOnly())
  1190. throw new RuntimeException("buffer read-only");
  1191. try
  1192. {
  1193. writeLock();
  1194. if(offset < 0 || offset > contentMgr.getLength())
  1195. throw new ArrayIndexOutOfBoundsException(offset);
  1196. contentMgr.insert(offset,seg);
  1197. integerArray.clear();
  1198. for(int i = 0; i < seg.count; i++)
  1199. {
  1200. if(seg.array[seg.offset + i] == '\n')
  1201. integerArray.add(i);
  1202. }
  1203. if(!getFlag(UNDO_IN_PROGRESS))
  1204. {
  1205. undoMgr.contentInserted(offset,seg.count,
  1206. seg.toString(),!getFlag(DIRTY));
  1207. }
  1208. contentInserted(offset,seg.count,integerArray);
  1209. }
  1210. finally
  1211. {
  1212. writeUnlock();
  1213. }
  1214. } //}}}
  1215. //{{{ remove() method
  1216. /**
  1217. * Removes the specified rang efrom the buffer.
  1218. * @param offset The start offset
  1219. * @param length The number of characters to remove
  1220. */
  1221. public void remove(int offset, int length)
  1222. {
  1223. if(length == 0)
  1224. return;
  1225. if(isReadOnly())
  1226. throw new RuntimeException("buffer read-only");
  1227. try
  1228. {
  1229. writeLock();
  1230. if(offset < 0 || length < 0
  1231. || offset + length > contentMgr.getLength())
  1232. throw new ArrayIndexOutOfBoundsException(offset + ":" + length);
  1233. int startLine = offsetMgr.getLineOfOffset(offset);
  1234. contentMgr.getText(offset,length,seg);
  1235. int numLines = 0;
  1236. for(int i = 0; i < seg.count; i++)
  1237. {
  1238. if(seg.array[seg.offset + i] == '\n')
  1239. numLines++;
  1240. }
  1241. if(!getFlag(UNDO_IN_PROGRESS))
  1242. {
  1243. undoMgr.contentRemoved(offset,length,
  1244. seg.toString(),!getFlag(DIRTY));
  1245. }
  1246. contentMgr.remove(offset,length);
  1247. if(lastTokenizedLine >= startLine)
  1248. lastTokenizedLine = -1;
  1249. offsetMgr.contentRemoved(startLine,offset,numLines,length);
  1250. if(numLines > 0)
  1251. {
  1252. for(int i = 0; i < inUseFVMs.length; i++)
  1253. {
  1254. if(inUseFVMs[i] != null)
  1255. inUseFVMs[i]._invalidate(startLine);
  1256. }
  1257. }
  1258. fireContentRemoved(startLine,offset,numLines,length);
  1259. setDirty(true);
  1260. }
  1261. finally
  1262. {
  1263. writeUnlock();
  1264. }
  1265. } //}}}
  1266. //{{{ removeTrailingWhiteSpace() method
  1267. /**
  1268. * Removes trailing whitespace from all lines in the specified list.
  1269. * @param list The line numbers
  1270. * @since jEdit 3.2pre1
  1271. */
  1272. public void removeTrailingWhiteSpace(int[] lines)
  1273. {
  1274. try
  1275. {
  1276. beginCompoundEdit();
  1277. for(int i = 0; i < lines.length; i++)
  1278. {
  1279. int pos, lineStart, lineEnd, tail;
  1280. getLineText(lines[i],seg);
  1281. // blank line
  1282. if (seg.count == 0) continue;
  1283. lineStart = seg.offset;
  1284. lineEnd = seg.offset + seg.count - 1;
  1285. for (pos = lineEnd; pos >= lineStart; pos--)
  1286. {
  1287. if (!Character.isWhitespace(seg.array[pos]))
  1288. break;
  1289. }
  1290. tail = lineEnd - pos;
  1291. // no whitespace
  1292. if (tail == 0) continue;
  1293. remove(getLineEndOffset(lines[i]) - 1 - tail,tail);
  1294. }
  1295. }
  1296. finally
  1297. {
  1298. endCompoundEdit();
  1299. }
  1300. } //}}}
  1301. //{{{ shiftIndentLeft() method
  1302. /**
  1303. * Shifts the indent of each line in the specified list to the left.
  1304. * @param lines The line numbers
  1305. * @since jEdit 3.2pre1
  1306. */
  1307. public void shiftIndentLeft(int[] lines)
  1308. {
  1309. int tabSize = getTabSize();
  1310. int indentSize = getIndentSize();
  1311. boolean noTabs = getBooleanProperty("noTabs");
  1312. try
  1313. {
  1314. beginCompoundEdit();
  1315. for(int i = 0; i < lines.length; i++)
  1316. {
  1317. int lineStart = getLineStartOffset(lines[i]);
  1318. String line = getLineText(lines[i]);
  1319. int whiteSpace = MiscUtilities
  1320. .getLeadingWhiteSpace(line);
  1321. if(whiteSpace == 0)
  1322. continue;
  1323. int whiteSpaceWidth = Math.max(0,MiscUtilities
  1324. .getLeadingWhiteSpaceWidth(line,tabSize)
  1325. - indentSize);
  1326. remove(lineStart,whiteSpace);
  1327. insert(lineStart,MiscUtilities
  1328. .createWhiteSpace(whiteSpaceWidth,
  1329. (noTabs ? 0 : tabSize)));
  1330. }
  1331. }
  1332. finally
  1333. {
  1334. endCompoundEdit();
  1335. }
  1336. } //}}}
  1337. //{{{ shiftIndentRight() method
  1338. /**
  1339. * Shifts the indent of each line in the specified list to the right.
  1340. * @param lines The line numbers
  1341. * @since jEdit 3.2pre1
  1342. */
  1343. public void shiftIndentRight(int[] lines)
  1344. {
  1345. try
  1346. {
  1347. beginCompoundEdit();
  1348. int tabSize = getTabSize();
  1349. int indentSize = getIndentSize();
  1350. boolean noTabs = getBooleanProperty("noTabs");
  1351. for(int i = 0; i < lines.length; i++)
  1352. {
  1353. int lineStart = getLineStartOffset(lines[i]);
  1354. String line = getLineText(lines[i]);
  1355. int whiteSpace = MiscUtilities
  1356. .getLeadingWhiteSpace(line);
  1357. // silly usability hack
  1358. //if(lines.length != 1 && whiteSpace == 0)
  1359. // continue;
  1360. int whiteSpaceWidth = MiscUtilities
  1361. .getLeadingWhiteSpaceWidth(
  1362. line,tabSize) + indentSize;
  1363. remove(lineStart,whiteSpace);
  1364. insert(lineStart,MiscUtilities
  1365. .createWhiteSpace(whiteSpaceWidth,
  1366. (noTabs ? 0 : tabSize)));
  1367. }
  1368. }
  1369. finally
  1370. {
  1371. endCompoundEdit();
  1372. }
  1373. } //}}}
  1374. //}}}
  1375. //{{{ Undo
  1376. //{{{ undo() method
  1377. /**
  1378. * Undoes the most recent edit.
  1379. *
  1380. * @since jEdit 4.0pre1
  1381. */
  1382. public void undo(JEditTextArea textArea)
  1383. {
  1384. if(undoMgr == null)
  1385. return;
  1386. if(!isEditable())
  1387. {
  1388. textArea.getToolkit().beep();
  1389. return;
  1390. }
  1391. try
  1392. {
  1393. writeLock();
  1394. setFlag(UNDO_IN_PROGRESS,true);
  1395. if(!undoMgr.undo(textArea))
  1396. textArea.getToolkit().beep();
  1397. }
  1398. finally
  1399. {
  1400. setFlag(UNDO_IN_PROGRESS,false);
  1401. writeUnlock();
  1402. }
  1403. } //}}}
  1404. //{{{ redo() method
  1405. /**
  1406. * Redoes the most recently undone edit. Returns true if the redo was
  1407. * successful.
  1408. *
  1409. * @since jEdit 2.7pre2
  1410. */
  1411. public void redo(JEditTextArea textArea)
  1412. {
  1413. if(undoMgr == null)
  1414. return;
  1415. if(!isEditable())
  1416. {
  1417. Toolkit.getDefaultToolkit().beep();
  1418. return;
  1419. }
  1420. try
  1421. {
  1422. writeLock();
  1423. setFlag(UNDO_IN_PROGRESS,true);
  1424. if(!undoMgr.redo(textArea))
  1425. textArea.getToolkit().beep();
  1426. }
  1427. finally
  1428. {
  1429. setFlag(UNDO_IN_PROGRESS,false);
  1430. writeUnlock();
  1431. }
  1432. } //}}}
  1433. //{{{ beginCompoundEdit() method
  1434. /**
  1435. * Starts a compound edit. All edits from now on until
  1436. * <code>endCompoundEdit()</code> are called will be merged
  1437. * into one. This can be used to make a complex operation
  1438. * undoable in one step. Nested calls to
  1439. * <code>beginCompoundEdit()</code> behave as expected,
  1440. * requiring the same number of <code>endCompoundEdit()</code>
  1441. * calls to end the edit.
  1442. * @see #endCompoundEdit()
  1443. * @see #undo()
  1444. */
  1445. public void beginCompoundEdit()
  1446. {
  1447. if(getFlag(TEMPORARY))
  1448. return;
  1449. try
  1450. {
  1451. writeLock();
  1452. undoMgr.beginCompoundEdit();
  1453. }
  1454. finally
  1455. {
  1456. writeUnlock();
  1457. }
  1458. } //}}}
  1459. //{{{ endCompoundEdit() method
  1460. /**
  1461. * Ends a compound edit. All edits performed since
  1462. * <code>beginCompoundEdit()</code> was called can now
  1463. * be undone in one step by calling <code>undo()</code>.
  1464. * @see #beginCompoundEdit()
  1465. * @see #undo()
  1466. */
  1467. public void endCompoundEdit()
  1468. {
  1469. if(getFlag(TEMPORARY))
  1470. return;
  1471. try
  1472. {
  1473. writeLock();
  1474. undoMgr.endCompoundEdit();
  1475. }
  1476. finally
  1477. {
  1478. writeUnlock();
  1479. }
  1480. }//}}}
  1481. //{{{ insideCompoundEdit() method
  1482. /**
  1483. * Returns if a compound edit is currently active.
  1484. * @since jEdit 3.1pre1
  1485. */
  1486. public boolean insideCompoundEdit()
  1487. {
  1488. return undoMgr.insideCompoundEdit();
  1489. } //}}}
  1490. //}}}
  1491. //{{{ Buffer events
  1492. //{{{ addBufferChangeListener() method
  1493. /**
  1494. * Adds a buffer change listener.
  1495. * @param listener The listener
  1496. * @since jEdit 4.0pre1
  1497. */
  1498. public void addBufferChangeListener(BufferChangeListener l)
  1499. {
  1500. bufferListeners.addElement(l);
  1501. } //}}}
  1502. //{{{ removeBufferChangeListener() method
  1503. /**
  1504. * Removes a buffer change listener.
  1505. * @param listener The listener
  1506. * @since jEdit 4.0pre1
  1507. */
  1508. public void removeBufferChangeListener(BufferChangeListener l)
  1509. {
  1510. bufferListeners.removeElement(l);
  1511. } //}}}
  1512. //}}}
  1513. //{{{ Property methods
  1514. //{{{ propertiesChanged() method
  1515. /**
  1516. * Reloads settings from the properties. This should be called
  1517. * after the <code>syntax</code> or <code>folding</code>
  1518. * buffer-local properties are changed.
  1519. */
  1520. public void propertiesChanged()
  1521. {
  1522. setTokenMarker(mode.getTokenMarker());
  1523. String folding = getStringProperty("folding");
  1524. if("explicit".equals(folding))
  1525. setFoldHandler(new ExplicitFoldHandler());
  1526. else if("indent".equals(folding))
  1527. setFoldHandler(new IndentFoldHandler());
  1528. else
  1529. setFoldHandler(new DummyFoldHandler());
  1530. } //}}}
  1531. //{{{ getTabSize() method
  1532. /**
  1533. * Returns the tab size used in this buffer. This is equivalent
  1534. * to calling getProperty("tabSize").
  1535. */
  1536. public int getTabSize()
  1537. {
  1538. return getIntegerProperty("tabSize",8);
  1539. } //}}}
  1540. //{{{ getIndentSize() method
  1541. /**
  1542. * Returns the indent size used in this buffer. This is equivalent
  1543. * to calling getProperty("indentSize").
  1544. * @since jEdit 2.7pre1
  1545. */
  1546. public final int getIndentSize()
  1547. {
  1548. return getIntegerProperty("indentSize",8);
  1549. } //}}}
  1550. //{{{ getProperty() method
  1551. /**
  1552. * Returns the value of a buffer-local property.
  1553. * @param name The property name. For backwards compatibility, this
  1554. * is an <code>Object</code>, not a <code>String</code>.
  1555. */
  1556. public Object getProperty(Object name)
  1557. {
  1558. // First try the buffer-local properties
  1559. Object o = properties.get(name);
  1560. if(o != null)
  1561. return o;
  1562. // For backwards compatibility
  1563. if(!(name instanceof String))
  1564. return null;
  1565. // Now try mode.<mode>.<property>
  1566. if(mode != null)
  1567. return mode.getProperty((String)name);
  1568. else
  1569. {
  1570. // Now try buffer.<property>
  1571. String value = jEdit.getProperty("buffer." + name);
  1572. if(value == null)
  1573. return null;
  1574. // Try returning it as an integer first
  1575. try
  1576. {
  1577. return new Integer(value);
  1578. }
  1579. catch(NumberFormatException nf)
  1580. {
  1581. return value;
  1582. }
  1583. }
  1584. } //}}}
  1585. //{{{ setProperty() method
  1586. /**
  1587. * Sets the value of a buffer-local property.
  1588. * @param name The property name
  1589. * @param value The property value
  1590. * @since jEdit 4.0pre1
  1591. */
  1592. public void setProperty(String name, Object value)
  1593. {
  1594. putProperty(name,value);
  1595. } //}}}
  1596. //{{{ unsetProperty() method
  1597. /**
  1598. * Clears the value of a buffer-local property.
  1599. * @param name The property name
  1600. * @since jEdit 4.0pre1
  1601. */
  1602. public void unsetProperty(String name)
  1603. {
  1604. properties.remove(name);
  1605. } //}}}
  1606. //{{{ getStringProperty() method
  1607. /**
  1608. * Returns the value of a string property.
  1609. * @param name The property name
  1610. * @since jEdit 4.0pre1
  1611. */
  1612. public String getStringProperty(String name)
  1613. {
  1614. Object obj = getProperty(name);
  1615. if(obj != null)
  1616. return obj.toString();
  1617. else
  1618. return null;
  1619. } //}}}
  1620. //{{{ setStringProperty() method
  1621. /**
  1622. * Sets a string property.
  1623. * @param name The property name
  1624. * @param value The value
  1625. * @since jEdit 4.0pre1
  1626. */
  1627. public void setStringProperty(String name, String value)
  1628. {
  1629. setProperty(name,value);
  1630. } //}}}
  1631. //{{{ getBooleanProperty() method
  1632. /**
  1633. * Returns the value of a boolean property.
  1634. * @param name The property name
  1635. * @since jEdit 4.0pre1
  1636. */
  1637. public boolean getBooleanProperty(String name)
  1638. {
  1639. Object obj = getProperty(name);
  1640. if(obj instanceof Boolean)
  1641. return ((Boolean)obj).booleanValue();
  1642. else if("true".equals(obj) || "on".equals(obj) || "yes".equals(obj))
  1643. return true;
  1644. else
  1645. return false;
  1646. } //}}}
  1647. //{{{ setBooleanProperty() method
  1648. /**
  1649. * Sets a boolean property.
  1650. * @param name The property name
  1651. * @param value The value
  1652. * @since jEdit 4.0pre1
  1653. */
  1654. public void setBooleanProperty(String name, boolean value)
  1655. {
  1656. setProperty(name,value ? Boolean.TRUE : Boolean.FALSE);
  1657. } //}}}
  1658. //{{{ getIntegerProperty() method
  1659. /**
  1660. * Returns the value of an integer property.
  1661. * @param name The property name
  1662. * @since jEdit 4.0pre1
  1663. */
  1664. public int getIntegerProperty(String name, int defaultValue)
  1665. {
  1666. Object obj = getProperty(name);
  1667. if(obj instanceof Number)
  1668. return ((Number)obj).intValue();
  1669. else
  1670. {
  1671. try
  1672. {
  1673. int value = Integer.parseInt(getStringProperty(name));
  1674. properties.put(name,new Integer(value));
  1675. return value;
  1676. }
  1677. catch(Exception e)
  1678. {
  1679. return defaultValue;
  1680. }
  1681. }
  1682. } //}}}
  1683. //{{{ setIntegerProperty() method
  1684. /**
  1685. * Sets an integer property.
  1686. * @param name The property name
  1687. * @param value The value
  1688. * @since jEdit 4.0pre1
  1689. */
  1690. public void setIntegerProperty(String name, int value)
  1691. {
  1692. setProperty(name,new Integer(value));
  1693. } //}}}
  1694. //{{{ getKeywordMapAtOffset() method
  1695. /**
  1696. * Returns the syntax highlighting keyword map in effect at the
  1697. * specified offset. Used by the <b>Complete Word</b> command to
  1698. * complete keywords.
  1699. * @param offset The offset
  1700. * @since jEdit 4.0pre3
  1701. */
  1702. public KeywordMap getKeywordMapAtOffset(int offset)
  1703. {
  1704. return getRuleSetAtOffset(offset).getKeywords();
  1705. } //}}}
  1706. //{{{ getContextSensitiveProperty() method
  1707. /**
  1708. * Some settings, like comment start and end strings, can
  1709. * vary between different parts of a buffer (HTML text and inline
  1710. * JavaScript, for example).
  1711. * @param offset The offset
  1712. * @param name The property name
  1713. * @since jEdit 4.0pre3
  1714. */
  1715. public String getContextSensitiveProperty(int offset, String name)
  1716. {
  1717. ParserRuleSet rules = getRuleSetAtOffset(offset);
  1718. Object value = null;
  1719. Hashtable rulesetProps = rules.getProperties();
  1720. if(rulesetProps != null)
  1721. value = rulesetProps.get(name);
  1722. if(value == null)
  1723. {
  1724. value = rules.getMode().getProperty(name);
  1725. if(value == null)
  1726. value = mode.getProperty(name);
  1727. }
  1728. if(value == null)
  1729. return null;
  1730. else
  1731. return String.valueOf(value);
  1732. } //}}}
  1733. //}}}
  1734. //{{{ Edit modes, syntax highlighting, auto indent
  1735. //{{{ getMode() method
  1736. /**
  1737. * Returns this buffer's edit mode.
  1738. */
  1739. public final Mode getMode()
  1740. {
  1741. return mode;
  1742. } //}}}
  1743. //{{{ setMode() method
  1744. /**
  1745. * Sets this buffer's edit mode. Note that calling this before a buffer
  1746. * is loaded will have no effect; in that case, set the "mode" property
  1747. * to the name of the mode. A bit inelegant, I know...
  1748. * @param mode The mode
  1749. */
  1750. public void setMode(Mode mode)
  1751. {
  1752. /* This protects against stupid people (like me)
  1753. * doing stuff like buffer.setMode(jEdit.getMode(...)); */
  1754. if(mode == null)
  1755. throw new NullPointerException("Mode must be non-null");
  1756. // still need to set up new fold handler, etc even if mode not
  1757. // changed.
  1758. //if(this.mode == mode)
  1759. // return;
  1760. //{{{ Reset cached properties
  1761. if(getProperty("tabSize")
  1762. .equals(mode.getProperty("tabSize")))
  1763. unsetProperty("tabSize");
  1764. if(getProperty("indentSize")
  1765. .equals(mode.getProperty("indentSize")))
  1766. unsetProperty("indentSize");
  1767. if(getProperty("maxLineLen")
  1768. .equals(mode.getProperty("maxLineLen")))
  1769. unsetProperty("maxLineLen");
  1770. //}}}
  1771. Mode oldMode = this.mode;
  1772. this.mode = mode;
  1773. //{{{ Cache these for improved performance
  1774. putProperty("tabSize",getProperty("tabSize"));
  1775. putProperty("indentSize",getProperty("indentSize"));
  1776. putProperty("maxLineLen",getProperty("maxLineLen"));
  1777. //}}}
  1778. propertiesChanged(); // sets up token marker
  1779. // don't fire it for initial mode set
  1780. if(oldMode != null && !getFlag(TEMPORARY))
  1781. {
  1782. EditBus.send(new BufferUpdate(this,null,
  1783. BufferUpdate.MODE_CHANGED));
  1784. }
  1785. } //}}}
  1786. //{{{ setMode() method
  1787. /**
  1788. * Sets this buffer's edit mode by calling the accept() method
  1789. * of each registered edit mode.
  1790. */
  1791. public void setMode()
  1792. {
  1793. setModeForFirstLine(getLineText(0));
  1794. } //}}}
  1795. //{{{ indentLine() method
  1796. /**
  1797. * If auto indent is enabled, this method is called when the `Tab'
  1798. * or `Enter' key is pressed to perform mode-specific indentation
  1799. * and return true, or return false if a normal tab is to be inserted.
  1800. * @param line The line number to indent
  1801. * @param canIncreaseIndent If false, nothing will be done if the
  1802. * calculated indent is greater than the current
  1803. * @param canDecreaseIndent If false, nothing will be done if the
  1804. * calculated indent is less than the current
  1805. * @return true if the tab key event should be swallowed (ignored)
  1806. * false if a real tab should be inserted
  1807. */
  1808. public boolean indentLine(int lineIndex, boolean canIncreaseIndent,
  1809. boolean canDecreaseIndent)
  1810. {
  1811. if(lineIndex == 0)
  1812. return false;
  1813. // Get properties
  1814. String openBrackets = (String)getProperty("indentOpenBrackets");
  1815. String closeBrackets = (String)getProperty("indentCloseBrackets");
  1816. String _indentPrevLine = (String)getProperty("indentPrevLine");
  1817. boolean doubleBracketIndent = getBooleanProperty("doubleBracketIndent");
  1818. RE indentPrevLineRE = null;
  1819. if(openBrackets == null)
  1820. openBrackets = "";
  1821. if(closeBrackets == null)
  1822. closeBrackets = "";
  1823. if(_indentPrevLine != null)
  1824. {
  1825. try
  1826. {
  1827. indentPrevLineRE = new RE(_indentPrevLine,
  1828. RE.REG_ICASE,RESearchMatcher.RE_SYNTAX_JEDIT);
  1829. }
  1830. catch(REException re)
  1831. {
  1832. Log.log(Log.ERROR,this,"Invalid 'indentPrevLine'"
  1833. + " regexp: " + _indentPrevLine);
  1834. Log.log(Log.ERROR,this,re);
  1835. }
  1836. }
  1837. int tabSize = getTabSize();
  1838. int indentSize = getIndentSize();
  1839. boolean noTabs = getBooleanProperty("noTabs");
  1840. Element map = getDefaultRootElement();
  1841. String prevLine = null;
  1842. String line = null;
  1843. Element lineElement = map.getElement(lineIndex);
  1844. int start = lineElement.getStartOffset();
  1845. // Get line text
  1846. line = getText(start,lineElement.getEndOffset() - start - 1);
  1847. for(int i = lineIndex - 1; i >= 0; i--)
  1848. {
  1849. lineElement = map.getElement(i);
  1850. int lineStart = lineElement.getStartOffset();
  1851. int len = lineElement.getEndOffset() - lineStart - 1;
  1852. if(len != 0)
  1853. {
  1854. prevLine = getText(lineStart,len);
  1855. break;
  1856. }
  1857. }
  1858. if(prevLine == null)
  1859. return false;
  1860. /*
  1861. * If 'prevLineIndent' matches a line --> +1
  1862. */
  1863. boolean prevLineMatches = (indentPrevLineRE == null ? false
  1864. : indentPrevLineRE.isMatch(prevLine));
  1865. /*
  1866. * On the previous line,
  1867. * if(bob) { --> +1
  1868. * if(bob) { } --> 0
  1869. * } else if(bob) { --> +1
  1870. */
  1871. boolean prevLineStart = true; // False after initial indent
  1872. int prevLineIndent = 0; // Indent width (tab expanded)
  1873. int prevLineBrackets = 0; // Additional bracket indent
  1874. for(int i = 0; i < prevLine.length(); i++)
  1875. {
  1876. char c = prevLine.charAt(i);
  1877. switch(c)
  1878. {
  1879. case ' ':
  1880. if(prevLineStart)
  1881. prevLineIndent++;
  1882. break;
  1883. case '\t':
  1884. if(prevLineStart)
  1885. {
  1886. prevLineIndent += (tabSize
  1887. - (prevLineIndent
  1888. % tabSize));
  1889. }
  1890. break;
  1891. default:
  1892. prevLineStart = false;
  1893. if(closeBrackets.indexOf(c) != -1)
  1894. prevLineBrackets = Math.max(
  1895. prevLineBrackets-1,0);
  1896. else if(openBrackets.indexOf(c) != -1)
  1897. {
  1898. /*
  1899. * If supressBracketAfterIndent is true
  1900. * and we have something that looks like:
  1901. * if(bob)
  1902. * {
  1903. * then the 'if' will not shift the indent,
  1904. * because of the {.
  1905. *
  1906. * If supressBracketAfterIndent is false,
  1907. * the above would be indented like:
  1908. * if(bob)
  1909. * {
  1910. */
  1911. if(!doubleBracketIndent)
  1912. prevLineMatches = false;
  1913. prevLineBrackets++;
  1914. }
  1915. break;
  1916. }
  1917. }
  1918. // This is a hack so that auto indent does not go haywire
  1919. // with explicit folding. Proper fix will be done later,
  1920. // when the auto indent is rewritten.
  1921. if(prevLineBrackets == 3)
  1922. prevLineBrackets = 0;
  1923. /*
  1924. * On the current line,
  1925. * } --> -1
  1926. * } else if(bob) { --> -1
  1927. * if(bob) { } --> 0
  1928. */
  1929. boolean lineStart = true; // False after initial indent
  1930. int lineIndent = 0; // Indent width (tab expanded)
  1931. int lineWidth = 0; // White space count
  1932. int lineBrackets = 0; // Additional bracket indent
  1933. int closeBracketIndex = -1; // For lining up closing
  1934. // and opening brackets
  1935. for(int i = 0; i < line.length(); i++)
  1936. {
  1937. char c = line.charAt(i);
  1938. switch(c)
  1939. {
  1940. case ' ':
  1941. if(lineStart)
  1942. {
  1943. lineIndent++;
  1944. lineWidth++;
  1945. }
  1946. break;
  1947. case '\t':
  1948. if(lineStart)
  1949. {

Large files files are truncated, but you can click here to view the full file