type = ((ArrayType)type).componentType();
}
if(type instanceof ClassType) {
- final List<Location> locations = ((ClassType)type).allLineLocations();
+ final ClassType clsType = (ClassType)type;
+ final List<Location> locations = clsType.allLineLocations();
if(locations.size() > 0) {
final Location location = locations.get(0);
return ApplicationManager.getApplication().runReadAction(new Computable<SourcePosition>() {
public SourcePosition compute() {
SourcePosition position = debugProcess.getPositionManager().getSourcePosition(location);
- // adjust position
- final PsiClass classAt = JVMNameUtil.getClassAt(position);
- if (classAt != null) {
- final SourcePosition classPosition = SourcePosition.createFromElement(classAt);
- if (classPosition != null) {
- position = classPosition;
+ // adjust position for non-anonymous classes
+ if (clsType.name().indexOf("$") < 0) {
+ final PsiClass classAt = JVMNameUtil.getClassAt(position);
+ if (classAt != null) {
+ final SourcePosition classPosition = SourcePosition.createFromElement(classAt);
+ if (classPosition != null) {
+ position = classPosition;
+ }
}
}
return position;
final ThreadReference thread = ((ThreadStartEvent)event).thread();
getManagerThread().schedule(new DebuggerCommandImpl() {
protected void action() throws Exception {
+ getVirtualMachineProxy().threadStarted(thread);
myDebugProcessDispatcher.getMulticaster().threadStarted(DebugProcessEvents.this, thread);
}
});
final ThreadReference thread = ((ThreadDeathEvent)event).thread();
getManagerThread().schedule(new DebuggerCommandImpl() {
protected void action() throws Exception {
+ getVirtualMachineProxy().threadStopped(thread);
myDebugProcessDispatcher.getMulticaster().threadStopped(DebugProcessEvents.this, thread);
}
});
import com.intellij.debugger.engine.requests.RequestManagerImpl;
import com.intellij.debugger.jdi.StackFrameProxyImpl;
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
-import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
import com.intellij.debugger.ui.breakpoints.Breakpoint;
import com.intellij.debugger.ui.breakpoints.BreakpointWithHighlighter;
import com.intellij.debugger.ui.breakpoints.LineBreakpoint;
import com.intellij.execution.runners.ProgramRunner;
import com.intellij.idea.ActionsBundle;
import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiCompiledElement;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.unscramble.ThreadState;
+import com.intellij.util.Alarm;
import com.intellij.xdebugger.AbstractDebuggerSession;
import com.sun.jdi.ObjectCollectedException;
import com.sun.jdi.ThreadReference;
public static final int EVENT_START_WAIT_ATTACH = 9;
public static final int EVENT_DISPOSE = 10;
public static final int EVENT_REFRESH_VIEWS_ONLY = 11;
+ public static final int EVENT_THREADS_REFRESH = 12;
private volatile boolean myIsEvaluating;
private volatile int myIgnoreFiltersFrameCountThreshold = 0;
private final DebuggerContextImpl SESSION_EMPTY_CONTEXT;
//Thread, user is currently stepping through
private final Set<ThreadReferenceProxyImpl> mySteppingThroughThreads = new HashSet<ThreadReferenceProxyImpl>();
+ protected final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
public boolean isSteppingThrough(ThreadReferenceProxyImpl threadProxy) {
return mySteppingThroughThreads.contains(threadProxy);
ApplicationManager.getApplication().assertIsDispatchThread();
getProcess().dispose();
getContextManager().setState(SESSION_EMPTY_CONTEXT, STATE_DISPOSED, EVENT_DISPOSE, null);
+ Disposer.dispose(myUpdateAlarm);
}
// ManagerCommands
}
public void threadStarted(DebugProcess proc, ThreadReference thread) {
- ((VirtualMachineProxyImpl)proc.getVirtualMachineProxy()).threadStarted(thread);
- DebuggerInvocationUtil.invokeLater(getProject(), new Runnable() {
- public void run() {
- final DebuggerStateManager contextManager = getContextManager();
- contextManager.fireStateChanged(contextManager.getContext(), EVENT_REFRESH_VIEWS_ONLY);
- }
- });
+ notifyThreadsRefresh();
}
public void threadStopped(DebugProcess proc, ThreadReference thread) {
- ((VirtualMachineProxyImpl)proc.getVirtualMachineProxy()).threadStopped(thread);
- DebuggerInvocationUtil.invokeLater(getProject(), new Runnable() {
+ notifyThreadsRefresh();
+ }
+
+ private void notifyThreadsRefresh() {
+ myUpdateAlarm.cancelAllRequests();
+ myUpdateAlarm.addRequest(new Runnable() {
public void run() {
final DebuggerStateManager contextManager = getContextManager();
- contextManager.fireStateChanged(contextManager.getContext(), EVENT_REFRESH_VIEWS_ONLY);
+ contextManager.fireStateChanged(contextManager.getContext(), EVENT_THREADS_REFRESH);
}
- });
+ }, 100, ModalityState.NON_MODAL);
}
}
import com.intellij.debugger.engine.jdi.StackFrameProxy;
import com.intellij.debugger.impl.DebuggerContextImpl;
import com.intellij.debugger.impl.DebuggerContextUtil;
+import com.intellij.debugger.impl.DebuggerSession;
import com.intellij.debugger.impl.DebuggerStateManager;
import com.intellij.debugger.jdi.StackFrameProxyImpl;
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
}
/*invoked in swing thread*/
- protected void rebuild(final boolean updateOnly) {
+ protected void rebuild(int event) {
final DebuggerContextImpl context = getContext();
final boolean paused = context.getDebuggerSession().isPaused();
-
- if (!paused || !updateOnly) {
+ final boolean isRefresh = event == DebuggerSession.EVENT_REFRESH ||
+ event == DebuggerSession.EVENT_REFRESH_VIEWS_ONLY ||
+ event == DebuggerSession.EVENT_THREADS_REFRESH;
+ if (!paused || !isRefresh) {
myThreadsCombo.removeAllItems();
synchronized (myFramesList) {
myFramesList.clear();
if (paused) {
final DebugProcessImpl process = context.getDebugProcess();
if (process != null) {
- process.getManagerThread().schedule(new RefreshFramePanelCommand(updateOnly && myThreadsCombo.getItemCount() != 0));
+ process.getManagerThread().schedule(new RefreshFramePanelCommand(isRefresh && myThreadsCombo.getItemCount() != 0));
}
}
}
stateManager.addListener(new DebuggerContextListener() {
public void changeEvent(DebuggerContextImpl newContext, int event) {
myContext = newContext;
- if (event != DebuggerSession.EVENT_REFRESH_VIEWS_ONLY) {
+ if (event != DebuggerSession.EVENT_REFRESH_VIEWS_ONLY && event != DebuggerSession.EVENT_THREADS_REFRESH) {
refresh();
}
}
protected abstract DebuggerTree createTreeView();
- protected void rebuild(final boolean updateOnly) {
+ protected void rebuild(int event) {
DebuggerSession debuggerSession = getContext().getDebuggerSession();
if(debuggerSession == null) {
return;
import com.intellij.debugger.actions.DebuggerAction;
import com.intellij.debugger.actions.DebuggerActions;
+import com.intellij.debugger.impl.DebuggerContextImpl;
+import com.intellij.debugger.impl.DebuggerSession;
import com.intellij.debugger.impl.DebuggerStateManager;
import com.intellij.debugger.ui.impl.watch.DebuggerTree;
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl;
-import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionPopupMenu;
import com.intellij.openapi.actionSystem.CommonShortcuts;
import com.intellij.openapi.project.Project;
setUpdateEnabled(true);
}
+ protected void changeEvent(DebuggerContextImpl newContext, int event) {
+ if (event != DebuggerSession.EVENT_THREADS_REFRESH) {
+ super.changeEvent(newContext, event);
+ }
+ }
protected DebuggerTree createTreeView() {
return new InspectDebuggerTree(getProject());
import com.intellij.debugger.impl.DebuggerContextImpl;
import com.intellij.debugger.impl.DebuggerContextListener;
-import com.intellij.debugger.impl.DebuggerSession;
import com.intellij.debugger.impl.DebuggerStateManager;
import com.intellij.debugger.ui.DebuggerView;
import com.intellij.openapi.Disposable;
myRebuildAlarm.addRequest(new Runnable() {
public void run() {
try {
- rebuild(event == DebuggerSession.EVENT_REFRESH || event == DebuggerSession.EVENT_REFRESH_VIEWS_ONLY);
+ rebuild(event);
}
catch (VMDisconnectedException e) {
// ignored
}
}
- protected abstract void rebuild(boolean updateOnly);
+ protected abstract void rebuild(int event);
protected final void registerDisposable(Disposable disposable) {
myDisposables.add(disposable);
import com.intellij.debugger.actions.DebuggerAction;
import com.intellij.debugger.actions.DebuggerActions;
+import com.intellij.debugger.impl.DebuggerContextImpl;
+import com.intellij.debugger.impl.DebuggerSession;
import com.intellij.debugger.impl.DebuggerStateManager;
import com.intellij.debugger.ui.impl.watch.DebuggerTree;
import com.intellij.openapi.Disposable;
return new FrameDebuggerTree(getProject());
}
+ protected void changeEvent(DebuggerContextImpl newContext, int event) {
+ if (event != DebuggerSession.EVENT_THREADS_REFRESH) {
+ super.changeEvent(newContext, event);
+ }
+ }
protected ActionPopupMenu createPopupMenu() {
ActionGroup group = (ActionGroup)ActionManager.getInstance().getAction(DebuggerActions.FRAME_PANEL_POPUP);
import com.intellij.debugger.ui.impl.watch.DebuggerTree;
import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl;
import com.intellij.debugger.ui.impl.watch.WatchItemDescriptor;
-import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionPopupMenu;
import com.intellij.openapi.actionSystem.CommonShortcuts;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
}
protected void changeEvent(DebuggerContextImpl newContext, int event) {
+ if (event == DebuggerSession.EVENT_THREADS_REFRESH) {
+ return;
+ }
if(event == DebuggerSession.EVENT_ATTACHED) {
DebuggerTreeNodeImpl root = (DebuggerTreeNodeImpl) getWatchTree().getModel().getRoot();
if(root != null) {
*/
package com.intellij.ide;
-import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider;
import com.intellij.lang.Language;
import com.intellij.lang.StdLanguages;
import com.intellij.psi.codeStyle.CodeStyleSettingsCustomizable;
+import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider;
import org.jetbrains.annotations.NotNull;
/**
return GENERAL_CODE_SAMPLE;
}
+ @Override
+ public int getRightMargin(@NotNull SettingsType settingsType) {
+ if (settingsType == SettingsType.WRAPPING_AND_BRACES_SETTINGS) return 37;
+ return super.getRightMargin(settingsType);
+ }
+
@Override
public void customizeSettings(@NotNull CodeStyleSettingsCustomizable consumer, @NotNull SettingsType settingsType) {
consumer.showAllStandardOptions();
package com.intellij.refactoring.changeSignature;
import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
return method != null && isInsideMethodSignature(element, method) && Comparing.equal(method, bannedInfo.getMethod());
}
+ @Override
+ public boolean isMoveParameterAvailable(PsiElement element, boolean left) {
+ if (element instanceof PsiParameter) {
+ final PsiParameter parameter = (PsiParameter)element;
+ final PsiElement declarationScope = parameter.getDeclarationScope();
+ if (declarationScope instanceof PsiMethod) {
+ final PsiMethod method = (PsiMethod)declarationScope;
+ final int parameterIndex = method.getParameterList().getParameterIndex(parameter);
+ if (left) {
+ return parameterIndex > 0;
+ } else {
+ return parameterIndex < method.getParameterList().getParametersCount() - 1;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void moveParameter(final PsiElement element, final Editor editor, final boolean left) {
+ final PsiParameter parameter = (PsiParameter)element;
+ final PsiMethod method = (PsiMethod)parameter.getDeclarationScope();
+ final int parameterIndex = method.getParameterList().getParameterIndex(parameter);
+ new WriteCommandAction(element.getProject(), MOVE_PARAMETER){
+ @Override
+ protected void run(Result result) throws Throwable {
+ final PsiParameterList parameterList = method.getParameterList();
+ final PsiParameter[] parameters = parameterList.getParameters();
+ final int deltaOffset = editor.getCaretModel().getOffset() - parameter.getTextRange().getStartOffset();
+ final PsiParameter frst = left ? parameters[parameterIndex - 1] : parameter;
+ final PsiParameter scnd = left ? parameter : parameters[parameterIndex + 1];
+ final int startOffset = frst.getTextRange().getStartOffset();
+ final int endOffset = scnd.getTextRange().getEndOffset();
+
+ final PsiFile file = method.getContainingFile();
+ final Document document = PsiDocumentManager.getInstance(getProject()).getDocument(file);
+ if (document != null) {
+ final String comma_whitespace_between =
+ document.getText().substring(frst.getTextRange().getEndOffset(), scnd.getTextRange().getStartOffset());
+ document.replaceString(startOffset, endOffset, scnd.getText() + comma_whitespace_between + frst.getText());
+ editor.getCaretModel().moveToOffset(document.getText().indexOf(parameter.getText(), startOffset) + deltaOffset);
+ }
+ }
+ }.execute();
+ }
+
private static boolean isInsideMethodSignature(PsiElement element, @NotNull PsiMethod method) {
final PsiCodeBlock body = method.getBody();
if (body != null) {
public abstract String getCodeSample(@NotNull SettingsType settingsType);
+ public int getRightMargin(@NotNull SettingsType settingsType) {
+ return settingsType == SettingsType.WRAPPING_AND_BRACES_SETTINGS ? 30 : -1;
+ }
+
public void customizeSettings(@NotNull CodeStyleSettingsCustomizable consumer, @NotNull SettingsType settingsType) {
}
return null;
}
+ public static int getRightMargin(Language lang, @NotNull SettingsType settingsType) {
+ for (LanguageCodeStyleSettingsProvider provider : Extensions.getExtensions(EP_NAME)) {
+ if (provider.getLanguage().equals(lang)) {
+ return provider.getRightMargin(settingsType);
+ }
+ }
+ return -1;
+ }
+
@Nullable
public static Language getLanguage(String langName) {
for (LanguageCodeStyleSettingsProvider provider : Extensions.getExtensions(EP_NAME)) {
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.stubs.StubBase;
+import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.templateLanguages.OuterLanguageElement;
import com.intellij.util.ArrayUtil;
import com.intellij.util.SmartList;
@Nullable
public static PsiElement getStubOrPsiParent(@Nullable PsiElement element) {
- return getStubOrPsiParentOfType(element, PsiElement.class);
+ if (element instanceof StubBasedPsiElement) {
+ StubBase stub = (StubBase)((StubBasedPsiElement)element).getStub();
+ if (stub != null) {
+ //noinspection unchecked
+ final StubElement parentStub = stub.getParentStub();
+ return parentStub != null ? parentStub.getPsi() : null;
+ }
+
+ }
+ return element != null ? element.getParent() : null;
}
@Nullable
package com.intellij.application.options;
import com.intellij.application.options.codeStyle.CodeStyleSchemesModel;
+import com.intellij.codeStyle.CodeStyleFacade;
import com.intellij.ide.DataManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
somethingChanged();
}
});
+
+ updatePreview();
}
public void setModel(final CodeStyleSchemesModel model) {
private Editor createEditor() {
EditorFactory editorFactory = EditorFactory.getInstance();
- myTextToReformat = getPreviewText();
- Document editorDocument = editorFactory.createDocument(myTextToReformat);
+ Document editorDocument = editorFactory.createDocument("");
EditorEx editor = (EditorEx)editorFactory.createViewer(editorDocument);
-
+ fillEditorSettings(editor.getSettings());
myLastDocumentModificationStamp = editor.getDocument().getModificationStamp();
-
- EditorSettings editorSettings = editor.getSettings();
- fillEditorSettings(editorSettings);
-
- updatePreviewHighlighter(editor);
-
return editor;
}
- private void updatePreviewHighlighter(final EditorEx editor) {
- EditorColorsScheme scheme = editor.getColorsScheme();
- scheme.setColor(EditorColors.CARET_ROW_COLOR, null);
-
- editor.setHighlighter(createHighlighter(scheme));
- }
-
- protected void updatePreviewEditor() {
- myTextToReformat = getPreviewText();
- updatePreview();
- updatePreviewHighlighter((EditorEx)myEditor);
- }
-
- protected abstract EditorHighlighter createHighlighter(final EditorColorsScheme scheme);
-
private void fillEditorSettings(final EditorSettings editorSettings) {
editorSettings.setWhitespacesShown(true);
editorSettings.setLineMarkerAreaShown(false);
editorSettings.setFoldingOutlineShown(false);
editorSettings.setAdditionalColumnsCount(0);
editorSettings.setAdditionalLinesCount(1);
- final int rightMargin = getRightMargin();
- if (rightMargin > 0) {
- editorSettings.setRightMargin(rightMargin);
- }
}
- protected abstract int getRightMargin();
+ protected void updatePreview() {
+ updateEditor();
+ updatePreviewHighlighter((EditorEx)myEditor);
+ }
- public final void updatePreview() {
+ private void updateEditor() {
if (!myShouldUpdatePreview || !myEditor.getComponent().isShowing()) {
return;
}
if (myLastDocumentModificationStamp != myEditor.getDocument().getModificationStamp()) {
myTextToReformat = myEditor.getDocument().getText();
+ } else {
+ myTextToReformat = getPreviewText();
}
int currOffs = myEditor.getScrollingModel().getVerticalScrollOffset();
replaceText(finalProject);
}
}, null, null);
- myEditor.getSettings().setRightMargin(getRightMargin());
+
+ myEditor.getSettings().setRightMargin(getAdjustedRightMargin());
myLastDocumentModificationStamp = myEditor.getDocument().getModificationStamp();
myEditor.getScrollingModel().scrollVertically(currOffs);
}
+ private int getAdjustedRightMargin() {
+ int result = getRightMargin();
+ return result > 0 ? result : CodeStyleFacade.getInstance(getCurrentProject()).getRightMargin();
+ }
+
+ protected abstract int getRightMargin();
+
private void replaceText(final Project project) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
prepareForReformat(psiFile);
apply(mySettings);
CodeStyleSettings clone = mySettings.clone();
- if (getRightMargin() > 0) {
- clone.RIGHT_MARGIN = getRightMargin();
- }
+ clone.RIGHT_MARGIN = getAdjustedRightMargin();
CodeStyleSettingsManager.getInstance(project).setTemporarySettings(clone);
return project;
}
+ private void updatePreviewHighlighter(final EditorEx editor) {
+ EditorColorsScheme scheme = editor.getColorsScheme();
+ scheme.setColor(EditorColors.CARET_ROW_COLOR, null);
+ editor.setHighlighter(createHighlighter(scheme));
+ }
+
+ protected abstract EditorHighlighter createHighlighter(final EditorColorsScheme scheme);
+
@NotNull
protected abstract FileType getFileType();
try {
myUpdateAlarm.cancelAllRequests();
if (isSomethingChanged()) {
- updatePreview();
+ updateEditor();
}
}
finally {
final int selIndex = myIndentOptionsTabs.getSelectedIndex();
if (selIndex != myLastSelectedTab) {
myLastSelectedTab = selIndex;
- updatePreviewEditor();
+ updatePreview();
somethingChanged();
}
}
}
- protected int getRightMargin() {
- return -1;
- }
-
public JComponent getPanel() {
return myPanel;
}
}
}
onLanguageChange(language);
- updatePreviewEditor();
+ updatePreview();
}
public Language getSelectedLanguage() {
return sample;
}
+ @Override
+ protected int getRightMargin() {
+ if (myLanguage == null) return -1;
+ return LanguageCodeStyleSettingsProvider.getRightMargin(myLanguage, getSettingsType());
+ }
+
@NotNull
@Override
protected FileType getFileType() {
setPanelLanguage(langs[0]);
}
else {
- updatePreviewEditor();
+ updatePreview();
}
tabbedPane.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
else {
// If settings are language-specific
previewPanel.add(getEditor().getComponent(), BorderLayout.CENTER);
- updatePreviewEditor();
+ updatePreview();
}
}
protected abstract void initTables();
- protected int getRightMargin() {
- return -1;
- }
-
protected void resetImpl(final CodeStyleSettings settings) {
TreeModel treeModel = myOptionsTree.getModel();
TreeNode root = (TreeNode)treeModel.getRoot();
addOption("VARIABLE_ANNOTATION_WRAP", ApplicationBundle.message("wrapping.local.variables.annotation"), WRAP_OPTIONS, WRAP_VALUES);
addOption("ENUM_CONSTANTS_WRAP", ApplicationBundle.message("wrapping.enum.constants"), WRAP_OPTIONS, WRAP_VALUES);
}
-
- protected int getRightMargin() {
- return 37;
- }
}
\ No newline at end of file
package com.intellij.application.options.colors;
+import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
import com.intellij.codeInsight.daemon.impl.TrafficLightRenderer;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.editor.*;
static void installTrafficLights(EditorEx editor) {
ErrorStripeRenderer renderer = new TrafficLightRenderer(null,null,null,null){
- protected DaemonCodeAnalyzerStatus getDaemonCodeAnalyzerStatus(boolean fillErrorsCount) {
+ protected DaemonCodeAnalyzerStatus getDaemonCodeAnalyzerStatus(boolean fillErrorsCount, SeverityRegistrar severityRegistrar) {
DaemonCodeAnalyzerStatus status = new DaemonCodeAnalyzerStatus();
status.errorAnalyzingFinished = true;
status.errorCount = new int[]{1, 2};
import com.intellij.util.Processor;
import com.intellij.util.ui.EmptyIcon;
import com.intellij.util.ui.UIUtil;
+import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.net.URL;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
public class TrafficLightRenderer implements ErrorStripeRenderer {
@NonNls private static final String HTML_FOOTER = "</body></html>";
@NonNls private static final String BR = "<br>";
@NonNls private static final String NO_PASS_FOR_MESSAGE_KEY_SUFFIX = ".for";
+ private final SeverityRegistrar mySeverityRegistrar;
public TrafficLightRenderer(Project project, DaemonCodeAnalyzerImpl highlighter, Document document, PsiFile file) {
myProject = project;
myDaemonCodeAnalyzer = highlighter;
myDocument = document;
myFile = file;
+ mySeverityRegistrar = SeverityRegistrar.getInstance(myProject);
}
public static class DaemonCodeAnalyzerStatus {
for (ProgressableTextEditorHighlightingPass passStatus : passStati) {
s += String.format("(%s %2.0f%% %b)", passStatus.getPresentableName(), passStatus.getProgress() *100, passStatus.isFinished());
}
+ s += "; error count: "+errorCount.length + ": "+new TIntArrayList(errorCount);
return s;
}
}
@Nullable
- protected DaemonCodeAnalyzerStatus getDaemonCodeAnalyzerStatus(boolean fillErrorsCount) {
+ protected DaemonCodeAnalyzerStatus getDaemonCodeAnalyzerStatus(boolean fillErrorsCount, SeverityRegistrar severityRegistrar) {
if (myFile == null || myProject.isDisposed() || !myDaemonCodeAnalyzer.isHighlightingAvailable(myFile)) return null;
List<String> noInspectionRoots = new ArrayList<String>();
status.noInspectionRoots = noInspectionRoots.isEmpty() ? null : ArrayUtil.toStringArray(noInspectionRoots);
status.noHighlightingRoots = noHighlightingRoots.isEmpty() ? null : ArrayUtil.toStringArray(noHighlightingRoots);
- final SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(myProject);
status.errorCount = new int[severityRegistrar.getSeveritiesCount()];
status.rootsNumber = roots.length;
fillDaemonCodeAnalyzerErrorsStatus(status, fillErrorsCount, severityRegistrar);
protected void fillDaemonCodeAnalyzerErrorsStatus(final DaemonCodeAnalyzerStatus status,
final boolean fillErrorsCount,
final SeverityRegistrar severityRegistrar) {
- if (fillErrorsCount) Arrays.fill(status.errorCount, 0);
final int count = severityRegistrar.getSeveritiesCount() - 1;
final HighlightSeverity maxPossibleSeverity = severityRegistrar.getSeverityByIndex(count);
final HighlightSeverity[] maxFoundSeverity = {null};
}
}
else {
+ if (maxFoundSeverity[0] == null || severityRegistrar.compare(maxFoundSeverity[0], infoSeverity) < 0) {
+ maxFoundSeverity[0] = infoSeverity;
+ }
if (infoSeverity == maxPossibleSeverity) {
- status.errorCount[count] = 1;
return false;
}
- if (maxFoundSeverity[0] == null || severityRegistrar.compare(maxFoundSeverity[0], infoSeverity) <= 0) {
- maxFoundSeverity[0] = infoSeverity;
- }
}
return true;
}
}
public String getTooltipMessage() {
- DaemonCodeAnalyzerStatus status = getDaemonCodeAnalyzerStatus(true);
+ DaemonCodeAnalyzerStatus status = getDaemonCodeAnalyzerStatus(true, mySeverityRegistrar);
if (status == null) return null;
@NonNls String text = HTML_HEADER;
int currentSeverityErrors = 0;
for (int i = status.errorCount.length - 1; i >= 0; i--) {
if (status.errorCount[i] > 0) {
- final HighlightSeverity severity = SeverityRegistrar.getInstance(myProject).getSeverityByIndex(i);
+ final HighlightSeverity severity = mySeverityRegistrar.getSeverityByIndex(i);
text += BR;
String name = status.errorCount[i] > 1 ? StringUtil.pluralize(severity.toString().toLowerCase()) : severity.toString().toLowerCase();
text += status.errorAnalyzingFinished
}
private Icon getIcon() {
- DaemonCodeAnalyzerStatus status = getDaemonCodeAnalyzerStatus(false);
+ DaemonCodeAnalyzerStatus status = getDaemonCodeAnalyzerStatus(false, mySeverityRegistrar);
if (status == null) {
return NO_ICON;
return NO_ANALYSIS_ICON;
}
- boolean atLeastOnePassFinished = status.errorAnalyzingFinished;
- for (ProgressableTextEditorHighlightingPass passStatus : status.passStati) {
- atLeastOnePassFinished |= passStatus.isFinished();
- }
Icon icon = HighlightDisplayLevel.DO_NOT_SHOW.getIcon();
- if (atLeastOnePassFinished) {
- SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(myProject);
- for (int i = status.errorCount.length - 1; i >= 0; i--) {
- if (status.errorCount[i] != 0) {
- icon = severityRegistrar.getRendererIconByIndex(i);
- break;
- }
+ for (int i = status.errorCount.length - 1; i >= 0; i--) {
+ if (status.errorCount[i] != 0) {
+ icon = mySeverityRegistrar.getRendererIconByIndex(i);
+ break;
}
}
if (status.errorAnalyzingFinished) return icon;
- //icon = HighlightDisplayLevel.createIconByMask(Color.blue);
double progress = getOverallProgress(status);
TruncatingIcon trunc = new TruncatingIcon(icon, (int)(icon.getIconWidth() * progress), (int)(icon.getIconHeight() * progress));
return LayeredIcon.create(trunc, STARING_EYE_ICON);
@Override
public void repaintTooltipWindow() {
if (myPanel != null) {
- myPanel.updatePanel(myTrafficLightRenderer.getDaemonCodeAnalyzerStatus(true));
+ myPanel.updatePanel(myTrafficLightRenderer.getDaemonCodeAnalyzerStatus(true,
+ SeverityRegistrar.getInstance(
+ myTrafficLightRenderer.getProject())));
}
}
package com.intellij.ide.actions;
-import com.intellij.CommonBundle;
-import com.intellij.history.LocalHistory;
-import com.intellij.history.LocalHistoryAction;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.IdeView;
import com.intellij.ide.util.DirectoryChooserUtil;
-import com.intellij.ide.util.DirectoryUtil;
import com.intellij.openapi.actionSystem.*;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.command.CommandProcessor;
-import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.InputValidatorEx;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.file.PsiDirectoryFactory;
import com.intellij.util.Icons;
-import com.intellij.util.IncorrectOperationException;
-
-import java.io.File;
public class CreateDirectoryOrPackageAction extends AnAction implements DumbAware {
public CreateDirectoryOrPackageAction() {
}
public void actionPerformed(AnActionEvent e) {
- DataContext dataContext = e.getDataContext();
-
- IdeView view = LangDataKeys.IDE_VIEW.getData(dataContext);
- Project project = PlatformDataKeys.PROJECT.getData(dataContext);
+ IdeView view = e.getData(LangDataKeys.IDE_VIEW);
+ Project project = e.getData(PlatformDataKeys.PROJECT);
PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view);
if (directory == null) return;
boolean isDirectory = !PsiDirectoryFactory.getInstance(project).isPackage(directory);
- MyInputValidator validator = new MyInputValidator(project, directory, isDirectory);
+ CreateDirectoryOrPackageHandler validator = new CreateDirectoryOrPackageHandler(project, directory, isDirectory,
+ isDirectory ? "\\/" : ".");
Messages.showInputDialog(project, isDirectory
? IdeBundle.message("prompt.enter.new.directory.name")
: IdeBundle.message("prompt.enter.new.package.name"),
isDirectory ? IdeBundle.message("title.new.directory") : IdeBundle.message("title.new.package"),
Messages.getQuestionIcon(), "", validator);
- if (validator.myCreatedElement == null) return;
-
- view.selectElement(validator.myCreatedElement);
+ final PsiElement result = validator.getCreatedElement();
+ if (result != null && view != null) {
+ view.selectElement(result);
+ }
}
public void update(AnActionEvent event) {
Presentation presentation = event.getPresentation();
- DataContext dataContext = event.getDataContext();
- Project project = PlatformDataKeys.PROJECT.getData(dataContext);
+ Project project = event.getData(PlatformDataKeys.PROJECT);
if (project == null) {
presentation.setVisible(false);
presentation.setEnabled(false);
return;
}
- IdeView view = LangDataKeys.IDE_VIEW.getData(dataContext);
+ IdeView view = event.getData(LangDataKeys.IDE_VIEW);
if (view == null) {
presentation.setVisible(false);
presentation.setEnabled(false);
}
}
- protected class MyInputValidator implements InputValidatorEx {
- private final Project myProject;
- private final PsiDirectory myDirectory;
- private final boolean myIsDirectory;
- private PsiElement myCreatedElement = null;
-
- public MyInputValidator(Project project, PsiDirectory directory, boolean isDirectory) {
- myProject = project;
- myDirectory = directory;
- myIsDirectory = isDirectory;
- }
-
- public boolean checkInput(String inputString) {
- return true;
- }
-
- public String getErrorText(String inputString) {
- if (FileTypeManager.getInstance().isFileIgnored(inputString)) {
- return "Trying to create a " + (myIsDirectory ? "directory" : "package") + " with ignored name, result will not be visible";
- }
- if (!myIsDirectory && inputString.length() > 0 && !PsiDirectoryFactory.getInstance(myProject).isValidPackageName(inputString)) {
- return "Not a valid package name";
- }
- return null;
- }
-
- public boolean canClose(String inputString) {
- final String subDirName = inputString;
-
- if (subDirName.length() == 0) {
- Messages.showMessageDialog(myProject, IdeBundle.message("error.name.should.be.specified"), CommonBundle.getErrorTitle(),
- Messages.getErrorIcon());
- return false;
- }
-
- final boolean multiCreation = myIsDirectory
- ? subDirName.indexOf('/') != -1 || subDirName.indexOf('\\') != -1
- : subDirName.indexOf('.') != -1;
-
- if (!multiCreation) {
- try {
- myDirectory.checkCreateSubdirectory(subDirName);
- }
- catch (IncorrectOperationException ex) {
- Messages.showMessageDialog(myProject, CreateElementActionBase.filterMessage(ex.getMessage()), CommonBundle.getErrorTitle(),
- Messages.getErrorIcon());
- return false;
- }
- }
-
- Runnable command = new Runnable() {
- public void run() {
- final Runnable run = new Runnable() {
- public void run() {
- LocalHistoryAction action = LocalHistoryAction.NULL;
- try {
- String actionName;
- String dirPath = myDirectory.getVirtualFile().getPresentableUrl();
- actionName = IdeBundle.message("progress.creating.directory", dirPath, File.separator, subDirName);
- action = LocalHistory.getInstance().startAction(actionName);
-
- final PsiDirectory createdDir;
- if (myIsDirectory) {
- createdDir = DirectoryUtil.createSubdirectories(subDirName, myDirectory, "\\/");
- }
- else {
- createdDir = DirectoryUtil.createSubdirectories(subDirName, myDirectory, ".");
- }
-
-
- myCreatedElement = createdDir;
-
- }
- catch (final IncorrectOperationException ex) {
- ApplicationManager.getApplication().invokeLater(new Runnable() {
- public void run() {
- Messages.showMessageDialog(myProject, CreateElementActionBase.filterMessage(ex.getMessage()),
- CommonBundle.getErrorTitle(), Messages.getErrorIcon());
- }
- });
- }
- finally {
- action.finish();
- }
- }
- };
- ApplicationManager.getApplication().runWriteAction(run);
- }
- };
- CommandProcessor.getInstance().executeCommand(myProject, command, myIsDirectory
- ? IdeBundle.message("command.create.directory")
- : IdeBundle.message("command.create.package"), null);
-
- return myCreatedElement != null;
- }
- }
}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.ide.actions;
+
+import com.intellij.CommonBundle;
+import com.intellij.history.LocalHistory;
+import com.intellij.history.LocalHistoryAction;
+import com.intellij.ide.IdeBundle;
+import com.intellij.ide.util.DirectoryUtil;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.InputValidatorEx;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.impl.file.PsiDirectoryFactory;
+import com.intellij.util.IncorrectOperationException;
+
+import java.io.File;
+
+public class CreateDirectoryOrPackageHandler implements InputValidatorEx {
+ private final Project myProject;
+ private final PsiDirectory myDirectory;
+ private final boolean myIsDirectory;
+ private PsiDirectory myCreatedElement = null;
+ private String myDelimiters;
+
+ public CreateDirectoryOrPackageHandler(Project project, PsiDirectory directory, boolean isDirectory, final String delimiters) {
+ myProject = project;
+ myDirectory = directory;
+ myIsDirectory = isDirectory;
+ myDelimiters = delimiters;
+ }
+
+ public boolean checkInput(String inputString) {
+ return true;
+ }
+
+ public String getErrorText(String inputString) {
+ if (FileTypeManager.getInstance().isFileIgnored(inputString)) {
+ return "Trying to create a " + (myIsDirectory ? "directory" : "package") + " with ignored name, result will not be visible";
+ }
+ if (!myIsDirectory && inputString.length() > 0 && !PsiDirectoryFactory.getInstance(myProject).isValidPackageName(inputString)) {
+ return "Not a valid package name";
+ }
+ return null;
+ }
+
+ public boolean canClose(String inputString) {
+ final String subDirName = inputString;
+
+ if (subDirName.length() == 0) {
+ Messages.showMessageDialog(myProject, IdeBundle.message("error.name.should.be.specified"), CommonBundle.getErrorTitle(),
+ Messages.getErrorIcon());
+ return false;
+ }
+
+ final boolean multiCreation = StringUtil.containsAnyChar(subDirName, myDelimiters);
+ if (!multiCreation) {
+ try {
+ myDirectory.checkCreateSubdirectory(subDirName);
+ }
+ catch (IncorrectOperationException ex) {
+ Messages.showMessageDialog(myProject, CreateElementActionBase.filterMessage(ex.getMessage()), CommonBundle.getErrorTitle(),
+ Messages.getErrorIcon());
+ return false;
+ }
+ }
+
+ Runnable command = new Runnable() {
+ public void run() {
+ final Runnable run = new Runnable() {
+ public void run() {
+ LocalHistoryAction action = LocalHistoryAction.NULL;
+ try {
+ String actionName;
+ String dirPath = myDirectory.getVirtualFile().getPresentableUrl();
+ actionName = IdeBundle.message("progress.creating.directory", dirPath, File.separator, subDirName);
+ action = LocalHistory.getInstance().startAction(actionName);
+
+ myCreatedElement = DirectoryUtil.createSubdirectories(subDirName, myDirectory, myDelimiters);
+
+ }
+ catch (final IncorrectOperationException ex) {
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ public void run() {
+ Messages.showMessageDialog(myProject, CreateElementActionBase.filterMessage(ex.getMessage()),
+ CommonBundle.getErrorTitle(), Messages.getErrorIcon());
+ }
+ });
+ }
+ finally {
+ action.finish();
+ }
+ }
+ };
+ ApplicationManager.getApplication().runWriteAction(run);
+ }
+ };
+ CommandProcessor.getInstance().executeCommand(myProject, command, myIsDirectory
+ ? IdeBundle.message("command.create.directory")
+ : IdeBundle.message("command.create.package"), null);
+
+ return myCreatedElement != null;
+ }
+
+ public PsiDirectory getCreatedElement() {
+ return myCreatedElement;
+ }
+}
private DirectoryChooserUtil() {
}
+ @Nullable
public static PsiDirectory getOrChooseDirectory(IdeView view) {
PsiDirectory[] dirs = view.getDirectories();
if (dirs.length == 0) return null;
}
}
+ @Nullable
public static PsiDirectory selectDirectory(Project project,
PsiDirectory[] packageDirectories,
PsiDirectory defaultDirectory,
import com.intellij.openapi.editor.FoldRegion;
import com.intellij.openapi.editor.FoldingGroup;
+import com.intellij.openapi.editor.ex.FoldingListener;
import com.intellij.openapi.editor.ex.FoldingModelEx;
import com.intellij.openapi.editor.impl.FoldRegionImpl;
import com.intellij.openapi.editor.markup.TextAttributes;
hostRegion.putUserData(FOLD_REGION_WINDOW, window);
return window;
}
+
+ @Override
+ public boolean addListener(@NotNull FoldingListener listener) {
+ return myDelegate.addListener(listener);
+ }
+
+ @Override
+ public boolean removeListener(@NotNull FoldingListener listener) {
+ return myDelegate.removeListener(listener);
+ }
}
final String currentCommandName = processor.getCurrentCommandName();
if (!Comparing.strEqual(EditorBundle.message("typing.in.editor.command.name"), currentCommandName) &&
!Comparing.strEqual(EditorBundle.message("paste.command.name"), currentCommandName) &&
+ !Comparing.strEqual(LanguageChangeSignatureDetector.MOVE_PARAMETER, currentCommandName) &&
!Comparing.equal(EditorActionUtil.DELETE_COMMAND_GROUP, processor.getCurrentCommandGroupId())) {
return;
}
*/
package com.intellij.refactoring.changeSignature;
+import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
* Date: Sep 6, 2010
*/
public interface LanguageChangeSignatureDetector {
-
+ String MOVE_PARAMETER = "Parameter Move";
@Nullable
ChangeInfo createCurrentChangeSignature(final @NotNull PsiElement element,
TextRange getHighlightingRange(PsiElement element);
boolean wasBanned(PsiElement element, @NotNull ChangeInfo bannedInfo);
+
+ boolean isMoveParameterAvailable(PsiElement parameter, boolean left);
+
+ void moveParameter(PsiElement parameter, Editor editor, boolean left);
}
--- /dev/null
+package com.intellij.refactoring.changeSignature;
+
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.psi.PsiElement;
+
+/**
+ * User: anna
+ * Date: Sep 10, 2010
+ */
+public abstract class MoveParameterAction extends AnAction{
+ private final boolean myLeft;
+ private static final Logger LOG = Logger.getInstance("#" + MoveParameterAction.class.getName());
+
+ public MoveParameterAction(boolean left) {
+ super();
+ myLeft = left;
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ final DataContext dataContext = e.getDataContext();
+ final PsiElement psiElement = LangDataKeys.PSI_ELEMENT.getData(dataContext);
+ LOG.assertTrue(psiElement != null);
+ final Editor editor = PlatformDataKeys.EDITOR.getData(dataContext);
+ LanguageChangeSignatureDetectors.INSTANCE.forLanguage(psiElement.getLanguage()).moveParameter(psiElement, editor, myLeft);
+ }
+
+
+ @Override
+ public void update(AnActionEvent e) {
+ final Presentation presentation = e.getPresentation();
+ presentation.setEnabled(false);
+ final DataContext dataContext = e.getDataContext();
+ final Editor editor = PlatformDataKeys.EDITOR.getData(dataContext);
+ if (editor != null) {
+ final PsiElement psiElement = LangDataKeys.PSI_ELEMENT.getData(dataContext);
+ if (psiElement != null) {
+ final LanguageChangeSignatureDetector detector = LanguageChangeSignatureDetectors.INSTANCE.forLanguage(psiElement.getLanguage());
+ if (detector != null) {
+ presentation.setEnabled(detector.isMoveParameterAvailable(psiElement, myLeft));
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.refactoring.changeSignature;
+
+/**
+ * User: anna
+ * Date: Sep 10, 2010
+ */
+public class MoveParameterLeftAction extends MoveParameterAction {
+ public MoveParameterLeftAction() {
+ super(true);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.refactoring.changeSignature;
+
+/**
+ * User: anna
+ * Date: Sep 10, 2010
+ */
+public class MoveParameterRightAction extends MoveParameterAction {
+ public MoveParameterRightAction() {
+ super(false);
+ }
+}
import com.intellij.openapi.util.Comparing;
import com.intellij.util.ImageLoader;
import com.intellij.util.containers.HashMap;
-import com.intellij.util.ui.EmptyIcon;
import javax.swing.*;
import java.awt.*;
import java.util.Map;
public class HighlightDisplayLevel {
- private static final Icon EMPTY = new EmptyIcon(12, 12);
private static final Map<HighlightSeverity, HighlightDisplayLevel> ourMap = new HashMap<HighlightSeverity, HighlightDisplayLevel>();
public static final HighlightDisplayLevel GENERIC_SERVER_ERROR_OR_WARNING = new HighlightDisplayLevel(HighlightSeverity.GENERIC_SERVER_ERROR_OR_WARNING,
private static final Image ourErrorMaskImage = ImageLoader.loadFromResource("/general/errorMask.png");
}
+ private static final int EMPTY_ICON_DIM = 12;
public static Icon createIconByMask(final Color renderColor) {
return new Icon() {
public void paintIcon(Component c, Graphics g, int x, int y) {
public int getIconWidth() {
- return EMPTY.getIconWidth();
+ return EMPTY_ICON_DIM;
}
public int getIconHeight() {
- return EMPTY.getIconHeight();
+ return EMPTY_ICON_DIM;
}
};
}
});
}
catch (ProcessCanceledException e) {
- runDone(onDone);
+ try {
+ runDone(onDone);
+ }
+ catch (ProcessCanceledException ignored) {
+ //todo[kirillk] added by Nik to fix IDEA-58475. I'm not sure that it is correct solution
+ }
}
}
return endOffset;
}
+ // Normalization.
int maxPreferredOffsetToUse = maxPreferredOffset >= endOffset ? endOffset - 1 : maxPreferredOffset;
+ maxPreferredOffsetToUse = maxPreferredOffsetToUse < startOffset ? startOffset : maxPreferredOffsetToUse;
+
// Try to find target offset that is not greater than preferred position.
for (int i = maxPreferredOffsetToUse; i > startOffset; i--) {
char c = text.charAt(i);
+ if (c == '\n') {
+ return i + 1;
+ }
if (WHITE_SPACES.contains(c)) {
return i < maxPreferredOffsetToUse ? i + 1 : i;
// Try to find target offset that is greater than preferred position.
for (int i = maxPreferredOffsetToUse + 1; i < endOffset; i++) {
char c = text.charAt(i);
+ if (c == '\n') {
+ return i + 1;
+ }
+
if (WHITE_SPACES.contains(c)) {
return i;
}
return i;
}
}
- return maxPreferredOffset;
+
+ return allowToBeyondMaxPreferredOffset ? endOffset : maxPreferredOffset;
}
private static boolean isIdSymbol(char c) {
int caretOffset = caretModel.getOffset();
int anchorLineEndOffset = document.getLineEndOffset(lineNumber);
- List<? extends TextChange> softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(logicalPosition.line);
- for (TextChange softWrap : softWraps) {
+ List<? extends SoftWrap> softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(logicalPosition.line);
+ for (SoftWrap softWrap : softWraps) {
if (!editor.getSoftWrapModel().isVisible(softWrap)) {
continue;
}
- if (softWrap.getStart() == caretOffset) {
+
+ int softWrapOffset = softWrap.getStart();
+ if (softWrapOffset == caretOffset) {
// There are two possible situations:
// *) caret is located on a visual line before soft wrap-introduced line feed;
// *) caret is located on a visual line after soft wrap-introduced line feed;
return visualCaret.column - position.column - 1;
}
}
- if (softWrap.getStart() > caretOffset) {
- anchorLineEndOffset = softWrap.getStart();
+ if (softWrapOffset > caretOffset) {
+ anchorLineEndOffset = softWrapOffset;
break;
}
// Same offset corresponds to all soft wrap-introduced symbols, however, current method should behave differently in
// situations when the caret is located just before the soft wrap and at the next visual line.
- if (softWrap.getStart() == caretOffset) {
+ if (softWrapOffset == caretOffset) {
boolean visuallyBeforeSoftWrap = caretModel.getVisualPosition().line < editor.offsetToVisualPosition(caretOffset).line;
if (visuallyBeforeSoftWrap) {
- anchorLineEndOffset = softWrap.getStart();
+ anchorLineEndOffset = softWrapOffset;
break;
}
}
--- /dev/null
+/*
+ * Copyright 2000-2010 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;
+
+/**
+ * Defines common interface for <code>'soft wrap'</code>, i.e. for virtual line break that doesn't present at actual file on a disk
+ * but is used exclusively during document representation.
+ *
+ * @author Denis Zhdanov
+ * @since Aug 30, 2010 6:07:00 PM
+ */
+public interface SoftWrap extends TextChange {
+
+ /**
+ * @return number of columns between current soft wrap end and first column on a visual line
+ */
+ int getIndentInColumns();
+
+ /**
+ * @return number of pixels between current soft wrap end and first column on a visual line
+ */
+ int getIndentInPixels();
+}
* @return soft wrap registered for the given offset within the current model if any; <code>null</code> otherwise
*/
@Nullable
- TextChange getSoftWrap(int offset);
+ SoftWrap getSoftWrap(int offset);
/**
* Allows to ask current model about all soft wraps registered for the given document offsets range.
* @return all soft wraps registered for the target document offsets range
*/
@NotNull
- List<? extends TextChange> getSoftWrapsForRange(int start, int end);
+ List<? extends SoftWrap> getSoftWrapsForRange(int start, int end);
/**
* Allows to ask current model about all soft wraps registered for the given document line.
* @return all soft wraps registered for the given document line
*/
@NotNull
- List<? extends TextChange> getSoftWrapsForLine(int documentLine);
+ List<? extends SoftWrap> getSoftWrapsForLine(int documentLine);
/**
* Allows to answer if given soft wrap is shown.
* @param softWrap soft wrap to check
* @return <code>true</code> if given soft wrap is visible; <code>false</code> otherwise
*/
- boolean isVisible(TextChange softWrap);
+ boolean isVisible(SoftWrap softWrap);
/**
* Notifies current model that target document is about to be changed at current caret location.
boolean isInsideOrBeforeSoftWrap(@NotNull VisualPosition visual);
/**
- * Asks current model to calculate indent width of the given soft wrap in pixels.
- * <p/>
- * <code>'Soft wrap indent width'</code> here means visual width of soft wrap-introduced editor space on a line
- * that contains document text just after soft wrap. Basically, resulting value is a resulting width sum of soft
- * wrap symbols that follow last soft wrap line feed plus <code>'after soft wrap'</code> drawing width.
- *
- * @param softWrap soft wrap which indent width should be calculated
- * @return indent width in pixels for the given soft wrap
- */
- int getSoftWrapIndentWidthInPixels(@NotNull TextChange softWrap);
-
- /**
- * Asks current model to calculate indent width of the given soft wrap in {@link VisualPosition#column columns}.
- * <p/>
- * <code>'Soft wrap indent width'</code> here means number of soft wrap symbols that follow last soft wrap line
- * feed plus one symbol for <code>'after soft wrap'</code> drawing.
- *
- * @param softWrap soft wrap which indent width should be calculated
- * @return indent width in columns for the given soft wrap
+ * Callback method to ask soft wrap model to release all resources.
*/
- int getSoftWrapIndentWidthInColumns(@NotNull TextChange softWrap);
+ void release();
}
sb.append("<whitespace>");
}
else
+ //This is really stupid and inconvinient builder - it breaks any normal pattern with uppercase
if(Character.isUpperCase(c)) {
sb.append('[').append(Character.toUpperCase(c)).append(Character.toLowerCase(c)).append(']');
}
--- /dev/null
+/*
+ * @(#)HRuleView.java 1.33 05/11/17
+ *
+ * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
+ * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ */
+package com.intellij.codeInsight.hint;
+
+import java.awt.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.*;
+import javax.swing.text.html.CSS;
+import javax.swing.text.html.HTML;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.StyleSheet;
+import java.util.Enumeration;
+import java.lang.Integer;
+
+/**
+ * A view implementation to display an html horizontal
+ * rule.
+ *
+ * @author Timothy Prinzing
+ * @author Sara Swanson
+ * @version 1.33 11/17/05
+ */
+class CustomHrView extends View {
+
+ private Color myColor;
+
+ /**
+ * Creates a new view that represents an <hr> element.
+ *
+ * @param elem the element to create a view for
+ */
+ public CustomHrView(Element elem, Color color) {
+ super(elem);
+ myColor = color;
+ }
+
+
+ public void paint(Graphics g, Shape a) {
+ Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
+ int x = 0;
+ int y = alloc.y + SPACE_ABOVE + (int)topMargin;
+ int width = alloc.width - (int)(leftMargin + rightMargin);
+ int height = 1;
+ if (size > 0) height = size;
+
+ // Align the rule horizontally.
+ switch (alignment) {
+ case StyleConstants.ALIGN_CENTER:
+ x = alloc.x + (alloc.width / 2) - (width / 2);
+ break;
+ case StyleConstants.ALIGN_RIGHT:
+ x = alloc.x + alloc.width - width - (int)rightMargin;
+ break;
+ case StyleConstants.ALIGN_LEFT:
+ default:
+ x = alloc.x + (int)leftMargin;
+ break;
+ }
+
+ // Paint either a shaded rule or a solid line.
+ if (noshade != null) {
+ g.setColor(myColor);
+ g.fillRect(x, y, width, height);
+ }
+ else {
+ Color bg = getContainer().getBackground();
+ Color bottom, top;
+ if (bg == null || bg.equals(Color.white)) {
+ top = Color.darkGray;
+ bottom = Color.lightGray;
+ }
+ else {
+ top = Color.darkGray;
+ bottom = Color.white;
+ }
+ g.setColor(bottom);
+ g.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
+ g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
+ g.setColor(top);
+ g.drawLine(x, y, x + width - 1, y);
+ g.drawLine(x, y, x, y + height - 1);
+ }
+
+ }
+
+
+ /**
+ * Calculates the desired shape of the rule... this is
+ * basically the preferred size of the border.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the desired span
+ * @see View#getPreferredSpan
+ */
+ public float getPreferredSpan(int axis) {
+ switch (axis) {
+ case View.X_AXIS:
+ return 1;
+ case View.Y_AXIS:
+ if (size > 0) {
+ return size + SPACE_ABOVE + SPACE_BELOW + topMargin + bottomMargin;
+ }
+ else {
+ if (noshade != null) {
+ return 2 + SPACE_ABOVE + SPACE_BELOW + topMargin + bottomMargin;
+ }
+ else {
+ return SPACE_ABOVE + SPACE_BELOW + topMargin + bottomMargin;
+ }
+ }
+ default:
+ throw new IllegalArgumentException("Invalid axis: " + axis);
+ }
+ }
+
+ /**
+ * Gets the resize weight for the axis.
+ * The rule is: rigid vertically and flexible horizontally.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the weight
+ */
+ public int getResizeWeight(int axis) {
+ if (axis == View.X_AXIS) {
+ return 1;
+ }
+ else if (axis == View.Y_AXIS) {
+ return 0;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ /**
+ * Determines how attractive a break opportunity in
+ * this view is. This is implemented to request a forced break.
+ *
+ * @param axis may be either View.X_AXIS or View.Y_AXIS
+ * @param pos the potential location of the start of the
+ * broken view (greater than or equal to zero).
+ * This may be useful for calculating tab
+ * positions.
+ * @param len specifies the relative length from <em>pos</em>
+ * where a potential break is desired. The value must be greater
+ * than or equal to zero.
+ * @return the weight, which should be a value between
+ * ForcedBreakWeight and BadBreakWeight.
+ */
+ public int getBreakWeight(int axis, float pos, float len) {
+ if (axis == X_AXIS) {
+ return ForcedBreakWeight;
+ }
+ return BadBreakWeight;
+ }
+
+ public View breakView(int axis, int offset, float pos, float len) {
+ return null;
+ }
+
+ /**
+ * Provides a mapping from the document model coordinate space
+ * to the coordinate space of the view mapped to it.
+ *
+ * @param pos the position to convert
+ * @param a the allocated region to render into
+ * @return the bounding box of the given position
+ * @throws BadLocationException if the given position does not
+ * represent a valid location in the associated document
+ * @see View#modelToView
+ */
+ public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
+ int p0 = getStartOffset();
+ int p1 = getEndOffset();
+ if ((pos >= p0) && (pos <= p1)) {
+ Rectangle r = a.getBounds();
+ if (pos == p1) {
+ r.x += r.width;
+ }
+ r.width = 0;
+ return r;
+ }
+ return null;
+ }
+
+ /**
+ * Provides a mapping from the view coordinate space to the logical
+ * coordinate space of the model.
+ *
+ * @param x the X coordinate
+ * @param y the Y coordinate
+ * @param a the allocated region to render into
+ * @return the location within the model that best represents the
+ * given point of view
+ * @see View#viewToModel
+ */
+ public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
+ Rectangle alloc = (Rectangle)a;
+ if (x < alloc.x + (alloc.width / 2)) {
+ bias[0] = Position.Bias.Forward;
+ return getStartOffset();
+ }
+ bias[0] = Position.Bias.Backward;
+ return getEndOffset();
+ }
+
+ /**
+ * Fetches the attributes to use when rendering. This is
+ * implemented to multiplex the attributes specified in the
+ * model with a StyleSheet.
+ */
+ public AttributeSet getAttributes() {
+ return attr;
+ }
+
+ // --- variables ------------------------------------------------
+
+ private float topMargin;
+ private float bottomMargin;
+ private float leftMargin;
+ private float rightMargin;
+ private int alignment = StyleConstants.ALIGN_CENTER;
+ private String noshade = "true";
+ private int size = 0;
+
+ private static final int SPACE_ABOVE = 3;
+ private static final int SPACE_BELOW = 3;
+
+ /**
+ * View Attributes.
+ */
+ private AttributeSet attr;
+}
+
int y1 = p1.y;
int y2 = p2.y;
- int savedScrollOfset = editor.getScrollingModel().getHorizontalScrollOffset();
- if (savedScrollOfset > 0) {
+ int savedScrollOffset = editor.getScrollingModel().getHorizontalScrollOffset();
+ if (savedScrollOffset > 0) {
editor.stopOptimizedScrolling();
editor.getScrollingModel().scrollHorizontally(0);
}
foldingModel.setFoldingEnabled(isFoldingEnabled);
}
- if (savedScrollOfset > 0) {
+ if (savedScrollOffset > 0) {
editor.stopOptimizedScrolling();
- editor.getScrollingModel().scrollHorizontally(savedScrollOfset);
+ editor.getScrollingModel().scrollHorizontally(savedScrollOffset);
}
JComponent component = new JComponent() {
int startLine = editor.offsetToLogicalPosition(range.getStartOffset()).line;
int endLine = Math.min(editor.offsetToLogicalPosition(range.getEndOffset()).line + 1, editor.getDocument().getLineCount() - 1);
- if (editor.logicalPositionToXY(new LogicalPosition(startLine, 0)).y >= editor.logicalPositionToXY(new LogicalPosition(endLine, 0)).y) return null;
+ //if (editor.logicalPositionToXY(new LogicalPosition(startLine, 0)).y >= editor.logicalPositionToXY(new LogicalPosition(endLine, 0)).y) return null;
+ if (startLine >= endLine) return null;
EditorFragmentComponent fragmentComponent = createEditorFragmentComponent(editor, startLine, endLine, showFolding, true);
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
+import javax.swing.text.*;
+import javax.swing.text.html.HTML;
+import javax.swing.text.html.HTMLEditorKit;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
protected void stripDescription() {
}
- static JEditorPane initPane(@NonNls String text, HintHint hintHint, JLayeredPane layeredPane) {
+ static JEditorPane initPane(@NonNls String text, final HintHint hintHint, JLayeredPane layeredPane) {
final Ref<Dimension> prefSize = new Ref<Dimension>(null);
text = "<html><head>" +
- UIUtil.getCssFontDeclaration(hintHint.getTextFont(), hintHint.getTextForeground()) +
+ UIUtil.getCssFontDeclaration(hintHint.getTextFont(), hintHint.getTextForeground(), hintHint.getLinkForeground()) +
"</head><body>" +
getHtmlBody(text) +
"</body></html>";
- final JEditorPane pane = new JEditorPane(UIUtil.HTML_MIME, text) {
+
+ final JEditorPane pane = new JEditorPane() {
@Override
public Dimension getPreferredSize() {
return prefSize.get() != null ? prefSize.get() : super.getPreferredSize();
}
};
+ final HTMLEditorKit.HTMLFactory factory = new HTMLEditorKit.HTMLFactory() {
+ @Override
+ public View create(Element elem) {
+ AttributeSet attrs = elem.getAttributes();
+ Object elementName = attrs.getAttribute(AbstractDocument.ElementNameAttribute);
+ Object o = (elementName != null) ? null : attrs.getAttribute(StyleConstants.NameAttribute);
+ if (o instanceof HTML.Tag) {
+ HTML.Tag kind = (HTML.Tag)o;
+ if (kind == HTML.Tag.HR) {
+ return new CustomHrView(elem, hintHint.getTextForeground());
+ }
+ }
+ return super.create(elem);
+ }
+ };
+
+ HTMLEditorKit kit = new HTMLEditorKit() {
+ @Override
+ public ViewFactory getViewFactory() {
+ return factory;
+ }
+ };
+ pane.setEditorKit(kit);
+ pane.setText(text);
+
pane.setCaretPosition(0);
pane.setEditable(false);
return pane;
}
+
public static void setColors(JComponent pane) {
pane.setForeground(Color.black);
pane.setBackground(HintUtil.INFORMATION_COLOR);
}
protected static String getHtmlBody(@NonNls String text) {
+ String result = text;
if (!text.startsWith("<html>")) {
- return text.replaceAll("\n", "<br>");
+ result = text.replaceAll("\n", "<br>");
}
- final int bodyIdx = text.indexOf("<body>");
- final int closedBodyIdx = text.indexOf("</body>");
- if (bodyIdx != -1 && closedBodyIdx != -1) {
- return text.substring(bodyIdx + "<body>".length(), closedBodyIdx);
+ else {
+ final int bodyIdx = text.indexOf("<body>");
+ final int closedBodyIdx = text.indexOf("</body>");
+ if (bodyIdx != -1 && closedBodyIdx != -1) {
+ result = text.substring(bodyIdx + "<body>".length(), closedBodyIdx);
+ }
+ else {
+ text = StringUtil.trimStart(text, "<html>").trim();
+ text = StringUtil.trimEnd(text, "</html>").trim();
+ text = StringUtil.trimStart(text, "<body>").trim();
+ text = StringUtil.trimEnd(text, "</body>").trim();
+ result = text;
+ }
}
- text = StringUtil.trimStart(text, "<html>").trim();
- text = StringUtil.trimEnd(text, "</html>").trim();
- text = StringUtil.trimStart(text, "<body>").trim();
- text = StringUtil.trimEnd(text, "</body>").trim();
- return text;
+
+ return result;
}
public boolean equals(Object o) {
return useGraphite(awtTooltip) ? Color.white : UIManager.getColor("ToolTip.foreground");
}
+ public Color getLinkForeground(boolean awtTooltip) {
+ return useGraphite(awtTooltip) ? new Color(209, 209, 255) : Color.blue;
+ }
+
public Color getTextBackground(boolean awtTooltip) {
return useGraphite(awtTooltip) ? new Color(100, 100, 100, 230) : UIManager.getColor("ToolTip.background");
}
hideCurrent(null);
}
}
+
}
private ActionToolbarImpl.MyTimerListener myTimerListener;
public ActionToolbarImpl(final String place,
- final ActionGroup actionGroup,
+ @NotNull final ActionGroup actionGroup,
final boolean horizontal,
DataManager dataManager,
ActionManagerEx actionManager,
}
public ActionToolbarImpl(final String place,
- final ActionGroup actionGroup,
+ @NotNull final ActionGroup actionGroup,
final boolean horizontal,
DataManager dataManager,
ActionManagerEx actionManager,
}
int lineFeedsToSkip = visualLineNumber - visLineStart.line;
- List<? extends TextChange> softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(logLine);
- for (TextChange softWrap : softWraps) {
- int softWrapLineFeedsNumber = StringUtil.countNewLines(softWrap.getText());
+ List<? extends SoftWrap> softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(logLine);
+ for (SoftWrap softWrap : softWraps) {
+ CharSequence softWrapText = softWrap.getText();
+ int softWrapLineFeedsNumber = StringUtil.countNewLines(softWrapText);
if (softWrapLineFeedsNumber < lineFeedsToSkip) {
lineFeedsToSkip -= softWrapLineFeedsNumber;
// Point to the first non-white space symbol at the target soft wrap visual line or to the first non-white space symbol
// of document line that follows it if possible.
- CharSequence softWrapText = softWrap.getText();
int softWrapTextLength = softWrapText.length();
boolean skip = true;
for (int j = 0; j < softWrapTextLength; j++) {
int offset = editor.logicalPositionToOffset(logical);
if (offset < editor.getDocument().getTextLength()) {
- TextChange softWrap = softWrapModel.getSoftWrap(offset);
+ SoftWrap softWrap = softWrapModel.getSoftWrap(offset);
if (softWrap == null) {
// Same offset may correspond to positions on different visual lines in case of soft wraps presence
// (all soft-wrap introduced virtual text is mapped to the same offset as the first document symbol after soft wrap).
super();
copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_EDITOR_USE_SOFT_WRAPS));
}
-
- //TODO den remove
- @Override
- public void setSelected(AnActionEvent e, boolean state) {
- Editor editor = getEditor(e);
- if (editor != null) {
- editor.getSettings().setUseSoftWraps(state);
- }
- }
}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.ex;
+
+import com.intellij.openapi.editor.FoldRegion;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Defines common contract for clients interested in folding processing.
+ *
+ * @author Denis Zhdanov
+ * @since Sep 8, 2010 11:20:28 AM
+ */
+public interface FoldingListener {
+
+ /**
+ * Informs that <code>'collapsed'</code> state of given fold region is just changed.
+ * <p/>
+ * <b>Note:</b> listener should delay fold region state processing until {@link #onFoldProcessingEnd()} is called.
+ * I.e. folding model may return inconsistent data between current moment and {@link #onFoldProcessingEnd()}.
+ *
+ * @param region fold region that is just collapsed or expanded
+ */
+ void onFoldRegionStateChange(@NotNull FoldRegion region);
+
+ /**
+ * Informs that fold processing is done.
+ */
+ void onFoldProcessingEnd();
+}
@Nullable
FoldRegion createFoldRegion(int startOffset, int endOffset, @NotNull String placeholder, FoldingGroup group);
+
+ boolean addListener(@NotNull FoldingListener listener);
+
+ boolean removeListener(@NotNull FoldingListener listener);
}
*/
package com.intellij.openapi.editor.ex;
-import com.intellij.openapi.editor.TextChange;
+import com.intellij.openapi.editor.SoftWrap;
import org.jetbrains.annotations.NotNull;
-import java.util.Collection;
-
/**
* Defines a contract for the callbacks for soft wraps management notifications (addition/removal).
*
*
* @param softWrap newly registered soft wrap
*/
- void softWrapAdded(@NotNull TextChange softWrap);
+ void softWrapAdded(@NotNull SoftWrap softWrap);
/**
* This method is assumed to be called every time soft wrap(s) is removed.
*/
package com.intellij.openapi.editor.ex;
-import com.intellij.openapi.editor.LogicalPosition;
-import com.intellij.openapi.editor.SoftWrapModel;
-import com.intellij.openapi.editor.TextChange;
-import com.intellij.openapi.editor.VisualPosition;
+import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType;
import org.jetbrains.annotations.NotNull;
/**
* @return unmodifiable collection of soft wraps currently registered within the current model
*/
- List<? extends TextChange> getRegisteredSoftWraps();
+ List<? extends SoftWrap> getRegisteredSoftWraps();
/**
* Tries to find index of the target soft wrap at {@link #getRegisteredSoftWraps() soft wraps collection}.
*/
int getMinDrawingWidthInPixels(@NotNull SoftWrapDrawingType drawingType);
- /**
- * Allows to ask for the minimal width in columns required for painting of the given type.
- *
- * @param drawingType target drawing type
- * @return width in columns required for the painting of the given type
- */
- int getMinDrawingWidthInColumns(@NotNull SoftWrapDrawingType drawingType);
-
/**
* Registers given listener within the current model
*
* @return <code>true</code> if given listener was not registered before; <code>false</code> otherwise
*/
boolean addSoftWrapChangeListener(@NotNull SoftWrapChangeListener listener);
-
- /**
- * Asks current model to define approximate soft wraps for the lines range defined by the given lines if necessary.
- * <p/>
- * The main idea is to calculate exact soft wraps positions during editor repainting because we have complete
- * information about font types used for text representation there. However, there is a possible case that we need to
- * perform intermediate soft wraps calculations. E.g. we may open big document and than may want to scroll to the middle
- * of it, hence, need to define vertical offset to apply to viewport position. However, vertical offset value depends on
- * soft wraps between current visible area and target logical line and that soft wraps are not applied yet. We may call this
- * method in order to define approximate soft wraps number and positions then in order to make scrolling more precise.
- *
- * @param line1 one of the target lines boundaries (not imposed to be greater or less than the other boundary)
- * @param line2 another boundary line (not imposed to be greater or less than the other boundary)
- */
- void defineApproximateSoftWraps(int line1, int line2);
}
}
int visualLinesToSkip = line - resVisStart.line;
- List<? extends TextChange> softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(resultLogLine);
+ List<? extends SoftWrap> softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(resultLogLine);
for (int i = 0; i < softWraps.size(); i++) {
- TextChange softWrap = softWraps.get(i);
+ SoftWrap softWrap = softWraps.get(i);
CharSequence text = document.getCharsSequence();
if (visualLinesToSkip <= 0) {
VisualPosition visual = editor.offsetToVisualPosition(softWrap.getStart() - 1);
return resVisEnd.column;
}
// We need to find visual column for line feed of the next soft wrap.
- TextChange nextSoftWrap = softWraps.get(i + 1);
+ SoftWrap nextSoftWrap = softWraps.get(i + 1);
VisualPosition visual = editor.offsetToVisualPosition(nextSoftWrap.getStart() - 1);
int result = visual.column;
int x = editor.visualPositionToXY(visual).x;
public static int calcOffset(Editor editor, CharSequence text, int start, int end, int columnNumber, int tabSize) {
final int maxScanIndex = Math.min(start + columnNumber + 1, end);
SoftWrapModel softWrapModel = editor.getSoftWrapModel();
- List<? extends TextChange> softWraps = softWrapModel.getSoftWrapsForRange(start, maxScanIndex);
+ List<? extends SoftWrap> softWraps = softWrapModel.getSoftWrapsForRange(start, maxScanIndex);
int startToUse = start;
int x = 0;
AtomicInteger currentColumn = new AtomicInteger();
- for (TextChange softWrap : softWraps) {
+ for (SoftWrap softWrap : softWraps) {
// There is a possible case that target column points inside soft wrap-introduced virtual space.
if (currentColumn.get() >= columnNumber) {
return startToUse;
}
- int result = calcSoftWrapUnawareOffset(editor, text, startToUse, softWrap.getEnd(), columnNumber, tabSize, x, currentColumn);
+ int result = calcSoftWrapUnawareOffset(
+ editor, text, startToUse, softWrap.getEnd(), columnNumber, tabSize, x, currentColumn
+ );
if (result >= 0) {
return result;
}
startToUse = softWrap.getStart();
- x = softWrapModel.getSoftWrapIndentWidthInPixels(softWrap);
+ x = softWrap.getIndentInPixels();
}
// There is a possible case that target column points inside soft wrap-introduced virtual space.
public static int calcColumnNumber(Editor editor, CharSequence text, int start, int offset, int tabSize) {
boolean useOptimization = true;
if (editor != null) {
- TextChange softWrap = editor.getSoftWrapModel().getSoftWrap(start);
+ SoftWrap softWrap = editor.getSoftWrapModel().getSoftWrap(start);
useOptimization = softWrap == null;
}
boolean hasNonTabs = false;
for (int i = end - 1; i >= start; i--) {
switch (text.charAt(i)) {
case '\n': startToUse = i + 1; break loop;
- case '\t': lastTabSymbolIndex = i;
+ case '\t': if (lastTabSymbolIndex < 0) lastTabSymbolIndex = i;
}
}
// Calculate number of columns up to the latest tabulation symbol.
for (int i = startToUse; i <= lastTabSymbolIndex; i++) {
+ SoftWrap softWrap = editor.getSoftWrapModel().getSoftWrap(i);
+ if (softWrap != null) {
+ x = softWrap.getIndentInPixels();
+ }
char c = text.charAt(i);
prevX = x;
switch (c) {
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.editor.highlighter.HighlighterClient;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
+import com.intellij.openapi.editor.impl.EditorDocumentPriorities;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileTypes.PlainSyntaxHighlighter;
import com.intellij.openapi.fileTypes.SyntaxHighlighter;
}
public int getPriority() {
- return 2;
+ return EditorDocumentPriorities.LEXER_EDITOR;
}
private static boolean segmentsEqual(SegmentArrayWithData a1, int idx1, SegmentArrayWithData a2, int idx2, final int offsetShift) {
}
if (newColumnNumber < 0) newColumnNumber = 0;
- if (newLineNumber < 0) newLineNumber = 0;
+
+ // There is a possible case that caret is located at the first line and use presses 'Shift+Up'. We want to select all text
+ // from the document start to the current caret position then. So, we have a dedicated flag for tracking that.
+ boolean selectToDocumentStart = false;
+ if (newLineNumber < 0) {
+ selectToDocumentStart = true;
+ newLineNumber = 0;
+ }
VisualPosition pos = new VisualPosition(newLineNumber, newColumnNumber);
int lastColumnNumber = newColumnNumber;
newColumnNumber = myEditor.offsetToVisualPosition(offset).column;
}
else {
- TextChange softWrap = myEditor.getSoftWrapModel().getSoftWrap(offset + 1);
+ SoftWrap softWrap = myEditor.getSoftWrapModel().getSoftWrap(offset + 1);
// There is a possible case that tabulation symbol is the last document symbol represented on a visual line before
// soft wrap. We can't just use column from 'offset + 1' because it would point on a next visual line.
if (softWrap == null) {
selectionModel.setBlockSelection(blockSelectionStart, getLogicalPosition());
}
else {
- selectionModel.setSelection(selectionStart, getOffset());
+ int endOffsetToUse = getOffset();
+ if (selectToDocumentStart) {
+ endOffsetToUse = 0;
+ }
+ else if (pos.line >= myEditor.getVisibleLineCount()) {
+ endOffsetToUse = myEditor.getDocument().getTextLength();
+ }
+ selectionModel.setSelection(selectionStart, endOffsetToUse);
}
}
else {
}
public int getPriority() {
- return 3;
+ return EditorDocumentPriorities.CARET_MODEL;
}
private void setCurrentLogicalCaret(LogicalPosition position) {
int y = myEditor.visualPositionToXY(visualPosition).y;
int lineHeight = myEditor.getLineHeight();
int height = lineHeight;
- List<? extends TextChange> softWraps = myEditor.getSoftWrapModel().getSoftWrapsForRange(startOffset, endOffset);
- for (TextChange softWrap : softWraps) {
+ List<? extends SoftWrap> softWraps = myEditor.getSoftWrapModel().getSoftWrapsForRange(startOffset, endOffset);
+ for (SoftWrap softWrap : softWraps) {
height += StringUtil.countNewLines(softWrap.getText()) * lineHeight;
}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.openapi.editor.ex.PrioritizedDocumentListener;
+
+/**
+ * Holds values to use for common {@link PrioritizedDocumentListener prioritized document listeners} used within standard IntelliJ
+ * editor.
+ *
+ * @author Denis Zhdanov
+ * @since Sep 13, 2010 2:30:48 PM
+ */
+public class EditorDocumentPriorities {
+
+ public static final int SOFT_WRAP_MODEL = 40;
+
+ /**
+ * Assuming that range marker listeners work only with document offsets and don't perform document dimension mappings like
+ * {@code 'logical position -> visual position'}, {@code 'offset -> logical position'} etc.
+ */
+ public static final int RANGE_MARKER = 60;
+ public static final int FOLD_MODEL = 80;
+ public static final int LEXER_EDITOR = 100;
+ public static final int CARET_MODEL = 120;
+ public static final int SELECTION_MODEL = 140;
+ public static final int EDITOR_DOCUMENT_ADAPTER = 160;
+
+ private EditorDocumentPriorities() {
+ }
+}
revalidateMarkup();
repaint();
}
-
+
public void paint(Graphics g) {
((ApplicationImpl)ApplicationManager.getApplication()).editorPaintStart();
private VisualPosition offsetToLineStartPosition(int offset) {
offset = Math.min(myEditor.getDocument().getTextLength() - 1, offset);
- int line = myEditor.getDocument().getLineNumber(offset);
- return myEditor.logicalToVisualPosition(new LogicalPosition(line, 0));
+ return new VisualPosition(myEditor.offsetToVisualPosition(offset).line, 0);
}
private void paintFoldingTree(Graphics2D g) {
private boolean myEmbeddedIntoDialogWrapper;
private CachedFontContent myLastCache;
private boolean mySpacesHaveSameWidth;
+
+ /**
+ * There is a possible case that specific font is used for particular text drawing operation (e.g. for 'before' and 'after'
+ * soft wraps drawings). Hence, even if {@link #mySpacesHaveSameWidth} is <code>true</code>, space size for that specific
+ * font may be different. So, we define additional flag that should indicate that {@link #myLastCache} should be reset.
+ */
+ private boolean myForceRefreshFont;
private boolean mySoftWrapsChanged;
private Point myLastBackgroundPosition = null;
myDocument.addDocumentListener(myEditorDocumentAdapter);
myDocument.addDocumentListener(mySoftWrapModel);
+ myFoldingModel.addListener(mySoftWrapModel);
+
myIndentsModel = new IndentsModelImpl(this);
myCaretModel.addCaretListener(new CaretListener() {
private LightweightHint myCurrentHint = null;
mySoftWrapModel.addSoftWrapChangeListener(new SoftWrapChangeListener() {
@Override
- public void softWrapAdded(@NotNull TextChange softWrap) {
+ public void softWrapAdded(@NotNull SoftWrap softWrap) {
mySoftWrapsChanged = true;
int softWrapLine = myDocument.getLineNumber(softWrap.getStart());
mySizeContainer.update(softWrapLine, softWrapLine, softWrapLine);
myDocument.removeDocumentListener(mySelectionModel);
myDocument.removeDocumentListener(mySoftWrapModel);
+ myFoldingModel.removeListener(mySoftWrapModel);
+
+ mySoftWrapModel.release();
+
MarkupModelEx markupModel = (MarkupModelEx)myDocument.getMarkupModel(myProject, false);
if (markupModel instanceof MarkupModelImpl) {
markupModel.removeMarkupModelListener(myMarkupModelListener);
offset = region.getEndOffset();
}
else {
- TextChange softWrap = mySoftWrapModel.getSoftWrap(offset);
+ SoftWrap softWrap = mySoftWrapModel.getSoftWrap(offset);
if (softWrap != null) {
// There is a possible case that soft wrap contains more than one line feed inside and we need to start counting not
// from its first line.
if (logical.softWrapLinesOnCurrentLogicalLine > 0) {
int linesToSkip = logical.softWrapLinesOnCurrentLogicalLine;
- List<? extends TextChange> softWraps = getSoftWrapModel().getSoftWrapsForLine(logLine);
- for (TextChange softWrap : softWraps) {
+ List<? extends SoftWrap> softWraps = getSoftWrapModel().getSoftWrapsForLine(logLine);
+ for (SoftWrap softWrap : softWraps) {
if (myFoldingModel.isOffsetCollapsed(softWrap.getStart()) && myFoldingModel.isOffsetCollapsed(softWrap.getStart() - 1)) {
continue;
}
- int lineFeeds = StringUtil.countNewLines(softWrap.getText());
- linesToSkip -= lineFeeds;
+ linesToSkip--; // Assuming here that every soft wrap has exactly one line feed
if (linesToSkip > 0) {
continue;
}
lineStartOffset = softWrap.getStart();
- int widthInColumns = getSoftWrapModel().getSoftWrapIndentWidthInColumns(softWrap);
- int widthInPixels = getSoftWrapModel().getSoftWrapIndentWidthInPixels(softWrap);
+ int widthInColumns = softWrap.getIndentInColumns();
+ int widthInPixels = softWrap.getIndentInPixels();
if (widthInColumns <= column) {
column -= widthInColumns;
reserved = widthInPixels;
fontType = state.getMergedAttributes().getFontType();
}
// We need to consider 'before soft wrap drawing'.
- TextChange softWrap = getSoftWrapModel().getSoftWrap(offset);
+ SoftWrap softWrap = getSoftWrapModel().getSoftWrap(offset);
if (softWrap != null && offset > startOffset) {
column++;
x += getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED);
}
int start = logicalPositionToOffset(logicalPosition);
+ getSoftWrapModel().registerSoftWrapsIfNecessary(clip, start);
// There is a possible case that we need to draw background from the start of soft wrap-introduced visual line. Given position
// has valid 'y' coordinate then at it shouldn't be affected by soft wrap that corresponds to the visual line start offset.
// Hence, we store information about soft wrap to be skipped for further processing and adjust 'x' coordinate value if necessary.
TIntHashSet softWrapsToSkip = new TIntHashSet();
- TextChange softWrap = getSoftWrapModel().getSoftWrap(start);
+ SoftWrap softWrap = getSoftWrapModel().getSoftWrap(start);
if (softWrap != null) {
- position.x = getSoftWrapModel().getSoftWrapIndentWidthInPixels(softWrap);
+ position.x = softWrap.getIndentInPixels();
softWrapsToSkip.add(softWrap.getStart());
}
CharSequence text = myDocument.getCharsNoThreadCheck();
int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
- outer:
while (!iterationState.atEnd() && !lIterator.atEnd()) {
int hEnd = iterationState.getEndOffset();
int lEnd = lIterator.getEnd();
- getSoftWrapModel().registerSoftWrapIfNecessary(text, start, hEnd, position.x, fontType);
if (hEnd >= lEnd) {
FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
flushBackground(g, clip);
if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
- getSoftWrapModel().registerSoftWrapIfNecessary(text, start, myDocument.getTextLength(), position.x, fontType);
paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight, defaultBackground, caretRowPainted);
}
AtomicBoolean caretRowPainted)
{
int startToUse = start;
- List<? extends TextChange> softWraps = getSoftWrapModel().getSoftWrapsForRange(start, end);
- for (TextChange softWrap : softWraps) {
+ List<? extends SoftWrap> softWraps = getSoftWrapModel().getSoftWrapsForRange(start, end);
+ for (SoftWrap softWrap : softWraps) {
int softWrapStart = softWrap.getStart();
if (processSoftWrap.contains(softWrapStart)) {
continue;
return position.x;
}
- private void drawSoftWrap(Graphics g, TextChange softWrap, Point position, int fontType, Color defaultBackground, Rectangle clip,
+ private void drawSoftWrap(Graphics g, SoftWrap softWrap, Point position, int fontType, Color defaultBackground, Rectangle clip,
AtomicBoolean caretRowPainted) {
// The main idea is to to do the following:
// *) update given drawing position coordinates in accordance with the current soft wrap;
else {
FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
if (collapsedFolderAt != null) {
- TextChange softWrap = mySoftWrapModel.getSoftWrap(collapsedFolderAt.getStartOffset());
+ SoftWrap softWrap = mySoftWrapModel.getSoftWrap(collapsedFolderAt.getStartOffset());
if (softWrap != null && logicalPosition.get() != null) {
position.x = drawStringWithSoftWraps(
g, chars, collapsedFolderAt.getStartOffset(), collapsedFolderAt.getStartOffset(), position, clip, effectColor, effectType,
Color fontColor,
AtomicReference<LogicalPosition> startDrawingLogicalPosition)
{
+
int startToUse = start;
// There is a possible case that starting logical line is split by soft-wraps and it's part after the split should be drawn.
if (startDrawingLogicalPosition.get() != null) {
softWrapLinesToSkip = startDrawingLogicalPosition.get().softWrapLinesOnCurrentLogicalLine;
}
- TextChange lastSkippedSoftWrap = null;
+ SoftWrap lastSkippedSoftWrap = null;
if (softWrapLinesToSkip > 0) {
- List<? extends TextChange> softWraps = getSoftWrapModel().getSoftWrapsForLine(startDrawingLogicalPosition.get().line);
- for (TextChange softWrap : softWraps) {
- softWrapLinesToSkip -= StringUtil.countNewLines(softWrap.getText());
+ List<? extends SoftWrap> softWraps = getSoftWrapModel().getSoftWrapsForLine(startDrawingLogicalPosition.get().line);
+ for (SoftWrap softWrap : softWraps) {
+ softWrapLinesToSkip--; // Assuming that soft wrap has a single line feed all the time
if (softWrapLinesToSkip <= 0) {
lastSkippedSoftWrap = softWrap;
startToUse = softWrap.getStart();
startDrawingLogicalPosition.set(null);
outer:
- for (TextChange softWrap : getSoftWrapModel().getSoftWrapsForRange(startToUse, end)) {
+ for (SoftWrap softWrap : getSoftWrapModel().getSoftWrapsForRange(startToUse, end)) {
char[] softWrapChars = softWrap.getChars();
if (softWrap.equals(lastSkippedSoftWrap)) {
// to draw soft wrap indent if any and 'after soft wrap' sign.
int i = CharArrayUtil.lastIndexOf(softWrapChars, '\n', 0, softWrapChars.length);
if (i < softWrapChars.length - 1) {
+ position.x = 0; // Soft wrap starts new visual line
position.x = drawString(
g, softWrapChars, i + 1, softWrapChars.length, position, clip, effectColor, effectType, fontType, fontColor
);
}
position.x += mySoftWrapModel.paint(g, SoftWrapDrawingType.AFTER_SOFT_WRAP, position.x, position.y, getLineHeight());
+ myForceRefreshFont = true;
continue;
}
);
}
mySoftWrapModel.paint(g, SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED, position.x, position.y, getLineHeight());
+ myForceRefreshFont = true;
// Reset 'x' coordinate because of new line start.
position.x = 0;
);
}
position.x += mySoftWrapModel.paint(g, SoftWrapDrawingType.AFTER_SOFT_WRAP, position.x, position.y, getLineHeight());
+ myForceRefreshFont = true;
}
return position.x = drawString(g, text, startToUse, end, position, clip, effectColor, effectType, fontType, fontColor);
}
}
private void drawCharsCached(Graphics g, char[] data, int start, int end, int x, int y, int fontType, Color color) {
- if (mySpacesHaveSameWidth && myLastCache != null && spacesOnly(data, start, end)) {
+ if (!myForceRefreshFont && mySpacesHaveSameWidth && myLastCache != null && spacesOnly(data, start, end)) {
myLastCache.addContent(g, data, start, end, x, y, null);
}
else {
+ myForceRefreshFont = false;
FontInfo fnt = EditorUtil.fontForChar(data[start], fontType, this);
drawCharsCached(g, data, start, end, x, y, fnt, color);
}
getGlobal().setLineSpacing(lineSpacing);
}
+ @Nullable
public Object clone() {
return null;
}
}
public int getPriority() {
- return 5;
+ return EditorDocumentPriorities.EDITOR_DOCUMENT_ADAPTER;
}
}
myOldEndLine = offsetToLogicalPosition(e.getOffset() + e.getOldLength()).line;
}
+ // Commented as nobody uses this method.
+ //private int getVisualPositionLine(int offset) {
+ // // Do round up of offset to the nearest line start (valid since we need only line)
+ // // This is needed for preventing access to lexer editor highlighter regions [that are reset] during bulk mode operation
+ //
+ // int line = calcLogicalLineNumber(offset);
+ // return logicalToVisualLine(line);
+ //}
+
public synchronized void update(int startLine, int newEndLine, int oldEndLine) {
final int lineWidthSize = myLineWidths.size();
if (lineWidthSize == 0 || myDocument.getTextLength() <= 0) {
final int fontSize = myScheme.getEditorFontSize();
final String fontName = myScheme.getEditorFontName();
- List<? extends TextChange> softWraps = getSoftWrapModel().getRegisteredSoftWraps();
+ List<? extends SoftWrap> softWraps = getSoftWrapModel().getRegisteredSoftWraps();
int softWrapsIndex = -1;
for (int line = 0; line < lineCount; line++) {
}
while (softWrapsIndex < softWraps.size() && line < lineCount) {
- TextChange softWrap = softWraps.get(softWrapsIndex);
+ SoftWrap softWrap = softWraps.get(softWrapsIndex);
if (softWrap.getStart() > offset) {
break;
}
softWrapsIndex++;
if (softWrap.getStart() == offset) {
maxPreviousSoftWrappedWidth = Math.max(maxPreviousSoftWrappedWidth, x);
- x = getSoftWrapModel().getSoftWrapIndentWidthInPixels(softWrap);
+ x = softWrap.getIndentInPixels();
}
}
if (line + 1 >= lineCount) {
}
private int getContentHeight() {
- return myLineWidths.size() * getLineHeight();
+ return getVisibleLineCount() * getLineHeight();
}
}
fontType = state.getMergedAttributes().getFontType();
}
- TextChange softWrap = getSoftWrapModel().getSoftWrap(i);
+ SoftWrap softWrap = getSoftWrapModel().getSoftWrap(i);
if (softWrap != null) {
column++; // For 'after soft wrap' drawing.
x = getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP);
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.ex.DocumentEx;
+import com.intellij.openapi.editor.ex.FoldingListener;
import com.intellij.openapi.editor.ex.FoldingModelEx;
import com.intellij.openapi.editor.ex.PrioritizedDocumentListener;
import com.intellij.openapi.editor.markup.TextAttributes;
import java.awt.*;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentListener {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorFoldingModelImpl");
+
+ private final Set<FoldingListener> myListeners = new CopyOnWriteArraySet<FoldingListener>();
+
private boolean myIsFoldingEnabled;
private final EditorImpl myEditor;
private final FoldRegionsTree myFoldTree;
final FoldingGroup group = region.getGroup();
if (group != null) {
myGroups.putValue(group, region);
+ for (FoldingListener listener : myListeners) {
+ listener.onFoldRegionStateChange(region);
+ }
}
return true;
}
myFoldRegionsProcessed = true;
((FoldRegionImpl) region).setExpandedInternal(true);
+ notifyListenersOnFoldRegionStateChange(region);
}
public void collapseFoldRegion(FoldRegion region) {
myFoldRegionsProcessed = true;
((FoldRegionImpl) region).setExpandedInternal(false);
+ notifyListenersOnFoldRegionStateChange(region);
}
private void notifyBatchFoldingProcessingDone() {
+ try {
+ doNotifyBatchFoldingProcessingDone();
+ } finally {
+ for (FoldingListener listener : myListeners) {
+ listener.onFoldProcessingEnd();
+ }
+ }
+ }
+
+ private void doNotifyBatchFoldingProcessingDone() {
myFoldTree.rebuild();
myEditor.updateCaretCursor();
int column = -1;
int line = -1;
+ int offsetToUse = -1;
FoldRegion collapsed = myFoldTree.fetchOutermost(caretOffset);
if (myCaretPositionSaved) {
int savedOffset = myEditor.logicalPositionToOffset(new LogicalPosition(mySavedCaretY, mySavedCaretX));
FoldRegion collapsedAtSaved = myFoldTree.fetchOutermost(savedOffset);
- column = mySavedCaretX;
- line = collapsedAtSaved != null ? collapsedAtSaved.getDocument().getLineNumber(collapsedAtSaved.getStartOffset()) : mySavedCaretY;
+ if (collapsedAtSaved == null) {
+ column = mySavedCaretX;
+ line = mySavedCaretY;
+ }
+ else {
+ offsetToUse = collapsedAtSaved.getStartOffset();
+ }
}
if (collapsed != null && column == -1) {
line = collapsed.getDocument().getLineNumber(collapsed.getStartOffset());
- column = myEditor.getCaretModel().getVisualPosition().column;
+ column = myEditor.offsetToLogicalPosition(collapsed.getStartOffset()).column;
}
boolean oldCaretPositionSaved = myCaretPositionSaved;
- if (column != -1) {
- LogicalPosition log = new LogicalPosition(line, 0);
- VisualPosition vis = myEditor.logicalToVisualPosition(log);
- VisualPosition pos = new VisualPosition(vis.line, column);
- myEditor.getCaretModel().moveToVisualPosition(pos);
+ if (offsetToUse >= 0) {
+ myEditor.getCaretModel().moveToOffset(offsetToUse);
+ }
+ else if (column != -1) {
+ myEditor.getCaretModel().moveToLogicalPosition(new LogicalPosition(line, column));
}
else {
myEditor.getCaretModel().moveToLogicalPosition(caretPosition);
}
public int getPriority() {
- return 1;
+ return EditorDocumentPriorities.FOLD_MODEL;
}
public FoldRegion createFoldRegion(int startOffset, int endOffset, @NotNull String placeholder, FoldingGroup group) {
LOG.assertTrue(region.isValid());
return region;
}
+
+ @Override
+ public boolean addListener(@NotNull FoldingListener listener) {
+ return myListeners.add(listener);
+ }
+
+ @Override
+ public boolean removeListener(@NotNull FoldingListener listener) {
+ return myListeners.remove(listener);
+ }
+
+ private void notifyListenersOnFoldRegionStateChange(@NotNull FoldRegion foldRegion) {
+ for (FoldingListener listener : myListeners) {
+ listener.onFoldRegionStateChange(foldRegion);
+ }
+ }
}
document.addDocumentListener(new PrioritizedDocumentListener() {
public int getPriority() {
- return 0; // Need to make sure we invalidate all the stuff before someone (like LineStatusTracker) starts to modify highlights.
+ return EditorDocumentPriorities.RANGE_MARKER; // Need to make sure we invalidate all the stuff before someone (like LineStatusTracker) starts to modify highlights.
}
public void beforeDocumentChange(DocumentEvent event) {}
}
private Point calcOffsetsToScroll(LogicalPosition pos, ScrollType scrollType, Rectangle viewRect) {
- // There is a possible case that the user opens huge document with many number of soft-wrapped line.
- // Suppose that he or she wants to move viewport to such a logical position that many document lines between current
- // viewport position and the target one are not displayed before. That means that we can't be sure about vertical offset
- // to be applied to the viewport. Hence, we ask soft wrap model to roughly define soft wraps on a trail.
LogicalPosition firstVisibleLineStart = myEditor.xyToLogicalPosition(viewRect.getLocation());
- myEditor.getSoftWrapModel().defineApproximateSoftWraps(firstVisibleLineStart.line, pos.line);
Point targetLocation = myEditor.logicalPositionToXY(pos);
}
public int getPriority() {
- return 4;
+ return EditorDocumentPriorities.SELECTION_MODEL;
}
public SelectionModelImpl(EditorImpl editor) {
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
-import com.intellij.openapi.editor.ex.EditorEx;
-import com.intellij.openapi.editor.ex.PrioritizedDocumentListener;
-import com.intellij.openapi.editor.ex.SoftWrapChangeListener;
-import com.intellij.openapi.editor.ex.SoftWrapModelEx;
+import com.intellij.openapi.editor.ex.*;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.impl.softwrap.*;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.util.text.CharArrayUtil;
+import com.intellij.openapi.editor.impl.softwrap.mapping.CachingSoftWrapDataMapper;
+import com.intellij.openapi.editor.impl.softwrap.mapping.SoftWrapApplianceManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
* @author Denis Zhdanov
* @since Jun 8, 2010 12:47:32 PM
*/
-public class SoftWrapModelImpl implements SoftWrapModelEx, DocumentListener {
+public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentListener, FoldingListener {
+
+ /** Upper boundary of time interval to check editor settings. */
+ private static final long EDITOR_SETTINGS_CHECK_PERIOD_MILLIS = 10000;
private final List<DocumentListener> myDocumentListeners = new ArrayList<DocumentListener>();
+ private final List<FoldingListener> myFoldListeners = new ArrayList<FoldingListener>();
- private final SoftWrapDataMapper myDataMapper;
+ private final CachingSoftWrapDataMapper myDataMapper;
private final SoftWrapsStorage myStorage;
private final SoftWrapPainter myPainter;
private final SoftWrapApplianceManager myApplianceManager;
- private final SoftWrapDocumentChangeManager myDocumentChangeManager;
private final EditorEx myEditor;
/** Holds number of 'active' calls, i.e. number of methods calls of the current object within the current call stack. */
- private int myActive;
+ private int myActive;
+ /** Holds timestamp of the last editor settings check. */
+ private long myLastSettingsCheckTimeMillis;
+ private boolean myLastUseSoftWraps;
public SoftWrapModelImpl(@NotNull EditorEx editor) {
this(editor, new SoftWrapsStorage(), new CompositeSoftWrapPainter(editor));
public SoftWrapModelImpl(@NotNull final EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull SoftWrapPainter painter,
EditorTextRepresentationHelper representationHelper) {
this(
- editor, storage, painter, new DefaultSoftWrapApplianceManager(storage, editor, painter, representationHelper),
- new SoftWrapDataMapper(editor, storage, representationHelper), new SoftWrapDocumentChangeManager(editor, storage)
+ editor, storage, painter, new SoftWrapApplianceManager(storage, editor, painter, representationHelper),
+ new CachingSoftWrapDataMapper(editor, storage, representationHelper)
);
+ myApplianceManager.addListener(myDataMapper);
}
public SoftWrapModelImpl(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull SoftWrapPainter painter,
- @NotNull DefaultSoftWrapApplianceManager applianceManager, @NotNull SoftWrapDataMapper dataMapper,
- @NotNull SoftWrapDocumentChangeManager documentChangeManager)
- {
- this(editor, storage, painter, (SoftWrapApplianceManager)applianceManager, dataMapper, documentChangeManager);
- myDocumentListeners.add(applianceManager.getDocumentListener());
- }
-
- public SoftWrapModelImpl(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull SoftWrapPainter painter,
- @NotNull SoftWrapApplianceManager applianceManager, @NotNull SoftWrapDataMapper dataMapper,
- @NotNull SoftWrapDocumentChangeManager documentChangeManager)
+ @NotNull SoftWrapApplianceManager applianceManager, @NotNull CachingSoftWrapDataMapper dataMapper)
{
myEditor = editor;
myStorage = storage;
myPainter = painter;
myApplianceManager = applianceManager;
myDataMapper = dataMapper;
- myDocumentChangeManager = documentChangeManager;
- myDocumentListeners.add(myDocumentChangeManager);
- Collections.sort(myDocumentListeners, PrioritizedDocumentListener.COMPARATOR);
+ myDocumentListeners.add(myApplianceManager);
+ myFoldListeners.add(myApplianceManager);
}
public boolean isSoftWrappingEnabled() {
- return myEditor.getSettings().isUseSoftWraps() && !myEditor.isOneLineMode();
+ if (myEditor.isOneLineMode()) {
+ return false;
+ }
+
+ // Profiling shows that editor settings lookup have impact at overall performance if called often.
+ // Hence, we cache value used last time.
+ if (System.currentTimeMillis() - myLastSettingsCheckTimeMillis <= EDITOR_SETTINGS_CHECK_PERIOD_MILLIS) {
+ return myLastUseSoftWraps;
+ }
+ myLastSettingsCheckTimeMillis = System.currentTimeMillis();
+ return myLastUseSoftWraps = myEditor.getSettings().isUseSoftWraps();
}
@Nullable
- public TextChange getSoftWrap(int offset) {
+ public SoftWrap getSoftWrap(int offset) {
if (!isSoftWrappingEnabled()) {
return null;
}
@NotNull
@Override
- public List<? extends TextChange> getSoftWrapsForRange(int start, int end) {
+ public List<? extends SoftWrap> getSoftWrapsForRange(int start, int end) {
if (!isSoftWrappingEnabled()) {
return Collections.emptyList();
}
startIndex = -startIndex - 1;
}
- List<TextChangeImpl> softWraps = myStorage.getSoftWraps();
+ List<? extends SoftWrap> softWraps = myStorage.getSoftWraps();
if (startIndex >= softWraps.size()) {
return Collections.emptyList();
}
@Override
@NotNull
- public List<? extends TextChange> getSoftWrapsForLine(int documentLine) {
+ public List<? extends SoftWrap> getSoftWrapsForLine(int documentLine) {
if (!isSoftWrappingEnabled()) {
return Collections.emptyList();
}
}
int result = 0;
FoldingModel foldingModel = myEditor.getFoldingModel();
- for (TextChange softWrap : myStorage.getSoftWraps()) {
+ for (SoftWrap softWrap : myStorage.getSoftWraps()) {
if (!foldingModel.isOffsetCollapsed(softWrap.getStart())) {
- result += StringUtil.countNewLines(softWrap.getText());
+ result++; // Assuming that soft wrap has single line feed all the time
}
}
return result;
}
- @Override
- public void defineApproximateSoftWraps(int line1, int line2) {
- if (!isSoftWrappingEnabled()) {
- return;
- }
- int startLine = line1;
- int endLine = line2;
- if (line1 > line2) {
- startLine = line2;
- endLine = line1;
- }
-
- // Normalization.
- Document document = myEditor.getDocument();
- startLine = Math.max(0, startLine);
- endLine = Math.max(0, Math.min(endLine, document.getLineCount() - 1));
-
- myApplianceManager.registerSoftWrapIfNecessary(
- document.getCharsSequence(), document.getLineStartOffset(startLine), document.getLineEndOffset(endLine), 0, Font.PLAIN, true
- );
- }
-
- public void registerSoftWrapIfNecessary(@NotNull CharSequence text, int start, int end, int x, int fontType) {
+ //TODO den add doc
+ public void registerSoftWrapsIfNecessary(@NotNull Rectangle clip, int startOffset) {
if (!isSoftWrappingEnabled()) {
return;
}
- myDocumentChangeManager.syncSoftWraps();
myActive++;
try {
- myApplianceManager.registerSoftWrapIfNecessary(text, start, end, x, fontType, false);
+ myApplianceManager.registerSoftWrapIfNecessary(clip, startOffset);
}
finally {
myActive--;
}
@Override
- public List<? extends TextChange> getRegisteredSoftWraps() {
+ public List<? extends SoftWrap> getRegisteredSoftWraps() {
if (!isSoftWrappingEnabled()) {
return Collections.emptyList();
}
}
@Override
- public boolean isVisible(TextChange softWrap) {
+ public boolean isVisible(SoftWrap softWrap) {
FoldingModel foldingModel = myEditor.getFoldingModel();
int start = softWrap.getStart();
if (!foldingModel.isOffsetCollapsed(start)) {
if (!isSoftWrappingEnabled()) {
return 0;
}
- return myPainter.paint(g, drawingType, x, y, lineHeight);
+ return myPainter.paint(g, drawingType, x, y, lineHeight);
}
@Override
return myPainter.getMinDrawingWidth(drawingType);
}
- @Override
- public int getMinDrawingWidthInColumns(@NotNull SoftWrapDrawingType drawingType) {
- return myPainter.getMinDrawingWidth(drawingType) > 0 ? 1 : 0;
- }
-
@NotNull
@Override
public LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visual) {
- if (myActive > 0 || !isSoftWrappingEnabled() || myStorage.isEmpty() || myEditor.getDocument().getTextLength() <= 0) {
+ if (!prepareToMapping()) {
return myEditor.visualToLogicalPosition(visual, false);
}
myActive++;
@NotNull
@Override
public LogicalPosition offsetToLogicalPosition(int offset) {
- if (myActive > 0 || !isSoftWrappingEnabled() || myStorage.isEmpty() || myEditor.getDocument().getTextLength() <= 0) {
+ if (!prepareToMapping()) {
return myEditor.offsetToLogicalPosition(offset, false);
}
myActive++;
@NotNull
public LogicalPosition adjustLogicalPosition(LogicalPosition defaultLogical, int offset) {
- if (myActive > 0 || !isSoftWrappingEnabled()) {
+ if (!prepareToMapping()) {
return defaultLogical;
}
@NotNull
public VisualPosition adjustVisualPosition(@NotNull LogicalPosition logical, @NotNull VisualPosition defaultVisual) {
- if (myActive > 0 || !isSoftWrappingEnabled() || myStorage.isEmpty()) {
+ if (!prepareToMapping()) {
return defaultVisual;
}
myActive++;
try {
- return myDataMapper.adjustVisualPosition(logical, defaultVisual);
+ return myDataMapper.logicalToVisualPosition(logical, defaultVisual);
}
finally {
myActive--;
}
}
+ /**
+ * Encapsulates preparations for performing document dimension mapping (e.g. visual to logical position) and answers
+ * if soft wraps-aware processing should be used (e.g. there is no need to consider soft wraps if user configured them
+ * not to be used).
+ *
+ * @return <code>true</code> if soft wraps-aware processing should be used; <code>false</code> otherwise
+ */
+ private boolean prepareToMapping() {
+ boolean useSoftWraps = myActive <= 0 && isSoftWrappingEnabled() && !myStorage.isEmpty() && myEditor.getDocument().getTextLength() > 0;
+ if (!useSoftWraps) {
+ return useSoftWraps;
+ }
+
+ myApplianceManager.dropDataIfNecessary();
+ return true;
+ //
+ //Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea();
+ //if (visibleArea.width <= 0) {
+ // // We don't know visible area width, hence, can't calculate soft wraps positions.
+ // return false;
+ //}
+ //
+ //myActive++;
+ //try {
+ // LogicalPosition logicalPosition = myEditor.xyToLogicalPosition(visibleArea.getLocation());
+ // int offset = myEditor.logicalPositionToOffset(logicalPosition);
+ // myApplianceManager.registerSoftWrapIfNecessary(visibleArea, offset);
+ // return true;
+ //}
+ //finally {
+ // myActive--;
+ //}
+ }
+
/**
* Allows to answer if given visual position points to soft wrap-introduced virtual space.
*
return false;
}
- TextChange softWrap = model.getSoftWrap(offset);
+ SoftWrap softWrap = model.getSoftWrap(offset);
if (softWrap == null) {
return false;
}
int offsetLineStart = myEditor.logicalPositionToOffset(logLineStart);
softWrap = model.getSoftWrap(offsetLineStart);
if (softWrap != null) {
- x = model.getSoftWrapIndentWidthInColumns(softWrap) * EditorUtil.getSpaceWidth(Font.PLAIN, myEditor);
+ x = softWrap.getIndentInPixels();
}
}
int width = EditorUtil.textWidthInColumns(myEditor, myEditor.getDocument().getCharsSequence(), offset - 1, offset, x);
return countBeforeSoftWrap ? visual.column >= softWrapStartColumn : visual.column > softWrapStartColumn;
}
- @Override
- public int getSoftWrapIndentWidthInPixels(@NotNull TextChange softWrap) {
- if (!isSoftWrappingEnabled()) {
- return 0;
- }
- char[] chars = softWrap.getChars();
- int result = myPainter.getMinDrawingWidth(SoftWrapDrawingType.AFTER_SOFT_WRAP);
-
- int start = 0;
- int end = chars.length;
-
- int i = CharArrayUtil.lastIndexOf(chars, '\n', 0, chars.length);
- if (i >= 0) {
- start = i + 1;
- }
-
- if (start < end) {
- result += EditorUtil.textWidth(myEditor, softWrap.getText(), start, end, Font.PLAIN, 0);
- }
-
- return result;
- }
-
- @Override
- public int getSoftWrapIndentWidthInColumns(@NotNull TextChange softWrap) {
- if (!isSoftWrappingEnabled()) {
- return 0;
- }
- char[] chars = softWrap.getChars();
- int result = 1; // For 'after soft wrap' drawing
-
- int start = 0;
- int i = CharArrayUtil.lastIndexOf(chars, '\n', 0, chars.length);
- if (i >= 0) {
- start = i + 1;
- }
- result += chars.length - start;
-
- return result;
- }
-
@Override
public void beforeDocumentChangeAtCaret() {
CaretModel caretModel = myEditor.getCaretModel();
if (!isInsideSoftWrap(visualCaretPosition)) {
return;
}
- if (myDocumentChangeManager.makeHardWrap(caretModel.getOffset())) {
- // Restore caret position.
- caretModel.moveToVisualPosition(visualCaretPosition);
- }
+ //TODO den implement
+ //if (myDocumentChangeManager.makeHardWrap(caretModel.getOffset())) {
+ // // Restore caret position.
+ // caretModel.moveToVisualPosition(visualCaretPosition);
+ //}
}
@Override
return myStorage.addSoftWrapChangeListener(listener);
}
+ @Override
+ public int getPriority() {
+ return EditorDocumentPriorities.SOFT_WRAP_MODEL;
+ }
+
@Override
public void beforeDocumentChange(DocumentEvent event) {
if (!isSoftWrappingEnabled()) {
listener.documentChanged(event);
}
}
+
+ @Override
+ public void onFoldRegionStateChange(@NotNull FoldRegion region) {
+ if (!isSoftWrappingEnabled()) {
+ return;
+ }
+ for (FoldingListener listener : myFoldListeners) {
+ listener.onFoldRegionStateChange(region);
+ }
+ }
+
+ @Override
+ public void onFoldProcessingEnd() {
+ if (!isSoftWrappingEnabled()) {
+ return;
+ }
+ for (FoldingListener listener : myFoldListeners) {
+ listener.onFoldProcessingEnd();
+ }
+ }
+
+ @Override
+ public void release() {
+ myDataMapper.release();
+ myApplianceManager.release();
+ myStorage.removeAll();
+ }
}
+++ /dev/null
-/*
- * Copyright 2000-2010 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.softwrap;
-
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.editor.EditorSettings;
-import com.intellij.openapi.editor.LanguageLineWrapPositionStrategy;
-import com.intellij.openapi.editor.LineWrapPositionStrategy;
-import com.intellij.openapi.editor.event.DocumentListener;
-import com.intellij.openapi.editor.ex.EditorEx;
-import com.intellij.openapi.editor.ex.util.EditorUtil;
-import com.intellij.openapi.editor.impl.EditorTextRepresentationHelper;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.codeStyle.CodeStyleSettings;
-import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
-import gnu.trove.TIntArrayList;
-import gnu.trove.TIntObjectHashMap;
-import org.jetbrains.annotations.NotNull;
-
-import java.awt.*;
-import java.nio.CharBuffer;
-
-/**
- * Default {@link SoftWrapApplianceManager} implementation that is built with the following design guide lines:
- * <pre>
- * <ul>
- * <li>
- * perform soft wrap processing per-logical line, i.e. every time current manager is asked to process
- * particular text range, it calculates logical lines that contain all target symbols, checks if they should
- * be soft-wrapped and registers corresponding soft wraps if necessary;
- * </li>
- * <li>
- * objects of this class remember processed logical lines and perform new processing for them only if visible
- * area width is changed;
- * </li>
- * <li>
- * {@link SoftWrapsStorage#removeAll() drops all registered soft wraps} if visible area width is changed;
- * </li>
- * </ul>
- * </pre>
- * <p/>
- * Not thread-safe.
- *
- * @author Denis Zhdanov
- * @since Jul 5, 2010 10:01:27 AM
- */
-public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager {
-
- /** Enumerates possible type of soft wrap indents to use. */
- enum IndentType {
- /** Don't apply special indent to soft-wrapped line at all. */
- NONE,
-
- /**
- * Indent soft wraps for the {@link EditorSettings#getCustomSoftWrapIndent() user-defined number of columns}
- * to the start of the previous visual line.
- */
- CUSTOM,
-
- /**
- * Tries to indents soft-wrapped line start to the location of the first non white-space symbols of the previous visual line.
- * <p/>
- * Falls back to {@link #NONE} if indentation to the previous visual line start is considered to be unappropriated.
- */
- AUTOMATIC
- }
-
- private static final int DEFAULT_INDENT_SIZE = 4;
-
- /**
- * Holds information about logical lines for which soft wrap is calculated as a set of
- * <code>(logical line number; temporary)</code> pairs.
- */
- private final TIntObjectHashMap<Boolean> myProcessedLogicalLines = new TIntObjectHashMap<Boolean>();
-
- private final DocumentListener myDocumentListener = new LineOrientedDocumentChangeAdapter() {
- @Override
- public void beforeDocumentChange(int startLine, int endLine, int symbolsDifference) {
- dropData(startLine, endLine);
- }
-
- @Override
- public void afterDocumentChange(int startLine, int endLine, int symbolsDifference) {
- dropData(startLine, endLine);
- }
-
- @Override
- public int getPriority() {
- return SoftWrapConstants.SOFT_WRAP_APPLIANCE_LISTENER_PRIORITY;
- }
-
- private void dropData(int startLine, int endLine) {
- Document document = myEditor.getDocument();
- for (int i = startLine; i <= endLine; i++) {
- myProcessedLogicalLines.remove(i);
-
- // Calculate approximate soft wraps positions using plain font.
- // Note: we don't update 'myProcessedLogicalLines' collection here, i.e. soft wraps will be recalculated precisely
- // during standard editor repainting iteration.
- if (i < document.getLineCount()) {
- processLogicalLine(document.getCharsSequence(), i, Font.PLAIN, getIndentToUse(), true);
- }
- }
- }
- };
-
- private final EditorTextRepresentationHelper myTextRepresentationHelper;
- private final SoftWrapsStorage myStorage;
- private final EditorEx myEditor;
- private final SoftWrapPainter myPainter;
-
- private boolean myCustomIndentUsedLastTime;
- private int myCustomIndentValueUsedLastTime;
- private int myVisibleAreaWidth;
-
- public DefaultSoftWrapApplianceManager(SoftWrapsStorage storage,
- EditorEx editor,
- SoftWrapPainter painter,
- EditorTextRepresentationHelper textRepresentationHelper)
- {
- myStorage = storage;
- myEditor = editor;
- myPainter = painter;
- myTextRepresentationHelper = textRepresentationHelper;
- }
-
- @SuppressWarnings({"AssignmentToForLoopParameter"})
- @Override
- public void registerSoftWrapIfNecessary(@NotNull CharSequence text, int start, int end, int x, int fontType, boolean temporary) {
- dropDataIfNecessary();
-
- if (myVisibleAreaWidth <= 0 || start >= end) {
- return;
- }
-
- IndentType indent = getIndentToUse();
- boolean useCustomIndent = indent == IndentType.CUSTOM;
- int currentCustomIndent = myEditor.getSettings().getCustomSoftWrapIndent();
- if (useCustomIndent ^ myCustomIndentUsedLastTime || (useCustomIndent && myCustomIndentValueUsedLastTime != currentCustomIndent)) {
- myProcessedLogicalLines.clear();
- }
- myCustomIndentUsedLastTime = useCustomIndent;
- myCustomIndentValueUsedLastTime = currentCustomIndent;
-
- Document document = myEditor.getDocument();
- int startLine = document.getLineNumber(start);
- int endLine = document.getLineNumber(end);
- for (int i = startLine; i <= endLine; i++) {
- if (!myProcessedLogicalLines.contains(i) || (!temporary && myProcessedLogicalLines.get(i))) {
- processLogicalLine(text, i, fontType, indent, temporary);
- myProcessedLogicalLines.put(i, temporary);
- }
- }
- }
-
- private IndentType getIndentToUse() {
- if (myEditor.getSettings().isUseCustomSoftWrapIndent()) {
- return IndentType.CUSTOM;
- }
- return !myEditor.isViewer() && !myEditor.getDocument().isWritable() ? IndentType.AUTOMATIC : IndentType.NONE;
- }
-
- public DocumentListener getDocumentListener() {
- return myDocumentListener;
- }
-
- private void dropDataIfNecessary() {
- int currentVisibleAreaWidth = myEditor.getScrollingModel().getVisibleArea().width;
- if (myVisibleAreaWidth == currentVisibleAreaWidth) {
- return;
- }
-
- // Drop information about processed lines then.
- myProcessedLogicalLines.clear();
- myStorage.removeAll();
- myVisibleAreaWidth = currentVisibleAreaWidth;
- }
-
- private void processLogicalLine(CharSequence text, int line, int fontType, IndentType indentType, boolean temporary) {
- Document document = myEditor.getDocument();
- int startOffset = document.getLineStartOffset(line);
- int endOffset = document.getLineEndOffset(line);
-
- // There is a possible case that this method is called for the approximate soft wraps positions calculation. E.g. the
- // user can insert a long string to the end of the document and we don't want to perform horizontal scrolling to its end.
- // Hence, we approximately define soft wraps for the inserted text assuming that there precise calculation will be performed
- // on regular editor repainting iteration. However, we need to drop all those temporary soft wraps registered for
- // the same line before.
- myStorage.removeInRange(startOffset, endOffset + 1/* add 1 to handle possible situation when soft wrap is registered at the line end */);
-
- if (indentType == IndentType.NONE) {
- TIntArrayList offsets = calculateSoftWrapOffsets(text, startOffset, endOffset, fontType, 0);
- registerSoftWraps(offsets, 0, temporary);
- return;
- }
-
- // Understand if it's worth to define indent for soft wrap(s) to create and perform their actual construction and registration.
- int prevLineIndentInColumns = 0;
-
- int firstNonSpaceSymbolIndex = startOffset;
- for (; firstNonSpaceSymbolIndex < endOffset; firstNonSpaceSymbolIndex++) {
- char c = text.charAt(firstNonSpaceSymbolIndex);
- if (c != ' ' && c != '\t') {
- break;
- }
- }
- if (firstNonSpaceSymbolIndex > startOffset) {
- prevLineIndentInColumns = myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, startOffset, firstNonSpaceSymbolIndex, 0);
- }
-
- int spaceWidth = EditorUtil.getSpaceWidth(fontType, myEditor);
- if (indentType == IndentType.CUSTOM) {
- int indentInColumns = myEditor.getSettings().getCustomSoftWrapIndent();
- TIntArrayList offsets = calculateSoftWrapOffsets(
- text, startOffset, endOffset, fontType, (indentInColumns + prevLineIndentInColumns) * spaceWidth
- );
- registerSoftWraps(offsets, indentInColumns + prevLineIndentInColumns, temporary);
- return;
- }
-
- int indentInColumns = getIndentSize();
- int indentInColumnsToUse = 0;
- TIntArrayList softWrapOffsetsToUse = null;
- for (; indentInColumns >= 0; indentInColumns--) {
- TIntArrayList offsets = calculateSoftWrapOffsets(
- text, startOffset, endOffset, fontType, (prevLineIndentInColumns + indentInColumns) * spaceWidth
- );
- if (softWrapOffsetsToUse == null) {
- softWrapOffsetsToUse = offsets;
- indentInColumnsToUse = indentInColumns;
- continue;
- }
- if (softWrapOffsetsToUse.size() > offsets.size()) {
- softWrapOffsetsToUse = offsets;
- indentInColumnsToUse = indentInColumns;
- }
- }
-
- if (indentInColumnsToUse <= 0) {
- processLogicalLine(text, line, fontType, IndentType.NONE, temporary);
- }
- else {
- registerSoftWraps(softWrapOffsetsToUse, indentInColumnsToUse + prevLineIndentInColumns, temporary);
- }
- }
-
- @SuppressWarnings({"AssignmentToForLoopParameter"})
- private TIntArrayList calculateSoftWrapOffsets(CharSequence text, int start, int end, int fontType, int reservedWidth) {
- TIntArrayList result = new TIntArrayList();
-
- // Find offsets where soft wraps should be applied for the logical line in case of no indent usage.
- int x = 0;
- int beforeSoftWrapDrawingWidth = myPainter.getMinDrawingWidth(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED);
- int prevSoftWrapOffset = start;
- CharBuffer buffer = CharBuffer.wrap(text);
- for (int i = start; i < end; i++) {
- int symbolWidth = myTextRepresentationHelper.textWidth(buffer, i, i + 1, fontType, x);
- if (x + symbolWidth + beforeSoftWrapDrawingWidth >= myVisibleAreaWidth) {
- int offset = calculateSoftWrapOffset(text, i, prevSoftWrapOffset, end);
- if (offset >= end || offset <= prevSoftWrapOffset) {
- // There is no way to insert soft wrap.
- return result;
- }
- result.add(offset);
- i = offset - 1; // Subtract one because of loop increment.
- prevSoftWrapOffset = offset;
- x = myPainter.getMinDrawingWidth(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED)
- + myPainter.getMinDrawingWidth(SoftWrapDrawingType.AFTER_SOFT_WRAP) + reservedWidth;
- continue;
- }
- x += symbolWidth;
- }
- return result;
- }
-
- private int getIndentSize() {
- Project project = myEditor.getProject();
- if (project == null) return DEFAULT_INDENT_SIZE;
- CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project);
- if (settings == null) return DEFAULT_INDENT_SIZE;
- VirtualFile file = myEditor.getVirtualFile();
- if (file == null) return DEFAULT_INDENT_SIZE;
- return settings.getIndentSize(file.getFileType());
- }
-
- private void registerSoftWraps(TIntArrayList offsets, int indentInColumns, boolean temporary) {
- for (int i = 0; i < offsets.size(); i++) {
- int offset = offsets.getQuick(i);
- myStorage.storeOrReplace(new TextChangeImpl("\n" + StringUtil.repeatSymbol(' ', indentInColumns), offset), !temporary);
- }
- }
-
- /**
- * Calculates offset to use for soft wrap appliance from the given <code>(min; max]</code> interval.
- *
- * @param text target text holder
- * @param preferred preferred position to use for soft wrapping. Implies that all symbols from <code>(preferred; max)</code>
- * will be represented beyond the visible area, i.e. current method should try to find wrapping point
- * at <code>(min; preferred]</code> interval;
- * @param min min offset to use (exclusive)
- * @param max max offset to use (inclusive)
- * @return wrapping offset to use (given <code>'max'</code> value should be returned if no more suitable point is found)
- */
- private int calculateSoftWrapOffset(CharSequence text, int preferred, int min, int max) {
- LineWrapPositionStrategy strategy = LanguageLineWrapPositionStrategy.INSTANCE.forEditor(myEditor);
- return strategy.calculateWrapPosition(text, min, max, preferred, true);
- }
-}
+++ /dev/null
-/*
- * Copyright 2000-2010 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.softwrap;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Defines a contract for the service that receives notifications about requests to represent particular text and
- * creates and registers new soft wraps for that if necessary.
- * <p/>
- * Implementations of this interface are not obliged to be thread-safe.
- *
- * @author Denis Zhdanov
- * @since Jul 5, 2010 9:46:12 AM
- */
-public interface SoftWrapApplianceManager {
-
- /**
- * Defines a callback that is invoked on request to draw target text fragment and that can register new soft wraps in order
- * to correctly represent it.
- * <p/>
- * Target text fragment to represent belongs to the given char sequence and lays at <code>[start; end)</code> interval.
- * <p/>
- * Please note that it's possible for soft wrap to occur inside <code>[start; end)</code> region - e.g. there is a possible
- * case that particular single token is too long and we want to split it.
- * <p/>
- * <b>Note:</b> it's assumed that this method is called only on editor repainting.
- *
- * @param text target text holder
- * @param start start offset of the token to process within the given char array (inclusive)
- * @param end end offset of the token to process within the given char array (exclusive)
- * @param x <code>'x'</code> coordinate within the given graphics buffer that will be used to start drawing the text
- * @param fontType font type used for the target text fragment representation
- * @param temporary defines type of the current call. <code>'Temporary'</code> means that soft wraps registered during
- * the processing should be recalculated on further invocations; they may be reused otherwise
- */
- void registerSoftWrapIfNecessary(@NotNull CharSequence text, int start, int end, int x, int fontType, boolean temporary);
-}
+++ /dev/null
-/*
- * Copyright 2000-2010 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.softwrap;
-
-import com.intellij.openapi.editor.ex.PrioritizedDocumentListener;
-
-/**
- * Common place to store soft wrap-related constants.
- *
- * @author Denis Zhdanov
- * @since Aug 23, 2010 6:16:23 PM
- */
-public class SoftWrapConstants {
-
- /** {@link PrioritizedDocumentListener#getPriority() document listener's priority} to use with {@link SoftWrapDocumentChangeManager} */
- public static final int DOCUMENT_CHANGE_LISTENER_PRIORITY = 5;
-
- /** {@link PrioritizedDocumentListener#getPriority() document listener's priority} to use with {@link SoftWrapApplianceManager} */
- public static final int SOFT_WRAP_APPLIANCE_LISTENER_PRIORITY = 4;
-
- private SoftWrapConstants() {
- }
-}
*/
package com.intellij.openapi.editor.impl.softwrap;
-import com.intellij.openapi.editor.*;
-import com.intellij.openapi.editor.ex.EditorEx;
-import com.intellij.openapi.editor.impl.EditorTextRepresentationHelper;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.util.text.CharArrayUtil;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.LogicalPosition;
+import com.intellij.openapi.editor.VisualPosition;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.awt.*;
-import java.util.List;
/**
- * Encapsulates logic of various mappings (e.g. {@code 'offset -> logical position'}) and adjustments (e.g. adjust soft wrap unaware
- * logical position for the target visual position).
+ * Every document that is exposed to end-user via IJ editor has a number of various dimensions ({@link LogicalPosition logical}
+ * and {@link VisualPosition visual} positions, {@link Document#getCharsSequence() text offset} etc. It's very important to be
+ * able to map one dimension to another and do that effectively.
+ * <p/>
+ * Current interface defines a contract for such a mapper.
*
* @author Denis Zhdanov
- * @since Jul 7, 2010 2:31:04 PM
+ * @since Aug 31, 2010 10:23:15 AM
*/
-public class SoftWrapDataMapper {
-
- private final EditorTextRepresentationHelper myTextRepresentationHelper;
- private final EditorEx myEditor;
- private final SoftWrapsStorage myStorage;
- //private final FontTypeProvider myFontTypeProvider;
-
- //public SoftWrapDataMapper(EditorEx editor,
- // SoftWrapsStorage storage,
- // EditorTextRepresentationHelper textRepresentationHelper)
- //{
- // this(editor, storage, textRepresentationHelper, new IterationStateFontTypeProvider(editor));
- //}
-
- public SoftWrapDataMapper(EditorEx editor,
- SoftWrapsStorage storage,
- EditorTextRepresentationHelper textRepresentationHelper/*,
- FontTypeProvider fontTypeProvider*/)
- {
- myEditor = editor;
- myStorage = storage;
- myTextRepresentationHelper = textRepresentationHelper;
- //myFontTypeProvider = fontTypeProvider;
- }
-
- @NotNull
- public LogicalPosition visualToLogical(@NotNull VisualPosition visual) {
- return toLogical(new VisualPositionBasedStrategy(visual));
- }
-
- @NotNull
- public LogicalPosition offsetToLogicalPosition(int offset) {
- OffsetBasedStrategy strategy = new OffsetBasedStrategy(myTextRepresentationHelper, myEditor.getDocument(), offset);
- return toLogical(strategy);
- }
-
- @NotNull
- private LogicalPosition toLogical(LogicalPositionCalculatorStrategy strategy) {
- LogicalPositionCalculator calculator = new LogicalPositionCalculator(strategy);
- return calculator.calculate();
- }
+public interface SoftWrapDataMapper {
+ /**
+ * Maps given visual position to corresponding logical.
+ *
+ * @param visual visual position to map
+ * @return logical position that corresponds to the given visual position
+ * @throws IllegalStateException if it's not possible to perform a mapping
+ */
@NotNull
- public VisualPosition adjustVisualPosition(@NotNull LogicalPosition logical, @NotNull VisualPosition visual) {
- if (logical.visualPositionAware) {
- // We don't need to recalculate logical position adjustments because given object already has them.
- return logical.toVisualPosition();
- }
-
- List<TextChangeImpl> softWraps = myStorage.getSoftWraps();
-
- // Check if there are registered soft wraps before the target logical position.
- int maxOffset = myEditor.logicalPositionToOffset(logical);
- int endIndex = myStorage.getSoftWrapIndex(maxOffset);
- if (endIndex < 0) {
- endIndex = -endIndex - 2; // We subtract '2' instead of '1' here in order to point to offset of the first soft wrap the
- // is located before the given logical position.
- }
-
- // Return eagerly if no soft wraps are registered before the target offset.
- if (endIndex < 0 || endIndex >= softWraps.size()) {
- return visual;
- }
-
- int lineDiff = 0;
- int column = -1;
-
- int targetLogicalLineStartOffset = myEditor.logicalPositionToOffset(new LogicalPosition(logical.line, 0));
- for (int i = endIndex; i >= 0; i--) {
- TextChange softWrap = softWraps.get(i);
- if (softWrap == null) {
- assert false;
- continue;
- }
-
- if (!isVisible(softWrap)) {
- continue;
- }
-
- CharSequence softWrapText = softWrap.getText();
- int softWrapLines = StringUtil.countNewLines(softWrapText);
-
- // Count lines introduced by the current soft wrap. We assume that the soft wrap is located before target offset,
- // hence, we're free to count all of its line feeds.
- lineDiff += softWrapLines;
-
- // Count soft wrap column offset only if it's located at the same line as the target offset.
- if (softWrapLines > 0 && softWrap.getStart() >= targetLogicalLineStartOffset) {
- for (int j = softWrapText.length() - 1; j >= 0; j--) {
- if (softWrapText.charAt(j) == '\n') {
- column = maxOffset - softWrap.getStart() - j + 1;
- break;
- }
- }
- }
- }
-
- int columnToUse = column >= 0 ? column : visual.column;
- return new VisualPosition(visual.line + lineDiff, columnToUse);
- }
-
- private boolean isVisible(TextChange softWrap) {
- FoldingModel foldingModel = myEditor.getFoldingModel();
- int start = softWrap.getStart();
-
- // There is a possible case that folding region starts just after soft wrap, i.e. soft wrap and folding region share the
- // same offset. However, soft wrap is shown, hence, we also check offset just before the target one.
- return !foldingModel.isOffsetCollapsed(start) || !foldingModel.isOffsetCollapsed(start - 1);
- }
-
- private static class Context implements Cloneable {
-
- public int logicalLine;
- public int logicalColumn;
- public int visualLine;
- public int visualColumn;
- public int offset;
- public int softWrapLinesBefore;
- public int softWrapLinesCurrent;
- public int softWrapColumnDiff;
- public int foldedLines;
- public int foldingColumnDiff;
- public int x;
-
- @NotNull
- public LogicalPosition build() {
- return new LogicalPosition(
- logicalLine, logicalColumn, softWrapLinesBefore, softWrapLinesCurrent, softWrapColumnDiff, foldedLines, foldingColumnDiff
- );
- }
-
- @Override
- protected Context clone() {
- Context result = new Context();
- result.logicalLine = logicalLine;
- result.logicalColumn = logicalColumn;
- result.visualLine = visualLine;
- result.visualColumn = visualColumn;
- result.offset = offset;
- result.softWrapLinesBefore = softWrapLinesBefore;
- result.softWrapLinesCurrent = softWrapLinesCurrent;
- result.softWrapColumnDiff = softWrapColumnDiff;
- result.foldedLines = foldedLines;
- result.foldingColumnDiff = foldingColumnDiff;
- result.x = x;
- return result;
- }
-
- private void onNewLine() {
- softWrapLinesBefore += softWrapLinesCurrent;
- softWrapLinesCurrent = 0;
- softWrapColumnDiff = 0;
- foldingColumnDiff = 0;
- x = 0;
- }
- }
-
- private class LogicalPositionCalculator {
-
- public final LogicalPositionCalculatorStrategy strategy;
-
- public Context context = new Context();
-
- LogicalPositionCalculator(LogicalPositionCalculatorStrategy strategy) {
- this.strategy = strategy;
- }
-
- @NotNull
- public LogicalPosition calculate() {
- FoldingProvider foldRegions = new FoldingProvider();
- SoftWrapsProvider softWraps = new SoftWrapsProvider();
-
- FoldRegion foldRegion = foldRegions.get();
- TextChange softWrap = softWraps.get();
-
- LogicalPosition result = null;
- while (true) {
- if (foldRegion == null && softWrap == null || strategy.exceeds(context)) {
- return strategy.build(context);
- }
-
- if (foldRegion != null && softWrap != null) {
- if (softWrap.getStart() <= foldRegion.getStartOffset()) {
- result = process(softWrap);
- softWrap = softWraps.get();
- }
- else {
- result = process(foldRegion);
- foldRegion = foldRegions.get();
- }
- }
- else {
- if (foldRegion != null) {
- result = process(foldRegion);
- foldRegion = foldRegions.get();
- }
- if (softWrap != null) {
- result = process(softWrap);
- softWrap = softWraps.get();
- }
- }
- if (result != null) {
- return result;
- }
- }
- }
-
- @Nullable
- private LogicalPosition process(@NotNull FoldRegion region) {
- int endDocumentOffset = myEditor.getDocument().getTextLength();
- if (region.getEndOffset() >= endDocumentOffset) {
- return advanceToOffset(endDocumentOffset).build();
- }
- if (region.getStartOffset() > context.offset) {
- Context newContext = advanceToOffset(region.getStartOffset());
- if (strategy.exceeds(newContext)) {
- return strategy.build(context);
- }
- context = newContext;
- }
-
- Document document = myEditor.getDocument();
- CharSequence text = document.getCharsSequence();
- int foldingStartLine = document.getLineNumber(region.getStartOffset());
-
- Context afterFolding = context.clone();
- afterFolding.logicalLine += document.getLineNumber(region.getEndOffset()) - foldingStartLine;
- int visualColumnInc = region.getPlaceholderText().length(); // Assuming that no tabulations are used at placeholder.
- afterFolding.visualColumn += visualColumnInc;
-
- int i = CharArrayUtil.shiftBackwardUntil(text, region.getEndOffset() - 1, "\n");
- // Process multi-line folding.
- if (i >= region.getStartOffset()) {
- afterFolding.x = myTextRepresentationHelper.textWidth(text, i + 1, region.getEndOffset(), Font.PLAIN, 0);
- afterFolding.logicalColumn = myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, i + 1, region.getEndOffset(), 0);
- afterFolding.softWrapLinesBefore += afterFolding.softWrapLinesCurrent;
- afterFolding.softWrapLinesCurrent = 0;
- afterFolding.softWrapColumnDiff = 0;
- afterFolding.foldedLines += document.getLineNumber(region.getEndOffset()) - foldingStartLine;
- afterFolding.foldingColumnDiff = afterFolding.visualColumn - afterFolding.logicalColumn;
- }
- // Process single-line folding
- else {
- int width = myTextRepresentationHelper.textWidth(text, region.getStartOffset(), region.getEndOffset(), Font.PLAIN, context.x);
- int logicalColumnInc = myTextRepresentationHelper.toVisualColumnSymbolsNumber(
- text, region.getStartOffset(), region.getEndOffset(), context.x
- );
- afterFolding.logicalColumn += logicalColumnInc;
- afterFolding.x += width;
- afterFolding.foldingColumnDiff += visualColumnInc - logicalColumnInc;
- }
- afterFolding.offset = region.getEndOffset();
-
- if (!strategy.exceeds(afterFolding)) {
- context = afterFolding;
- return null;
- }
-
- return strategy.build(context, region);
- }
-
- @Nullable
- private LogicalPosition process(@NotNull TextChange softWrap) {
- int endDocumentOffset = myEditor.getDocument().getTextLength();
- if (softWrap.getStart() >= endDocumentOffset) {
- return strategy.build(context);
- }
- Context newContext = advanceToOffset(softWrap.getStart());
- if (strategy.exceeds(newContext)) {
- return strategy.build(context);
- }
- Document document = myEditor.getDocument();
- int lastUsedLogicalLine = document.getLineNumber(context.offset);
- context = newContext;
-
- // Create context that points to the soft wrap end visual position.
- Context afterSoftWrap = context.clone();
- int lineFeeds = StringUtil.countNewLines(softWrap.getText());
- afterSoftWrap.visualLine += lineFeeds;
- afterSoftWrap.visualColumn = myEditor.getSoftWrapModel().getSoftWrapIndentWidthInColumns(softWrap);
- afterSoftWrap.x = myEditor.getSoftWrapModel().getSoftWrapIndentWidthInPixels(softWrap);
- if (lastUsedLogicalLine == context.logicalLine) {
- afterSoftWrap.softWrapLinesCurrent += lineFeeds;
- }
- else {
- afterSoftWrap.softWrapLinesBefore += context.softWrapLinesCurrent;
- afterSoftWrap.softWrapLinesCurrent = lineFeeds;
- }
- afterSoftWrap.softWrapColumnDiff = afterSoftWrap.visualColumn - afterSoftWrap.logicalColumn;
- afterSoftWrap.foldingColumnDiff = 0;
-
- if (!strategy.exceeds(afterSoftWrap)) {
- context = afterSoftWrap;
- return null;
- }
- return strategy.build(context, softWrap);
- }
-
- private Context advanceToOffset(int newOffset) {
- Context result = context.clone();
- if (result.offset == newOffset) {
- return result;
- }
-
- Document document = myEditor.getDocument();
- CharSequence text = document.getCharsSequence();
- int lastUsedLogicalLine = document.getLineNumber(context.offset);
- int currentLogicalLine = document.getLineNumber(newOffset);
-
- // Update state to the offset that corresponds to the same logical line that was used last time.
- if (currentLogicalLine == lastUsedLogicalLine) {
- int columnDiff = myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, result.offset, newOffset, result.x);
- result.logicalColumn += columnDiff;
- result.visualColumn += columnDiff;
- if (strategy.recalculateX(result)) {
- result.x += myTextRepresentationHelper.textWidth(text, result.offset, newOffset, Font.PLAIN, result.x);
- }
- }
- // Update state to offset that doesn't correspond to the same logical line that was used last time.
- else {
- int lineDiff = currentLogicalLine - lastUsedLogicalLine;
- result.logicalLine += lineDiff;
- result.visualLine += lineDiff;
- int startLineOffset = document.getLineStartOffset(currentLogicalLine);
- result.visualColumn = myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, startLineOffset, newOffset, 0);
- result.logicalColumn = result.visualColumn;
- result.onNewLine();
- if (strategy.recalculateX(result)) {
- result.x = myTextRepresentationHelper.textWidth(text, startLineOffset, newOffset, Font.PLAIN, 0);
- }
- }
- result.offset = newOffset;
- return result;
- }
- }
+ LogicalPosition visualToLogical(@NotNull VisualPosition visual) throws IllegalStateException;
/**
- * Strategy interface for performing logical position calculation.
+ * Maps given offset to corresponding logical position.
+ *
+ * @param offset offset to map
+ * @return logical position that corresponds to the given offset
+ * @throws IllegalStateException if it's not possible to perform a mapping
*/
- private interface LogicalPositionCalculatorStrategy {
- boolean exceeds(Context context);
-
- /**
- * Profiling shows that visual symbol width calculation (necessary for <code>'x'</code> coordinate update) is quite expensive.
- * However, the whole logical position calculation algorithm contains many iterations and there is a big chance that many of them
- * don't require <code>'x'</code> recalculation (e.g. we may map visual position to logical and see that current context visual
- * line is less than the target, hence, we understand that 'x' coordinate will be reset to zero).
- * <p/>
- * This method allows to answer if we need to perform <code>'x'</code> recalculation for the given context (assuming that all
- * its another parameters are up-to-date).
- *
- * @param context
- * @return
- */
- boolean recalculateX(Context context);
- @NotNull LogicalPosition build(Context context);
- @NotNull LogicalPosition build(Context context, FoldRegion region);
- @NotNull LogicalPosition build(Context context, TextChange softWrap);
- }
-
- private static class VisualPositionBasedStrategy implements LogicalPositionCalculatorStrategy {
-
- private final VisualPosition myTargetVisual;
-
- VisualPositionBasedStrategy(VisualPosition visual) {
- myTargetVisual = visual;
- }
-
- @Override
- public boolean exceeds(Context context) {
- return context.visualLine > myTargetVisual.line
- || (context.visualLine == myTargetVisual.line && context.visualColumn > myTargetVisual.column);
- }
-
- @Override
- public boolean recalculateX(Context context) {
- return context.visualLine == myTargetVisual.line;
- }
-
- @NotNull
- @Override
- public LogicalPosition build(Context context) {
- if (context.visualLine == myTargetVisual.line) {
- context.logicalColumn += myTargetVisual.column - context.visualColumn;
- return context.build();
- }
- context.logicalLine += myTargetVisual.line - context.visualLine;
- context.logicalColumn = myTargetVisual.column;
- context.onNewLine();
- return context.build();
- }
-
- @NotNull
- @Override
- public LogicalPosition build(Context context, FoldRegion region) {
- // We just point to the logical position of folding region start if visual position points to collapsed fold region placeholder.
- return context.build();
- }
-
- @NotNull
- @Override
- public LogicalPosition build(Context context, TextChange softWrap) {
- if (myTargetVisual.line == context.visualLine) {
- context.softWrapColumnDiff = myTargetVisual.column - context.logicalColumn - context.foldingColumnDiff;
- }
- else {
- context.foldingColumnDiff = 0;
- context.softWrapLinesCurrent += myTargetVisual.line - context.visualLine;
- context.softWrapColumnDiff = myTargetVisual.column - context.logicalColumn;
- }
- return context.build();
- }
- }
-
- private static class OffsetBasedStrategy implements LogicalPositionCalculatorStrategy {
-
- private final EditorTextRepresentationHelper myRepresentationHelper;
- private final Document myDocument;
-
- private final int myOffset;
-
- OffsetBasedStrategy(EditorTextRepresentationHelper representationHelper, Document document, int offset) {
- myRepresentationHelper = representationHelper;
- myDocument = document;
- myOffset = offset;
- }
-
- @Override
- public boolean exceeds(Context context) {
- return context.offset > myOffset;
- }
-
- @Override
- public boolean recalculateX(Context context) {
- return true;
- }
-
- @NotNull
- @Override
- public LogicalPosition build(Context context) {
- int targetLogicalLine = myDocument.getLineNumber(myOffset);
- if (targetLogicalLine == context.logicalLine) {
- context.logicalColumn
- += myRepresentationHelper.toVisualColumnSymbolsNumber(myDocument.getCharsSequence(), context.offset, myOffset, context.x);
- return context.build();
- }
- context.logicalLine = targetLogicalLine;
- int i = CharArrayUtil.shiftBackwardUntil(myDocument.getCharsSequence(), myOffset - 1, "\n");
- if (i >= context.offset) {
- context.logicalColumn = myRepresentationHelper.toVisualColumnSymbolsNumber(myDocument.getCharsSequence(), i + 1, myOffset, 0);
- }
- else {
- context.logicalColumn
- = myRepresentationHelper.toVisualColumnSymbolsNumber(myDocument.getCharsSequence(), context.offset, myOffset, context.x);
- }
- context.onNewLine();
- return context.build();
- }
-
- @NotNull
- @Override
- public LogicalPosition build(Context context, FoldRegion region) {
- // We want to return logical position that corresponds to the visual start of the given folding region.
- int startLine = myDocument.getLineNumber(region.getStartOffset());
- int endLine = myDocument.getLineNumber(myOffset);
- int lineFeeds = endLine - startLine;
-
- if (lineFeeds > 0) {
- context.logicalLine += lineFeeds;
- context.foldedLines += lineFeeds;
- context.onNewLine();
- int i = CharArrayUtil.shiftBackwardUntil(myDocument.getCharsSequence(), myOffset - 1, "\n");
- context.logicalColumn = myRepresentationHelper.toVisualColumnSymbolsNumber(myDocument.getCharsSequence(), i + 1, myOffset, 0);
- context.foldingColumnDiff = context.visualColumn - context.logicalColumn;
- }
- else {
- int logicalColumns
- = myRepresentationHelper.toVisualColumnSymbolsNumber(myDocument.getCharsSequence(), region.getStartOffset(), myOffset, context.x);
- context.logicalColumn += logicalColumns;
- context.foldingColumnDiff -= logicalColumns;
- }
- return context.build();
- }
-
- @NotNull
- @Override
- public LogicalPosition build(Context context, TextChange softWrap) {
- assert false; // Don't expect soft wrap do affect offset-based mapping request.
- return new LogicalPosition(0, 0);
- }
- }
+ @NotNull
+ LogicalPosition offsetToLogicalPosition(int offset) throws IllegalStateException;
/**
- * Strategy interface for providing font type to use during working with editor text.
- * <p/>
- * It's primary purpose is to relief unit testing.
+ * Maps given logical position to corresponding visual position.
+ *
+ * @param logical logical position to map
+ * @param softWrapUnawareVisual soft wrap-unaware visual position that corresponds to the given logical position
+ * @return visual position that corresponds to the given logical position
+ * @throws IllegalStateException if it's not possible to perform a mapping
*/
- //interface FontTypeProvider {
- // void init(int start);
- // int getFontType(int offset);
- // void cleanup();
- //}
-
- //private static class IterationStateFontTypeProvider implements FontTypeProvider {
- //
- // private final EditorEx myEditor;
- //
- // private IterationState myState;
- // private int myFontType;
- //
- // private IterationStateFontTypeProvider(EditorEx editor) {
- // myEditor = editor;
- // }
- //
- // @Override
- // public void init(int start) {
- // myState = new IterationState(myEditor, start, false);
- // myFontType = myState.getMergedAttributes().getFontType();
- // }
- //
- // @Override
- // public int getFontType(int offset) {
- // if (offset >= myState.getEndOffset()) {
- // myState.advance();
- // myFontType = myState.getMergedAttributes().getFontType();
- // }
- // return myFontType;
- // }
- //
- // @Override
- // public void cleanup() {
- // myState = null;
- // }
- //}
-
- private class SoftWrapsProvider {
-
- private final List<? extends TextChange> mySoftWraps;
- private int myIndex;
-
- SoftWrapsProvider() {
- mySoftWraps = myStorage.getSoftWraps();
- }
-
- @Nullable
- public TextChange get() {
- if (myIndex < 0) {
- return null;
- }
- while (myIndex < mySoftWraps.size()) {
- TextChange result = mySoftWraps.get(myIndex++);
- if (isVisible(result)) {
- return result;
- }
- }
- return null;
- }
- }
-
- private class FoldingProvider {
-
- private final FoldRegion[] myFoldRegions;
- private int myIndex;
-
- FoldingProvider() {
- myFoldRegions = myEditor.getFoldingModel().fetchTopLevel();
- }
-
- @Nullable
- public FoldRegion get() {
- if (myFoldRegions == null || myIndex < 0) {
- return null;
- }
- while (myIndex < myFoldRegions.length) {
- FoldRegion result = myFoldRegions[myIndex++];
- if (!result.isExpanded()) {
- return result;
- }
- }
- return null;
- }
- }
+ VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logical, @NotNull VisualPosition softWrapUnawareVisual)
+ throws IllegalStateException;
}
+++ /dev/null
-/*
- * Copyright 2000-2010 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.softwrap;
-
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.event.DocumentEvent;
-import com.intellij.openapi.editor.event.DocumentListener;
-import com.intellij.openapi.editor.ex.PrioritizedDocumentListener;
-import gnu.trove.TIntHashSet;
-import gnu.trove.TIntProcedure;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-
-/**
- * Encapsulates the logic of performing necessary changes on soft wraps during target document change.
- *
- * @author Denis Zhdanov
- * @since Jul 7, 2010 2:28:10 PM
- */
-public class SoftWrapDocumentChangeManager implements PrioritizedDocumentListener {
-
- private final TIntHashSet mySoftWrapsToRemoveIndices = new TIntHashSet();
-
- private final SoftWrapsStorage myStorage;
- private final Editor myEditor;
-
- public SoftWrapDocumentChangeManager(@NotNull Editor editor, @NotNull SoftWrapsStorage storage) {
- myStorage = storage;
- myEditor = editor;
- }
-
- /**
- * Performs {@code 'soft wrap' -> 'hard wrap'} conversion for soft wrap at the given offset if any.
- *
- * @param offset offset that may point to soft wrap to make hard wrap
- * @return <code>true</code> if given offset points to soft wrap that was made hard wrap; <code>false</code> otherwise
- */
- public boolean makeHardWrap(int offset) {
- TextChangeImpl softWrap = myStorage.getSoftWrap(offset);
- if (softWrap == null) {
- return false;
- }
-
- myStorage.removeByIndex(myStorage.getSoftWrapIndex(offset));
- myEditor.getDocument().replaceString(softWrap.getStart(), softWrap.getEnd(), softWrap.getText());
- return true;
- }
-
- /**
- * Flushes all changes to be applied to {@link SoftWrapsStorage registered soft wraps}.
- * <p/>
- * I.e. the main idea is that current manager listens for document changes but defers soft wraps modification
- * until this method is called.
- */
- public void syncSoftWraps() {
- mySoftWrapsToRemoveIndices.forEach(new TIntProcedure() {
- @Override
- public boolean execute(int value) {
- myStorage.removeByIndex(value);
- return true;
- }
- });
- mySoftWrapsToRemoveIndices.clear();
- }
-
- @Override
- public void beforeDocumentChange(DocumentEvent event) {
- // Mark soft wraps to be removed.
- Document document = myEditor.getDocument();
- int startLine = document.getLineNumber(event.getOffset());
- int startOffset = document.getLineStartOffset(startLine);
- int endLine = document.getLineNumber(event.getOffset() + event.getOldLength());
- int endOffset = document.getLineEndOffset(endLine);
- markSoftWrapsForDeletion(startOffset, endOffset);
- }
-
- @Override
- public void documentChanged(DocumentEvent event) {
- // Update offsets for soft wraps that remain after the changed line(s).
- applyDocumentChangeDiff(event);
- }
-
- @Override
- public int getPriority() {
- return SoftWrapConstants.DOCUMENT_CHANGE_LISTENER_PRIORITY;
- }
-
- private void markSoftWrapsForDeletion(int startOffset, int endOffset) {
- List<TextChangeImpl> softWraps = myStorage.getSoftWraps();
- int index = myStorage.getSoftWrapIndex(startOffset);
- if (index < 0) {
- index = -index - 1;
- }
- for (int i = index; i < softWraps.size(); i++) {
- TextChangeImpl softWrap = softWraps.get(i);
- if (softWrap.getStart() >= endOffset) {
- break;
- }
- mySoftWrapsToRemoveIndices.add(i);
- }
- }
-
- private void applyDocumentChangeDiff(DocumentEvent event) {
- List<TextChangeImpl> softWraps = myStorage.getSoftWraps();
-
- // We use 'offset + 1' here because soft wrap is represented before the document symbol at the same offset, hence, document
- // modification at particular offset doesn't affect soft wrap registered for the same offset.
- int index = myStorage.getSoftWrapIndex(event.getOffset() + 1);
- if (index < 0) {
- index = -index - 1;
- }
-
- int diff = event.getNewLength() - event.getOldLength();
- for (int i = index; i < softWraps.size(); i++) {
- TextChangeImpl softWrap = softWraps.get(i);
- if (mySoftWrapsToRemoveIndices.contains(i)) {
- continue;
- }
-
- // Process only soft wraps not affected by the document change. E.g. there is a possible case that whole document text
- // is replaced, hence, all registered soft wraps are affected by that and no offset advance should be performed.
- if (softWrap.getStart() >= event.getOffset() + event.getOldLength()) {
- softWrap.advance(diff);
- }
- }
- }
-}
*/
package com.intellij.openapi.editor.impl.softwrap;
-import com.intellij.openapi.editor.CaretModel;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.SoftWrapModel;
-import com.intellij.openapi.editor.TextChange;
+import com.intellij.openapi.editor.*;
/**
* Holds utility methods for soft wraps-related processing.
CaretModel caretModel = editor.getCaretModel();
SoftWrapModel softWrapModel = editor.getSoftWrapModel();
int offset = caretModel.getOffset();
- TextChange softWrap = softWrapModel.getSoftWrap(offset);
+ SoftWrap softWrap = softWrapModel.getSoftWrap(offset);
if (softWrap == null) {
return false;
}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.softwrap;
+
+import com.intellij.openapi.editor.SoftWrap;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * {@link SoftWrap} implementation that is built around {@link TextChangeImpl}.
+ *
+ * @author Denis Zhdanov
+ * @since Sep 1, 2010 2:39:06 PM
+ */
+public class SoftWrapImpl implements SoftWrap {
+
+ private final TextChangeImpl myChange;
+ private final int myIndentInColumns;
+ private final int myIndentInPixels;
+
+ public SoftWrapImpl(@NotNull TextChangeImpl change, int indentInColumns, int indentInPixels) {
+ myChange = change;
+ myIndentInColumns = indentInColumns;
+ myIndentInPixels = indentInPixels;
+ }
+
+ @Override
+ public int getStart() {
+ return myChange.getStart();
+ }
+
+ @Override
+ public int getEnd() {
+ return myChange.getEnd();
+ }
+
+ @NotNull
+ @Override
+ public CharSequence getText() {
+ return myChange.getText();
+ }
+
+ @NotNull
+ @Override
+ public char[] getChars() {
+ return myChange.getChars();
+ }
+
+ @Override
+ public int getIndentInColumns() {
+ return myIndentInColumns;
+ }
+
+ @Override
+ public int getIndentInPixels() {
+ return myIndentInPixels;
+ }
+
+ public TextChangeImpl getChange() {
+ return myChange;
+ }
+
+ public void advance(int diff) {
+ myChange.advance(diff);
+ }
+
+ @Override
+ public String toString() {
+ return myChange.toString();
+ }
+}
*/
package com.intellij.openapi.editor.impl.softwrap;
+import com.intellij.openapi.editor.SoftWrap;
import com.intellij.openapi.editor.ex.SoftWrapChangeListener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.*;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Holds registered soft wraps and provides monitoring and management facilities for them.
*/
public class SoftWrapsStorage {
- private final List<TextChangeImpl> myWraps = new ArrayList<TextChangeImpl>();
- private final List<TextChangeImpl> myWrapsView = Collections.unmodifiableList(myWraps);
- private final Set<SoftWrapChangeListener> myListeners = new CopyOnWriteArraySet<SoftWrapChangeListener>();
+ private final List<SoftWrapImpl> myWraps = new ArrayList<SoftWrapImpl>();
+ private final List<SoftWrapImpl> myWrapsView = Collections.unmodifiableList(myWraps);
+ private final List<SoftWrapChangeListener> myListeners = new ArrayList<SoftWrapChangeListener>();
/**
* @return <code>true</code> if there is at least one soft wrap registered at the current storage; <code>false</code> otherwise
}
@Nullable
- public TextChangeImpl getSoftWrap(int offset) {
+ public SoftWrap getSoftWrap(int offset) {
int i = getSoftWrapIndex(offset);
return i >= 0 ? myWraps.get(i) : null;
}
* @return view for registered soft wraps sorted by offset in ascending order if any; empty collection otherwise
*/
@NotNull
- public List<TextChangeImpl> getSoftWraps() {
+ public List<SoftWrapImpl> getSoftWraps() {
return myWrapsView;
}
// is a bottleneck. The most probable reason is a big number of interface calls.
while (start <= end) {
int i = (start + end) >>> 1;
- TextChangeImpl softWrap = myWraps.get(i);
+ SoftWrap softWrap = myWraps.get(i);
int softWrapOffset = softWrap.getStart();
if (softWrapOffset > offset) {
end = i - 1;
* @param notifyListeners flag that indicates if registered listeners should be notified about soft wrap registration
* @return previous soft wrap object stored for the same offset if any; <code>null</code> otherwise
*/
+ @SuppressWarnings({"ForLoopReplaceableByForEach"})
@Nullable
- public TextChangeImpl storeOrReplace(TextChangeImpl softWrap, boolean notifyListeners) {
+ public SoftWrap storeOrReplace(SoftWrapImpl softWrap, boolean notifyListeners) {
int i = getSoftWrapIndex(softWrap.getStart());
if (i >= 0) {
return myWraps.set(i, softWrap);
i = -i - 1;
myWraps.add(i, softWrap);
if (notifyListeners) {
- for (SoftWrapChangeListener listener : myListeners) {
- listener.softWrapAdded(softWrap);
+ // Use explicit loop as profiling shows that iterator-based processing is quite slow.
+ for (int j = 0; j < myListeners.size(); j++) {
+ myListeners.get(j).softWrapAdded(softWrap);
}
}
return null;
* @return removed soft wrap if the one was found for the given index; <code>null</code> otherwise
*/
@Nullable
- public TextChangeImpl removeByIndex(int index) {
+ public SoftWrap removeByIndex(int index) {
if (index < 0 || index >= myWraps.size()) {
return null;
}
- TextChangeImpl removed = myWraps.remove(index);
+ SoftWrap removed = myWraps.remove(index);
notifyListenersAboutRemoval();
return removed;
}
int endIndex = startIndex;
for (; endIndex < myWraps.size(); endIndex++) {
- TextChangeImpl softWrap = myWraps.get(endIndex);
+ SoftWrap softWrap = myWraps.get(endIndex);
if (softWrap.getStart() >= endOffset) {
break;
}
}
-
+
if (endIndex > startIndex) {
myWraps.subList(startIndex, endIndex).clear();
notifyListenersAboutRemoval();
private final Map<SoftWrapDrawingType, char[]> mySymbols = new EnumMap<SoftWrapDrawingType, char[]>(SoftWrapDrawingType.class);
private final Map<SoftWrapDrawingType, FontInfo> myFonts = new EnumMap<SoftWrapDrawingType, FontInfo>(SoftWrapDrawingType.class);
- private final Map<SoftWrapDrawingType, Integer> myWidths = new EnumMap<SoftWrapDrawingType, Integer>(SoftWrapDrawingType.class);
+
+ /** Use array here because profiling indicates that using EnumMap here gives significant performance degradation. */
+ private final int[] myWidths = new int[SoftWrapDrawingType.values().length];
private final Map<SoftWrapDrawingType, Integer> myVGaps = new EnumMap<SoftWrapDrawingType, Integer>(SoftWrapDrawingType.class);
private final TextDrawingCallback myDrawingCallback;
@Override
public int getMinDrawingWidth(@NotNull SoftWrapDrawingType drawingType) {
- return myWidths.get(drawingType);
+ return myWidths[drawingType.ordinal()];
}
@Override
mySymbols.put(entry.getKey(), buffer);
myFonts.put(entry.getKey(), fontInfo);
FontMetrics metrics = component.getFontMetrics(fontInfo.getFont());
- myWidths.put(entry.getKey(), metrics.charWidth(buffer[0]));
+ myWidths[entry.getKey().ordinal()] = metrics.charWidth(buffer[0]);
int vGap = metrics.getDescent();
myVGaps.put(entry.getKey(), vGap);
}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.softwrap.mapping;
+
+import com.intellij.openapi.editor.impl.softwrap.mapping.DataProvider;
+import com.intellij.openapi.util.Pair;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Abstract super class for {@link DataProvider} implementations that use the same key all the time.
+ *
+ * @author Denis Zhdanov
+ * @since Aug 31, 2010 12:19:32 PM
+ */
+public abstract class AbstractDataProvider<K extends Comparable<? super K>, V> implements DataProvider<K, V> {
+
+ private final K myKey;
+
+ public AbstractDataProvider(K key) {
+ myKey = key;
+ }
+
+ @Nullable
+ @Override
+ public Pair<K, V> getData() {
+ V data = doGetData();
+ return data == null ? null : new Pair<K, V>(myKey, data);
+ }
+
+ /**
+ * @return current data
+ */
+ @Nullable
+ protected abstract V doGetData();
+}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.softwrap.mapping;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * Generic {@link DataProvider} implementation that assumes that target data is available as list.
+ * <p/>
+ * Not thread-safe.
+ *
+ * @author Denis Zhdanov
+ * @since Aug 31, 2010 12:56:58 PM
+ */
+public abstract class AbstractListBasedDataProvider<K extends Comparable<? super K>, V> extends AbstractDataProvider<K, V> {
+
+ private final List<V> myData;
+ private int myIndex;
+
+ public AbstractListBasedDataProvider(@NotNull K key, @NotNull List<V> data) {
+ super(key);
+
+ // We use given collection instead of copying it to private list because profiling indicates that as an expensive operation
+ // when performed frequently.
+ myData = data;
+ }
+
+ @Override
+ protected V doGetData() {
+ if (myIndex < myData.size()) {
+ return myData.get(myIndex);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean next() {
+ return ++myIndex < myData.size();
+ }
+
+ @Override
+ public void advance(int sortingKey) {
+
+ // We inline binary search here because profiling indicates that as a performance boost.
+ int start = myIndex;
+ int end = myData.size() - 1;
+
+ // We inline binary search here because profiling indicates that it becomes bottleneck to use Collections.binarySearch().
+ while (start <= end) {
+ int i = (end + start) >>> 1;
+ V data = myData.get(i);
+ if (getSortingKey(data) < sortingKey) {
+ start = i + 1;
+ continue;
+ }
+ if (getSortingKey(data) > sortingKey) {
+ end = i - 1;
+ continue;
+ }
+
+ myIndex = i;
+ return;
+ }
+ myIndex = start;
+ }
+
+ @Override
+ public int getSortingKey() {
+ if (myIndex < myData.size()) {
+ return getSortingKey(myData.get(myIndex));
+ }
+ return 0;
+ }
+
+ /**
+ * Sub-classes are expected to be able to derive sorting key for the every data instance.
+ *
+ * @param data target data which sorting key should be returned
+ * @return sorting key for the given data
+ */
+ protected abstract int getSortingKey(@NotNull V data);
+}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.softwrap.mapping;
+
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.FoldRegion;
+import com.intellij.openapi.editor.impl.EditorTextRepresentationHelper;
+import com.intellij.openapi.editor.impl.softwrap.SoftWrapsStorage;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.Pair;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.*;
+
+/**
+ * Abstract super class for mapping strategies that encapsulates shared logic of advancing the context to the points
+ * of specific types. I.e. it's main idea is to ask sub-class if target dimension lays inside particular region
+ * (e.g. soft wrap, fold region etc) and use common algorithm for advancing context to the region's end in the case
+ * of negative answer.
+ * <p/>
+ * Not thread-safe.
+ *
+ * @param <T> resulting document dimension type
+ */
+abstract class AbstractMappingStrategy<T> implements MappingStrategy<T> {
+
+ protected static final CacheEntry SEARCH_KEY = new CacheEntry(0, null, null, null);
+
+ protected final CacheEntry myCacheEntry;
+ protected final Editor myEditor;
+ protected final EditorTextRepresentationHelper myRepresentationHelper;
+ protected final SoftWrapsStorage myStorage;
+ private final T myEagerMatch;
+
+ AbstractMappingStrategy(Computable<Pair<CacheEntry, T>> cacheEntryProvider, Editor editor, SoftWrapsStorage storage,
+ EditorTextRepresentationHelper representationHelper) throws IllegalStateException
+ {
+ myEditor = editor;
+ myStorage = storage;
+ myRepresentationHelper = representationHelper;
+
+ Pair<CacheEntry, T> pair = cacheEntryProvider.compute();
+ myCacheEntry = pair.first;
+ myEagerMatch = pair.second;
+ }
+
+ @Nullable
+ @Override
+ public T eagerMatch() {
+ return myEagerMatch;
+ }
+
+ @NotNull
+ @Override
+ public ProcessingContext buildInitialContext() {
+ return myCacheEntry.buildStartLineContext();
+ }
+
+ protected FoldingData getFoldRegionData(FoldRegion foldRegion) {
+ return myCacheEntry.getFoldingData().get(foldRegion.getStartOffset());
+ }
+
+ @Override
+ public T advance(ProcessingContext context, int offset) {
+ T result = buildIfExceeds(context, offset);
+ if (result != null) {
+ return result;
+ }
+
+ // Update context state and continue processing.
+ context.logicalLine = myEditor.getDocument().getLineNumber(offset);
+ int diff = offset - context.offset;
+ context.visualColumn += diff;
+ context.logicalColumn += diff;
+ context.offset = offset;
+ return null;
+ }
+
+ @Nullable
+ protected abstract T buildIfExceeds(ProcessingContext context, int offset);
+
+ @Override
+ public T processFoldRegion(ProcessingContext context, FoldRegion foldRegion) {
+ T result = buildIfExceeds(context, foldRegion);
+ if (result != null) {
+ return result;
+ }
+
+ Document document = myEditor.getDocument();
+ int endOffsetLogicalLine = document.getLineNumber(foldRegion.getEndOffset());
+ int collapsedSymbolsWidthInColumns;
+ if (context.logicalLine == endOffsetLogicalLine) {
+ // Single-line fold region.
+ FoldingData foldingData = getFoldRegionData(foldRegion);
+ if (foldingData == null) {
+ assert false;
+ collapsedSymbolsWidthInColumns = context.visualColumn * myRepresentationHelper.textWidth(" ", 0, 1, Font.PLAIN, 0);
+ }
+ else {
+ collapsedSymbolsWidthInColumns = foldingData.getCollapsedSymbolsWidthInColumns();
+ }
+ }
+ else {
+ // Multi-line fold region.
+ collapsedSymbolsWidthInColumns = myRepresentationHelper.toVisualColumnSymbolsNumber(
+ document.getCharsSequence(), foldRegion.getStartOffset(), foldRegion.getEndOffset(), 0
+ );
+ context.softWrapColumnDiff = 0;
+ context.softWrapLinesBefore += context.softWrapLinesCurrent;
+ context.softWrapLinesCurrent = 0;
+ }
+
+ context.advance(foldRegion, collapsedSymbolsWidthInColumns);
+ return null;
+ }
+
+ @Nullable
+ protected abstract T buildIfExceeds(@NotNull ProcessingContext context, @NotNull FoldRegion foldRegion);
+
+ @Override
+ public T processTabulation(ProcessingContext context, TabData tabData) {
+ T result = buildIfExceeds(context, tabData);
+ if (result != null) {
+ return result;
+ }
+
+ context.visualColumn += tabData.widthInColumns;
+ context.logicalColumn += tabData.widthInColumns;
+ context.offset++;
+ return null;
+ }
+
+ @Nullable
+ protected abstract T buildIfExceeds(ProcessingContext context, TabData tabData);
+}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.softwrap.mapping;
+
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.FoldRegion;
+import com.intellij.openapi.editor.impl.EditorTextRepresentationHelper;
+import gnu.trove.TIntObjectHashMap;
+import gnu.trove.TIntObjectProcedure;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Encapsulates information to cache for the single visual line.
+ */
+@SuppressWarnings("unchecked")
+class CacheEntry implements Comparable<CacheEntry>, Cloneable {
+
+ private static final TIntObjectHashMap<FoldingData> DUMMY = new TIntObjectHashMap<FoldingData>();
+
+ public int visualLine;
+
+ public int startLogicalLine;
+ public int startLogicalColumn;
+ public int startOffset;
+ public int startSoftWrapLinesBefore;
+ public int startSoftWrapLinesCurrent;
+ public int startSoftWrapColumnDiff;
+ public int startFoldedLines;
+ public int startFoldingColumnDiff;
+
+ public int endOffset;
+ public int endLogicalLine;
+ public int endLogicalColumn;
+ public int endVisualColumn;
+ public int endSoftWrapLinesBefore;
+ public int endSoftWrapLinesCurrent;
+ public int endSoftWrapColumnDiff;
+ public int endFoldedLines;
+ public int endFoldingColumnDiff;
+
+ public boolean locked;
+
+ private final List<CacheEntry> myCache;
+ private final Editor myEditor;
+ private final EditorTextRepresentationHelper myRepresentationHelper;
+
+ /** Holds positions for the tabulation symbols on a target visual line sorted by offset in ascending order. */
+ private List<TabData> myTabPositions = Collections.EMPTY_LIST;
+
+ /** Holds information about single line fold regions representation data. */
+ private TIntObjectHashMap<FoldingData> myFoldingData = DUMMY;
+
+ CacheEntry(int visualLine, Editor editor, EditorTextRepresentationHelper representationHelper, List<CacheEntry> cache) {
+ this.visualLine = visualLine;
+ myEditor = editor;
+ myRepresentationHelper = representationHelper;
+ myCache = cache;
+ }
+
+ public void setLineStartPosition(@NotNull ProcessingContext context, int i) {
+ assert context.visualColumn == 0;
+ startLogicalLine = context.logicalLine;
+ startLogicalColumn = context.logicalColumn;
+ visualLine = context.visualLine;
+ startOffset = context.offset;
+ startSoftWrapLinesBefore = context.softWrapLinesBefore;
+ startSoftWrapLinesCurrent = context.softWrapLinesCurrent;
+ startSoftWrapColumnDiff = context.softWrapColumnDiff;
+ startFoldedLines = context.foldedLines;
+ startFoldingColumnDiff = context.foldingColumnDiff;
+
+ if (i > 1 && (startOffset - myCache.get(i - 1).endOffset) > 1) {
+ assert false;
+ }
+ }
+
+ public void setLineEndPosition(@NotNull ProcessingContext context) {
+ endOffset = context.offset;
+ endLogicalLine = context.logicalLine;
+ endLogicalColumn = context.logicalColumn;
+ endVisualColumn = context.visualColumn;
+ endSoftWrapLinesBefore = context.softWrapLinesBefore;
+ endSoftWrapLinesCurrent = context.softWrapLinesCurrent;
+ endSoftWrapColumnDiff = context.softWrapColumnDiff;
+ endFoldedLines = context.foldedLines;
+ endFoldingColumnDiff = context.foldingColumnDiff;
+ }
+
+ public ProcessingContext buildStartLineContext() {
+ ProcessingContext result = new ProcessingContext(myEditor, myRepresentationHelper);
+ result.logicalLine = startLogicalLine;
+ result.logicalColumn = startLogicalColumn;
+ result.offset = startOffset;
+ result.visualLine = visualLine;
+ result.visualColumn = 0;
+ result.softWrapLinesBefore = startSoftWrapLinesBefore;
+ result.softWrapLinesCurrent = startSoftWrapLinesCurrent;
+ result.softWrapColumnDiff = startSoftWrapColumnDiff;
+ result.foldedLines = startFoldedLines;
+ result.foldingColumnDiff = startFoldingColumnDiff;
+ return result;
+ }
+
+ public ProcessingContext buildEndLineContext() {
+ ProcessingContext result = new ProcessingContext(myEditor, myRepresentationHelper);
+ result.logicalLine = endLogicalLine;
+ result.logicalColumn = endLogicalColumn;
+ result.offset = endOffset;
+ result.visualLine = visualLine;
+ result.visualColumn = endVisualColumn;
+ result.softWrapLinesBefore = endSoftWrapLinesBefore;
+ result.softWrapLinesCurrent = endSoftWrapLinesCurrent;
+ result.softWrapColumnDiff = endSoftWrapColumnDiff;
+ result.foldedLines = endFoldedLines;
+ result.foldingColumnDiff = endFoldingColumnDiff;
+ return result;
+ }
+
+ public void store(FoldRegion foldRegion, int startX) {
+ store(new FoldingData(foldRegion, startX, myRepresentationHelper, myEditor), foldRegion.getStartOffset());
+ }
+
+ public void store(FoldingData foldData, int offset) {
+ if (myFoldingData == DUMMY) {
+ myFoldingData = new TIntObjectHashMap<FoldingData>();
+ }
+ myFoldingData.put(offset, foldData);
+ }
+
+ public TIntObjectHashMap<FoldingData> getFoldingData() {
+ return myFoldingData;
+ }
+
+ public List<TabData> getTabData() {
+ return myTabPositions;
+ }
+
+ public void storeTabData(ProcessingContext context) {
+ storeTabData(new TabData(context));
+ }
+
+ public void storeTabData(TabData tabData) {
+ if (myTabPositions == Collections.EMPTY_LIST) {
+ myTabPositions = new ArrayList<TabData>();
+ }
+ myTabPositions.add(tabData);
+ }
+
+ public void advance(final int offsetDiff) {
+ startOffset += offsetDiff;
+ endOffset += offsetDiff;
+ for (TabData tabData : myTabPositions) {
+ tabData.offset += offsetDiff;
+ }
+ final TIntObjectHashMap<FoldingData> newFoldingData = new TIntObjectHashMap<FoldingData>();
+ myFoldingData.forEachEntry(new TIntObjectProcedure<FoldingData>() {
+ @Override
+ public boolean execute(int offset, FoldingData foldingData) {
+ newFoldingData.put(offset + offsetDiff, foldingData);
+ return true;
+ }
+ });
+ myFoldingData = newFoldingData;
+ }
+
+ @Override
+ public int compareTo(CacheEntry e) {
+ return visualLine - e.visualLine;
+ }
+
+ @Override
+ public String toString() {
+ return "visual line: " + visualLine + ", offsets: " + startOffset + "-" + endOffset;
+ }
+
+ @Override
+ protected CacheEntry clone() {
+ final CacheEntry result = new CacheEntry(visualLine, myEditor, myRepresentationHelper, myCache);
+
+ result.startLogicalLine = startLogicalLine;
+ result.startLogicalColumn = startLogicalColumn;
+ result.startOffset = startOffset;
+ result.startSoftWrapLinesBefore = startSoftWrapLinesBefore;
+ result.startSoftWrapLinesCurrent = startSoftWrapLinesCurrent;
+ result.startSoftWrapColumnDiff = startSoftWrapColumnDiff;
+ result.startFoldedLines = startFoldedLines;
+ result.startFoldingColumnDiff = startFoldingColumnDiff;
+
+ result.endOffset = endOffset;
+ result.endLogicalLine = endLogicalLine;
+ result.endLogicalColumn = endLogicalColumn;
+ result.endVisualColumn = endVisualColumn;
+ result.endSoftWrapLinesBefore = endSoftWrapLinesBefore;
+ result.endSoftWrapLinesCurrent = endSoftWrapLinesCurrent;
+ result.endSoftWrapColumnDiff = endSoftWrapColumnDiff;
+ result.endFoldedLines = endFoldedLines;
+ result.endFoldingColumnDiff = endFoldingColumnDiff;
+
+ myFoldingData.forEachEntry(new TIntObjectProcedure<FoldingData>() {
+ @Override
+ public boolean execute(int offset, FoldingData foldData) {
+ result.store(foldData, offset);
+ return true;
+ }
+ });
+ for (TabData tabPosition : myTabPositions) {
+ result.storeTabData(tabPosition);
+ }
+
+ return result;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.softwrap.mapping;
+
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.ex.EditorEx;
+import com.intellij.openapi.editor.ex.FoldingModelEx;
+import com.intellij.openapi.editor.impl.EditorTextRepresentationHelper;
+import com.intellij.openapi.editor.impl.softwrap.*;
+import com.intellij.openapi.util.Pair;
+import gnu.trove.TIntObjectProcedure;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.List;
+
+/**
+ * {@link SoftWrapDataMapper} implementation that is implemented using the following principles:
+ * <pre>
+ * <ul>
+ * <li>it caches document dimension for starts of all visual lines;</li>
+ * <li>
+ * every time document position mapping should be performed this mapper chooses target visual line to process
+ * and calculates target position from its start (note that we can optimize that in order to choose calculation
+ * direction - 'from start' or 'from end');
+ * </li>
+ * <li>
+ * information about target visual lines is updated incrementally on document changes, i.e. we're trying to reduce
+ * calculations number as much as possible;
+ * </li>
+ * </ul>
+ * </pre>
+ * <p/>
+ * Not thread-safe.
+ *
+ * @author Denis Zhdanov
+ * @since Aug 31, 2010 10:24:47 AM
+ */
+public class CachingSoftWrapDataMapper implements SoftWrapDataMapper, SoftWrapAwareDocumentParsingListener {
+
+ /** Caches information for the document visual line starts sorted in ascending order. */
+ private final List<CacheEntry> myCache = new ArrayList<CacheEntry>();
+ private final CacheEntry mySearchKey = new CacheEntry(0, null, null, null);
+ private final List<CacheEntry> myNotAffectedByUpdateTailCacheEntries = new ArrayList<CacheEntry>();
+ private final CacheState myBeforeChangeState = new CacheState();
+ private final CacheState myAfterChangeState = new CacheState();
+
+ private final EditorEx myEditor;
+ private final SoftWrapsStorage myStorage;
+ private final EditorTextRepresentationHelper myRepresentationHelper;
+
+ public CachingSoftWrapDataMapper(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage,
+ @NotNull EditorTextRepresentationHelper representationHelper)
+ {
+ myEditor = editor;
+ myStorage = storage;
+ myRepresentationHelper = representationHelper;
+ }
+
+ @NotNull
+ @Override
+ public LogicalPosition visualToLogical(@NotNull VisualPosition visual) throws IllegalStateException {
+ if (myCache.isEmpty()) {
+ return new LogicalPosition(visual.line, visual.column, 0, 0, 0, 0, 0);
+ }
+
+ // There is a possible case that we're asked to map visual position that lay beyond the document content (e.g. there is a
+ // 'virtual space' below the document text and it's perfectly possible to map visual position that points into it).
+ // We consider that there are no soft wraps, tabulations and fold regions between the last cached visual line and target line then.
+ if (!myCache.isEmpty()) {
+ CacheEntry cacheEntry = myCache.get(myCache.size() - 1);
+ if (visual.line > cacheEntry.visualLine) {
+ int lineDiff = visual.line - cacheEntry.visualLine;
+ return new LogicalPosition(
+ cacheEntry.endLogicalLine + lineDiff, visual.column, cacheEntry.endSoftWrapLinesBefore + cacheEntry.endSoftWrapLinesCurrent,
+ 0, 0, cacheEntry.endFoldedLines, 0
+ );
+ }
+ }
+
+ return calculate(new VisualToLogicalCalculationStrategy(visual, myCache, myEditor, myStorage, myRepresentationHelper));
+ }
+
+ @NotNull
+ @Override
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ return calculate(new OffsetToLogicalCalculationStrategy(offset, myEditor, myRepresentationHelper, myCache, myStorage));
+ }
+
+ @Override
+ public VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logical, @NotNull VisualPosition softWrapUnawareVisual)
+ throws IllegalStateException
+ {
+ // We can't use standard strategy-based approach with logical -> visual mapping because folding processing quite often
+ // temporarily disables folding. So, there is an inconsistency between cached data (folding aware) and current folding
+ // state. So, we use direct soft wraps adjustment instead of normal calculation.
+
+
+ if (logical.visualPositionAware) {
+ // We don't need to recalculate logical position adjustments because given object already has them.
+ return logical.toVisualPosition();
+ }
+
+ List<? extends SoftWrap> softWraps = myStorage.getSoftWraps();
+
+ // Check if there are registered soft wraps before the target logical position.
+ int maxOffset = myEditor.logicalPositionToOffset(logical);
+ int endIndex = myStorage.getSoftWrapIndex(maxOffset);
+ if (endIndex < 0) {
+ endIndex = -endIndex - 2; // We subtract '2' instead of '1' here in order to point to offset of the first soft wrap that
+ // is located before the given logical position.
+ }
+
+ // Return eagerly if no soft wraps are registered before the target offset.
+ if (endIndex < 0 || endIndex >= softWraps.size()) {
+ return softWrapUnawareVisual;
+ }
+
+ int lineDiff = 0;
+ int column = -1;
+
+ int targetLogicalLineStartOffset = myEditor.logicalPositionToOffset(new LogicalPosition(logical.line, 0));
+ for (int i = endIndex; i >= 0; i--) {
+ SoftWrap softWrap = softWraps.get(i);
+ if (softWrap == null) {
+ assert false;
+ continue;
+ }
+
+ // Count soft wrap column offset only if it's located at the same line as the target offset.
+ if (column < 0 && softWrap.getStart() >= targetLogicalLineStartOffset) {
+ column = softWrap.getIndentInColumns() + myRepresentationHelper.toVisualColumnSymbolsNumber(
+ myEditor.getDocument().getCharsSequence(), softWrap.getStart(), maxOffset, softWrap.getIndentInPixels()
+ );
+
+ // Count lines introduced by the current soft wrap. We assume that every soft wrap has a single line feed.
+ lineDiff++;
+ }
+ else {
+ // We assume that every soft wrap has a single line feed.
+ lineDiff += i + 1;
+ break;
+ }
+ }
+
+ int columnToUse = column >= 0 ? column : softWrapUnawareVisual.column;
+ return new VisualPosition(softWrapUnawareVisual.line + lineDiff, columnToUse);
+ }
+
+ public void release() {
+ myCache.clear();
+ }
+
+ private <T> T calculate(@NotNull MappingStrategy<T> strategy) throws IllegalStateException {
+ T eagerMatch = strategy.eagerMatch();
+ if (eagerMatch != null) {
+ return eagerMatch;
+ }
+
+ ProcessingContext context = strategy.buildInitialContext();
+
+ // Folding model doesn't return information about fold regions if their 'enabled' state is set to 'false'. Unfortunately,
+ // such situation occurs not only on manual folding disabling but on internal processing as well. E.g. 'enabled' is set
+ // to 'false' during fold preview representation. So, we set it to 'true' in order to retrieve fold regions and restore
+ // to previous state after that.
+ FoldingModelEx foldingModel = myEditor.getFoldingModel();
+ boolean foldingState = foldingModel.isFoldingEnabled();
+ foldingModel.setFoldingEnabled(true);
+ CompositeDataProvider provider;
+ try {
+ provider = new CompositeDataProvider(
+ new SoftWrapsDataProvider(myStorage), new FoldingDataProvider(myEditor.getFoldingModel().fetchTopLevel()),
+ getTabulationDataProvider(context.visualLine)
+ );
+ }
+ finally {
+ foldingModel.setFoldingEnabled(foldingState);
+ }
+ provider.advance(context.offset);
+
+ while (provider.hasData()) {
+ Pair<SoftWrapDataProviderKeys, ?> data = provider.getData();
+ T result = null;
+ int sortingKey = provider.getSortingKey();
+
+ // There is a possible case that, say, fold region is soft wrapped. We don't want to perform unnecessary then.
+ if (context.offset < sortingKey) {
+ result = strategy.advance(context, sortingKey);
+ if (result != null) {
+ return result;
+ }
+ }
+
+ switch (data.first) {
+ case SOFT_WRAP: result = strategy.processSoftWrap(context, (SoftWrap)data.second); break;
+ case COLLAPSED_FOLDING: result = strategy.processFoldRegion(context, (FoldRegion)data.second); break;
+ case TABULATION: result = strategy.processTabulation(context, (TabData)data.second); break;
+ }
+ if (result != null) {
+ return result;
+ }
+ provider.next();
+ }
+ return strategy.build(context);
+ }
+
+ private TabulationDataProvider getTabulationDataProvider(int visualLine) throws IllegalStateException {
+ mySearchKey.visualLine = visualLine;
+ int i = Collections.binarySearch(myCache, mySearchKey);
+ List<TabData> tabs;
+ if (i >= 0) {
+ tabs = myCache.get(i).getTabData();
+ }
+ else {
+ assert false;
+ tabs = Collections.emptyList();
+ }
+ return new TabulationDataProvider(tabs);
+ }
+
+ @Override
+ public void onProcessedSymbol(@NotNull ProcessingContext context) {
+ switch (context.symbol) {
+ case '\n': onLineFeed(context); break;
+ case '\t': onTabulation(context); break;
+ default: onNonLineFeedSymbol(context); break;
+ }
+ }
+
+ @Override
+ public void onCollapsedFoldRegion(@NotNull FoldRegion foldRegion, int x, int visualLine) {
+ CacheEntry cacheEntry = getCacheEntryForVisualLine(visualLine);
+ cacheEntry.store(foldRegion, x);
+ }
+
+ @Override
+ public void beforeSoftWrap(@NotNull ProcessingContext context) {
+ CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine);
+ cacheEntry.setLineEndPosition(context);
+ }
+
+ @Override
+ public void afterSoftWrapLineFeed(@NotNull ProcessingContext context) {
+ CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine);
+ cacheEntry.setLineStartPosition(context, context.visualLine);
+ }
+
+ @Override
+ public void revertToOffset(final int offset, int visualLine) {
+ CacheEntry cacheEntry = getCacheEntryForVisualLine(visualLine);
+
+ // Remove information about cached tabulation symbol data.
+ int tabIndex = 0;
+ for (; tabIndex < cacheEntry.getTabData().size(); tabIndex++) {
+ if (cacheEntry.getTabData().get(tabIndex).offset >= offset) {
+ break;
+ }
+ }
+ if (tabIndex < cacheEntry.getTabData().size() - 1) {
+ cacheEntry.getTabData().subList(tabIndex, cacheEntry.getTabData().size()).clear();
+ }
+
+ // Remove information about cached single line fold regions.
+ cacheEntry.getFoldingData().retainEntries(new TIntObjectProcedure<FoldingData>() {
+ @Override
+ public boolean execute(int foldStartOffset, FoldingData data) {
+ return foldStartOffset < offset;
+ }
+ });
+ }
+
+ /**
+ * Updates inner cache for the context that points to line feed symbol
+ *
+ * @param context processing context that points to line feed symbol
+ */
+ private void onLineFeed(@NotNull ProcessingContext context) {
+ CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine);
+ cacheEntry.setLineEndPosition(context);
+
+ cacheEntry = getCacheEntryForVisualLine(context.visualLine + 1);
+
+ ProcessingContext newLineContext = context.clone();
+ newLineContext.onNewLine();
+ newLineContext.offset++;
+ cacheEntry.setLineStartPosition(newLineContext, context.visualLine + 1);
+ cacheEntry.setLineEndPosition(newLineContext);
+ }
+
+ /**
+ * Updates inner cache for the context that points to tabulation symbol.
+ *
+ * @param context processing context that points to tabulation symbol
+ */
+ private void onTabulation(@NotNull ProcessingContext context) {
+ onNonLineFeedSymbol(context);
+ CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine);
+ cacheEntry.storeTabData(context);
+ }
+
+ /**
+ * Process given context in assumption that it points to non-line feed symbol.
+ *
+ * @param context context that is assumed to point to non-line feed symbol
+ */
+ private void onNonLineFeedSymbol(@NotNull ProcessingContext context) {
+ CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine);
+ Document document = myEditor.getDocument();
+ if (context.offset >= document.getTextLength() - 1) {
+ cacheEntry.setLineEndPosition(context);
+ }
+
+ if (context.offset < 0 || (context.offset > 0 && context.offset < document.getTextLength()
+ && document.getCharsSequence().charAt(context.offset - 1) != '\n'))
+ {
+ return;
+ }
+
+ // Process first symbol on a new line.
+ cacheEntry.getTabData().clear();
+ cacheEntry.getFoldingData().clear();
+ cacheEntry.setLineStartPosition(context.clone(), context.visualLine);
+ }
+
+ private CacheEntry getCacheEntryForVisualLine(int visualLine) {
+ mySearchKey.visualLine = visualLine;
+ int i = Collections.binarySearch(myCache, mySearchKey);
+ CacheEntry result;
+ if (i < 0) {
+ i = -i - 1;
+ myCache.add(i, result = new CacheEntry(visualLine, myEditor, myRepresentationHelper, myCache));
+ }
+ else if (myBeforeChangeState.valid && i > myBeforeChangeState.endCacheEntryIndex && myCache.get(i).locked) {
+ myCache.set(i, result = myCache.get(i).clone());
+ }
+ else {
+ result = myCache.get(i);
+ }
+ return result;
+ }
+
+ @Override
+ public void onRecalculationStart(int startOffset, int endOffset) {
+ myBeforeChangeState.updateByDocumentOffsets(startOffset, endOffset);
+ if (!myBeforeChangeState.valid) {
+ return;
+ }
+ myNotAffectedByUpdateTailCacheEntries.clear();
+ myNotAffectedByUpdateTailCacheEntries.addAll(myCache.subList(myBeforeChangeState.endCacheEntryIndex + 1, myCache.size()));
+ myCache.subList(myBeforeChangeState.startCacheEntryIndex + 1, myCache.size()).clear();
+ for (CacheEntry entry : myNotAffectedByUpdateTailCacheEntries) {
+ entry.locked = true;
+ }
+ }
+
+ @Override
+ public void onRecalculationEnd(int startOffset, int endOffset) {
+ if (!myBeforeChangeState.valid) {
+ return;
+ }
+ int endIndex = Math.max(0, myCache.size() - 2); // -1 because of zero-based indexing; one more -1 in assumption that
+ // re-parsing always adds number of target cache entries plus one
+ // (because of line feed at the end).
+ myAfterChangeState.updateByCacheIndices(myBeforeChangeState.startCacheEntryIndex, endIndex);
+ myCache.subList(myAfterChangeState.endCacheEntryIndex + 1, myCache.size()).clear();
+ myCache.addAll(myNotAffectedByUpdateTailCacheEntries);
+ for (CacheEntry entry : myNotAffectedByUpdateTailCacheEntries) {
+ entry.locked = false;
+ }
+ applyStateChange();
+
+
+ //Document document = myEditor.getDocument();
+ //CharSequence text = document.getCharsSequence();
+ //System.out.println("--------------------------------------------------");
+ //System.out.println("|");
+ //System.out.println("|");
+ //System.out.println(text);
+ //System.out.println("- - - - - - - - - - - - - - - - - -");
+ //System.out.println("text length: " + text.length() + ", soft wraps: " + myStorage.getSoftWraps());
+ //for (int i = 0; i < myCache.size(); i++) {
+ // CacheEntry entry = myCache.get(i);
+ // // TODO den unwrap
+ // try {
+ // System.out.printf("line %d. %d-%d: '%s'%n", i, entry.startOffset, entry.endOffset,
+ // text.subSequence(entry.startOffset,Math.min(entry.endOffset, text.length())));
+ // }
+ // catch (Throwable e) {
+ // e.printStackTrace();
+ // }
+ //}
+ //if (!myCache.isEmpty() && myCache.get(myCache.size() - 1).endOffset < text.length() - 1) {
+ // System.out.printf("Incomplete re-parsing detected! Document length is %d but last processed offset is %s%n", text.length(),
+ // myCache.get(myCache.size() - 1).endOffset);
+ //}
+
+
+ //for (CacheEntry cacheEntry : myCache) {
+ // if (cacheEntry.startOffset > 0) {
+ // if (text.charAt(cacheEntry.startOffset - 1) != '\n' && myStorage.getSoftWrap(cacheEntry.startOffset) == null) {
+ // assert false;
+ // }
+ // }
+ // if (cacheEntry.endOffset < document.getTextLength()) {
+ // if (text.charAt(cacheEntry.endOffset) != '\n' && myStorage.getSoftWrap(cacheEntry.endOffset) == null) {
+ // assert false;
+ // }
+ // }
+ //}
+ }
+
+ /**
+ * Is assumed to be called for updating {@link #myCache document dimensions cache} entries that lay after document position identified
+ * by {@link #myAfterChangeState} in order to apply to them diff between {@link #myBeforeChangeState} and {@link #myAfterChangeState}.
+ * <p/>
+ * I.e. the general idea of incremental cache update is the following:
+ * <p/>
+ * <pre>
+ * <ol>
+ * <li>We are notified on document region update;</li>
+ * <li>We remember significant information about target region;</li>
+ * <li>Region data recalculation is performed;</li>
+ * <li>Diff between current region state and remembered one is applied to the cache;</li>
+ * </ol>
+ </pre>
+ */
+ private void applyStateChange() {
+ int visualLinesDiff = myAfterChangeState.visualLines - myBeforeChangeState.visualLines;
+ int logicalLinesDiff = myAfterChangeState.logicalLines - myBeforeChangeState.logicalLines;
+ int softWrappedLinesDiff = myAfterChangeState.softWrapLines - myBeforeChangeState.softWrapLines;
+ int foldedLinesDiff = myAfterChangeState.foldedLines - myBeforeChangeState.foldedLines;
+ int offsetsDiff = (myAfterChangeState.endOffset - myAfterChangeState.startOffset)
+ - (myBeforeChangeState.endOffset - myBeforeChangeState.startOffset);
+
+ if (myNotAffectedByUpdateTailCacheEntries.isEmpty()) {
+ return;
+ }
+
+ for (CacheEntry cacheEntry : myNotAffectedByUpdateTailCacheEntries) {
+ cacheEntry.visualLine += visualLinesDiff;
+ cacheEntry.startLogicalLine += logicalLinesDiff;
+ cacheEntry.endLogicalLine += logicalLinesDiff;
+ cacheEntry.advance(offsetsDiff);
+ cacheEntry.startSoftWrapLinesBefore += softWrappedLinesDiff;
+ cacheEntry.endSoftWrapLinesBefore += softWrappedLinesDiff;
+ cacheEntry.startFoldedLines += foldedLinesDiff;
+ cacheEntry.endFoldedLines += foldedLinesDiff;
+ }
+
+ int offset = myNotAffectedByUpdateTailCacheEntries.get(0).startOffset + 1/* in order to exclude soft wrap from previous line if any*/;
+ if (offsetsDiff == 0 || offset >= myEditor.getDocument().getTextLength()) {
+ return;
+ }
+
+ int softWrapIndex = myStorage.getSoftWrapIndex(offset);
+ if (softWrapIndex < 0) {
+ softWrapIndex = -softWrapIndex - 1;
+ }
+
+ List<SoftWrapImpl> softWraps = myStorage.getSoftWraps();
+ for (int i = softWrapIndex; i < softWraps.size(); i++) {
+ softWraps.get(i).advance(offsetsDiff);
+ }
+ }
+
+ private class CacheState {
+
+ public boolean valid = true;
+
+ public int visualLines;
+ public int logicalLines;
+ public int softWrapLines;
+ public int foldedLines;
+ public int startOffset;
+ public int endOffset;
+ public int startCacheEntryIndex;
+ public int endCacheEntryIndex;
+
+ public void updateByDocumentOffsets(int startOffset, int endOffset) {
+ this.startOffset = startOffset;
+ this.endOffset = endOffset;
+ startCacheEntryIndex = MappingUtil.getCacheEntryIndexForOffset(startOffset, myEditor.getDocument(), myCache);
+ endCacheEntryIndex = MappingUtil.getCacheEntryIndexForOffset(endOffset, myEditor.getDocument(), myCache);
+
+ if (startCacheEntryIndex < 0 || endCacheEntryIndex < 0) {
+ valid = false;
+ // This is correct situation for, say, initial cache update.
+ return;
+ }
+ updateByCacheIndices(startCacheEntryIndex, endCacheEntryIndex);
+
+ }
+
+ public void updateByCacheIndices(int startIndex, int endIndex) {
+ reset();
+ startOffset = myCache.get(startIndex).startOffset;
+ endOffset = myCache.get(endIndex).endOffset;
+ startCacheEntryIndex = startIndex;
+ endCacheEntryIndex = endIndex;
+ visualLines = endIndex - startIndex + 1;
+
+ CacheEntry startEntry = myCache.get(startIndex);
+ CacheEntry endEntry = myCache.get(endIndex);
+ logicalLines = endEntry.endLogicalLine - startEntry.startLogicalLine + 1;
+ foldedLines = endEntry.endFoldedLines - startEntry.startFoldedLines;
+ softWrapLines = endEntry.endSoftWrapLinesBefore + endEntry.endSoftWrapLinesCurrent - startEntry.startSoftWrapLinesBefore
+ - startEntry.startSoftWrapLinesCurrent;
+ }
+
+ public void reset() {
+ logicalLines = 0;
+ softWrapLines = 0;
+ foldedLines = 0;
+ endCacheEntryIndex = 0;
+ valid = true;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2000-2010 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.softwrap.mapping;
+
+import com.intellij.openapi.editor.impl.softwrap.mapping.DataProvider;
+import com.intellij.openapi.util.Pair;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+
+/**
+ * There is an ubiquitous situation when single source has various different sub-sources. E.g. there is a document
+ * and its soft wraps, fold regions etc.
+ * <p/>
+ * So, it's often convenient to encapsulate those sources in order to provide every sub-source instance one-by-one
+ * in particular order. This class does exactly that job, i.e. it encapsulates multiple {@link DataProvider} instance
+ * and exposes their data by {@link DataProvider#getSortingKey() sorting order}.
+ * <p/>
+ * Not thread-safe.
+ *
+ * @author Denis Zhdanov
+ * @since Aug 31, 2010 10:57:44 AM
+ */
+public class CompositeDataProvider {
+
+ /**
+ * We use array here for performance reasons (on the basis of profiling results analysis).
+ */
+ private final DataProvider<? extends Comparable<?>, ?>[] myProviders;
+
+ /**
+ * Creates new <code>CompositeDataProvider</code> object with the given providers to register.
+ *
+ * @param providers providers to register within the current composite provider
+ */
+ public CompositeDataProvider(@NotNull DataProvider<? extends Comparable<?>, ?> ... providers) {
+ // We assume here that given array ownership belongs to the current object now.
+ for (int i = 0; i < providers.length; i++) {
+ DataProvider<? extends Comparable<?>, ?> provider = providers[i];
+ if (provider.getData() == null) {
+ providers[i] = null;
+ }
+ }
+ myProviders = providers;
+ sort();
+ }
+
+ /**
+ * Allows to answer if there is any data at the current provider.
+ * <p/>
+ * I.e. it's safe to call {@link #getData()} if this method returns <code>true</code>.
+ *
+ * @return <code>true</code> if there is a data at the current provider; <code>false</code> otherwise
+ */
+ public boolean hasData() {
+ for (DataProvider<? extends Comparable<?>, ?> provider : myProviders) {
+ if (provider != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Asks current provider to ignore all data which {@link DataProvider#getSortingKey() sorting keys} are less than the given value.
+ *
+ * @param sortingKey min sorting key to use
+ */
+ public void advance(int sortingKey) {
+ for (int i = 0; i < myProviders.length; i++) {
+ DataProvider<? extends Comparable<?>, ?> provider = myProviders[i];
+ if (provider == null) {
+ continue;
+ }
+ provider.advance(sortingKey);
+ if (provider.getData() == null) {
+ myProviders[i] = null;
+ }
+ }
+ sort();
+ }
+
+ /**
+ * Retrieves the data with the the smallest {@link DataProvider#getSortingKey() sorting key} from registered providers.
+ * <p/>
+ * <b>Note:</b> subsequent invocations of this method are expected to return the same value unless {@link #next()} is called.
+ *
+ * @param <K> data key type
+ * @param <V> data value type
+ * @return <code>(key; value)</code> pair that is {@link DataProvider#getData() returned from data provider} with the
+ * smallest {@link DataProvider#getSortingKey() sorting key}
+ * @throws IllegalStateException if no data left at the current composite provider
+ * @see #hasData()
+ */
+ @SuppressWarnings({"unchecked"})
+ @NotNull
+ public <K, V> Pair<K, V> getData() throws IllegalStateException {
+ if (!hasData()) {
+ throw new IllegalStateException("No more data is available within the current provider.");
+ }
+ DataProvider<?, ?> provider = myProviders[0];
+ Pair<K, V> result = (Pair<K, V>)provider.getData();
+ if (result == null) {
+ throw new IllegalStateException(String.format("Programming error detected - registered data provider doesn't have data (%s). "
+ + "Registered providers: %s", provider, Arrays.toString(myProviders)));
+ }
+ return result;
+ }
+
+ /**
+ * Allows to ask for the {@link DataProvider#getSortingKey() sorting key} of the {@link #getData() current data}.
+ * <p/>
+ * It was possible to add sorting key to the {@link #getData()} result but the idea was to avoid unnecessary boxing/un-boxing.
+ *
+ * @return sorting key for the {@link #getData() current data} if any.
+ * @throws IllegalStateException if there is no data within the current provider
+ */
+ public int getSortingKey() throws IllegalStateException {
+ if (!hasData()) {
+ throw new IllegalStateException("No more data is available within the current provider");
+ }
+
+ return myProviders[0].getSortingKey();
+ }
+
+ /**
+ * Asks current provider to drop the data {@link #getData() returned last time} and advance to the next data with the
+ * smallest {@link DataProvider#getSortingKey() sorting key}.
+ *
+ * @return <code>true</code> if there is more data at the current provider ({@link #hasData()} returns <code>true</code>
+ * and {@link #getData()} doesn't throw exception); <code>false</code> otherwise
+ */
+ public boolean next() {
+ if (!hasData()) {
+ return false;
+ }
+
+ DataProvider<?, ?> provider = myProviders[0];
+ if (!provider.next()) {
+ myProviders[0] = null;
+ }
+ if (hasData()) {
+ sort();
+ return true;
+ }
+ return false;
+ }
+
+ private void sort() {
+ if (myProviders.length <= 0) {
+ return;
+ }
+
+ // Profiling results analysis-implied optimization.
+ int i = 0;
+ DataProvider<? extends Comparable<?>, ?> dataProvider = myProviders[0];
+ for (int j = 1; j < myProviders.length; j++) {
+ DataProvider<? extends Comparable<?>, ?> candidate = myProviders[j];
+ if (candidate == null) {
+ continue;
+ }
+ if (dataProvider == null || compare(dataProvider, candidate) > 0) {
+ i = j;
+ dataProvider = candidate;
+ }
+ }
+
+ if (i > 0) {
+ myProviders[i] = myProviders[0];
+ myProviders[0] = dataProvider;
+ }
+ }
+
+ @SuppressWarnings({"unchecked"})
+ private static int compare(DataProvider<? extends Comparable<?>, ?> o1, DataProvider<? extends Comparable<?>, ?> o2) {
+ // We assume here that given data providers have data to expose.
+ int result = o1.getSortingKey() - o2.getSortingKey();
+ if (result != 0) {
+ return result;
+ }
+
+ Pair<? extends Comparable<Object>, ?> d1 = (Pair<? extends Comparable<Object>, ?>)o1.getData();
+ Pair<? extends Comparable<Object>, ?> d2 = (Pair<? extends Comparable<Object>, ?>)o2.getData();
+ if (d1 != null && d2 != null) {
+ return d1.first.compareTo(d2.first);
+ }
+ return result;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.softwrap.mapping;
+
+import com.intellij.openapi.util.Pair;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Iterator;
+
+/**
+ * Defines contract for providing data sorted by int values.
+ * <p/>
+ * Expected usage is very similar to the {@link Iterator}'s one - the same {@link #getData() current data} and
+ * {@link #getSortingKey() sorting key} are exposed until {@link #next()} is called.
+ * <p/>
+ * Implementations of this interface are not obliged to be thread-safe.
+ *
+ * @author Denis Zhdanov
+ * @since Aug 31, 2010 10:56:14 AM
+ */
+public interface DataProvider<K extends Comparable<? super K>, V> {
+
+ /**
+ * @return current data if any is available; <code>null</code> otherwise
+ */
+ @Nullable
+ Pair<K, V> getData();
+
+ /**
+ * @return sorting key for the {@link #getData() current data}
+ */
+ int getSortingKey();
+
+ /**
+ * Asks current data provider to advance to the next data if any.
+ *
+ * @return flag that shows if current provider has more data, i.e. {@link #getData()} is guaranteed to return
+ * not-<code>null</code> value if this method returns <code>true</code>
+ */
+ boolean next();
+
+ /**
+ * Asks current provider to ignore all data which {@link DataProvider#getSortingKey() sorting keys} are less than the given value.
+ *
+ * @param sortingKey min sorting key to use
+ */
+ void advance(int sortingKey);
+}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.softwrap.mapping;
+
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.FoldRegion;
+import com.intellij.openapi.editor.impl.EditorTextRepresentationHelper;
+
+/**
+ * Caches information about number of logical columns inside the collapsed single line folding.
+ */
+class FoldingData {
+
+ int widthInColumns = -1;
+ int startX;
+ private final Editor myEditor;
+ private final EditorTextRepresentationHelper myRepresentationHelper;
+ private final FoldRegion myFoldRegion;
+
+ FoldingData(FoldRegion foldRegion, int startX, EditorTextRepresentationHelper representationHelper, Editor editor) {
+ myFoldRegion = foldRegion;
+ this.startX = startX;
+ myRepresentationHelper = representationHelper;
+ myEditor = editor;
+ }
+
+ public int getCollapsedSymbolsWidthInColumns() {
+ if (widthInColumns < 0) {
+ Document document = myEditor.getDocument();
+ widthInColumns = myRepresentationHelper.toVisualColumnSymbolsNumber(
+ document.getCharsSequence(), myFoldRegion.getStartOffset(), myFoldRegion.getEndOffset(), startX
+ );
+ }
+
+ return widthInColumns;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.softwrap.mapping;
+
+import com.intellij.openapi.editor.FoldRegion;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Adapts information about fold regions received as an array to {@link DataProvider} API.
+ *
+ * @author Denis Zhdanov
+ * @since Aug 31, 2010 12:42:02 PM
+ */
+public class FoldingDataProvider extends AbstractDataProvider<SoftWrapDataProviderKeys, FoldRegion> {
+
+ private final FoldRegion[] myFoldRegions;
+ private int myIndex;
+
+ public FoldingDataProvider(@Nullable FoldRegion[] foldRegions) {
+ super(SoftWrapDataProviderKeys.COLLAPSED_FOLDING);
+ myFoldRegions = foldRegions;
+
+ if (foldRegions == null) {
+ return;
+ }
+ for (; myIndex < myFoldRegions.length; myIndex++) {
+ if (!myFoldRegions[myIndex].isExpanded()) {
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected FoldRegion doGetData() {
+ if (myFoldRegions == null || myIndex >= myFoldRegions.length) {
+ return null;
+ }
+ return myFoldRegions[myIndex];
+ }
+
+ @Override
+ public boolean next() {
+ if (myFoldRegions == null) {
+ return false;
+ }
+
+ while (++myIndex < myFoldRegions.length) {
+ if (!myFoldRegions[myIndex].isExpanded()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void advance(int sortingKey) {
+ // We inline binary search here because profiling indicates that as a performance boost.
+ int start = myIndex;
+ int end = myFoldRegions.length - 1;
+
+ // We inline binary search here because profiling indicates that it becomes bottleneck to use Collections.binarySearch().
+ while (start <= end) {
+ int i = (end + start) >>> 1;
+ FoldRegion foldRegion = myFoldRegions[i];
+ if (foldRegion.getStartOffset() < sortingKey) {
+ start = i + 1;
+ continue;
+ }
+ if (foldRegion.getStartOffset() > sortingKey) {
+ end = i - 1;
+ continue;
+ }
+
+ myIndex = i;
+ break;
+ }
+ myIndex = start;
+ }
+
+ @Override
+ public int getSortingKey() {
+ if (myFoldRegions == null || myIndex >= myFoldRegions.length) {
+ return 0;
+ }
+ return myFoldRegions[myIndex].getStartOffset();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.softwrap.mapping;
+
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.impl.EditorTextRepresentationHelper;
+import com.intellij.openapi.editor.impl.softwrap.SoftWrapsStorage;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.Pair;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+* @author Denis Zhdanov
+* @since Sep 9, 2010 10:08:08 AM
+*/
+@SuppressWarnings({"UnusedDeclaration"})
+class LogicalToVisualMappingStrategy extends AbstractMappingStrategy<VisualPosition> {
+
+ private final LogicalPosition myTargetLogical;
+
+ LogicalToVisualMappingStrategy(@NotNull final LogicalPosition logical, Editor editor, final SoftWrapsStorage storage,
+ EditorTextRepresentationHelper representationHelper, final List<CacheEntry> cache)
+ throws IllegalStateException
+ {
+ super(new Computable<Pair<CacheEntry, VisualPosition>>() {
+ @Override
+ public Pair<CacheEntry, VisualPosition> compute() {
+ int start = 0;
+ int end = cache.size() - 1;
+
+ // We inline binary search here because profiling indicates that it becomes bottleneck to use Collections.binarySearch().
+ while (start <= end) {
+ int i = (end + start) >>> 1;
+ CacheEntry cacheEntry = cache.get(i);
+
+ // There is a possible case that single logical line is represented on multiple visual lines due to soft wraps processing.
+ // Hence, we check for bot logical line and logical columns during searching 'anchor' cache entry.
+
+ if (cacheEntry.endLogicalLine < logical.line
+ || (cacheEntry.endLogicalLine == logical.line && storage.getSoftWrap(cacheEntry.endOffset) != null
+ && cacheEntry.endLogicalColumn <= logical.column))
+ {
+ start = i + 1;
+ continue;
+ }
+ if (cacheEntry.startLogicalLine > logical.line
+ || (cacheEntry.startLogicalLine == logical.line
+ && cacheEntry.startLogicalColumn > logical.column))
+ {
+ end = i - 1;
+ continue;
+ }
+
+ // There is a possible case that currently found cache entry corresponds to soft-wrapped line and soft wrap occurred
+ // at target logical column. We need to return cache entry for the next visual line then (because single logical column
+ // is shared for 'before soft wrap' and 'after soft wrap' positions and we want to use the one that points to
+ // 'after soft wrap' position).
+ if (cacheEntry.endLogicalLine == logical.line && cacheEntry.endLogicalColumn == logical.column && i < cache.size() - 1) {
+ CacheEntry nextLineCacheEntry = cache.get(i + 1);
+ if (nextLineCacheEntry.startLogicalLine == logical.line
+ && nextLineCacheEntry.startLogicalColumn == logical.column)
+ {
+ return new Pair<CacheEntry, VisualPosition>(nextLineCacheEntry, null);
+ }
+ }
+ return new Pair<CacheEntry, VisualPosition>(cacheEntry, null);
+ }
+
+ throw new IllegalStateException(String.format(
+ "Can't map logical position (%s) to visual position. Reason: no cached information information about target visual "
+ + "line is found. Registered entries: %s", logical, cache
+ ));
+ }
+ }, editor, storage, representationHelper);
+ myTargetLogical = logical;
+ }
+
+ @Override
+ protected VisualPosition buildIfExceeds(ProcessingContext context, int offset) {
+ if (context.logicalLine < myTargetLogical.line) {
+ return null;
+ }
+
+ int diff = myTargetLogical.column - context.logicalColumn;
+ if (offset - context.offset < diff) {
+ return null;
+ }
+
+ context.visualColumn += diff;
+ // Don't update other dimensions like logical position and offset because we need only visual position here.
+ return context.buildVisualPosition();
+ }
+
+ @Override
+ protected VisualPosition buildIfExceeds(@NotNull ProcessingContext context, @NotNull FoldRegion foldRegion) {
+ int foldEndLine = myEditor.getDocument().getLineNumber(foldRegion.getEndOffset());
+ if (myTargetLogical.line > foldEndLine) {
+ return null;
+ }
+
+ if (myTargetLogical.line < foldEndLine) {
+ // Map all logical position that point inside collapsed fold region to visual position of its start.
+ return context.buildVisualPosition();
+ }
+
+ int foldEndColumn = getFoldRegionData(foldRegion).getCollapsedSymbolsWidthInColumns();
+ if (foldEndLine == context.logicalLine) {
+ // Single-line fold region.
+ foldEndColumn += context.logicalColumn;
+ }
+
+ if (foldEndColumn <= myTargetLogical.column) {
+ return null;
+ }
+
+ // Map all logical position that point inside collapsed fold region to visual position of its start.
+ return context.buildVisualPosition();
+ }
+
+ @Override
+ protected VisualPosition buildIfExceeds(ProcessingContext context, TabData tabData) {
+ if (context.logicalLine < myTargetLogical.line) {
+ return null;
+ }
+
+ int diff = myTargetLogical.column - context.logicalColumn;
+ if (diff >= tabData.widthInColumns) {
+ return null;
+ }
+
+ context.logicalColum