0001: /*
0002: * Buffer.java
0003: *
0004: * Copyright (C) 1998-2003 Peter Graves
0005: * $Id: Buffer.java,v 1.52 2004/08/30 17:13:48 piso Exp $
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License
0009: * as published by the Free Software Foundation; either version 2
0010: * of the License, or (at your option) any later version.
0011: *
0012: * This program is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0015: * GNU General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * along with this program; if not, write to the Free Software
0019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0020: */
0021:
0022: package org.armedbear.j;
0023:
0024: import java.awt.Cursor;
0025: import java.io.BufferedInputStream;
0026: import java.io.BufferedOutputStream;
0027: import java.io.BufferedReader;
0028: import java.io.IOException;
0029: import java.io.InputStream;
0030: import java.io.InputStreamReader;
0031: import java.io.OutputStream;
0032: import java.lang.ref.SoftReference;
0033: import java.util.ArrayList;
0034: import java.util.Iterator;
0035: import java.util.List;
0036: import java.util.zip.GZIPInputStream;
0037: import java.util.zip.GZIPOutputStream;
0038: import java.util.zip.ZipEntry;
0039: import javax.swing.Icon;
0040: import javax.swing.SwingUtilities;
0041: import javax.swing.undo.CannotRedoException;
0042: import javax.swing.undo.CannotUndoException;
0043: import javax.swing.undo.CompoundEdit;
0044: import javax.swing.undo.UndoableEdit;
0045:
0046: public class Buffer extends SystemBuffer {
0047: private static int untitledCount;
0048:
0049: protected boolean isUntitled;
0050:
0051: protected Formatter formatter;
0052:
0053: protected String title;
0054:
0055: private boolean needsParsing;
0056:
0057: boolean needsRenumbering;
0058:
0059: public final boolean needsRenumbering() {
0060: return needsRenumbering;
0061: }
0062:
0063: public final void needsRenumbering(boolean b) {
0064: needsRenumbering = b;
0065: }
0066:
0067: private int visibleLineCount;
0068:
0069: protected boolean supportsUndo = true;
0070:
0071: public final boolean supportsUndo() {
0072: return supportsUndo;
0073: }
0074:
0075: private int modCount;
0076: private int saveModCount; // Value of modCount when last saved.
0077:
0078: // Autosave.
0079: protected boolean autosaveEnabled;
0080: private File autosaveFile;
0081: private int autosaveModCount; // Value of modCount when last autosaved.
0082:
0083: private File cache;
0084: private String listing;
0085:
0086: public final String getListing() {
0087: return listing;
0088: }
0089:
0090: public final void setListing(String s) {
0091: this .listing = s;
0092: }
0093:
0094: private View lastView;
0095:
0096: private boolean backedUp = false; // Ignored for local buffers.
0097:
0098: private int fileType = FILETYPE_UNKNOWN;
0099:
0100: private Compression compression;
0101:
0102: public final Compression getCompression() {
0103: return compression;
0104: }
0105:
0106: public final void setCompression(Compression compression) {
0107: this .compression = compression;
0108: }
0109:
0110: protected PropertyList properties = new PropertyList();
0111:
0112: private BackgroundProcess backgroundProcess;
0113:
0114: private Mutex mutex = new Mutex();
0115: private final ReadWriteLock rwlock = new ReadWriteLock();
0116:
0117: private boolean isNewFile;
0118:
0119: protected Buffer parentBuffer;
0120:
0121: public final Buffer getParentBuffer() {
0122: return parentBuffer;
0123: }
0124:
0125: public final void setParentBuffer(Buffer b) {
0126: this .parentBuffer = b;
0127: }
0128:
0129: public boolean isPrimary() {
0130: return true;
0131: }
0132:
0133: public boolean isSecondary() {
0134: return false;
0135: }
0136:
0137: public boolean isPaired() {
0138: return isSecondary() || getSecondary() != null;
0139: }
0140:
0141: public Buffer getPrimary() {
0142: return null;
0143: }
0144:
0145: public Buffer getSecondary() {
0146: return null;
0147: }
0148:
0149: public float getSplit() {
0150: return 0.5F;
0151: }
0152:
0153: public void promote() {
0154: }
0155:
0156: public final boolean isNewFile() {
0157: return isNewFile;
0158: }
0159:
0160: private final void setNewFile(boolean b) {
0161: isNewFile = b;
0162: }
0163:
0164: protected Buffer() {
0165: // Add new buffer to global buffer list.
0166: Editor.getBufferList().add(this );
0167: }
0168:
0169: // Called only by Editor.newBuffer().
0170: public Buffer(int i /*ignored*/) {
0171: this ();
0172: Debug.assertTrue(Editor.getBufferList().contains(this ));
0173: initializeUndo();
0174: type = TYPE_NORMAL;
0175: isUntitled = true;
0176: ++untitledCount;
0177: String name = "Untitled-" + untitledCount;
0178: File directory = Editor.currentEditor().getCurrentDirectory();
0179: if (directory == null || directory.isRemote())
0180: directory = Directories.getUserHomeDirectory();
0181: setFile(File.getInstance(directory, name));
0182: autosaveEnabled = true;
0183: lineSeparator = System.getProperty("line.separator");
0184: mode = PlainTextMode.getMode();
0185: formatter = mode.getFormatter(this );
0186: setNewFile(true);
0187: try {
0188: lockWrite();
0189: } catch (InterruptedException e) {
0190: Log.debug(e);
0191: return; // Shouldn't happen.
0192: }
0193: try {
0194: appendLine("");
0195: renumber();
0196: setLoaded(true);
0197: } finally {
0198: unlockWrite();
0199: }
0200: }
0201:
0202: public Buffer(File file) {
0203: this ();
0204: Debug.assertTrue(Editor.getBufferList().contains(this ));
0205: initializeUndo();
0206: setFile(file);
0207: type = TYPE_NORMAL;
0208: autosaveEnabled = true;
0209: }
0210:
0211: public static Buffer createBuffer(File file) {
0212: if (file instanceof FtpFile) {
0213: FtpSession session = FtpSession.getSession((FtpFile) file);
0214: if (session == null)
0215: return null;
0216: return new RemoteBuffer((FtpFile) file, session);
0217: }
0218: if (file instanceof HttpFile) {
0219: if (Editor.getModeList().modeAccepts(IMAGE_MODE,
0220: file.getName()))
0221: return new RemoteBuffer(file);
0222: if (Editor.preferences().getBooleanProperty(
0223: Property.ENABLE_WEB)) {
0224: int modeId = Editor.getModeList().getModeIdForFileName(
0225: file.getName());
0226: if (modeId < 0 || modeId == HTML_MODE)
0227: return WebBuffer.createWebBuffer(file, null, null);
0228: }
0229: return new RemoteBuffer(file);
0230: }
0231: if (file instanceof SshFile) {
0232: SshFile sshFile = (SshFile) file;
0233: SshSession session = SshSession.getSession(sshFile);
0234: if (session == null)
0235: return null;
0236: if (!session.isLocked()) {
0237: Debug.bug();
0238: return null;
0239: }
0240: session.checkLogin();
0241: if (sshFile.getUserName() == null) {
0242: Debug.bug();
0243: sshFile.setUserName(session.getUserName());
0244: }
0245: if (!sshFile.getUserName().equals(session.getUserName())) {
0246: Debug.bug();
0247: session.unlock();
0248: return null;
0249: }
0250: session.unlock();
0251: return new RemoteBuffer(file);
0252: }
0253: // Special case for unsent messages.
0254: File dir = file.getParentFile();
0255: if (dir != null && dir.equals(Directories.getDraftsFolder())) {
0256: Mode sendMailMode = Editor.getModeList().getMode(
0257: SEND_MAIL_MODE);
0258: if (sendMailMode != null)
0259: return sendMailMode.createBuffer(file);
0260: }
0261: // Local file.
0262: return createBuffer(file, null, null);
0263: }
0264:
0265: protected static Buffer createBuffer(File file, File cache,
0266: String listing) {
0267: Compression compression = null;
0268: int fileType = Utilities.getFileType(cache != null ? cache
0269: : file);
0270: if (fileType == FILETYPE_GZIP) {
0271: // If we're looking at a remote file, gunzip the cached copy
0272: // of it; otherwise, gunzip the file itself into the cache.
0273: File uncompressed = cacheGZIP(cache != null ? cache : file);
0274: if (uncompressed != null) {
0275: cache = uncompressed;
0276: fileType = Utilities.getFileType(cache);
0277: compression = new Compression(COMPRESSION_GZIP);
0278: } else
0279: fileType = FILETYPE_BINARY; // Something went wrong.
0280: }
0281: if (fileType == FILETYPE_JPEG
0282: || Editor.getModeList().modeAccepts(IMAGE_MODE,
0283: file.getName())) {
0284: Buffer buffer = ImageBuffer.createImageBuffer(file, cache,
0285: listing);
0286: if (buffer != null) {
0287: buffer.setFileType(fileType);
0288: return buffer;
0289: }
0290: }
0291: // Normal case.
0292: Buffer buffer = new Buffer(file);
0293: Debug.assertTrue(Editor.getBufferList().contains(buffer));
0294: buffer.setFileType(fileType);
0295: buffer.setCache(cache);
0296: buffer.setListing(listing);
0297: buffer.setCompression(compression);
0298: if (file.isLocal() && !file.isFile())
0299: buffer.setNewFile(true);
0300: return buffer;
0301: }
0302:
0303: // For Session.createBuffers().
0304: public static Buffer precreateBuffer(File file) {
0305: if (file == null) {
0306: Debug.bug();
0307: return null;
0308: }
0309: if (file.isRemote()) {
0310: Debug.bug();
0311: return null;
0312: }
0313: // Special case for unsent messages.
0314: File dir = file.getParentFile();
0315: if (dir != null && dir.equals(Directories.getDraftsFolder())) {
0316: Mode sendMailMode = Editor.getModeList().getMode(
0317: SEND_MAIL_MODE);
0318: if (sendMailMode != null)
0319: return sendMailMode.createBuffer(file);
0320: }
0321: // Normal case.
0322: return new Buffer(file);
0323: }
0324:
0325: private boolean initialized;
0326:
0327: public synchronized boolean initialized() {
0328: return initialized;
0329: }
0330:
0331: public synchronized void setInitialized(boolean b) {
0332: initialized = b;
0333: }
0334:
0335: public synchronized void initialize() {
0336: Debug.assertTrue(!initialized);
0337: final File file = getFile();
0338: if (fileType == FILETYPE_UNKNOWN) {
0339: fileType = Utilities.getFileType(cache != null ? cache
0340: : file);
0341: if (fileType == FILETYPE_GZIP) {
0342: // If we're looking at a remote file, gunzip the cached copy
0343: // of it; otherwise, gunzip the file itself into the cache.
0344: File uncompressed = cacheGZIP(cache != null ? cache
0345: : file);
0346: if (uncompressed != null) {
0347: cache = uncompressed;
0348: fileType = Utilities.getFileType(cache);
0349: compression = new Compression(COMPRESSION_GZIP);
0350: } else
0351: fileType = FILETYPE_BINARY; // Something went wrong.
0352: }
0353: }
0354: mode = getDefaultMode();
0355: formatter = mode.getFormatter(this );
0356: if (fileType == FILETYPE_ZIP) {
0357: supportsUndo = false;
0358: type = TYPE_ARCHIVE;
0359: readOnly = true;
0360: } else if (fileType == FILETYPE_BINARY) {
0361: readOnly = true;
0362: } else if (fileType == FILETYPE_WORD) {
0363: readOnly = true;
0364: } else if (file != null) {
0365: FileHistoryEntry entry = FileHistory.getFileHistory()
0366: .findEntry(file.netPath());
0367: if (entry != null) {
0368: // Set encoding.
0369: final String encoding = entry.getEncoding();
0370: if (encoding != null
0371: && Utilities.isSupportedEncoding(encoding))
0372: file.setEncoding(encoding);
0373: // Set mode.
0374: mode = Editor.getModeList().getModeFromModeName(
0375: entry.getMode());
0376: if (mode == null)
0377: mode = Editor.getModeList()
0378: .getMode(PLAIN_TEXT_MODE);
0379: else if (mode.getId() == BINARY_MODE)
0380: readOnly = true;
0381: formatter = mode.getFormatter(this );
0382: // Properties from FileHistoryEntry override defaults set by
0383: // mode.
0384: properties.putAll(entry.getProperties());
0385: }
0386: }
0387: if (file != null) {
0388: if (file.getProtocol() == File.PROTOCOL_HTTP
0389: || file.getProtocol() == File.PROTOCOL_HTTPS)
0390: readOnly = true;
0391: }
0392: initialized = true;
0393: }
0394:
0395: public Mode getDefaultMode() {
0396: final File file = getFile();
0397: final ModeList modeList = Editor.getModeList();
0398: switch (fileType) {
0399: case FILETYPE_XML:
0400: return modeList.getMode(XML_MODE);
0401: case FILETYPE_SHELLSCRIPT: {
0402: Mode m = grovelModeFromFile(file);
0403: if (m != null)
0404: return m;
0405: return modeList.getMode(SHELL_SCRIPT_MODE);
0406: }
0407: case FILETYPE_PERL:
0408: return modeList.getMode(PERL_MODE);
0409: case FILETYPE_PHP:
0410: return modeList.getMode(PHP_MODE);
0411: case FILETYPE_ZIP:
0412: return modeList.getMode(ARCHIVE_MODE);
0413: case FILETYPE_GZIP:
0414: case FILETYPE_BINARY:
0415: return modeList.getMode(BINARY_MODE);
0416: case FILETYPE_JPEG:
0417: return modeList.getMode(IMAGE_MODE);
0418: case FILETYPE_WORD:
0419: return modeList.getMode(WORD_MODE);
0420: case FILETYPE_TEXT:
0421: default: {
0422: Mode m = grovelModeFromFile(file);
0423: if (m == null) {
0424: if (compression != null
0425: && compression.getType() == COMPRESSION_ZIP) {
0426: String entryName = compression.getEntryName();
0427: if (entryName != null)
0428: m = getModeForFileName(entryName);
0429: } else if (file != null) {
0430: m = getModeForFileName(file.getName());
0431: }
0432: if (m != null && m.getId() == IMAGE_MODE) {
0433: if (fileType == FILETYPE_TEXT)
0434: m = modeList.getMode(PLAIN_TEXT_MODE);
0435: else
0436: m = modeList.getMode(BINARY_MODE);
0437: } else if (m == null) {
0438: if (file != null) {
0439: if (file.getProtocol() == File.PROTOCOL_HTTP
0440: || file.getProtocol() == File.PROTOCOL_HTTPS)
0441: m = modeList.getMode(HTML_MODE);
0442: }
0443: if (m == null)
0444: m = modeList.getMode(PLAIN_TEXT_MODE);
0445: }
0446: }
0447: return m;
0448: }
0449: }
0450: }
0451:
0452: private static final Mode grovelModeFromFile(File file) {
0453: if (file == null)
0454: return null;
0455: if (!file.isLocal())
0456: return null;
0457: if (!file.isFile())
0458: return null;
0459: Mode mode = null;
0460: try {
0461: BufferedReader reader = new BufferedReader(
0462: new InputStreamReader(file.getInputStream()));
0463: String s = reader.readLine();
0464: if (s != null) {
0465: mode = grovelModeFromString(s);
0466: if (mode == null && s.startsWith("#!")) {
0467: // Consider second line too.
0468: s = reader.readLine();
0469: if (s != null)
0470: mode = grovelModeFromString(s);
0471: }
0472: }
0473: reader.close();
0474: } catch (IOException e) {
0475: Log.error(e);
0476: }
0477: return mode;
0478: }
0479:
0480: private static final Mode grovelModeFromString(String s) {
0481: if (s != null) {
0482: int begin = s.indexOf("-*-");
0483: if (begin >= 0) {
0484: s = s.substring(begin + 3);
0485: int end = s.indexOf("-*-");
0486: if (end >= 0) {
0487: s = s.substring(0, end).trim().toLowerCase();
0488: int index = s.indexOf("mode:");
0489: String modeName;
0490: if (index < 0) {
0491: // "-*- Lisp -*-"
0492: modeName = s;
0493: } else {
0494: // "-*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-"
0495: s = s.substring(5).trim();
0496: for (end = 0; end < s.length(); end++) {
0497: char c = s.charAt(end);
0498: if (c == ' ' || c == '\t' || c == ';')
0499: break;
0500: }
0501: modeName = s.substring(0, end);
0502: }
0503: return Editor.getModeList().getModeFromModeName(
0504: modeName);
0505: }
0506: }
0507: }
0508: return null;
0509: }
0510:
0511: // Locking.
0512: public final boolean isInUse() {
0513: return mutex.isInUse();
0514: }
0515:
0516: public void acquire() throws InterruptedException {
0517: mutex.acquire();
0518: }
0519:
0520: public synchronized void release() {
0521: mutex.release();
0522: }
0523:
0524: public boolean attempt() throws InterruptedException {
0525: return mutex.attempt();
0526: }
0527:
0528: public boolean attempt(long msecs) throws InterruptedException {
0529: return mutex.attempt(msecs);
0530: }
0531:
0532: public final boolean isLocked() {
0533: return isInUse();
0534: }
0535:
0536: public synchronized boolean lock() {
0537: try {
0538: return attempt();
0539: } catch (InterruptedException e) {
0540: return false;
0541: }
0542: }
0543:
0544: public synchronized void unlock() {
0545: release();
0546: }
0547:
0548: public final void lockRead() throws InterruptedException {
0549: rwlock.lockRead();
0550: }
0551:
0552: public final void unlockRead() {
0553: rwlock.unlockRead();
0554: }
0555:
0556: public final void lockWrite() throws InterruptedException {
0557: rwlock.lockWrite();
0558: }
0559:
0560: public final void unlockWrite() {
0561: rwlock.unlockWrite();
0562: }
0563:
0564: public final boolean isWriteLocked() {
0565: return rwlock.isWriteLocked();
0566: }
0567:
0568: public boolean isVisible() {
0569: for (EditorIterator it = new EditorIterator(); it.hasNext();)
0570: if (it.nextEditor().getBuffer() == this )
0571: return true;
0572:
0573: return false;
0574: }
0575:
0576: public void setWaitCursor() {
0577: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
0578: Editor ed = it.nextEditor();
0579: if (ed.getBuffer() == this )
0580: ed.setWaitCursor();
0581: }
0582: }
0583:
0584: public void setDefaultCursor() {
0585: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
0586: Editor ed = it.nextEditor();
0587: if (ed.getBuffer() == this )
0588: ed.setDefaultCursor();
0589: }
0590: }
0591:
0592: public final PropertyList getProperties() {
0593: return properties;
0594: }
0595:
0596: public final void setCache(File file) {
0597: cache = file;
0598: }
0599:
0600: public final File getCache() {
0601: return cache;
0602: }
0603:
0604: public final Formatter getFormatter() {
0605: return formatter;
0606: }
0607:
0608: public final void setFormatter(Formatter formatter) {
0609: this .formatter = formatter;
0610: }
0611:
0612: public boolean isReadOnly() {
0613: return readOnly || forceReadOnly;
0614: }
0615:
0616: private boolean busy;
0617:
0618: public synchronized final boolean isBusy() {
0619: return busy;
0620: }
0621:
0622: public synchronized final void setBusy(boolean b) {
0623: busy = b;
0624: }
0625:
0626: public final BackgroundProcess getBackgroundProcess() {
0627: return backgroundProcess;
0628: }
0629:
0630: public final void setBackgroundProcess(
0631: BackgroundProcess backgroundProcess) {
0632: this .backgroundProcess = backgroundProcess;
0633: }
0634:
0635: public File getCurrentDirectory() {
0636: return getFile() != null ? getFile().getParentFile()
0637: : Directories.getUserHomeDirectory();
0638: }
0639:
0640: // Subclasses should override this method if appropriate!
0641: public File getCompletionDirectory() {
0642: final File file = getFile();
0643: if (file != null) {
0644: if (file.isLocal() || file instanceof SshFile)
0645: return file.isDirectory() ? file : file.getParentFile();
0646: }
0647: return Directories.getUserHomeDirectory();
0648: }
0649:
0650: public View getInitialView() {
0651: View view = new View();
0652: view.setDot(getInitialDotPos());
0653: return view;
0654: }
0655:
0656: private int initialLineNumber;
0657: private int initialOffset;
0658:
0659: public Position getInitialDotPos() {
0660: Line line = getLine(initialLineNumber);
0661: if (line == null) {
0662: line = getFirstLine();
0663: return line != null ? new Position(line, 0) : null;
0664: }
0665: return new Position(line, Math
0666: .min(initialOffset, line.length()));
0667: }
0668:
0669: public void setInitialDotPos(int lineNumber, int offset) {
0670: initialLineNumber = lineNumber;
0671: initialOffset = offset;
0672: }
0673:
0674: private long lastActivated;
0675:
0676: public final long getLastActivated() {
0677: return lastActivated;
0678: }
0679:
0680: public final void setLastActivated(long l) {
0681: lastActivated = l;
0682: }
0683:
0684: public final int getLineCount() {
0685: return lineCount;
0686: }
0687:
0688: public final int getModCount() {
0689: return modCount;
0690: }
0691:
0692: public final synchronized void setModCount(int count) {
0693: if (count != modCount) {
0694: modCount = count;
0695: srText = null;
0696: }
0697: }
0698:
0699: public final synchronized void incrementModCount() {
0700: ++modCount;
0701: srText = null;
0702: }
0703:
0704: public final void setModCountWhenLastSaved(int count) {
0705: autosaveModCount = count;
0706: }
0707:
0708: public final KeyMap getKeyMapForMode() {
0709: // Should never return null.
0710: return mode.getKeyMap();
0711: }
0712:
0713: public final int getFileType() {
0714: return fileType;
0715: }
0716:
0717: private final void setFileType(int fileType) {
0718: this .fileType = fileType;
0719: }
0720:
0721: private static File cacheGZIP(File f) {
0722: try {
0723: File tempFile = Utilities.getTempFile();
0724: if (tempFile != null) {
0725: InputStream in = new GZIPInputStream(f.getInputStream());
0726: if (in != null) {
0727: OutputStream out = tempFile.getOutputStream();
0728: byte[] buf = new byte[4096];
0729: int bytesRead;
0730: while ((bytesRead = in.read(buf)) > 0)
0731: out.write(buf, 0, bytesRead);
0732: out.close();
0733: in.close();
0734: return tempFile;
0735: }
0736: }
0737: } catch (IOException e) {
0738: Log.error(e);
0739: }
0740: return null;
0741: }
0742:
0743: // Handles all the paperwork when we rename a buffer.
0744: public void changeFile(File f) {
0745: if (f == null)
0746: return;
0747: String newName = f.canonicalPath();
0748: if (newName == null)
0749: return;
0750: File oldFile = getFile();
0751: String oldName = oldFile != null ? oldFile.canonicalPath()
0752: : null;
0753: setFile(f);
0754: setCompression(null);
0755: type = TYPE_NORMAL;
0756: title = null;
0757: Mode oldMode = mode;
0758: if (oldMode == PlainTextMode.getMode()) {
0759: setModeFromFilename(newName);
0760: if (mode != oldMode) {
0761: // Mode has changed.
0762: needsParsing = true;
0763:
0764: // Make sure we parse the buffer before we display it.
0765: if (formatter != null)
0766: formatter.parseBuffer();
0767: }
0768: }
0769: readOnly = false;
0770: isUntitled = false;
0771: if (oldName != null)
0772: Autosave.rename(oldName, newName);
0773: Editor.getBufferList().modified();
0774: Sidebar.setUpdateFlagInAllFrames(SIDEBAR_BUFFER_LIST_CHANGED);
0775: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
0776: Editor editor = it.nextEditor();
0777: if (editor.getBuffer() == this ) {
0778: editor.updateLocation();
0779: editor.getDisplay().repaint();
0780: }
0781: }
0782: }
0783:
0784: protected void setModeFromFilename(String filename) {
0785: mode = Editor.getModeList().getModeForFileName(filename);
0786: if (mode == null)
0787: mode = Editor.getModeList().getMode(PLAIN_TEXT_MODE);
0788: formatter = mode.getFormatter(this );
0789: }
0790:
0791: private static Mode getModeForFileName(String filename) {
0792: if (filename.endsWith(".gz"))
0793: filename = filename.substring(0, filename.length() - 3);
0794:
0795: // Strip path prefix (if any).
0796: // Look for '/' on both Windows and Unix (filename might be a ZipEntry name).
0797: int index = filename.lastIndexOf('/');
0798: if (index >= 0)
0799: filename = filename.substring(index + 1);
0800:
0801: // Look for '\' on Windows only.
0802: if (Platform.isPlatformWindows()) {
0803: index = filename.lastIndexOf('\\');
0804: if (index >= 0)
0805: filename = filename.substring(index + 1);
0806: }
0807:
0808: return Editor.getModeList().getModeForFileName(filename);
0809: }
0810:
0811: public final void setMode(Mode mode) {
0812: this .mode = mode;
0813: formatter = mode.getFormatter(this );
0814: }
0815:
0816: public void changeMode(Mode newMode) {
0817: final int oldModeId = mode.getId();
0818: if (oldModeId == DIRECTORY_MODE)
0819: return;
0820: final int newModeId = newMode.getId();
0821: if (newModeId != oldModeId) {
0822: // Must reload buffer if changing into or out of binary mode.
0823: boolean reloading = newModeId == BINARY_MODE
0824: || oldModeId == BINARY_MODE;
0825: mode = newMode;
0826: formatter = mode.getFormatter(this );
0827: if (reloading) {
0828: reload();
0829: if (newModeId == IMAGE_MODE)
0830: return;
0831: }
0832:
0833: if (newModeId == BINARY_MODE) {
0834: readOnly = true;
0835: } else {
0836: final File file = getFile();
0837: if (file != null && !file.isRemote() && file.isFile())
0838: readOnly = !file.canWrite();
0839: }
0840:
0841: formatter.parseBuffer();
0842:
0843: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
0844: Editor ed = it.nextEditor();
0845: if (ed.getBuffer() == this ) {
0846: if (reloading) {
0847: ed.setDot(getFirstLine(), 0);
0848: ed.setMark(null);
0849: ed.setTopLine(getFirstLine());
0850: ed.moveCaretToDotCol();
0851: ed.updateLocation();
0852: }
0853: ed.setUpdateFlag(REPAINT);
0854: ed.updateDisplay();
0855: }
0856: }
0857: }
0858: }
0859:
0860: public final String getCommentStart() {
0861: return mode.getCommentStart();
0862: }
0863:
0864: public final String getCommentEnd() {
0865: return mode.getCommentEnd();
0866: }
0867:
0868: private long lastModified;
0869:
0870: public final long getLastModified() {
0871: return lastModified;
0872: }
0873:
0874: public final void setLastModified(long lastModified) {
0875: this .lastModified = lastModified;
0876: }
0877:
0878: protected void loadFile(File toBeLoaded) {
0879: try {
0880: int modeId = getModeId();
0881: if (modeId == ARCHIVE_MODE || modeId == WORD_MODE
0882: || modeId == XML_MODE)
0883: mode.loadFile(this , toBeLoaded);
0884: else {
0885: final String encoding = toBeLoaded.getEncoding();
0886: load(toBeLoaded.getInputStream(), encoding);
0887: if (encoding != null)
0888: saveProperties(); // Remember encoding for next time.
0889: }
0890: } catch (IOException e) {
0891: Log.error(e);
0892: }
0893: // Handle zero length files.
0894: if (getFirstLine() == null) {
0895: appendLine("");
0896: lineSeparator = System.getProperty("line.separator");
0897: }
0898: final File file = getFile();
0899: if (file != null && file.getProtocol() != File.PROTOCOL_HTTP)
0900: lastModified = toBeLoaded.lastModified();
0901: renumberOriginal();
0902: }
0903:
0904: public int load() {
0905: if (!isLoaded()) {
0906: try {
0907: lockWrite();
0908: } catch (InterruptedException e) {
0909: Log.error(e);
0910: return LOAD_FAILED;
0911: }
0912: try {
0913: final File file = getFile();
0914: final File toBeLoaded = cache != null ? cache : file;
0915: if (toBeLoaded.isFile()) {
0916: Editor editor = Editor.currentEditor();
0917: FastStringBuffer sb = new FastStringBuffer(
0918: "Loading");
0919: if (compression != null) {
0920: if (compression.getType() == COMPRESSION_ZIP) {
0921: String entryName = compression
0922: .getEntryName();
0923: if (entryName != null) {
0924: sb.append(' ');
0925: sb.append(entryName);
0926: }
0927: }
0928: } else if (file != null) {
0929: sb.append(' ');
0930: sb.append(file.getName());
0931: }
0932: sb.append("...");
0933: editor.status(sb.toString());
0934: Debug.assertTrue(mode != null);
0935: Debug.assertTrue(toBeLoaded != null);
0936: loadFile(toBeLoaded);
0937: // At this point, if we loaded from a cache, lastModified will
0938: // be set based on the cache file, which is not what we want
0939: // in the case of a local file.
0940: if (toBeLoaded == cache && file != null
0941: && !file.isRemote())
0942: lastModified = file.lastModified();
0943: formatter.parseBuffer();
0944: checkCVS();
0945: sb.append("done");
0946: editor.status(sb.toString());
0947: } else {
0948: // File doesn't exist.
0949: if (getFirstLine() == null) {
0950: appendLine("");
0951: lineSeparator = System
0952: .getProperty("line.separator");
0953: }
0954: renumberOriginal();
0955: setModeFromFilename(file.canonicalPath());
0956: setLoaded(true);
0957: }
0958: } catch (OutOfMemoryError e) {
0959: _empty();
0960: throw e;
0961: } finally {
0962: unlockWrite();
0963: }
0964: }
0965: return LOAD_COMPLETED;
0966: }
0967:
0968: private void reloadSucceeded() {
0969: Debug.assertTrue(!rwlock.isWriteLocked());
0970: Debug.assertTrue(SwingUtilities.isEventDispatchThread());
0971: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
0972: Editor ed = it.nextEditor();
0973: if (ed.getBuffer() != this )
0974: continue;
0975: View view = ed.getView(this );
0976: if (view != null) {
0977: Line dotLine = getLine(view.getDotLineNumber());
0978: if (dotLine == null)
0979: dotLine = getFirstLine();
0980: if (dotLine != null) {
0981: ed.setDot(dotLine, 0);
0982: ed.moveCaretToDotCol();
0983: }
0984: Line topLine = getLine(view.getTopLineNumber());
0985: if (topLine == null)
0986: topLine = dotLine;
0987: ed.setTopLine(topLine);
0988: } else {
0989: ed.setDot(getFirstLine(), 0);
0990: ed.moveCaretToDotCol();
0991: ed.setTopLine(getFirstLine());
0992: }
0993: ed.setUpdateFlag(REPAINT);
0994: ed.updateDisplay();
0995: }
0996: // The buffer is unmodified now, so we need to update its icon in the
0997: // buffer list(s).
0998: Sidebar.setUpdateFlagInAllFrames(SIDEBAR_REPAINT_BUFFER_LIST);
0999: }
1000:
1001: private void reloadFailed() {
1002: MessageDialog.showMessageDialog("Reload failed", "Error");
1003: }
1004:
1005: public void reload() {
1006: final File file = getFile();
1007: if (file == null)
1008: return;
1009: switch (file.getProtocol()) {
1010: case File.PROTOCOL_FTP:
1011: reloadFtp((FtpFile) file);
1012: return;
1013: case File.PROTOCOL_HTTP:
1014: case File.PROTOCOL_HTTPS:
1015: reloadHttp((HttpFile) file);
1016: return;
1017: case File.PROTOCOL_FILE:
1018: if (file.isFile()) {
1019: // Local file.
1020: if (getModeId() == ARCHIVE_MODE) {
1021: empty();
1022: resetUndo();
1023: mode.loadFile(this , file);
1024: } else
1025: reloadLocal(file);
1026: }
1027: reloadSucceeded();
1028: return;
1029: default:
1030: Debug.assertTrue(false);
1031: break;
1032: }
1033: }
1034:
1035: // Asynchronous.
1036: private void reloadFtp(FtpFile file) {
1037: Log.debug("reloadFtp");
1038: Debug.assertTrue(SwingUtilities.isEventDispatchThread());
1039: FtpSession session = FtpSession.getSession(file);
1040: final FtpLoadProcess ftpLoadProcess = new FtpLoadProcess(this ,
1041: file, session);
1042: Runnable successRunnable = new Runnable() {
1043: public void run() {
1044: File newCache = ftpLoadProcess.getCache();
1045: if (newCache != null) {
1046: Log.debug("newCache != null");
1047: if (cache != null && cache.isFile())
1048: cache.delete();
1049: cache = newCache;
1050: reloadLocal(cache);
1051: } else {
1052: // User cancelled.
1053: setLoaded(true);
1054: }
1055: setBusy(false);
1056: reloadSucceeded();
1057: }
1058: };
1059: ErrorRunnable errorRunnable = new ErrorRunnable("Reload failed") {
1060: public void run() {
1061: setBusy(false);
1062: reloadFailed();
1063: }
1064: };
1065: ftpLoadProcess
1066: .setProgressNotifier(new StatusBarProgressNotifier(this ));
1067: ftpLoadProcess.setSuccessRunnable(successRunnable);
1068: ftpLoadProcess.setErrorRunnable(errorRunnable);
1069: ftpLoadProcess.start();
1070: }
1071:
1072: private void reloadHttp(HttpFile file) {
1073: Log.debug("reloadHttp");
1074: Debug.assertTrue(SwingUtilities.isEventDispatchThread());
1075: final HttpLoadProcess httpLoadProcess = new HttpLoadProcess(
1076: this , file);
1077: Runnable successRunnable = new Runnable() {
1078: public void run() {
1079: File newCache = httpLoadProcess.getCache();
1080: if (newCache != null) {
1081: Log.debug("newCache != null");
1082: if (cache != null && cache.isFile())
1083: cache.delete();
1084: cache = newCache;
1085: reloadLocal(cache);
1086: } else {
1087: // User cancelled.
1088: setLoaded(true);
1089: }
1090: setBusy(false);
1091: reloadSucceeded();
1092: }
1093: };
1094: ErrorRunnable errorRunnable = new ErrorRunnable("Reload failed") {
1095: public void run() {
1096: setBusy(false);
1097: reloadFailed();
1098: }
1099: };
1100: httpLoadProcess
1101: .setProgressNotifier(new StatusBarProgressNotifier(this ));
1102: httpLoadProcess.setSuccessRunnable(successRunnable);
1103: httpLoadProcess.setErrorRunnable(errorRunnable);
1104: setBusy(true);
1105: new Thread(httpLoadProcess).start();
1106: }
1107:
1108: private void reloadLocal(File file) {
1109: try {
1110: lockWrite();
1111: } catch (InterruptedException e) {
1112: Log.error(e);
1113: return;
1114: }
1115: try {
1116: empty();
1117: loadFile(file);
1118: formatter.parseBuffer();
1119: } finally {
1120: unlockWrite();
1121: }
1122: unmodified();
1123: deleteAutosaveFile();
1124: resetUndo();
1125: }
1126:
1127: public synchronized void kill() {
1128: BufferList bufferList = Editor.getBufferList();
1129: if (!bufferList.contains(this )) {
1130: Debug.bug("buffer.kill() buffer not in list");
1131: return;
1132: }
1133:
1134: Marker.invalidateMarkers(this );
1135:
1136: Buffer buf = bufferList.getPreviousPrimaryBuffer(this );
1137: if (buf != null && buf.isPaired()) {
1138: Buffer secondary = buf.getSecondary();
1139: if (secondary != null) {
1140: if (secondary.getLastActivated() > buf
1141: .getLastActivated())
1142: buf = secondary;
1143: }
1144: }
1145: if (buf == null)
1146: buf = new Directory(getCurrentDirectory());
1147: // Copy editor list since switchToBuffer() may close an editor.
1148: ArrayList editors = new ArrayList();
1149: for (EditorIterator it = new EditorIterator(); it.hasNext();)
1150: editors.add(it.next());
1151: for (Iterator it = editors.iterator(); it.hasNext();) {
1152: Editor ed = (Editor) it.next();
1153: // Skip editor if it has been closed.
1154: if (Editor.getEditorList().contains(ed)) {
1155: if (ed.getBuffer() == this )
1156: ed.switchToBuffer(buf);
1157: ed.removeView(this );
1158: }
1159: }
1160: deleteAutosaveFile();
1161: bufferList.remove(this );
1162: dispose();
1163: Sidebar.setUpdateFlagInAllFrames(SIDEBAR_BUFFER_LIST_CHANGED
1164: | SIDEBAR_MODIFIED_BUFFER_COUNT);
1165: }
1166:
1167: public synchronized void relink() {
1168: BufferList bufferList = Editor.getBufferList();
1169: if (bufferList.contains(this )) {
1170: Debug.bug();
1171: return;
1172: }
1173: bufferList.add(this );
1174: }
1175:
1176: private Thread startTaggerThread(int priority) {
1177: if (mode != null) {
1178: Tagger tagger = mode.getTagger(this );
1179: if (tagger != null) {
1180: Thread thread = new Thread(tagger);
1181: thread.setPriority(priority);
1182: thread.setDaemon(true);
1183: thread.setName("tagger " + getFile().getName());
1184: thread.start();
1185: return thread;
1186: }
1187: }
1188: return null;
1189: }
1190:
1191: public boolean isTaggable() {
1192: final File file = getFile();
1193: if (file == null)
1194: return false;
1195: if (file.isRemote())
1196: return false;
1197: return mode.isTaggable();
1198: }
1199:
1200: // Runs tagger if tags == null.
1201: public List getTags(boolean update) {
1202: List tags = getTags();
1203: if (tags != null)
1204: return tags;
1205: if (mode != null) {
1206: Tagger tagger = mode.getTagger(this );
1207: if (tagger != null)
1208: tagger.run();
1209: }
1210: return getTags();
1211: }
1212:
1213: public boolean saveToCache() {
1214: if (lineSeparator == null)
1215: lineSeparator = System.getProperty("line.separator");
1216: final File tempFile = Utilities.getTempFile();
1217: if (tempFile != null) {
1218: File file = getFile();
1219: if (file != null)
1220: tempFile.setEncoding(file.getEncoding());
1221: if (writeFile(tempFile)) {
1222: final File oldCache = cache;
1223: cache = tempFile;
1224: if (oldCache != null && oldCache.isFile())
1225: oldCache.delete();
1226: return true;
1227: }
1228: }
1229: return false;
1230: }
1231:
1232: private boolean maybeWriteBackupFromCache() {
1233: if (cache == null) {
1234: Log.error("maybeWriteBackupFromCache cache is null");
1235: return false;
1236: }
1237: if (!cache.isFile()) {
1238: Log.error("maybeWriteBackupFromCache cache is not a file");
1239: return false;
1240: }
1241: if (!cache.canRead()) {
1242: Log
1243: .error("maybeWriteBackupFromCache cache is not readable");
1244: return false;
1245: }
1246: if (backedUp)
1247: return true; // We only want to do the backup once!
1248: File file = getFile();
1249: if (file == null) {
1250: Debug.bug();
1251: return false;
1252: }
1253: String name = file.getName();
1254: if (compression != null
1255: && compression.getType() == COMPRESSION_GZIP) {
1256: if (name.endsWith(".gz"))
1257: name = name.substring(0, name.length() - 3);
1258: }
1259: return backedUp = Utilities.makeBackup(cache, name, false);
1260: }
1261:
1262: public boolean save() {
1263: Debug.assertTrue(SwingUtilities.isEventDispatchThread());
1264: if (!isModified())
1265: return true;
1266: addUndoBoundary();
1267: boolean succeeded = false;
1268: final File file = getFile();
1269: if (file != null) {
1270: if (file.isLocal())
1271: succeeded = saveLocal(file);
1272: if (file instanceof FtpFile)
1273: succeeded = saveFtp();
1274: if (file instanceof SshFile)
1275: succeeded = saveSsh((SshFile) file);
1276: }
1277: if (succeeded && Editor.isLispInitialized())
1278: LispAPI.invokeAfterSaveHook(this );
1279: return succeeded;
1280: }
1281:
1282: private boolean saveLocal(final File file) {
1283: Debug.assertTrue(file.isLocal());
1284:
1285: if (compression != null
1286: && compression.getType() == COMPRESSION_GZIP)
1287: return saveLocalCompressed(file);
1288:
1289: try {
1290: writeBuffer();
1291: renumberOriginal();
1292: saved();
1293: lastModified = file.lastModified();
1294: } catch (SaveException e) {
1295: final String where = "Save";
1296: String message = e.getMessage();
1297: // Tell user exactly what error occurred.
1298: if (message != null)
1299: MessageDialog.showMessageDialog(message, where);
1300: // Display summary message.
1301: message = "Unable to save " + file.canonicalPath();
1302: MessageDialog.showMessageDialog(message, where);
1303: return false;
1304: }
1305:
1306: if (isTaggable())
1307: Editor.getTagFileManager().addToQueue(file.getParentFile(),
1308: mode);
1309: startTaggerThread(Thread.MIN_PRIORITY);
1310: boolean repaint = false;
1311: final ModeList modeList = Editor.getModeList();
1312: if (file.equals(Preferences.getPreferencesFile())) {
1313: // Reload preferences.
1314: Editor.loadPreferences();
1315: if (Editor.preferences().getBooleanProperty(
1316: Property.AUTO_RELOAD_KEY_MAPS)) {
1317: // Reload keymaps.
1318: KeyMap.reloadKeyMaps();
1319: }
1320: repaint = true;
1321: } else {
1322: if (Editor.preferences().getBooleanProperty(
1323: Property.AUTO_RELOAD_KEY_MAPS)) {
1324: // Reload mode-specific keymap(s) if modified.
1325: synchronized (modeList) {
1326: for (Iterator it = modeList.iterator(); it
1327: .hasNext();) {
1328: ModeListEntry entry = (ModeListEntry) it.next();
1329: Mode mode = entry.getMode(false);
1330: if (mode != null) {
1331: if (file.equals(mode.getKeyMapFile()))
1332: mode.deleteKeyMap();
1333: }
1334: }
1335: }
1336: // Global keymap.
1337: if (file.equals(KeyMap.getGlobalKeyMapFile()))
1338: KeyMap.deleteGlobalKeyMap();
1339: }
1340: }
1341: // Reload theme if modified.
1342: String theme = Editor.preferences().getStringProperty(
1343: Property.THEME);
1344: if (theme != null) {
1345: if (Utilities.isFilenameAbsolute(theme)) {
1346: if (file.canonicalPath().equals(
1347: File.getInstance(theme).canonicalPath())) {
1348: Editor.loadPreferences();
1349: repaint = true;
1350: }
1351: } else if (file.getName().equals(theme)) {
1352: Editor.loadPreferences();
1353: repaint = true;
1354: }
1355: }
1356:
1357: // Reload aliases if modified.
1358: if (file.equals(Editor.getAliasesFile()))
1359: Editor.reloadAliases();
1360:
1361: // Update listRegisters buffer (if any).
1362: File parent = file.getParentFile();
1363: if (parent != null
1364: && parent.equals(Directories.getRegistersDirectory())) {
1365: Buffer buf = Registers.findListRegistersBuffer();
1366: if (buf != null)
1367: buf.reload();
1368: }
1369:
1370: checkCVS();
1371:
1372: if (repaint) {
1373: // Force formatters to be re-initialized.
1374: for (BufferIterator it = new BufferIterator(); it.hasNext();) {
1375: Buffer buf = it.nextBuffer();
1376: if (buf.getFormatter() != null)
1377: buf.getFormatter().reset();
1378: }
1379: Display.initializeStaticValues();
1380: for (int i = 0; i < Editor.getFrameCount(); i++) {
1381: Frame f = Editor.getFrame(i);
1382: if (f != null) {
1383: f.resetDisplay();
1384: f.repaint();
1385: }
1386: }
1387: Editor.restoreFocus();
1388: }
1389: return true;
1390: }
1391:
1392: private boolean saveLocalCompressed(File file) {
1393: final String dialogTitle = "Save";
1394: final Editor editor = Editor.currentEditor();
1395: // Do this before saving changes to cache!
1396: if (!maybeWriteBackupFromCache()) {
1397: FastStringBuffer sb = new FastStringBuffer(
1398: "Unable to write backup file for ");
1399: sb.append(file.getName());
1400: sb.append(". Save anyway?");
1401: if (!editor.confirm(dialogTitle, sb.toString()))
1402: return false;
1403: }
1404: if (!saveToCache()) {
1405: FastStringBuffer sb = new FastStringBuffer(
1406: "Unable to write temporary file for ");
1407: sb.append(file.getName());
1408: MessageDialog.showMessageDialog(sb.toString(), dialogTitle);
1409: return false;
1410: }
1411: if (!compress(cache, file))
1412: return false;
1413: saved();
1414: lastModified = file.lastModified();
1415: return true;
1416: }
1417:
1418: private boolean compress(File source, File destination) {
1419: if (source == null) {
1420: Debug.bug();
1421: return false;
1422: }
1423: if (destination == null) {
1424: Debug.bug();
1425: return false;
1426: }
1427: if (!source.isFile()) {
1428: Debug.bug();
1429: return false;
1430: }
1431: final File tempFile = Utilities.getTempFile(destination
1432: .getParentFile());
1433: try {
1434: final int bufSize = 4096;
1435: BufferedInputStream in = new BufferedInputStream(source
1436: .getInputStream());
1437: GZIPOutputStream out = new GZIPOutputStream(
1438: new BufferedOutputStream(tempFile.getOutputStream()),
1439: bufSize);
1440: byte[] buffer = new byte[bufSize];
1441: while (true) {
1442: int bytesRead = in.read(buffer, 0, bufSize);
1443: if (bytesRead > 0)
1444: out.write(buffer, 0, bytesRead);
1445: else
1446: break;
1447: }
1448: in.close();
1449: out.flush();
1450: out.close();
1451: return Utilities.deleteRename(tempFile, destination);
1452: } catch (IOException e) {
1453: Log.error(e);
1454: return false;
1455: }
1456: }
1457:
1458: private boolean saveFtp() {
1459: Debug.assertTrue(SwingUtilities.isEventDispatchThread());
1460: Debug.assertTrue(getFile() instanceof FtpFile);
1461: final FtpFile file = (FtpFile) getFile();
1462: final FtpSession session = FtpSession.getSession(file);
1463: if (session == null)
1464: return false;
1465: Debug.assertTrue(session.isLocked());
1466: if (!lock()) {
1467: session.unlock();
1468: MessageDialog.showMessageDialog("Buffer is busy", file
1469: .netPath());
1470: return false;
1471: }
1472: Debug.assertTrue(isLocked());
1473: final Editor editor = Editor.currentEditor();
1474: editor.setWaitCursor();
1475: // Do this before saving changes to cache!
1476: if (!maybeWriteBackupFromCache()) {
1477: editor.setDefaultCursor();
1478: String message = "Unable to write backup file for "
1479: + file.getName() + ". Save anyway?";
1480: if (!editor.confirm("Save", message)) {
1481: unlock();
1482: session.unlock();
1483: return false;
1484: }
1485: editor.setWaitCursor();
1486: }
1487: if (!saveToCache()) {
1488: unlock();
1489: session.unlock();
1490: editor.setDefaultCursor();
1491: String message = "Unable to write temporary file for "
1492: + file.getName();
1493: MessageDialog.showMessageDialog(message, "Save");
1494: return false;
1495: }
1496: final FtpSaveProcess saveProcess;
1497: if (compression != null
1498: && compression.getType() == COMPRESSION_GZIP) {
1499: final File tempFile = Utilities.getTempFile();
1500: if (!compress(cache, tempFile)) {
1501: String message = "Unable to compress temporary file for "
1502: + file.getName();
1503: MessageDialog.showMessageDialog(message, "Save");
1504: return false;
1505: }
1506: saveProcess = new FtpSaveProcess(this , tempFile, file,
1507: session);
1508: } else
1509: saveProcess = new FtpSaveProcess(this , cache, file, session);
1510: saveProcess.setConfirmIfDestinationChanged(true);
1511: saveProcess.setTitle("Save");
1512: final Runnable successRunnable = new Runnable() {
1513: public void run() {
1514: saved();
1515: setListing(saveProcess.getListing());
1516: for (EditorIterator it = new EditorIterator(); it
1517: .hasNext();) {
1518: Editor ed = it.nextEditor();
1519: if (ed.getBuffer() == Buffer.this )
1520: ed.setDefaultCursor();
1521: }
1522: Sidebar.repaintBufferListInAllFrames();
1523: }
1524: };
1525: saveProcess.setSuccessRunnable(successRunnable);
1526: Debug.assertTrue(isLocked());
1527: saveProcess.start();
1528: return true;
1529: }
1530:
1531: private boolean saveSsh(final SshFile file) {
1532: final String title = "Save";
1533: final Editor editor = Editor.currentEditor();
1534: boolean succeeded = false;
1535: String message = null;
1536:
1537: // Do this before saving changes to cache!
1538: if (!maybeWriteBackupFromCache()) {
1539: message = "Unable to write backup file for "
1540: + file.getName() + ". Save anyway?";
1541: if (!editor.confirm(title, message))
1542: return false;
1543: }
1544:
1545: if (!saveToCache()) {
1546: message = "Unable to write temporary file for "
1547: + file.getName();
1548: MessageDialog.showMessageDialog(message, title);
1549: return false;
1550: }
1551:
1552: Ssh ssh = new Ssh();
1553:
1554: if (compression != null
1555: && compression.getType() == COMPRESSION_GZIP) {
1556: final File tempFile = Utilities.getTempFile();
1557: if (!compress(cache, tempFile)) {
1558: message = "Unable to compress temporary file for "
1559: + file.getName();
1560: MessageDialog.showMessageDialog(message, "Save");
1561: return false;
1562: }
1563: succeeded = ssh.copy(tempFile, file);
1564: } else
1565: succeeded = ssh.copy(cache, file);
1566:
1567: if (!succeeded)
1568: message = ssh.getErrorText();
1569:
1570: if (succeeded) {
1571: saved();
1572: } else {
1573: // Tell user exactly what error occurred.
1574: if (message != null)
1575: MessageDialog.showMessageDialog(message, title);
1576:
1577: // Display summary message.
1578: message = "Unable to save " + file.canonicalPath();
1579: MessageDialog.showMessageDialog(message, title);
1580: }
1581:
1582: return succeeded;
1583: }
1584:
1585: public void saveAs(File destination) {
1586: File file = getFile();
1587: if (file != null)
1588: destination.setEncoding(file.getEncoding());
1589: if (destination.isLocal())
1590: saveAsLocal(destination);
1591: else if (destination instanceof FtpFile)
1592: saveAsFtp((FtpFile) destination);
1593: else
1594: MessageDialog.showMessageDialog("Invalid destination",
1595: "Save As");
1596: }
1597:
1598: private boolean saveAsLocal(File destination) {
1599: Debug.assertTrue(SwingUtilities.isEventDispatchThread());
1600: if (destination == null)
1601: return false;
1602: if (destination.isDirectory()) {
1603: final String prompt = destination.canonicalPath().concat(
1604: " is a directory");
1605: MessageDialog.showMessageDialog(prompt, "Save As");
1606: return false;
1607: }
1608: setBusy(true);
1609: Editor.currentEditor().setWaitCursor();
1610: boolean succeeded = false;
1611: String message = null;
1612: if (lineSeparator == null)
1613: lineSeparator = System.getProperty("line.separator");
1614: final File tempFile = Utilities.getTempFile(destination
1615: .getParent());
1616: if (tempFile == null) {
1617: message = "Unable to create temporary file for "
1618: + destination.canonicalPath();
1619: } else {
1620: tempFile.setEncoding(destination.getEncoding());
1621: if (writeFile(tempFile)) {
1622: Log.debug("buffer written to "
1623: + tempFile.canonicalPath());
1624: if (Utilities.makeBackup(destination, false)) {
1625: destination.delete();
1626: if (tempFile.renameTo(destination)) {
1627: succeeded = true;
1628: } else {
1629: Log.error("unable to rename "
1630: + tempFile.canonicalPath() + " to "
1631: + destination.canonicalPath());
1632: message = "Unable to rename temporary file";
1633: }
1634: } else {
1635: Log.error("backup failed");
1636: message = "Unable to write backup file for "
1637: + destination.canonicalPath();
1638: }
1639: } else {
1640: Log.error("writeFile failed");
1641: message = "Unable to create temporary file in "
1642: + tempFile.getParent();
1643: }
1644: }
1645: setBusy(false);
1646: if (succeeded) {
1647: saved();
1648: changeFile(destination);
1649: setLastModified(getFile().lastModified());
1650: checkCVS();
1651: final String encoding = destination.getEncoding();
1652: if (encoding != null)
1653: saveProperties(); // Remember encoding for next time.
1654: if (isTaggable())
1655: Editor.getTagFileManager().addToQueue(
1656: destination.getParentFile(), mode);
1657: Sidebar
1658: .setUpdateFlagInAllFrames(SIDEBAR_REPAINT_BUFFER_LIST);
1659: } else {
1660: // Tell user exactly what error occurred.
1661: if (message != null)
1662: MessageDialog.showMessageDialog(message, "Save As");
1663: MessageDialog.showMessageDialog("Save failed", "Save As");
1664: }
1665: return succeeded;
1666: }
1667:
1668: private void saveAsFtp(final FtpFile destination) {
1669: Debug.assertTrue(SwingUtilities.isEventDispatchThread());
1670: FtpSession session = FtpSession.getSession(destination);
1671: if (session == null)
1672: return;
1673: Debug.assertTrue(session.isLocked());
1674: if (!lock()) {
1675: session.unlock();
1676: MessageDialog.showMessageDialog("Buffer is busy", getFile()
1677: .netPath());
1678: return;
1679: }
1680: final Editor editor = Editor.currentEditor();
1681: editor.setWaitCursor();
1682: if (!saveToCache()) {
1683: unlock();
1684: session.unlock();
1685: editor.setDefaultCursor();
1686: String message = "Unable to write temporary file for "
1687: + destination.netPath();
1688: MessageDialog.showMessageDialog(message, "Save As");
1689: return;
1690: }
1691: final FtpSaveProcess saveProcess = new FtpSaveProcess(this ,
1692: cache, destination, session);
1693: saveProcess.setConfirmOverwrite(true);
1694: saveProcess.setTitle("Save As");
1695: final Runnable successRunnable = new Runnable() {
1696: public void run() {
1697: saved();
1698: changeFile(destination);
1699: setListing(saveProcess.getListing());
1700: Sidebar
1701: .setUpdateFlagInAllFrames(SIDEBAR_REPAINT_BUFFER_LIST);
1702: for (EditorIterator it = new EditorIterator(); it
1703: .hasNext();) {
1704: Editor ed = it.nextEditor();
1705: if (ed.getBuffer() == Buffer.this )
1706: ed.setDefaultCursor();
1707: }
1708: Sidebar.repaintBufferListInAllFrames();
1709: }
1710: };
1711: saveProcess.setSuccessRunnable(successRunnable);
1712: Debug.assertTrue(isLocked());
1713: saveProcess.start();
1714: }
1715:
1716: public void saveCopy(File destination) {
1717: if (destination.isLocal())
1718: saveCopyLocal(destination);
1719: else if (destination instanceof FtpFile)
1720: saveCopyFtp((FtpFile) destination);
1721: else
1722: MessageDialog.showMessageDialog("Invalid destination",
1723: "Save Copy");
1724: }
1725:
1726: private void saveCopyLocal(File destination) {
1727: boolean succeeded = false;
1728: String message = null;
1729: if (lineSeparator == null)
1730: lineSeparator = System.getProperty("line.separator");
1731: File tempFile = Utilities.getTempFile(destination.getParent());
1732: if (tempFile == null) {
1733: message = "Unable to create temporary file for "
1734: + destination.canonicalPath();
1735: } else {
1736: tempFile.setEncoding(destination.getEncoding());
1737: if (writeFile(tempFile)) {
1738: Log.debug("buffer written to "
1739: + tempFile.canonicalPath());
1740: if (Utilities.makeBackup(destination, false)) {
1741: destination.delete();
1742: if (tempFile.renameTo(destination)) {
1743: succeeded = true;
1744: } else {
1745: Log.error("unable to rename "
1746: + tempFile.canonicalPath() + " to "
1747: + destination.canonicalPath());
1748: message = "Unable to rename temporary file";
1749: }
1750: } else {
1751: Log.error("backup failed");
1752: message = "Unable to write backup file for "
1753: + destination.canonicalPath();
1754: }
1755: } else {
1756: Log.error("writeFile failed");
1757: message = "Unable to create temporary file in "
1758: + tempFile.getParent();
1759: }
1760: }
1761: if (succeeded) {
1762: if (isTaggable())
1763: Editor.getTagFileManager().addToQueue(
1764: destination.getParentFile(), mode);
1765: } else {
1766: // Tell user exactly what error occurred.
1767: if (message != null)
1768: MessageDialog.showMessageDialog(message, "Save Copy");
1769: MessageDialog.showMessageDialog("Save failed", "Save Copy");
1770: }
1771: }
1772:
1773: private void saveCopyFtp(FtpFile destination) {
1774: Debug.assertTrue(SwingUtilities.isEventDispatchThread());
1775: FtpSession session = FtpSession.getSession(destination);
1776: if (session == null)
1777: return;
1778: Debug.assertTrue(session.isLocked());
1779: if (!lock()) {
1780: session.unlock();
1781: MessageDialog.showMessageDialog("Buffer is busy", getFile()
1782: .netPath());
1783: return;
1784: }
1785: final Editor editor = Editor.currentEditor();
1786: editor.setWaitCursor();
1787: if (!saveToCache()) {
1788: unlock();
1789: session.unlock();
1790: editor.setDefaultCursor();
1791: String message = "Unable to write temporary file for "
1792: + destination.netPath();
1793: MessageDialog.showMessageDialog(message, "Save Copy");
1794: return;
1795: }
1796: final Runnable successRunnable = new Runnable() {
1797: public void run() {
1798: for (EditorIterator it = new EditorIterator(); it
1799: .hasNext();) {
1800: Editor ed = it.nextEditor();
1801: if (ed.getBuffer() == Buffer.this )
1802: ed.setDefaultCursor();
1803: }
1804: Sidebar.repaintBufferListInAllFrames();
1805: }
1806: };
1807: final FtpSaveProcess saveProcess = new FtpSaveProcess(this ,
1808: cache, destination, session);
1809: saveProcess.setConfirmOverwrite(true);
1810: saveProcess.setTitle("Save Copy");
1811: saveProcess.setSuccessRunnable(successRunnable);
1812: Debug.assertTrue(isLocked());
1813: saveProcess.start();
1814: }
1815:
1816: // Removes tabs and spaces only.
1817: public void removeTrailingWhitespace() {
1818: boolean bufferChanged = false;
1819: CompoundEdit compoundEdit = null;
1820: try {
1821: lockWrite();
1822: } catch (InterruptedException e) {
1823: Log.error(e);
1824: return;
1825: }
1826: try {
1827: for (Line line = getFirstLine(); line != null; line = line
1828: .next()) {
1829: final String text = line.getText();
1830: final int originalLength = text.length();
1831: int length = originalLength;
1832: for (int i = originalLength - 1; i >= 0; i--) {
1833: char c = text.charAt(i);
1834: if (c == ' ' || c == '\t')
1835: --length;
1836: else
1837: break;
1838: }
1839: if (length != originalLength) {
1840: if (!bufferChanged) {
1841: // First change.
1842: compoundEdit = new CompoundEdit();
1843: bufferChanged = true;
1844: }
1845: compoundEdit.addEdit(new UndoLineEdit(this , line));
1846: line.setText(text.substring(0, length));
1847: }
1848: }
1849: if (bufferChanged)
1850: modified();
1851: } finally {
1852: unlockWrite();
1853: }
1854: if (compoundEdit != null && undoManager != null) {
1855: compoundEdit.end();
1856: undoManager.addEdit(compoundEdit);
1857: }
1858: if (bufferChanged) {
1859: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
1860: Editor ed = it.nextEditor();
1861: if (ed.getBuffer() == this ) {
1862: if (ed.getDotOffset() > ed.getDotLine().length()) {
1863: ed.getDot().setOffset(ed.getDotLine().length());
1864: if (getBooleanProperty(Property.RESTRICT_CARET)) {
1865: ed.moveCaretToDotCol();
1866: ed.updateDotLine();
1867: }
1868: }
1869: if (ed.getMark() != null) {
1870: if (ed.getMarkOffset() > ed.getMarkLine()
1871: .length())
1872: ed.getMark().setOffset(
1873: ed.getMarkLine().length());
1874: }
1875: ed.repaintDisplay();
1876: }
1877: }
1878: }
1879: }
1880:
1881: public synchronized void autosave() {
1882: if (autosaveEnabled && Autosave.isAutosaveEnabled())
1883: if (modCount != autosaveModCount)
1884: new Thread(autosaveRunnable, "autosave").start();
1885: }
1886:
1887: private final Runnable autosaveRunnable = new Runnable() {
1888: public void run() {
1889: try {
1890: lockRead();
1891: } catch (InterruptedException e) {
1892: Log.error(e);
1893: return;
1894: }
1895: try {
1896: autosaveInternal();
1897: } finally {
1898: unlockRead();
1899: }
1900: }
1901: };
1902:
1903: private void autosaveInternal() {
1904: if (autosaveFile == null) {
1905: final File autosaveDirectory = Autosave
1906: .getAutosaveDirectory();
1907: if (autosaveDirectory == null)
1908: return;
1909: autosaveFile = Utilities.getTempFile(autosaveDirectory);
1910: if (autosaveFile == null)
1911: return;
1912: // Update autosave catalog file.
1913: Autosave.put(getFile().netPath(), autosaveFile.getName());
1914: Autosave.flush();
1915: }
1916: autosaveFile.setEncoding(getFile().getEncoding());
1917: if (writeFile(autosaveFile))
1918: autosaveModCount = modCount;
1919: else
1920: Log.error("autosave writeFile failed");
1921: }
1922:
1923: public void deleteAutosaveFile() {
1924: if (autosaveFile != null)
1925: autosaveFile.delete();
1926: }
1927:
1928: public void setFirstLine(Line line) {
1929: if (!rwlock.isWriteLocked()) {
1930: Log
1931: .error("----- setFirstLine() called without write lock -----");
1932: Debug.dumpStack();
1933: }
1934: super .setFirstLine(line);
1935: }
1936:
1937: public void modified() {
1938: if (!rwlock.isWriteLocked()) {
1939: Log
1940: .error("----- modified() called without write lock -----");
1941: Debug.dumpStack();
1942: }
1943: if (modCount > saveModCount) {
1944: // Already modified.
1945: incrementModCount();
1946: } else {
1947: // First change.
1948: setModCount(saveModCount + 1);
1949: Sidebar
1950: .setUpdateFlagInAllFrames(SIDEBAR_MODIFIED_BUFFER_COUNT);
1951: Sidebar.repaintBufferListInAllFrames();
1952: }
1953: invalidate();
1954: }
1955:
1956: public void unmodified() {
1957: setModCount(0);
1958: saveModCount = 0;
1959: autosaveModCount = 0;
1960: Sidebar.setUpdateFlagInAllFrames(SIDEBAR_MODIFIED_BUFFER_COUNT);
1961: Sidebar.repaintBufferListInAllFrames();
1962: }
1963:
1964: public void saved() {
1965: saveModCount = modCount;
1966: autosaveModCount = modCount;
1967: if (isNewFile()) {
1968: for (Line line = getFirstLine(); line != null; line = line
1969: .next()) {
1970: line.setOriginalText(null);
1971: line.setNew(false);
1972: }
1973: setNewFile(false);
1974: } else {
1975: for (Line line = getFirstLine(); line != null; line = line
1976: .next()) {
1977: if (line.isModified())
1978: line.setSaved(true);
1979: }
1980: if (getBooleanProperty(Property.SHOW_CHANGE_MARKS))
1981: repaint();
1982: }
1983: backedUp = false; // Ignored for local buffers.
1984: Sidebar.setUpdateFlagInAllFrames(SIDEBAR_MODIFIED_BUFFER_COUNT);
1985: Sidebar.repaintBufferListInAllFrames();
1986: deleteAutosaveFile();
1987: }
1988:
1989: public void empty() {
1990: try {
1991: lockWrite();
1992: } catch (InterruptedException e) {
1993: Log.debug(e);
1994: return;
1995: }
1996: try {
1997: _empty();
1998: } finally {
1999: unlockWrite();
2000: }
2001: setTags(null);
2002: // Invalidate any stored views that are referencing the old contents
2003: // of this buffer.
2004: if (lastView != null)
2005: lastView.invalidate();
2006: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
2007: Editor ed = it.nextEditor();
2008: View view = ed.getView(this );
2009: if (view != null)
2010: view.invalidate();
2011: if (ed.getBuffer() == this ) {
2012: ed.setDot(null);
2013: ed.setMark(null);
2014: ed.setTopLine(null);
2015: }
2016: }
2017: }
2018:
2019: public void invalidate() {
2020: needsParsing = true;
2021: maxColsValid = false;
2022: setTags(null);
2023: }
2024:
2025: public final View getLastView() {
2026: if (lastView != null)
2027: return (View) lastView.clone();
2028: else
2029: return null;
2030: }
2031:
2032: public final void setLastView(View view) {
2033: lastView = (View) view.clone();
2034: }
2035:
2036: public void saveView(Editor editor) {
2037: final View view = saveViewInternal(editor);
2038: editor.setView(this , view);
2039: setLastView(view);
2040: }
2041:
2042: protected View saveViewInternal(Editor editor) {
2043: final Display display = editor.getDisplay();
2044: View view = editor.getView(this );
2045: if (view == null)
2046: view = new View();
2047: final Position dot = editor.getDot();
2048: view.dot = dot == null ? null : new Position(dot);
2049: final Position mark = editor.getMark();
2050: view.mark = mark == null ? null : new Position(mark);
2051: view.selection = editor.getSelection();
2052: view.setColumnSelection(editor.isColumnSelection());
2053: view.topLine = editor.getTopLine();
2054: if (view.topLine != null)
2055: view.topLineNumber = view.topLine.lineNumber();
2056: view.pixelsAboveTopLine = display.getPixelsAboveTopLine();
2057: view.shift = display.shift;
2058: view.caretCol = display.caretCol;
2059: view.timestamp = System.currentTimeMillis();
2060: if (view.dot == null) {
2061: view.lineNumber = 0;
2062: view.offs = 0;
2063: } else {
2064: view.lineNumber = view.dot.lineNumber();
2065: view.offs = view.dot.getOffset();
2066: }
2067: return view;
2068: }
2069:
2070: public boolean needsParsing() {
2071: return needsParsing;
2072: }
2073:
2074: public final void setNeedsParsing(boolean b) {
2075: needsParsing = b;
2076: }
2077:
2078: public boolean isModified() {
2079: return modCount != saveModCount;
2080: }
2081:
2082: public Line getLine(int lineNumber) {
2083: if (lineNumber < 0)
2084: return null;
2085: int n = 0;
2086: Line line = getFirstLine();
2087: while (line != null && n != lineNumber) {
2088: line = line.next();
2089: ++n;
2090: }
2091: return line;
2092: }
2093:
2094: /**
2095: * Returns position in buffer based on original line number. Never returns
2096: * null.
2097: *
2098: * @param lineNumber the original line number to look for
2099: * @param offset the offset within the line
2100: * @return the position.
2101: */
2102: public Position findOriginal(int lineNumber, int offset) {
2103: if (offset < 0)
2104: offset = 0;
2105: Line line = getFirstLine();
2106: while (line != null && line.originalLineNumber() != lineNumber)
2107: line = line.next();
2108: if (line != null)
2109: return new Position(line, Math.min(offset, line.length()));
2110: if (line == null) {
2111: // We didn't find the exact line we were looking for. Find the
2112: // line with the next highest original line number.
2113: line = getFirstLine();
2114: while (line != null
2115: && line.originalLineNumber() < lineNumber)
2116: line = line.next();
2117: }
2118: if (line != null)
2119: return new Position(line, 0);
2120: return getEnd();
2121: }
2122:
2123: // Convert position into absolute character offset from start of buffer.
2124: public int getAbsoluteOffset(Position pos) {
2125: Line targetLine = pos.getLine();
2126: int offset = 0;
2127: Line line = getFirstLine();
2128: while (line != null && line != targetLine) {
2129: offset += line.length() + 1;
2130: line = line.next();
2131: }
2132: if (line == null)
2133: return -1; // Line not in buffer.
2134: else
2135: return offset + pos.getOffset();
2136: }
2137:
2138: // Convert absolute character offset from start of buffer into position.
2139: public Position getPosition(int goal) {
2140: int offset = 0;
2141: Line line = getFirstLine();
2142: while (line != null) {
2143: // Line separator always counts as 1.
2144: offset += line.length() + 1;
2145: if (offset >= goal) {
2146: if (offset == goal)
2147: line = line.next();
2148: break;
2149: }
2150: line = line.next();
2151: }
2152: if (line == null)
2153: return null; // We hit the end of the buffer without reaching our goal.
2154: if (offset == goal)
2155: return new Position(line, 0);
2156: offset -= line.length() + 1;
2157: return new Position(line, goal - offset);
2158: }
2159:
2160: private UndoManager undoManager;
2161:
2162: protected void initializeUndo() {
2163: undoManager = new UndoManager();
2164: }
2165:
2166: public void resetUndo() {
2167: if (supportsUndo)
2168: undoManager.discardAllEdits();
2169: }
2170:
2171: public void resetRedo() {
2172: }
2173:
2174: public final UndoManager getUndoManager() {
2175: return undoManager;
2176: }
2177:
2178: public final void addEdit(UndoableEdit edit) {
2179: if (undoManager != null)
2180: undoManager.addEdit(edit);
2181: }
2182:
2183: public final void addUndoBoundary() {
2184: if (undoManager != null)
2185: undoManager.addEdit(UndoBoundary.getInstance());
2186: }
2187:
2188: public final void appendUndoFold(Editor editor) {
2189: if (undoManager != null)
2190: undoManager.appendUndoFold(editor);
2191: }
2192:
2193: public boolean canUndo() {
2194: return (undoManager != null && undoManager.canUndo());
2195: }
2196:
2197: public void undo() {
2198: if (undoManager != null) {
2199: if (needsRenumbering)
2200: renumber();
2201: try {
2202: undoManager.undo();
2203: } catch (CannotUndoException e) {
2204: Editor.currentEditor().status("Nothing to undo");
2205: }
2206: }
2207: }
2208:
2209: public boolean canRedo() {
2210: return (undoManager != null && undoManager.canRedo());
2211: }
2212:
2213: public void redo() {
2214: if (undoManager != null) {
2215: if (needsRenumbering)
2216: renumber();
2217: try {
2218: undoManager.redo();
2219: } catch (CannotRedoException e) {
2220: Editor.currentEditor().status("Nothing to redo");
2221: }
2222: }
2223: }
2224:
2225: public CompoundEdit beginCompoundEdit() {
2226: if (supportsUndo) {
2227: CompoundEdit compoundEdit = new CompoundEdit();
2228: undoManager.addEdit(compoundEdit);
2229: return compoundEdit;
2230: } else
2231: return null;
2232: }
2233:
2234: public void endCompoundEdit(CompoundEdit compoundEdit) {
2235: if (compoundEdit != null)
2236: compoundEdit.end();
2237: }
2238:
2239: protected void setText(String text) {
2240: try {
2241: lockWrite();
2242: } catch (InterruptedException e) {
2243: Log.debug(e);
2244: return;
2245: }
2246: try {
2247: empty();
2248: if (text != null) {
2249: FastStringReader reader = new FastStringReader(text);
2250: String s;
2251: while ((s = reader.readLine()) != null)
2252: appendLine(s);
2253: }
2254: if (getFirstLine() == null)
2255: appendLine("");
2256: renumber();
2257: invalidate();
2258: setLoaded(true);
2259: } finally {
2260: unlockWrite();
2261: }
2262: }
2263:
2264: // Inserts s at pos, moves pos past s.
2265: public void insertString(Position pos, String s) {
2266: try {
2267: lockWrite();
2268: } catch (InterruptedException e) {
2269: Log.error(e);
2270: return;
2271: }
2272: try {
2273: FastStringBuffer sb = new FastStringBuffer();
2274: final int limit = s.length();
2275: boolean skipLF = false;
2276: for (int i = 0; i < limit; i++) {
2277: char c = s.charAt(i);
2278: if (c == '\r') {
2279: if (sb.length() > 0) {
2280: insertChars(pos, sb.toString());
2281: sb.setLength(0);
2282: }
2283: insertLineSeparator(pos);
2284: skipLF = true;
2285: } else if (c == '\n') {
2286: if (skipLF) {
2287: skipLF = false;
2288: } else {
2289: if (sb.length() > 0) {
2290: insertChars(pos, sb.toString());
2291: sb.setLength(0);
2292: }
2293: insertLineSeparator(pos);
2294: }
2295: } else {
2296: skipLF = false;
2297: sb.append(c);
2298: }
2299: }
2300: if (sb.length() > 0)
2301: insertChars(pos, sb.toString());
2302: } finally {
2303: unlockWrite();
2304: }
2305: }
2306:
2307: // Inserts s at pos, moves pos past s.
2308: // String must not contain a line separator!
2309: public void insertChars(Position pos, String s) {
2310: final int length = s.length();
2311: if (length > 0) {
2312: String text = pos.getLine().getText();
2313: FastStringBuffer sb = new FastStringBuffer(text.length()
2314: + length);
2315: sb.append(text.substring(0, pos.getOffset()));
2316: sb.append(s);
2317: sb.append(text.substring(pos.getOffset()));
2318: pos.getLine().setText(sb.toString());
2319: pos.skip(length);
2320: modified();
2321: }
2322: }
2323:
2324: public void insertLineSeparator(Position pos) {
2325: final Line line = pos.getLine();
2326: final int offset = pos.getOffset();
2327: if (offset == 0) {
2328: final Line newLine = new TextLine("");
2329: newLine.setNew(true);
2330: // Assume that the line flags should carry over to the new line.
2331: newLine.setFlags(line.flags());
2332: final Line prevLine = line.previous();
2333: newLine.setPrevious(prevLine);
2334: if (prevLine != null) {
2335: prevLine.setNext(newLine);
2336: } else {
2337: Debug.assertTrue(line == getFirstLine());
2338: setFirstLine(newLine);
2339: }
2340: newLine.setNext(line);
2341: line.setPrevious(newLine);
2342: } else if (offset == line.length()) {
2343: final Line newLine = new TextLine("");
2344: newLine.setNew(true);
2345: // Assume that the line flags should carry over to the new line.
2346: newLine.setFlags(line.flags());
2347: final Line prevLine = line;
2348: final Line nextLine = line.next();
2349: newLine.setPrevious(prevLine);
2350: if (prevLine != null) {
2351: prevLine.setNext(newLine);
2352: } else {
2353: Debug.assertTrue(line == getFirstLine());
2354: setFirstLine(newLine);
2355: }
2356: newLine.setNext(nextLine);
2357: if (nextLine != null)
2358: nextLine.setPrevious(newLine);
2359: pos.moveTo(newLine, 0);
2360: } else {
2361: final String head = line.substring(0, offset);
2362: final String tail = line.substring(offset);
2363: line.setText(head);
2364: final Line nextLine = line.next();
2365: final Line newLine = new TextLine(tail);
2366: newLine.setNew(true);
2367: // Assume that the line flags should carry over to the new line.
2368: newLine.setFlags(line.flags());
2369: line.setNext(newLine);
2370: newLine.setPrevious(line);
2371: newLine.setNext(nextLine);
2372: if (nextLine != null)
2373: nextLine.setPrevious(newLine);
2374: pos.moveTo(newLine, 0);
2375: }
2376: needsRenumbering = true;
2377: modified();
2378: repaint();
2379: }
2380:
2381: // Repaint all windows displaying this buffer.
2382: public final void repaint() {
2383: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
2384: Editor ed = it.nextEditor();
2385: if (ed.getBuffer() == this )
2386: ed.repaintDisplay();
2387: }
2388: }
2389:
2390: public String getTitle() {
2391: if (title != null)
2392: return title;
2393: final File file = getFile();
2394: if (file != null)
2395: return file.netPath();
2396: else
2397: return "";
2398: }
2399:
2400: public final void setTitle(String s) {
2401: title = s;
2402: }
2403:
2404: public String getFileNameForDisplay() {
2405: final File file = getFile();
2406: if (file != null)
2407: return file.isRemote() ? file.netPath() : file
2408: .canonicalPath();
2409: return "";
2410: }
2411:
2412: // For the buffer list.
2413: public String toString() {
2414: if (title != null)
2415: return title;
2416: final File file = getFile();
2417: if (file == null) {
2418: // This case should be handled by toString() in the derived class.
2419: return null;
2420: }
2421: if (file instanceof HttpFile)
2422: return file.netPath();
2423: if (file.isRemote()) {
2424: // SSH, FTP.
2425: FastStringBuffer sb = new FastStringBuffer(file.getName());
2426: sb.append(" [");
2427: sb.append(file.netPath());
2428: sb.append(']');
2429: return sb.toString();
2430: }
2431: // Local file.
2432: if (file.isDirectory())
2433: return file.canonicalPath();
2434: return Editor.getBufferList().getUniqueName(this );
2435: }
2436:
2437: // For the buffer list.
2438: public Icon getIcon() {
2439: if (isModified())
2440: return Utilities.getIconFromFile("modified.png");
2441: else if (isReadOnly())
2442: return Utilities.getIconFromFile("read_only.png");
2443: else
2444: return Utilities.getIconFromFile("buffer.png");
2445: }
2446:
2447: public final int getCol(Position pos) {
2448: return getCol(pos.getLine(), pos.getOffset());
2449: }
2450:
2451: // Returns the absolute column for a given line and offset, based on the
2452: // tab width of the buffer.
2453: public final int getCol(Line line, int offset) {
2454: return getCol(line, offset, getTabWidth());
2455: }
2456:
2457: public final static int getCol(Line line, int offset, int tabWidth) {
2458: final int limit = Math.min(offset, line.length());
2459: int col = 0;
2460: for (int i = 0; i < limit; i++) {
2461: if (line.charAt(i) == '\t')
2462: col += tabWidth - col % tabWidth;
2463: else
2464: ++col;
2465: }
2466: return col;
2467: }
2468:
2469: // Return existing indentation of line (number of columns).
2470: public int getIndentation(Line line) {
2471: int indent = 0;
2472: final int tabWidth = getTabWidth();
2473: final int limit = line.length();
2474: for (int i = 0; i < limit; i++) {
2475: char c = line.charAt(i);
2476: if (c == '\t')
2477: indent += tabWidth - indent % tabWidth;
2478: else if (c == ' ')
2479: ++indent;
2480: else
2481: break;
2482: }
2483: return indent;
2484: }
2485:
2486: // No write locking, does not call modified().
2487: public void setIndentation(Line line, int indent) {
2488: // Skip over existing indentation.
2489: final int length = line.length();
2490: int i = 0;
2491: while (i < length && Character.isWhitespace(line.charAt(i)))
2492: ++i;
2493: if (i < length) {
2494: String remainder = line.substring(i);
2495: if (indent > 0) {
2496: // Put required indentation into stringbuffer.
2497: FastStringBuffer sb = getCorrectIndentationString(indent);
2498: sb.append(remainder);
2499: line.setText(sb.toString());
2500: } else
2501: line.setText(remainder);
2502: } else
2503: line.setText("");
2504: }
2505:
2506: public FastStringBuffer getCorrectIndentationString(int indent) {
2507: FastStringBuffer sb = new FastStringBuffer(256);
2508: if (getUseTabs()) {
2509: final int tabWidth = getTabWidth();
2510: int col = 0;
2511: while (col + tabWidth <= indent) {
2512: sb.append('\t');
2513: col += tabWidth;
2514: }
2515: while (col < indent) {
2516: sb.append(' ');
2517: ++col;
2518: }
2519: } else
2520: sb.append(Utilities.spaces(indent));
2521: return sb;
2522: }
2523:
2524: private boolean folded;
2525:
2526: public final void renumber() {
2527: folded = false;
2528: lineCount = 0;
2529: visibleLineCount = 0;
2530: for (Line line = getFirstLine(); line != null; line = line
2531: .next()) {
2532: line.setLineNumber(lineCount++);
2533: if (line.isHidden())
2534: folded = true;
2535: else
2536: ++visibleLineCount;
2537: }
2538: needsRenumbering = false;
2539: }
2540:
2541: protected void renumberOriginal() {
2542: folded = false;
2543: lineCount = 0;
2544: visibleLineCount = 0;
2545: for (Line line = getFirstLine(); line != null; line = line
2546: .next()) {
2547: line.setOriginalLineNumber(lineCount);
2548: line.setLineNumber(lineCount++);
2549: if (line.isHidden())
2550: folded = true;
2551: else
2552: ++visibleLineCount;
2553: }
2554: needsRenumbering = false;
2555: }
2556:
2557: protected void enforceOutputLimit(Property property) {
2558: Debug.assertTrue(property != null);
2559: final int outputLimit = Editor.preferences()
2560: .getIntegerProperty(property);
2561: if (outputLimit == 0 || lineCount <= outputLimit)
2562: return;
2563: try {
2564: lockWrite();
2565: } catch (InterruptedException e) {
2566: Log.error(e);
2567: return;
2568: }
2569: try {
2570: Position begin = new Position(getFirstLine(), 0);
2571: Line line = getFirstLine();
2572: for (int i = lineCount - outputLimit; i > 0; i--) {
2573: if (line.next() == null)
2574: break;
2575: line = line.next();
2576: }
2577: Position end = new Position(line, 0);
2578: Region r = new Region(this , begin, end);
2579: r.delete();
2580: if (needsRenumbering)
2581: renumber();
2582: resetUndo();
2583: } finally {
2584: unlockWrite();
2585: }
2586: }
2587:
2588: public void saveProperties() {
2589: if (type != TYPE_NORMAL)
2590: return;
2591: if (isUntitled)
2592: return;
2593: final File file = getFile();
2594: if (file == null)
2595: return;
2596: final String netPath = file.netPath();
2597: // Name must not contain any characters that can't appear in an XML
2598: // attribute value. In particular, an HTTP query might contain '&'.
2599: if (netPath.indexOf('<') >= 0 || netPath.indexOf('&') >= 0)
2600: return;
2601: FileHistoryEntry entry = new FileHistoryEntry();
2602: entry.setName(netPath);
2603: entry.setEncoding(file.getEncoding());
2604: entry.setMode(mode.toString());
2605: entry.setWhen(System.currentTimeMillis());
2606: entry.setProperties(properties);
2607: FileHistory fileHistory = FileHistory.getFileHistory();
2608: fileHistory.store(entry);
2609: fileHistory.save();
2610: }
2611:
2612: public void windowClosing() {
2613: }
2614:
2615: public void dispose() {
2616: if (cache != null && cache.isFile()) {
2617: // Only delete the cache file if no other buffer is using it.
2618: for (BufferIterator it = new BufferIterator(); it.hasNext();) {
2619: Buffer buf = it.nextBuffer();
2620: if (buf != this && buf.getCache() == cache)
2621: return;
2622: }
2623: // Not in use. OK to delete.
2624: cache.delete();
2625: }
2626: }
2627:
2628: protected void finalize() throws Throwable {
2629: try {
2630: lockWrite();
2631: } catch (InterruptedException e) {
2632: Log.debug(e);
2633: return;
2634: }
2635: try {
2636: empty();
2637: } finally {
2638: unlockWrite();
2639: }
2640: super .finalize();
2641: }
2642:
2643: public final boolean canBeRestored() {
2644: final File file = getFile();
2645: if (file != null && file.isRemote())
2646: return false;
2647: if (this instanceof RemoteBuffer)
2648: return false;
2649: switch (type) {
2650: case TYPE_NORMAL:
2651: case TYPE_ARCHIVE:
2652: case TYPE_DIRECTORY:
2653: return true;
2654: default:
2655: return false;
2656: }
2657: }
2658:
2659: public final boolean isUntitled() {
2660: return isUntitled;
2661: }
2662:
2663: private boolean isTransient;
2664:
2665: public boolean isTransient() {
2666: return isTransient;
2667: }
2668:
2669: public final void setTransient(boolean b) {
2670: unsplitOnClose = isTransient = b;
2671: }
2672:
2673: private boolean unsplitOnClose;
2674:
2675: public boolean unsplitOnClose() {
2676: return unsplitOnClose;
2677: }
2678:
2679: public final void setUnsplitOnClose(boolean b) {
2680: unsplitOnClose = b;
2681: }
2682:
2683: // Cache for getText().
2684: private SoftReference srText;
2685:
2686: // Never returns null.
2687: public synchronized String getText() {
2688: if (srText != null) {
2689: final String text = (String) srText.get();
2690: if (text != null)
2691: return text;
2692: }
2693: FastStringBuffer sb = new FastStringBuffer(16384);
2694: Line line = getFirstLine();
2695: if (line != null) {
2696: String text = line.getText();
2697: if (text != null)
2698: sb.append(text);
2699: line = line.next();
2700: while (line != null) {
2701: text = line.getText();
2702: if (text != null) {
2703: sb.append('\n');
2704: sb.append(text);
2705: }
2706: line = line.next();
2707: }
2708: }
2709: final String text = sb.toString();
2710: srText = new SoftReference(text);
2711: return text;
2712: }
2713:
2714: public boolean isEmpty() {
2715: Line first = getFirstLine();
2716: if (first == null)
2717: return true;
2718: if (first.next() != null)
2719: return false;
2720: return first.length() == 0;
2721: }
2722:
2723: public int getDisplayHeight() {
2724: return visibleLineCount * Display.getCharHeight();
2725: }
2726:
2727: // Returns cumulative height to top of target line.
2728: public int getY(Line line) {
2729: Debug.assertTrue(!line.isHidden());
2730: if (folded) {
2731: final int charHeight = Display.getCharHeight();
2732: int y = 0;
2733: for (Line l = getFirstLine(); l != null; l = l
2734: .nextVisible()) {
2735: if (l == line)
2736: break;
2737: else
2738: y += charHeight;
2739: }
2740: return y;
2741: } else
2742: return line.lineNumber() * Display.getCharHeight();
2743: }
2744:
2745: public int getDisplayWidth() {
2746: return Display.getGutterWidth(this ) + (getMaximumColumns() + 1)
2747: * Display.getCharWidth();
2748: }
2749:
2750: private int maxCols = 0;
2751: private boolean maxColsValid = false;
2752:
2753: public int getMaximumColumns() {
2754: if (maxCols == 0) {
2755: maxCols = calculateMaximumColumns();
2756: maxColsValid = true;
2757: }
2758: return maxCols;
2759: }
2760:
2761: private int calculateMaximumColumns() {
2762: if (getModeId() == BINARY_MODE)
2763: return getFirstLine().length();
2764: final int tabWidth = getTabWidth();
2765: int max = 0;
2766: for (Line line = getFirstLine(); line != null; line = line
2767: .next()) {
2768: int cols;
2769: if ((cols = getCol(line, line.length(), tabWidth)) > max)
2770: max = cols;
2771: }
2772: return max;
2773: }
2774:
2775: // Returns true if there was a change.
2776: public boolean validateMaximumColumns() {
2777: if (maxColsValid)
2778: return false;
2779: final int oldMaxCols = maxCols;
2780: maxCols = calculateMaximumColumns();
2781: maxColsValid = true;
2782: return maxCols != oldMaxCols;
2783: }
2784:
2785: public void setProperty(Property property, Object value) {
2786: properties.setProperty(property, value);
2787: }
2788:
2789: public void setProperty(Property property, boolean value) {
2790: properties.setProperty(property, value);
2791: }
2792:
2793: public void setProperty(Property property, int value) {
2794: properties.setProperty(property, value);
2795: }
2796:
2797: boolean setPropertyFromString(Property property, String value) {
2798: return properties.setPropertyFromString(property, value);
2799: }
2800:
2801: boolean removeProperty(Property property) {
2802: return properties.removeProperty(property);
2803: }
2804:
2805: public String getStringProperty(Property property) {
2806: Object value = properties.getProperty(property);
2807: if (value instanceof String)
2808: return (String) value;
2809: if (mode != null)
2810: return mode.getStringProperty(property);
2811: return (String) property.getDefaultValue();
2812: }
2813:
2814: public int getIntegerProperty(Property property) {
2815: if (!property.isIntegerProperty())
2816: Debug.bug();
2817: Object value = properties.getProperty(property);
2818: if (value instanceof Integer)
2819: return ((Integer) value).intValue();
2820: if (mode != null)
2821: return mode.getIntegerProperty(property);
2822: return ((Integer) property.getDefaultValue()).intValue();
2823: }
2824:
2825: public boolean getBooleanProperty(Property property) {
2826: if (!property.isBooleanProperty())
2827: Debug.bug();
2828: Object value = properties.getProperty(property);
2829: if (value == Boolean.TRUE)
2830: return true;
2831: if (value == Boolean.FALSE)
2832: return false;
2833: if (mode != null)
2834: return mode.getBooleanProperty(property);
2835: return ((Boolean) property.getDefaultValue()).booleanValue();
2836: }
2837:
2838: public final int getTabWidth() {
2839: return getIntegerProperty(Property.TAB_WIDTH);
2840: }
2841:
2842: public void setTabWidth(int tabWidth) {
2843: properties.setProperty(Property.TAB_WIDTH, tabWidth);
2844: }
2845:
2846: public final boolean getUseTabs() {
2847: return getBooleanProperty(Property.USE_TABS);
2848: }
2849:
2850: public final int getIndentSize() {
2851: return getIntegerProperty(Property.INDENT_SIZE);
2852: }
2853:
2854: public final void setIndentSize(int indentSize) {
2855: properties.setProperty(Property.INDENT_SIZE, indentSize);
2856: }
2857:
2858: private static final Cursor textCursor = Cursor
2859: .getPredefinedCursor(Cursor.TEXT_CURSOR);
2860:
2861: public Cursor getDefaultCursor() {
2862: return textCursor;
2863: }
2864:
2865: public Cursor getDefaultCursor(Position pos) {
2866: if (pos == null || pos.getLine() instanceof ImageLine)
2867: return Cursor.getDefaultCursor();
2868: else
2869: return textCursor;
2870: }
2871:
2872: public String getStatusText(Editor editor) {
2873: Debug.assertTrue(editor.getBuffer() == this );
2874: Position dot = editor.getDotCopy();
2875: if (dot == null)
2876: return null;
2877: final FastStringBuffer sb = new FastStringBuffer();
2878: if (cvsEntry != null) {
2879: sb.append("CVS ");
2880: final String revision = cvsEntry.getRevision();
2881: if (revision.equals("0")) {
2882: sb.append('A');
2883: } else {
2884: sb.append(revision);
2885: if (cvsEntry.getCheckoutTime() != lastModified)
2886: sb.append(" M");
2887: }
2888: sb.append(" ");
2889: }
2890: if (Editor.preferences().getBooleanProperty(
2891: Property.STATUS_BAR_DISPLAY_LINE_SEPARATOR)) {
2892: if (lineSeparator != null) {
2893: if (lineSeparator.equals("\n"))
2894: sb.append("LF ");
2895: else if (lineSeparator.equals("\r\n"))
2896: sb.append("CR+LF ");
2897: else if (lineSeparator.equals("\r"))
2898: sb.append("CR ");
2899: }
2900: }
2901: sb.append("Line ");
2902: sb.append(String.valueOf(dot.lineNumber() + 1));
2903: if (Editor.preferences().getBooleanProperty(
2904: Property.STATUS_BAR_DISPLAY_LINE_COUNT)) {
2905: sb.append(" of ");
2906: sb.append(String.valueOf(getLineCount()));
2907: }
2908: sb.append(" Col ");
2909: final Display display = editor.getDisplay();
2910: sb.append(String.valueOf(display.getAbsoluteCaretCol() + 1));
2911: if (getBooleanProperty(Property.WRAP))
2912: sb.append(" Wrap");
2913: if (Editor.isRecordingMacro())
2914: sb.append(" [Recording macro...]");
2915: return sb.toString();
2916: }
2917:
2918: public Expansion getExpansion(Position dot) {
2919: return new Expansion(dot, mode);
2920: }
2921:
2922: public void restoreView(Editor editor) {
2923: final Display display = editor.getDisplay();
2924: final View view = editor.getView(this );
2925: Debug.assertTrue(view != null);
2926: if (view != null) {
2927: if (view.getDot() == null) {
2928: Line line = getLine(view.getDotLineNumber());
2929: if (line != null) {
2930: int offset = view.getDotOffset();
2931: if (offset < 0)
2932: offset = 0;
2933: else if (offset > line.length())
2934: offset = line.length();
2935: view.setDot(new Position(line, offset));
2936: } else {
2937: line = getFirstLine();
2938: if (line != null)
2939: view.setDot(new Position(line, 0));
2940: }
2941: }
2942: editor.setDot(view.getDot() == null ? null : new Position(
2943: view.getDot()));
2944: editor.setMark(view.getMark() == null ? null
2945: : new Position(view.getMark()));
2946: editor.setSelection(view.getSelection());
2947: editor.setColumnSelection(view.isColumnSelection());
2948: if (view.getTopLine() == null) {
2949: view.topLine = getFirstLine();
2950: view.pixelsAboveTopLine = 0;
2951: }
2952: display.setTopLine(view.getTopLine());
2953: display.setPixelsAboveTopLine(view.pixelsAboveTopLine);
2954: display.setShift(view.getShift());
2955: display.setCaretCol(view.getCaretCol());
2956: }
2957: }
2958:
2959: private CVSEntry cvsEntry;
2960:
2961: public final CVSEntry getCVSEntry() {
2962: return cvsEntry;
2963: }
2964:
2965: public final void checkCVS() {
2966: cvsEntry = CVSEntry.parseEntryForFile(getFile());
2967: }
2968:
2969: public final boolean isKeyword(String s) {
2970: return mode.isKeyword(s);
2971: }
2972: }
|