| /******************************************************************************* |
| * Copyright (c) 2005, 2017 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| *******************************************************************************/ |
| package org.eclipse.dltk.internal.ui.editor; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.core.filebuffers.FileBuffers; |
| import org.eclipse.core.filebuffers.ITextFileBuffer; |
| import org.eclipse.core.filebuffers.ITextFileBufferManager; |
| import org.eclipse.core.filebuffers.LocationKind; |
| import org.eclipse.core.filesystem.IFileStore; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.dltk.core.BufferChangedEvent; |
| import org.eclipse.dltk.core.IBuffer; |
| import org.eclipse.dltk.core.IBufferChangedListener; |
| import org.eclipse.dltk.core.IOpenable; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.ui.DLTKUIPlugin; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.DefaultLineTracker; |
| import org.eclipse.jface.text.DocumentEvent; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IDocumentListener; |
| import org.eclipse.jface.text.ISynchronizable; |
| import org.eclipse.swt.widgets.Display; |
| |
| /** |
| * Adapts <code>IDocument</code> to <code>IBuffer</code>. Uses the same |
| * algorithm as the text widget to determine the buffer's line delimiter. All |
| * text inserted into the buffer is converted to this line delimiter. This class |
| * is <code>public</code> for test purposes only. |
| */ |
| public class DocumentAdapter implements IBuffer, IDocumentListener { |
| |
| /** |
| * Internal implementation of a NULL instanceof IBuffer. |
| */ |
| static private class NullBuffer implements IBuffer { |
| @Override |
| public void addBufferChangedListener(IBufferChangedListener listener) { |
| } |
| |
| @Override |
| public void append(char[] text) { |
| } |
| |
| @Override |
| public void append(String text) { |
| } |
| |
| @Override |
| public void close() { |
| } |
| |
| @Override |
| public char getChar(int position) { |
| return 0; |
| } |
| |
| @Override |
| public char[] getCharacters() { |
| return null; |
| } |
| |
| @Override |
| public String getContents() { |
| return null; |
| } |
| |
| @Override |
| public int getLength() { |
| return 0; |
| } |
| |
| @Override |
| public IOpenable getOwner() { |
| return null; |
| } |
| |
| @Override |
| public String getText(int offset, int length) { |
| return null; |
| } |
| |
| @Override |
| public IResource getUnderlyingResource() { |
| return null; |
| } |
| |
| @Override |
| public boolean hasUnsavedChanges() { |
| return false; |
| } |
| |
| @Override |
| public boolean isClosed() { |
| return false; |
| } |
| |
| @Override |
| public boolean isReadOnly() { |
| return true; |
| } |
| |
| @Override |
| public void removeBufferChangedListener( |
| IBufferChangedListener listener) { |
| } |
| |
| @Override |
| public void replace(int position, int length, char[] text) { |
| } |
| |
| @Override |
| public void replace(int position, int length, String text) { |
| } |
| |
| @Override |
| public void save(IProgressMonitor progress, boolean force) |
| throws ModelException { |
| } |
| |
| @Override |
| public void setContents(char[] contents) { |
| } |
| |
| @Override |
| public void setContents(String contents) { |
| } |
| } |
| |
| /** NULL implementing <code>IBuffer</code> */ |
| public final static IBuffer NULL = new NullBuffer(); |
| |
| /* |
| * |
| */ |
| private IPath fPath; |
| |
| /** |
| * Executes a document set content call in the ui thread. |
| */ |
| protected class DocumentSetCommand implements Runnable { |
| |
| private String fContents; |
| |
| @Override |
| public void run() { |
| fDocument.set(fContents); |
| } |
| |
| public void set(String contents) { |
| fContents = contents; |
| // Display.getDefault().syncExec(this); |
| DocumentAdapter.run(this); |
| } |
| } |
| |
| /** |
| * Executes a document replace call in the ui thread. |
| */ |
| protected class DocumentReplaceCommand implements Runnable { |
| |
| private int fOffset; |
| private int fLength; |
| private String fText; |
| |
| @Override |
| public void run() { |
| try { |
| fDocument.replace(fOffset, fLength, fText); |
| } catch (BadLocationException x) { |
| // ignore |
| } |
| } |
| |
| public void replace(int offset, int length, String text) { |
| fOffset = offset; |
| fLength = length; |
| fText = text; |
| Display.getDefault().syncExec(this); |
| } |
| } |
| |
| private static final boolean DEBUG_LINE_DELIMITERS = true; |
| |
| private IOpenable fOwner; |
| private IFile fFile; |
| private ITextFileBuffer fTextFileBuffer; |
| private IDocument fDocument; |
| |
| private DocumentSetCommand fSetCmd = new DocumentSetCommand(); |
| private DocumentReplaceCommand fReplaceCmd = new DocumentReplaceCommand(); |
| |
| private Set<String> fLegalLineDelimiters; |
| |
| private List<IBufferChangedListener> fBufferListeners = new ArrayList<>(3); |
| private IStatus fStatus; |
| |
| /** @since 4.0 */ |
| private LocationKind fLocationKind; |
| |
| /** @since 4.0 */ |
| private IFileStore fFileStore; |
| |
| /** |
| * Constructs a new document adapter. |
| */ |
| public DocumentAdapter(IOpenable owner, IFile file) { |
| |
| fOwner = owner; |
| fFile = file; |
| fPath = fFile.getFullPath(); |
| fLocationKind = LocationKind.IFILE; |
| |
| initialize(); |
| } |
| |
| /** |
| * Constructs a new document adapter. |
| * |
| * |
| */ |
| public DocumentAdapter(IOpenable owner, IPath path) { |
| Assert.isLegal(path != null); |
| |
| fOwner = owner; |
| fPath = path; |
| fLocationKind = LocationKind.NORMALIZE; |
| |
| initialize(); |
| } |
| |
| /** |
| * Constructs a new document adapter. |
| * |
| * @param owner |
| * the owner of this buffer |
| * @param fileStore |
| * the file store of the file that backs the buffer |
| * @param path |
| * the path of the file that backs the buffer |
| * @since 4.0 |
| */ |
| public DocumentAdapter(IOpenable owner, IFileStore fileStore, IPath path) { |
| Assert.isLegal(fileStore != null); |
| Assert.isLegal(path != null); |
| fOwner = owner; |
| fFileStore = fileStore; |
| fPath = path; |
| fLocationKind = LocationKind.NORMALIZE; |
| |
| initialize(); |
| } |
| |
| private void initialize() { |
| ITextFileBufferManager manager = FileBuffers.getTextFileBufferManager(); |
| try { |
| if (fFileStore != null) { |
| manager.connectFileStore(fFileStore, new NullProgressMonitor()); |
| fTextFileBuffer = manager |
| .getFileStoreTextFileBuffer(fFileStore); |
| } else { |
| manager.connect(fPath, fLocationKind, |
| new NullProgressMonitor()); |
| fTextFileBuffer = manager.getTextFileBuffer(fPath, |
| fLocationKind); |
| } |
| fDocument = fTextFileBuffer.getDocument(); |
| } catch (CoreException x) { |
| fStatus = x.getStatus(); |
| fDocument = manager.createEmptyDocument(fPath, |
| LocationKind.NORMALIZE); |
| if (fDocument instanceof ISynchronizable) |
| ((ISynchronizable) fDocument).setLockObject(new Object()); |
| } |
| fDocument.addPrenotifiedDocumentListener(this); |
| } |
| |
| /** |
| * Returns the status of this document adapter. |
| */ |
| public IStatus getStatus() { |
| if (fStatus != null) |
| return fStatus; |
| if (fTextFileBuffer != null) |
| return fTextFileBuffer.getStatus(); |
| return null; |
| } |
| |
| /** |
| * Returns the adapted document. |
| * |
| * @return the adapted document |
| */ |
| public IDocument getDocument() { |
| return fDocument; |
| } |
| |
| /* |
| * @see IBuffer#addBufferChangedListener(IBufferChangedListener) |
| */ |
| @Override |
| public void addBufferChangedListener(IBufferChangedListener listener) { |
| Assert.isNotNull(listener); |
| if (!fBufferListeners.contains(listener)) |
| fBufferListeners.add(listener); |
| } |
| |
| /* |
| * @see IBuffer#removeBufferChangedListener(IBufferChangedListener) |
| */ |
| @Override |
| public void removeBufferChangedListener(IBufferChangedListener listener) { |
| Assert.isNotNull(listener); |
| fBufferListeners.remove(listener); |
| } |
| |
| @Override |
| public void append(char[] text) { |
| append(new String(text)); |
| } |
| |
| @Override |
| public void append(String text) { |
| if (DEBUG_LINE_DELIMITERS) { |
| validateLineDelimiters(text); |
| } |
| fReplaceCmd.replace(fDocument.getLength(), 0, text); |
| } |
| |
| @Override |
| public void close() { |
| |
| if (isClosed()) |
| return; |
| |
| IDocument d = fDocument; |
| fDocument = null; |
| d.removePrenotifiedDocumentListener(this); |
| |
| if (fTextFileBuffer != null) { |
| ITextFileBufferManager manager = FileBuffers |
| .getTextFileBufferManager(); |
| try { |
| if (fFileStore != null) |
| manager.disconnectFileStore(fFileStore, |
| new NullProgressMonitor()); |
| else |
| manager.disconnect(fPath, fLocationKind, |
| new NullProgressMonitor()); |
| } catch (CoreException x) { |
| // ignore |
| } |
| fTextFileBuffer = null; |
| } |
| |
| fireBufferChanged(new BufferChangedEvent(this, 0, 0, null)); |
| fBufferListeners.clear(); |
| } |
| |
| @Override |
| public char getChar(int position) { |
| try { |
| return fDocument.getChar(position); |
| } catch (BadLocationException x) { |
| throw new ArrayIndexOutOfBoundsException(); |
| } |
| } |
| |
| @Override |
| public char[] getCharacters() { |
| String content = getContents(); |
| return content == null ? null : content.toCharArray(); |
| } |
| |
| @Override |
| public String getContents() { |
| return fDocument.get(); |
| } |
| |
| @Override |
| public int getLength() { |
| return fDocument.getLength(); |
| } |
| |
| @Override |
| public IOpenable getOwner() { |
| return fOwner; |
| } |
| |
| @Override |
| public String getText(int offset, int length) { |
| try { |
| return fDocument.get(offset, length); |
| } catch (BadLocationException x) { |
| throw new ArrayIndexOutOfBoundsException(); |
| } |
| } |
| |
| @Override |
| public IResource getUnderlyingResource() { |
| return fFile; |
| } |
| |
| @Override |
| public boolean hasUnsavedChanges() { |
| return fTextFileBuffer != null ? fTextFileBuffer.isDirty() : false; |
| } |
| |
| @Override |
| public boolean isClosed() { |
| return fDocument == null; |
| } |
| |
| @Override |
| public boolean isReadOnly() { |
| IResource resource = getUnderlyingResource(); |
| return resource == null ? true |
| : resource.getResourceAttributes().isReadOnly(); |
| } |
| |
| @Override |
| public void replace(int position, int length, char[] text) { |
| replace(position, length, new String(text)); |
| } |
| |
| @Override |
| public void replace(int position, int length, String text) { |
| if (DEBUG_LINE_DELIMITERS) { |
| validateLineDelimiters(text); |
| } |
| fReplaceCmd.replace(position, length, text); |
| } |
| |
| @Override |
| public void save(IProgressMonitor progress, boolean force) |
| throws ModelException { |
| try { |
| if (fTextFileBuffer != null) |
| fTextFileBuffer.commit(progress, force); |
| } catch (CoreException e) { |
| throw new ModelException(e); |
| } |
| } |
| |
| @Override |
| public void setContents(char[] contents) { |
| setContents(new String(contents)); |
| } |
| |
| @Override |
| public void setContents(String contents) { |
| int oldLength = fDocument.getLength(); |
| |
| if (contents == null) { |
| |
| if (oldLength != 0) |
| fSetCmd.set(""); //$NON-NLS-1$ |
| |
| } else { |
| |
| // set only if different |
| if (DEBUG_LINE_DELIMITERS) { |
| validateLineDelimiters(contents); |
| } |
| |
| if (!contents.equals(fDocument.get())) |
| fSetCmd.set(contents); |
| } |
| } |
| |
| private void validateLineDelimiters(String contents) { |
| |
| if (fLegalLineDelimiters == null) { |
| // collect all line delimiters in the document |
| HashSet<String> existingDelimiters = new HashSet<>(); |
| |
| for (int i = fDocument.getNumberOfLines() - 1; i >= 0; i--) { |
| try { |
| String curr = fDocument.getLineDelimiter(i); |
| if (curr != null) { |
| existingDelimiters.add(curr); |
| } |
| } catch (BadLocationException e) { |
| DLTKUIPlugin.log(e); |
| } |
| } |
| if (existingDelimiters.isEmpty()) { |
| return; // first insertion of a line delimiter: no test |
| } |
| fLegalLineDelimiters = existingDelimiters; |
| |
| } |
| |
| DefaultLineTracker tracker = new DefaultLineTracker(); |
| tracker.set(contents); |
| |
| int lines = tracker.getNumberOfLines(); |
| if (lines <= 1) |
| return; |
| |
| for (int i = 0; i < lines; i++) { |
| try { |
| String curr = tracker.getLineDelimiter(i); |
| if (curr != null && !fLegalLineDelimiters.contains(curr)) { |
| StringBuffer buf = new StringBuffer( |
| "WARNING: DocumentAdapter added new line delimiter to code: "); //$NON-NLS-1$ |
| for (int k = 0; k < curr.length(); k++) { |
| if (k > 0) |
| buf.append(' '); |
| buf.append((int) curr.charAt(k)); |
| } |
| IStatus status = new Status(IStatus.WARNING, |
| DLTKUIPlugin.PLUGIN_ID, IStatus.OK, buf.toString(), |
| new Throwable()); |
| DLTKUIPlugin.log(status); |
| } |
| } catch (BadLocationException e) { |
| DLTKUIPlugin.log(e); |
| } |
| } |
| } |
| |
| @Override |
| public void documentAboutToBeChanged(DocumentEvent event) { |
| // there is nothing to do here |
| } |
| |
| @Override |
| public void documentChanged(DocumentEvent event) { |
| fireBufferChanged(new BufferChangedEvent(this, event.getOffset(), |
| event.getLength(), event.getText())); |
| } |
| |
| private void fireBufferChanged(BufferChangedEvent event) { |
| if (fBufferListeners != null && fBufferListeners.size() > 0) { |
| Iterator<IBufferChangedListener> e = new ArrayList<>( |
| fBufferListeners).iterator(); |
| while (e.hasNext()) |
| e.next().bufferChanged(event); |
| } |
| } |
| |
| /** |
| * Run the given runnable in the UI thread. |
| * |
| * @param runnable |
| * the runnable |
| * @since 3.3 |
| */ |
| private static final void run(Runnable runnable) { |
| Display currentDisplay = Display.getCurrent(); |
| if (currentDisplay != null) |
| runnable.run(); |
| else |
| Display.getDefault().syncExec(runnable); |
| } |
| |
| } |