📄 buffer.java
字号:
/* * Buffer.java - jEdit buffer * :tabSize=8:indentSize=8:noTabs=false: * :folding=explicit:collapseFolds=1: * * Copyright (C) 1998, 2003 Slava Pestov * Portions copyright (C) 1999, 2000 mike dillon * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */package org.gjt.sp.jedit;//{{{ Importsimport gnu.regexp.*;import javax.swing.*;import javax.swing.text.*;import java.awt.Toolkit;import java.io.File;import java.util.*;import org.gjt.sp.jedit.browser.VFSBrowser;import org.gjt.sp.jedit.buffer.*;import org.gjt.sp.jedit.io.*;import org.gjt.sp.jedit.msg.*;import org.gjt.sp.jedit.search.RESearchMatcher;import org.gjt.sp.jedit.syntax.*;import org.gjt.sp.jedit.textarea.*;import org.gjt.sp.util.*;//}}}/** * A <code>Buffer</code> represents the contents of an open text * file as it is maintained in the computer's memory (as opposed to * how it may be stored on a disk).<p> * * In a BeanShell script, you can obtain the current buffer instance from the * <code>buffer</code> variable.<p> * * This class does not have a public constructor. * Buffers can be opened and closed using methods in the <code>jEdit</code> * class.<p> * * This class is partially thread-safe, however you must pay attention to two * very important guidelines: * <ul> * <li>Changes to a buffer can only be made from the AWT thread. * <li>When accessing the buffer from another thread, you must * grab a read lock if you plan on performing more than one call, to ensure that * the buffer contents are not changed by the AWT thread for the duration of the * lock. Only methods whose descriptions specify thread safety can be invoked * from other threads. * </ul> * * @author Slava Pestov * @version $Id: Buffer.java,v 1.126 2003/02/23 04:05:21 spestov Exp $ */public class Buffer implements EBComponent{ //{{{ Some constants /** * Line separator property. */ public static final String LINESEP = "lineSeparator"; /** * Backed up property. * @since jEdit 3.2pre2 */ public static final String BACKED_UP = "Buffer__backedUp"; /** * Caret info properties. * @since jEdit 3.2pre1 */ public static final String CARET = "Buffer__caret"; public static final String SELECTION = "Buffer__selection"; /** * This should be a physical line number, so that the scroll * position is preserved correctly across reloads (which will * affect virtual line numbers, due to fold being reset) */ public static final String SCROLL_VERT = "Buffer__scrollVert"; public static final String SCROLL_HORIZ = "Buffer__scrollHoriz"; /** * Character encoding used when loading and saving. * @since jEdit 3.2pre4 */ public static final String ENCODING = "encoding"; /** * This property is set to 'true' if the file has a trailing newline. * @since jEdit 4.0pre1 */ public static final String TRAILING_EOL = "trailingEOL"; /** * This property is set to 'true' if the file should be GZipped. * @since jEdit 4.0pre4 */ public static final String GZIPPED = "gzipped"; //}}} //{{{ Input/output methods //{{{ showInsertFileDialog() method /** * Displays the 'insert file' dialog box and inserts the selected file * into the buffer. * @param view The view * @since jEdit 2.7pre2 */ public void showInsertFileDialog(View view) { String[] files = GUIUtilities.showVFSFileDialog(view,null, VFSBrowser.OPEN_DIALOG,false); if(files != null) insertFile(view,files[0]); } //}}} //{{{ reload() method /** * Reloads the buffer from disk, asking for confirmation if the buffer * has unsaved changes. * @param view The view * @since jEdit 2.7pre2 */ public void reload(View view) { if(getFlag(DIRTY)) { String[] args = { name }; int result = GUIUtilities.confirm(view,"changedreload", args,JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if(result != JOptionPane.YES_OPTION) return; } view.getEditPane().saveCaretInfo(); load(view,true); } //}}} //{{{ load() method /** * Loads the buffer from disk, even if it is loaded already. * @param view The view * @param reload If true, user will not be asked to recover autosave * file, if any * * @since 2.5pre1 */ public boolean load(final View view, final boolean reload) { if(isPerformingIO()) { GUIUtilities.error(view,"buffer-multiple-io",null); return false; } setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false); setFlag(LOADING,true); // view text areas temporarily blank out while a buffer is // being loaded, to indicate to the user that there is no // data available yet. if(!getFlag(TEMPORARY)) EditBus.send(new BufferUpdate(this,view,BufferUpdate.LOAD_STARTED)); final boolean loadAutosave; if(reload || !getFlag(NEW_FILE)) { if(file != null) modTime = file.lastModified(); // Only on initial load if(!reload && autosaveFile != null && autosaveFile.exists()) loadAutosave = recoverAutosave(view); else { if(autosaveFile != null) autosaveFile.delete(); loadAutosave = false; } if(!loadAutosave) { // this returns false if initial sanity // checks (if the file is a directory, etc) // fail if(!vfs.load(view,this,path)) { setFlag(LOADING,false); return false; } } } else loadAutosave = false; //{{{ Do some stuff once loading is finished Runnable runnable = new Runnable() { public void run() { String newPath = getStringProperty( BufferIORequest.NEW_PATH); Segment seg = (Segment)getProperty( BufferIORequest.LOAD_DATA); IntegerArray endOffsets = (IntegerArray) getProperty(BufferIORequest.END_OFFSETS); // below remove() call only works if read only // is false. this is a slightly silly workaround. boolean readOnly = isReadOnly(); setFlag(READ_ONLY,false); // For `reload' command remove(0,getLength()); if(seg != null && endOffsets != null) { // This is faster than Buffer.insert() try { writeLock(); // theoretically a segment could // have seg.offset != 0 but // SegmentBuffer never does that contentMgr._setContent(seg.array,seg.count); contentInserted(0,seg.count,endOffsets); } catch(OutOfMemoryError oom) { Log.log(Log.ERROR,this,oom); VFSManager.error(view,path,"out-of-memory-error",null); } finally { writeUnlock(); } } setFlag(READ_ONLY,readOnly); unsetProperty(BufferIORequest.LOAD_DATA); unsetProperty(BufferIORequest.END_OFFSETS); unsetProperty(BufferIORequest.NEW_PATH); undoMgr.clear(); undoMgr.setLimit(jEdit.getIntegerProperty( "buffer.undoCount",100)); if(!getFlag(TEMPORARY)) finishLoading(); /* Ultra-obscure: we have to fire this event * after the buffer might have been collapsed * by finishLoading(), since finishLoading(), * unlike "official" fold APIs, does not notify * the text area to invalidate its cached * virtual to physical information. Note that * the text area's contentInserted() handler * updates 'lastPhysLine' even if the LOADING * flag is set. */ fireContentInserted(0,0,getLineCount(),getLength() - 1); setFlag(LOADING,false); // if reloading a file, clear dirty flag if(reload) setDirty(false); if(!loadAutosave && newPath != null && !path.equals(newPath)) setPath(newPath); // if loadAutosave is false, we loaded an // autosave file, so we set 'dirty' to true // note that we don't use setDirty(), // because a) that would send an unnecessary // message, b) it would also set the // AUTOSAVE_DIRTY flag, which will make // the autosave thread write out a // redundant autosave file if(loadAutosave) setFlag(DIRTY,true); // send some EditBus messages if(!getFlag(TEMPORARY)) { EditBus.send(new BufferUpdate(Buffer.this, view,BufferUpdate.LOADED)); //EditBus.send(new BufferUpdate(Buffer.this, // view,BufferUpdate.MARKERS_CHANGED)); } } }; //}}} if(getFlag(TEMPORARY)) runnable.run(); else VFSManager.runInAWTThread(runnable); return true; } //}}} //{{{ insertFile() method /** * Loads a file from disk, and inserts it into this buffer. * @param view The view * * @since 4.0pre1 */ public boolean insertFile(final View view, String path) { if(isPerformingIO()) { GUIUtilities.error(view,"buffer-multiple-io",null); return false; } setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false); path = MiscUtilities.constructPath(this.path,path); Buffer buffer = jEdit.getBuffer(path); if(buffer != null) { view.getTextArea().setSelectedText( buffer.getText(0,buffer.getLength())); return true; } VFS vfs = VFSManager.getVFSForPath(path); setFlag(IO,true); // this returns false if initial sanity // checks (if the file is a directory, etc) // fail if(!vfs.insert(view,this,path)) { setFlag(IO,false); return false; } // Do some stuff once loading is finished VFSManager.runInAWTThread(new Runnable() { public void run() { setFlag(IO,false); SegmentBuffer sbuf = (SegmentBuffer)getProperty( BufferIORequest.LOAD_DATA); if(sbuf != null) { unsetProperty(BufferIORequest.LOAD_DATA); view.getTextArea().setSelectedText(sbuf.toString()); } } }); return true; } //}}} //{{{ autosave() method /** * Autosaves this buffer. */ public void autosave() { if(autosaveFile == null || !getFlag(AUTOSAVE_DIRTY) || !getFlag(DIRTY) || getFlag(LOADING) || getFlag(IO)) return; setFlag(AUTOSAVE_DIRTY,false); VFSManager.runInWorkThread(new BufferIORequest( BufferIORequest.AUTOSAVE,null,this,null, VFSManager.getFileVFS(),autosaveFile.getPath())); } //}}} //{{{ saveAs() method /** * Prompts the user for a file to save this buffer to. * @param view The view * @param rename True if the buffer's path should be changed, false * if only a copy should be saved to the specified filename * @since jEdit 2.6pre5 */ public boolean saveAs(View view, boolean rename) { String[] files = GUIUtilities.showVFSFileDialog(view,path, VFSBrowser.SAVE_DIALOG,false); // files[] should have length 1, since the dialog type is // SAVE_DIALOG if(files == null) return false; return save(view,files[0],rename); } //}}} //{{{ save() method /** * Saves this buffer to the specified path name, or the current path * name if it's null. * @param view The view * @param path The path name to save the buffer to, or null to use * the existing path */ public boolean save(View view, String path) { return save(view,path,true); } //}}} //{{{ save() method /** * Saves this buffer to the specified path name, or the current path * name if it's null. * @param view The view * @param path The path name to save the buffer to, or null to use * the existing path * @param rename True if the buffer's path should be changed, false * if only a copy should be saved to the specified filename * @since jEdit 2.6pre5 */ public boolean save(final View view, String path, final boolean rename) { if(isPerformingIO()) { GUIUtilities.error(view,"buffer-multiple-io",null); return false; } setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false); if(path == null && getFlag(NEW_FILE)) return saveAs(view,rename); if(path == null && file != null) { long newModTime = file.lastModified(); if(newModTime != modTime && jEdit.getBooleanProperty("view.checkModStatus")) { Object[] args = { this.path }; int result = GUIUtilities.confirm(view, "filechanged-save",args, JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if(result != JOptionPane.YES_OPTION) return false; } } setFlag(IO,true); EditBus.send(new BufferUpdate(this,view,BufferUpdate.SAVING)); final String oldPath = this.path; final String newPath = (path == null ? this.path : path); VFS vfs = VFSManager.getVFSForPath(newPath); if(!vfs.save(view,this,newPath)) { setFlag(IO,false); return false; } // Once save is complete, do a few other things VFSManager.runInAWTThread(new Runnable() { public void run() { setFlag(IO,false); finishSaving(view,oldPath,newPath,rename, getBooleanProperty(BufferIORequest .ERROR_OCCURRED)); } }); return true; } //}}} //{{{ checkFileStatus() method public static final int FILE_NOT_CHANGED = 0; public static final int FILE_CHANGED = 1; public static final int FILE_DELETED = 2; /** * Check if the buffer has changed on disk. * @return One of <code>NOT_CHANGED</code>, <code>CHANGED</code>, or * <code>DELETED</code>. * * @since jEdit 4.1pre3 */ public int checkFileStatus() { // - don't do these checks while a save is in progress, // because for a moment newModTime will be greater than // oldModTime, due to the multithreading // - only supported on local file system if(!getFlag(IO) && !getFlag(LOADING) && file != null && !getFlag(NEW_FILE)) { boolean newReadOnly = (file.exists() && !file.canWrite()); if(newReadOnly != getFlag(READ_ONLY)) { setFlag(READ_ONLY,newReadOnly); EditBus.send(new BufferUpdate(this,null, BufferUpdate.DIRTY_CHANGED)); } long oldModTime = modTime; long newModTime = file.lastModified(); if(newModTime != oldModTime) { modTime = newModTime; if(!file.exists()) { setFlag(NEW_FILE,true); EditBus.send(new BufferUpdate(this,null, BufferUpdate.DIRTY_CHANGED)); return FILE_DELETED; } else return FILE_CHANGED; } } return FILE_NOT_CHANGED; } //}}} //{{{ checkModTime() method /** * Check if the buffer has changed on disk. * @since jEdit 4.1pre8 */ public void checkModTime(EditPane editPane) { View view = editPane.getView(); int status = checkFileStatus(); // still need to call the status check even if this option is off, // so that the write protection is updated if it changes on disk if(!jEdit.getBooleanProperty("view.checkModStatus")) return; if(status == FILE_DELETED) { Object[] args = { path }; GUIUtilities.message(view,"filedeleted",args); }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -