| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.openapi.editor.impl; |
| |
| import com.intellij.codeStyle.CodeStyleFacade; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.EditorFactory; |
| import com.intellij.openapi.editor.LazyRangeMarkerFactory; |
| import com.intellij.openapi.editor.RangeMarker; |
| import com.intellij.openapi.editor.event.DocumentAdapter; |
| import com.intellij.openapi.editor.event.DocumentEvent; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.UserDataHolderBase; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.util.containers.WeakList; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.List; |
| |
| public class LazyRangeMarkerFactoryImpl extends LazyRangeMarkerFactory { |
| private final Project myProject; |
| private static final Key<WeakList<LazyMarker>> LAZY_MARKERS_KEY = Key.create("LAZY_MARKERS_KEY"); |
| |
| public LazyRangeMarkerFactoryImpl(@NotNull Project project, @NotNull final FileDocumentManager fileDocumentManager) { |
| myProject = project; |
| |
| EditorFactory.getInstance().getEventMulticaster().addDocumentListener(new DocumentAdapter() { |
| @Override |
| public void beforeDocumentChange(DocumentEvent e) { |
| transformRangeMarkers(e); |
| } |
| |
| @Override |
| public void documentChanged(DocumentEvent e) { |
| transformRangeMarkers(e); |
| } |
| |
| private void transformRangeMarkers(@NotNull DocumentEvent e) { |
| Document document = e.getDocument(); |
| VirtualFile file = fileDocumentManager.getFile(document); |
| if (file == null || myProject.isDisposed()) { |
| return; |
| } |
| |
| WeakList<LazyMarker> lazyMarkers = getMarkers(file); |
| if (lazyMarkers == null) { |
| return; |
| } |
| |
| List<LazyMarker> markers = lazyMarkers.toStrongList(); |
| for (LazyMarker marker : markers) { |
| if (file.equals(marker.getFile())) { |
| marker.getOrCreateDelegate(); |
| } |
| } |
| } |
| }, project); |
| } |
| |
| static WeakList<LazyMarker> getMarkers(@NotNull VirtualFile file) { |
| return file.getUserData(LazyRangeMarkerFactoryImpl.LAZY_MARKERS_KEY); |
| } |
| |
| private static void addToLazyMarkersList(@NotNull LazyMarker marker, @NotNull VirtualFile file) { |
| WeakList<LazyMarker> markers = getMarkers(file); |
| |
| if (markers == null) { |
| markers = file.putUserDataIfAbsent(LAZY_MARKERS_KEY, new WeakList<LazyMarker>()); |
| } |
| markers.add(marker); |
| } |
| |
| private static void removeFromLazyMarkersList(@NotNull LazyMarker marker, @NotNull VirtualFile file) { |
| WeakList<LazyMarker> markers = getMarkers(file); |
| |
| if (markers != null) { |
| markers.remove(marker); |
| } |
| } |
| |
| @Override |
| @NotNull |
| public RangeMarker createRangeMarker(@NotNull final VirtualFile file, final int offset) { |
| return ApplicationManager.getApplication().runReadAction(new Computable<RangeMarker>() { |
| @Override |
| public RangeMarker compute() { |
| // even for already loaded document do not create range marker yet - wait until it really needed when e.g. user clicked to jump to OpenFileDescriptor |
| final LazyMarker marker = new OffsetLazyMarker(file, offset); |
| addToLazyMarkersList(marker, file); |
| return marker; |
| } |
| }); |
| } |
| |
| @Override |
| @NotNull |
| public RangeMarker createRangeMarker(@NotNull final VirtualFile file, final int line, final int column, final boolean persistent) { |
| return ApplicationManager.getApplication().runReadAction(new Computable<RangeMarker>() { |
| @Override |
| public RangeMarker compute() { |
| final Document document = FileDocumentManager.getInstance().getCachedDocument(file); |
| if (document != null) { |
| final int offset = calculateOffset(myProject, file, document, line, column); |
| return document.createRangeMarker(offset, offset, persistent); |
| } |
| |
| final LazyMarker marker = new LineColumnLazyMarker(file, line, column); |
| addToLazyMarkersList(marker, file); |
| return marker; |
| } |
| }); |
| } |
| |
| abstract static class LazyMarker extends UserDataHolderBase implements RangeMarker { |
| protected RangeMarker myDelegate; // the real range marker which is created only when document is opened, or (this) which means it's disposed |
| protected final VirtualFile myFile; |
| protected final int myInitialOffset; |
| |
| private LazyMarker(@NotNull VirtualFile file, int offset) { |
| myFile = file; |
| myInitialOffset = offset; |
| } |
| |
| boolean isDelegated() { |
| return myDelegate != null; |
| } |
| |
| @NotNull |
| public VirtualFile getFile() { |
| return myFile; |
| } |
| |
| @Nullable |
| protected final RangeMarker getOrCreateDelegate() { |
| if (myDelegate == null) { |
| Document document = FileDocumentManager.getInstance().getDocument(myFile); |
| if (document == null) { |
| return null; |
| } |
| myDelegate = createDelegate(myFile, document); |
| removeFromLazyMarkersList(this, myFile); |
| } |
| return isDisposed() ? null : myDelegate; |
| } |
| |
| @Nullable |
| protected abstract RangeMarker createDelegate(@NotNull VirtualFile file, @NotNull Document document); |
| |
| @Override |
| @NotNull |
| public Document getDocument() { |
| RangeMarker delegate = getOrCreateDelegate(); |
| if (delegate == null) { |
| //noinspection ConstantConditions |
| return FileDocumentManager.getInstance().getDocument(myFile); |
| } |
| return delegate.getDocument(); |
| } |
| |
| @Override |
| public int getStartOffset() { |
| return myDelegate == null || isDisposed() ? myInitialOffset : myDelegate.getStartOffset(); |
| } |
| |
| public boolean isDisposed() { |
| return myDelegate == this; |
| } |
| |
| |
| @Override |
| public int getEndOffset() { |
| return myDelegate == null || isDisposed() ? myInitialOffset : myDelegate.getEndOffset(); |
| } |
| |
| @Override |
| public boolean isValid() { |
| RangeMarker delegate = getOrCreateDelegate(); |
| return delegate != null && !isDisposed() && delegate.isValid(); |
| } |
| |
| @Override |
| public void setGreedyToLeft(boolean greedy) { |
| getOrCreateDelegate().setGreedyToLeft(greedy); |
| } |
| |
| @Override |
| public void setGreedyToRight(boolean greedy) { |
| getOrCreateDelegate().setGreedyToRight(greedy); |
| } |
| |
| @Override |
| public boolean isGreedyToRight() { |
| return getOrCreateDelegate().isGreedyToRight(); |
| } |
| |
| @Override |
| public boolean isGreedyToLeft() { |
| return getOrCreateDelegate().isGreedyToLeft(); |
| } |
| |
| @Override |
| public void dispose() { |
| assert !isDisposed(); |
| RangeMarker delegate = myDelegate; |
| if (delegate == null) { |
| removeFromLazyMarkersList(this, myFile); |
| myDelegate = this; // mark of disposed marker |
| } |
| else { |
| delegate.dispose(); |
| } |
| } |
| } |
| |
| private static class OffsetLazyMarker extends LazyMarker { |
| private OffsetLazyMarker(@NotNull VirtualFile file, int offset) { |
| super(file, offset); |
| } |
| |
| @Override |
| public boolean isValid() { |
| RangeMarker delegate = myDelegate; |
| if (delegate == null) { |
| Document document = FileDocumentManager.getInstance().getDocument(myFile); |
| return document != null; |
| } |
| |
| return super.isValid(); |
| } |
| |
| @Override |
| @NotNull |
| public RangeMarker createDelegate(@NotNull VirtualFile file, @NotNull final Document document) { |
| final int offset = Math.min(myInitialOffset, document.getTextLength()); |
| return document.createRangeMarker(offset, offset); |
| } |
| } |
| |
| private class LineColumnLazyMarker extends LazyMarker { |
| private final int myLine; |
| private final int myColumn; |
| |
| private LineColumnLazyMarker(@NotNull VirtualFile file, int line, int column) { |
| super(file, -1); |
| myLine = line; |
| myColumn = column; |
| } |
| |
| @Override |
| @Nullable |
| public RangeMarker createDelegate(@NotNull VirtualFile file, @NotNull Document document) { |
| if (document.getTextLength() == 0 && !(myLine == 0 && myColumn == 0)) { |
| return null; |
| } |
| |
| int offset = calculateOffset(myProject, file, document, myLine, myColumn); |
| return document.createRangeMarker(offset, offset); |
| } |
| |
| @Override |
| public boolean isValid() { |
| RangeMarker delegate = myDelegate; |
| if (delegate == null) { |
| Document document = FileDocumentManager.getInstance().getDocument(myFile); |
| return document != null && (document.getTextLength() != 0 || myLine == 0 && myColumn == 0); |
| } |
| |
| return super.isValid(); |
| } |
| |
| @Override |
| public int getStartOffset() { |
| getOrCreateDelegate(); |
| return super.getStartOffset(); |
| } |
| |
| @Override |
| public int getEndOffset() { |
| getOrCreateDelegate(); |
| return super.getEndOffset(); |
| } |
| } |
| |
| private static int calculateOffset(@NotNull Project project, @NotNull VirtualFile file, @NotNull Document document, final int line, final int column) { |
| int offset; |
| if (line < document.getLineCount()) { |
| final int lineStart = document.getLineStartOffset(line); |
| final int lineEnd = document.getLineEndOffset(line); |
| final CharSequence docText = document.getCharsSequence(); |
| final int tabSize = CodeStyleFacade.getInstance(project).getTabSize(file.getFileType()); |
| |
| offset = lineStart; |
| int col = 0; |
| while (offset < lineEnd && col < column) { |
| col += docText.charAt(offset) == '\t' ? tabSize : 1; |
| offset++; |
| } |
| } |
| else { |
| offset = document.getTextLength(); |
| } |
| return offset; |
| } |
| |
| } |