Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Schindl2016-02-19 18:24:20 +0000
committerTom Schindl2016-02-19 18:24:20 +0000
commit04c4cc20b1e804b86f78e5b12ef6f30670b3fece (patch)
tree4f8f365d26029217be731f0e2f6c5bdf835423de /bundles
parentdf69dd3f67f07cab816343aeae962365c13be333 (diff)
parentc82c996ca782fb04db7c2f281a07f7fa3fc796c5 (diff)
downloadorg.eclipse.efxclipse-04c4cc20b1e804b86f78e5b12ef6f30670b3fece.tar.gz
org.eclipse.efxclipse-04c4cc20b1e804b86f78e5b12ef6f30670b3fece.tar.xz
org.eclipse.efxclipse-04c4cc20b1e804b86f78e5b12ef6f30670b3fece.zip
Merge branch 'codeeditor'
Diffstat (limited to 'bundles')
-rw-r--r--bundles/code/org.eclipse.fx.code.editor.configuration.text.fx/.settings/ca.ecliptical.pde.ds.prefs6
-rw-r--r--bundles/code/org.eclipse.fx.code.editor.e4/META-INF/MANIFEST.MF3
-rw-r--r--bundles/code/org.eclipse.fx.code.editor.e4/OSGI-INF/services/org.eclipse.fx.code.editor.e4.internal.SearchProviderTypeProviderCF.xml9
-rw-r--r--bundles/code/org.eclipse.fx.code.editor.e4/src/org/eclipse/fx/code/editor/e4/internal/SearchProviderTypeProviderCF.java29
-rw-r--r--bundles/code/org.eclipse.fx.code.editor.fx.e4/META-INF/MANIFEST.MF6
-rw-r--r--bundles/code/org.eclipse.fx.code.editor.fx.e4/OSGI-INF/services/org.eclipse.fx.code.editor.fx.e4.internal.ContextInformationPresenterTypeProviderContextFunction.xml9
-rw-r--r--bundles/code/org.eclipse.fx.code.editor.fx.e4/src/org/eclipse/fx/code/editor/fx/e4/internal/ContextInformationPresenterTypeProviderContextFunction.java37
-rw-r--r--bundles/code/org.eclipse.fx.code.editor.fx.e4/src/org/eclipse/fx/code/editor/fx/e4/internal/EditorOpenerContextFunction.java1
-rw-r--r--bundles/code/org.eclipse.fx.code.editor.fx.themes/css/dark-highlight.css4
-rw-r--r--bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/ContextInformationPresenter.java9
-rw-r--r--bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/ContextInformationPresenterTypeProvider.java8
-rw-r--r--bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/FXCompletionProposal.java22
-rw-r--r--bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/internal/DefaultSourceViewerConfiguration.java48
-rw-r--r--bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/CodeReference.java12
-rw-r--r--bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/CompletionProposal.java11
-rw-r--r--bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/ContextInformation.java33
-rw-r--r--bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/SearchProvider.java19
-rw-r--r--bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/SearchProviderTypeProvider.java5
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/META-INF/MANIFEST.MF1
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/DefaultDocumentAdapter.java6
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/TextViewer.java12
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ContentAssistant.java37
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ContentProposalPopup.java233
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ContextInformationPopup.java175
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ICompletionProposal.java31
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContentAssistListener.java32
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContentAssistProcessor.java86
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContextInformation.java88
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContextInformationPresenter.java47
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContextInformationValidator.java45
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/AnnotationModelSupport.java198
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/InvisibleCharSupport.java174
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/LineNumberSupport.java147
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/TextAnnotationPresenterWrapper.java34
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/WrappedAnnotation.java9
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/WrappedLineRulerAnnotationPresenter.java70
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/WrappedTextAnnotationPresenter.java51
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/AnnotationPresenter.java10
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/IAnnotationPresenter.java11
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/ILineRulerAnnotationPresenter.java22
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/ITextAnnotationPresenter.java18
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/SourceViewer.java114
-rw-r--r--bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/SourceViewerConfiguration.java4
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/.settings/org.eclipse.pde.core.prefs2
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/META-INF/MANIFEST.MF4
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/build.properties1
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/DefaultContent.java2
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/FXBindUtil.java102
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/NodeCachePane.java43
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/ReuseCache.java66
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextArea.java142
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextLayoutContainer.java487
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/TextChangedEvent.java12
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/TextChangingEvent.java5
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/VerticalLineFlow.java158
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/HoverSupport.java53
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/StyledTextBehavior.java87
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/TextPositionSupport.java85
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/ContentView.java816
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/DebugMarker.java40
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/DynCachePane.java102
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/LineHelper.java242
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/LineNode.java718
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/LineRuler.java57
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/ScrollbarPane.java61
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/Scroller.java163
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/Segment.java60
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/TextNode.java543
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/VFlow.java323
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/Annotation.java4
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/AnnotationPresenter.java13
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/AnnotationProvider.java15
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/LineRulerAnnotationPresenter.java28
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/TextAnnotation.java7
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/TextAnnotationPresenter.java9
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/skin/StyledTextSkin.java1054
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/styledtextarea.css27
77 files changed, 5874 insertions, 1583 deletions
diff --git a/bundles/code/org.eclipse.fx.code.editor.configuration.text.fx/.settings/ca.ecliptical.pde.ds.prefs b/bundles/code/org.eclipse.fx.code.editor.configuration.text.fx/.settings/ca.ecliptical.pde.ds.prefs
new file mode 100644
index 000000000..7818b873d
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.code.editor.configuration.text.fx/.settings/ca.ecliptical.pde.ds.prefs
@@ -0,0 +1,6 @@
+classpath=true
+eclipse.preferences.version=1
+enabled=true
+path=OSGI-INF/services
+validationErrorLevel=error
+validationErrorLevel.missingImplicitUnbindMethod=error
diff --git a/bundles/code/org.eclipse.fx.code.editor.e4/META-INF/MANIFEST.MF b/bundles/code/org.eclipse.fx.code.editor.e4/META-INF/MANIFEST.MF
index e43ea980f..55694c066 100644
--- a/bundles/code/org.eclipse.fx.code.editor.e4/META-INF/MANIFEST.MF
+++ b/bundles/code/org.eclipse.fx.code.editor.e4/META-INF/MANIFEST.MF
@@ -29,5 +29,6 @@ Service-Component: OSGI-INF/services/org.eclipse.fx.code.editor.e4.internal.Docu
OSGI-INF/services/org.eclipse.fx.code.editor.e4.internal.DocumentContextFunction.xml,
OSGI-INF/services/org.eclipse.fx.code.editor.e4.internal.InputContextFunction.xml,
OSGI-INF/services/org.eclipse.fx.code.editor.e4.internal.ProposalComputerTypeProviderContextFunction.xml,
- OSGI-INF/services/org.eclipse.fx.code.editor.e4.internal.HoverInformationProviderTypeProviderCF.xml
+ OSGI-INF/services/org.eclipse.fx.code.editor.e4.internal.HoverInformationProviderTypeProviderCF.xml,
+ OSGI-INF/services/org.eclipse.fx.code.editor.e4.internal.SearchProviderTypeProviderCF.xml
Bundle-ActivationPolicy: lazy
diff --git a/bundles/code/org.eclipse.fx.code.editor.e4/OSGI-INF/services/org.eclipse.fx.code.editor.e4.internal.SearchProviderTypeProviderCF.xml b/bundles/code/org.eclipse.fx.code.editor.e4/OSGI-INF/services/org.eclipse.fx.code.editor.e4.internal.SearchProviderTypeProviderCF.xml
new file mode 100644
index 000000000..7a173f15b
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.code.editor.e4/OSGI-INF/services/org.eclipse.fx.code.editor.e4.internal.SearchProviderTypeProviderCF.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.2.0" name="org.eclipse.fx.code.editor.e4.internal.SearchProviderTypeProviderCF">
+ <property name="service.context.key" value="org.eclipse.fx.code.editor.services.SearchProvider"/>
+ <service>
+ <provide interface="org.eclipse.e4.core.contexts.IContextFunction"/>
+ </service>
+ <reference bind="registerService" cardinality="0..n" interface="org.eclipse.fx.code.editor.services.SearchProviderTypeProvider" name="registerService" policy="dynamic" policy-option="greedy" unbind="unregisterService"/>
+ <implementation class="org.eclipse.fx.code.editor.e4.internal.SearchProviderTypeProviderCF"/>
+</scr:component> \ No newline at end of file
diff --git a/bundles/code/org.eclipse.fx.code.editor.e4/src/org/eclipse/fx/code/editor/e4/internal/SearchProviderTypeProviderCF.java b/bundles/code/org.eclipse.fx.code.editor.e4/src/org/eclipse/fx/code/editor/e4/internal/SearchProviderTypeProviderCF.java
new file mode 100644
index 000000000..8a87499ba
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.code.editor.e4/src/org/eclipse/fx/code/editor/e4/internal/SearchProviderTypeProviderCF.java
@@ -0,0 +1,29 @@
+package org.eclipse.fx.code.editor.e4.internal;
+
+import java.util.Map;
+
+import org.eclipse.e4.core.contexts.IContextFunction;
+import org.eclipse.fx.code.editor.e4.InputBasedContextFunction;
+import org.eclipse.fx.code.editor.services.SearchProvider;
+import org.eclipse.fx.code.editor.services.SearchProviderTypeProvider;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+
+@SuppressWarnings("restriction")
+@Component(service=IContextFunction.class,property={"service.context.key=org.eclipse.fx.code.editor.services.SearchProvider"})
+public class SearchProviderTypeProviderCF extends InputBasedContextFunction<SearchProvider, SearchProviderTypeProvider> {
+
+ @Override
+ @Reference(cardinality=ReferenceCardinality.MULTIPLE,policy=ReferencePolicy.DYNAMIC,policyOption=ReferencePolicyOption.GREEDY)
+ public void registerService(SearchProviderTypeProvider service, Map<String, Object> properties) {
+ super.registerService(service, properties);
+ }
+
+ @Override
+ public void unregisterService(SearchProviderTypeProvider service) {
+ super.unregisterService(service);
+ }
+}
diff --git a/bundles/code/org.eclipse.fx.code.editor.fx.e4/META-INF/MANIFEST.MF b/bundles/code/org.eclipse.fx.code.editor.fx.e4/META-INF/MANIFEST.MF
index 8a2b4351b..a2d38101a 100644
--- a/bundles/code/org.eclipse.fx.code.editor.fx.e4/META-INF/MANIFEST.MF
+++ b/bundles/code/org.eclipse.fx.code.editor.fx.e4/META-INF/MANIFEST.MF
@@ -10,7 +10,8 @@ Require-Bundle: org.eclipse.e4.core.contexts;bundle-version="1.4.0",
org.eclipse.e4.ui.workbench;bundle-version="1.3.0",
org.eclipse.e4.core.di;bundle-version="1.5.0",
org.eclipse.e4.core.services;bundle-version="2.0.0",
- org.eclipse.fx.text.ui;bundle-version="2.2.0"
+ org.eclipse.fx.text.ui;bundle-version="2.2.0",
+ org.eclipse.text;bundle-version="3.5.400"
Import-Package: javax.inject,
org.eclipse.fx.code.editor;version="2.2.0",
org.eclipse.fx.code.editor.e4;version="2.2.0",
@@ -26,6 +27,7 @@ Service-Component: OSGI-INF/services/org.eclipse.fx.code.editor.fx.e4.internal.S
OSGI-INF/services/org.eclipse.fx.code.editor.fx.e4.internal.ActiveOutlineContextFunction.xml,
OSGI-INF/services/org.eclipse.fx.code.editor.fx.e4.internal.EditorOpenerContextFunction.xml,
OSGI-INF/services/org.eclipse.fx.code.editor.fx.e4.internal.EditorContainerServiceContextFunction.xml,
- OSGI-INF/services/org.eclipse.fx.code.editor.fx.e4.internal.CompletionProposalPresenterTypeProviderContextFunction.xml
+ OSGI-INF/services/org.eclipse.fx.code.editor.fx.e4.internal.CompletionProposalPresenterTypeProviderContextFunction.xml,
+ OSGI-INF/services/org.eclipse.fx.code.editor.fx.e4.internal.ContextInformationPresenterTypeProviderContextFunction.xml
Bundle-Vendor: Eclipse.org
Export-Package: org.eclipse.fx.code.editor.fx.e4
diff --git a/bundles/code/org.eclipse.fx.code.editor.fx.e4/OSGI-INF/services/org.eclipse.fx.code.editor.fx.e4.internal.ContextInformationPresenterTypeProviderContextFunction.xml b/bundles/code/org.eclipse.fx.code.editor.fx.e4/OSGI-INF/services/org.eclipse.fx.code.editor.fx.e4.internal.ContextInformationPresenterTypeProviderContextFunction.xml
new file mode 100644
index 000000000..f59cd12df
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.code.editor.fx.e4/OSGI-INF/services/org.eclipse.fx.code.editor.fx.e4.internal.ContextInformationPresenterTypeProviderContextFunction.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.2.0" name="org.eclipse.fx.code.editor.fx.e4.internal.ContextInformationPresenterTypeProviderContextFunction">
+ <property name="service.context.key" value="org.eclipse.fx.code.editor.fx.services.ContextInformationPresenter"/>
+ <service>
+ <provide interface="org.eclipse.e4.core.contexts.IContextFunction"/>
+ </service>
+ <reference bind="registerService" cardinality="0..n" interface="org.eclipse.fx.code.editor.fx.services.ContextInformationPresenterTypeProvider" name="registerService" policy="dynamic" policy-option="greedy" unbind="unregisterService"/>
+ <implementation class="org.eclipse.fx.code.editor.fx.e4.internal.ContextInformationPresenterTypeProviderContextFunction"/>
+</scr:component> \ No newline at end of file
diff --git a/bundles/code/org.eclipse.fx.code.editor.fx.e4/src/org/eclipse/fx/code/editor/fx/e4/internal/ContextInformationPresenterTypeProviderContextFunction.java b/bundles/code/org.eclipse.fx.code.editor.fx.e4/src/org/eclipse/fx/code/editor/fx/e4/internal/ContextInformationPresenterTypeProviderContextFunction.java
new file mode 100644
index 000000000..1ec5549b0
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.code.editor.fx.e4/src/org/eclipse/fx/code/editor/fx/e4/internal/ContextInformationPresenterTypeProviderContextFunction.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+* Copyright (c) 2015 BestSolution.at and others.
+* All rights reserved. This program and the accompanying materials
+* are made available under the terms of the Eclipse Public License v1.0
+* which accompanies this distribution, and is available at
+* http://www.eclipse.org/legal/epl-v10.html
+*
+* Contributors:
+* Tom Schindl<tom.schindl@bestsolution.at> - initial API and implementation
+*******************************************************************************/
+package org.eclipse.fx.code.editor.fx.e4.internal;
+
+import java.util.Map;
+
+import org.eclipse.e4.core.contexts.IContextFunction;
+import org.eclipse.fx.code.editor.e4.InputBasedContextFunction;
+import org.eclipse.fx.code.editor.fx.services.ContextInformationPresenter;
+import org.eclipse.fx.code.editor.fx.services.ContextInformationPresenterTypeProvider;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+
+@SuppressWarnings("restriction")
+@Component(service=IContextFunction.class,property={"service.context.key=org.eclipse.fx.code.editor.fx.services.ContextInformationPresenter"})
+public class ContextInformationPresenterTypeProviderContextFunction extends InputBasedContextFunction<ContextInformationPresenter, ContextInformationPresenterTypeProvider> {
+
+ @Reference(cardinality=ReferenceCardinality.MULTIPLE,policy=ReferencePolicy.DYNAMIC,policyOption=ReferencePolicyOption.GREEDY)
+ public void registerService(ContextInformationPresenterTypeProvider service, Map<String, Object> properties) {
+ super.registerService(service, properties);
+ }
+
+ public void unregisterService(ContextInformationPresenterTypeProvider service) {
+ super.unregisterService(service);
+ }
+} \ No newline at end of file
diff --git a/bundles/code/org.eclipse.fx.code.editor.fx.e4/src/org/eclipse/fx/code/editor/fx/e4/internal/EditorOpenerContextFunction.java b/bundles/code/org.eclipse.fx.code.editor.fx.e4/src/org/eclipse/fx/code/editor/fx/e4/internal/EditorOpenerContextFunction.java
index 033630534..2e14a2249 100644
--- a/bundles/code/org.eclipse.fx.code.editor.fx.e4/src/org/eclipse/fx/code/editor/fx/e4/internal/EditorOpenerContextFunction.java
+++ b/bundles/code/org.eclipse.fx.code.editor.fx.e4/src/org/eclipse/fx/code/editor/fx/e4/internal/EditorOpenerContextFunction.java
@@ -131,6 +131,7 @@ public class EditorOpenerContextFunction extends ServiceContextFunction<EditorOp
.filter( e -> e.test(uri)).findFirst()
.map( e -> e.getBundleClassURI(uri))
.orElse("bundleclass://org.eclipse.fx.code.editor.fx/org.eclipse.fx.code.editor.fx.TextEditor");
+// .orElse("bundleclass://at.bestsolution.set.fx/at.bestsolution.set.fx.editor.SetTextEditor");
part.setContributionURI(editorBundleURI);
part.setContributorURI("platform:/plugin/org.eclipse.fx.code.editor.fx.e4");
String iconUri = fileIconProvider
diff --git a/bundles/code/org.eclipse.fx.code.editor.fx.themes/css/dark-highlight.css b/bundles/code/org.eclipse.fx.code.editor.fx.themes/css/dark-highlight.css
index 5b5cf583a..9ad8aa231 100644
--- a/bundles/code/org.eclipse.fx.code.editor.fx.themes/css/dark-highlight.css
+++ b/bundles/code/org.eclipse.fx.code.editor.fx.themes/css/dark-highlight.css
@@ -33,6 +33,10 @@
-fx-background-color: #414141;
}
+.styled-text-area .current-line {
+ -fx-background-color: #414141;
+}
+
.styled-text-area .line-ruler-text {
-fx-text-fill: #c7c7c7;
}
diff --git a/bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/ContextInformationPresenter.java b/bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/ContextInformationPresenter.java
new file mode 100644
index 000000000..f4398d76d
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/ContextInformationPresenter.java
@@ -0,0 +1,9 @@
+package org.eclipse.fx.code.editor.fx.services;
+
+import org.eclipse.fx.code.editor.services.ContextInformation;
+import org.eclipse.fx.text.ui.contentassist.IContextInformation;
+
+
+public interface ContextInformationPresenter {
+ public IContextInformation createInformation(ContextInformation information);
+}
diff --git a/bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/ContextInformationPresenterTypeProvider.java b/bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/ContextInformationPresenterTypeProvider.java
new file mode 100644
index 000000000..11000a28a
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/ContextInformationPresenterTypeProvider.java
@@ -0,0 +1,8 @@
+package org.eclipse.fx.code.editor.fx.services;
+
+import org.eclipse.fx.code.editor.services.InputDependentTypeProviderService;
+
+@SuppressWarnings("restriction")
+public interface ContextInformationPresenterTypeProvider extends InputDependentTypeProviderService<ContextInformationPresenter> {
+
+}
diff --git a/bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/FXCompletionProposal.java b/bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/FXCompletionProposal.java
index 80084edbd..d545cd8b5 100644
--- a/bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/FXCompletionProposal.java
+++ b/bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/FXCompletionProposal.java
@@ -4,6 +4,7 @@ import java.util.function.Supplier;
import org.eclipse.fx.code.editor.services.CompletionProposal;
import org.eclipse.fx.text.ui.contentassist.ICompletionProposal;
+import org.eclipse.fx.text.ui.contentassist.IContextInformation;
import org.eclipse.fx.ui.controls.styledtext.TextSelection;
import org.eclipse.jface.text.IDocument;
@@ -14,15 +15,20 @@ public class FXCompletionProposal implements ICompletionProposal {
private final CompletionProposal proposal;
private final Supplier<Node> graphicSupplier;
private final CharSequence label;
+ private final String fHoverInfo;
- public FXCompletionProposal(CompletionProposal proposal, Supplier<Node> graphicSupplier) {
- this(proposal,proposal.getLabel(),graphicSupplier);
+ private final IContextInformation fContextInformation;
+
+ public FXCompletionProposal(CompletionProposal proposal, Supplier<Node> graphicSupplier, IContextInformation contextInformation, String hoverInfo) {
+ this(proposal, proposal.getLabel(), graphicSupplier, contextInformation, hoverInfo);
}
- public FXCompletionProposal(CompletionProposal proposal, CharSequence label, Supplier<Node> graphicSupplier) {
+ public FXCompletionProposal(CompletionProposal proposal, CharSequence label, Supplier<Node> graphicSupplier, IContextInformation contextInformation, String hoverInfo) {
this.proposal = proposal;
this.label = label;
this.graphicSupplier = graphicSupplier;
+ this.fContextInformation = contextInformation;
+ this.fHoverInfo = hoverInfo;
}
@Override
@@ -45,4 +51,14 @@ public class FXCompletionProposal implements ICompletionProposal {
org.eclipse.fx.code.editor.services.CompletionProposal.TextSelection selection = proposal.getSelection(document);
return new TextSelection(selection.offset, selection.length);
}
+
+ @Override
+ public IContextInformation getContextInformation() {
+ return fContextInformation;
+ }
+
+ @Override
+ public String getHoverInfo() {
+ return this.fHoverInfo;
+ }
}
diff --git a/bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/internal/DefaultSourceViewerConfiguration.java b/bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/internal/DefaultSourceViewerConfiguration.java
index 52f8aeee5..a3bd5cb69 100644
--- a/bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/internal/DefaultSourceViewerConfiguration.java
+++ b/bundles/code/org.eclipse.fx.code.editor.fx/src/org/eclipse/fx/code/editor/fx/services/internal/DefaultSourceViewerConfiguration.java
@@ -20,9 +20,11 @@ import org.eclipse.fx.text.ui.contentassist.ContentAssistContextData;
import org.eclipse.fx.text.ui.contentassist.ContentAssistant;
import org.eclipse.fx.text.ui.contentassist.ICompletionProposal;
import org.eclipse.fx.text.ui.contentassist.IContentAssistant;
+import org.eclipse.fx.text.ui.contentassist.IContextInformation;
import org.eclipse.fx.text.ui.presentation.IPresentationReconciler;
import org.eclipse.fx.text.ui.presentation.PresentationReconciler;
import org.eclipse.fx.text.ui.source.AnnotationPresenter;
+import org.eclipse.fx.text.ui.source.ILineRulerAnnotationPresenter;
import org.eclipse.fx.text.ui.source.ISourceViewer;
import org.eclipse.fx.text.ui.source.SourceViewerConfiguration;
import org.eclipse.fx.ui.controls.styledtext.TextSelection;
@@ -38,7 +40,7 @@ public class DefaultSourceViewerConfiguration extends SourceViewerConfiguration
private final PresentationReconciler reconciler;
private final ProposalComputer proposalComputer;
private final IAnnotationModel annotationModel;
- private final List<AnnotationPresenter> annotationPresenters;
+ private final AnnotationPresenter annotationPresenter;
private final HoverInformationProvider hoverInformationProvider;
private final CompletionProposalPresenter proposalPresenter;
private ContentAssistant contentAssistant;
@@ -49,7 +51,7 @@ public class DefaultSourceViewerConfiguration extends SourceViewerConfiguration
PresentationReconciler reconciler,
@Optional ProposalComputer proposalComputer,
@Optional IAnnotationModel annotationModel,
- @Optional AnnotationPresenter presenter,
+ @Optional AnnotationPresenter annotationPresenter,
@Optional HoverInformationProvider hoverInformationProvider,
@Optional CompletionProposalPresenter proposalPresenter
) {
@@ -59,11 +61,8 @@ public class DefaultSourceViewerConfiguration extends SourceViewerConfiguration
this.proposalComputer = proposalComputer;
this.annotationModel = annotationModel;
this.proposalPresenter = proposalPresenter == null ? DefaultProposal::new : proposalPresenter;
- if( presenter != null ) {
- this.annotationPresenters = Collections.singletonList(presenter);
- } else {
- this.annotationPresenters = Collections.emptyList();
- }
+
+ this.annotationPresenter = annotationPresenter;
}
@Override
@@ -109,8 +108,8 @@ public class DefaultSourceViewerConfiguration extends SourceViewerConfiguration
}
@Override
- public List<AnnotationPresenter> getAnnotationPresenters() {
- return annotationPresenters;
+ public AnnotationPresenter getAnnotationPresenter() {
+ return annotationPresenter;
}
@Override
@@ -160,5 +159,36 @@ public class DefaultSourceViewerConfiguration extends SourceViewerConfiguration
org.eclipse.fx.code.editor.services.CompletionProposal.TextSelection selection = proposal.getSelection(document);
return selection == null ? TextSelection.EMPTY : new TextSelection(selection.offset, selection.length);
}
+
+ @Override
+ public IContextInformation getContextInformation() {
+ return new IContextInformation() {
+ // TODO fix this here
+ @Override
+ public String getInformationDisplayString() {
+ return "InformationDisplayString";
+ }
+
+ @Override
+ public Node getGraphic() {
+ return null;
+ }
+
+ @Override
+ public int getContextInformationPosition() {
+ return 0;
+ }
+
+ @Override
+ public String getContextDisplayString() {
+ return "ContextDisplayString";
+ }
+ };
+ }
+
+ @Override
+ public String getHoverInfo() {
+ return null;
+ }
}
}
diff --git a/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/CodeReference.java b/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/CodeReference.java
new file mode 100644
index 000000000..976fc9103
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/CodeReference.java
@@ -0,0 +1,12 @@
+package org.eclipse.fx.code.editor;
+
+import org.eclipse.jface.text.IRegion;
+
+/**
+ * references a region inside a document.
+ *
+ */
+public interface CodeReference {
+ Input<?> getInput();
+ IRegion getRegion();
+}
diff --git a/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/CompletionProposal.java b/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/CompletionProposal.java
index 34429fcac..f22b753c0 100644
--- a/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/CompletionProposal.java
+++ b/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/CompletionProposal.java
@@ -18,6 +18,8 @@ public interface CompletionProposal {
public void apply(IDocument document);
public TextSelection getSelection(IDocument document);
+ public ContextInformation getContextInformation();
+
public static class BaseCompletetionProposal implements CompletionProposal {
private final CharSequence label;
@@ -26,13 +28,15 @@ public interface CompletionProposal {
private final int replacementOffset;
private final int replacementLength;
private final int cursorPosition;
+ private final ContextInformation contextInformation;
- public BaseCompletetionProposal(String replacementString, int replacementOffset, int replacementLength, CharSequence label) {
+ public BaseCompletetionProposal(String replacementString, int replacementOffset, int replacementLength, CharSequence label, ContextInformation contextInformation) {
this.replacementString = replacementString;
this.replacementOffset = replacementOffset;
this.replacementLength = replacementLength;
this.cursorPosition = replacementString.length();
this.label = label;
+ this.contextInformation = contextInformation;
}
@Override
@@ -70,6 +74,11 @@ public interface CompletionProposal {
public TextSelection getSelection(IDocument document) {
return new TextSelection(replacementOffset+cursorPosition, 0);
}
+
+ @Override
+ public ContextInformation getContextInformation() {
+ return contextInformation;
+ }
}
}
diff --git a/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/ContextInformation.java b/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/ContextInformation.java
new file mode 100644
index 000000000..1b77728b1
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/ContextInformation.java
@@ -0,0 +1,33 @@
+package org.eclipse.fx.code.editor.services;
+
+public interface ContextInformation {
+
+ public CharSequence getText();
+ public int getOffset();
+
+ public static class BaseContextInformation implements ContextInformation {
+
+ private final CharSequence label;
+
+ private final int offset;
+
+ public BaseContextInformation(int offset, CharSequence label) {
+ this.offset = offset;
+ this.label = label;
+ }
+
+ @Override
+ public CharSequence getText() {
+ return label;
+ }
+
+ @Override
+ public int getOffset() {
+ return offset;
+ }
+
+ }
+
+
+
+}
diff --git a/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/SearchProvider.java b/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/SearchProvider.java
new file mode 100644
index 000000000..5e937932e
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/SearchProvider.java
@@ -0,0 +1,19 @@
+package org.eclipse.fx.code.editor.services;
+
+import java.util.List;
+import java.util.concurrent.Future;
+
+import org.eclipse.fx.code.editor.CodeReference;
+
+public interface SearchProvider {
+
+
+
+ Future<CodeReference> findDeclaration(int offset);
+
+ Future<List<CodeReference>> findImplementations(int offset);
+
+ Future<List<CodeReference>> findReferences(int offset);
+
+
+}
diff --git a/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/SearchProviderTypeProvider.java b/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/SearchProviderTypeProvider.java
new file mode 100644
index 000000000..bfb64af7d
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.code.editor/src/org/eclipse/fx/code/editor/services/SearchProviderTypeProvider.java
@@ -0,0 +1,5 @@
+package org.eclipse.fx.code.editor.services;
+
+public interface SearchProviderTypeProvider extends InputDependentTypeProviderService<SearchProvider> {
+
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/META-INF/MANIFEST.MF b/bundles/code/org.eclipse.fx.text.ui/META-INF/MANIFEST.MF
index 103034e93..7139dd936 100644
--- a/bundles/code/org.eclipse.fx.text.ui/META-INF/MANIFEST.MF
+++ b/bundles/code/org.eclipse.fx.text.ui/META-INF/MANIFEST.MF
@@ -18,3 +18,4 @@ Export-Package: org.eclipse.fx.text.ui,
org.eclipse.fx.text.ui.rules;x-internal:=true,
org.eclipse.fx.text.ui.source
Bundle-Vendor: Eclipse.org
+Import-Package: com.google.common.collect
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/DefaultDocumentAdapter.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/DefaultDocumentAdapter.java
index 443173e3d..a608a2677 100644
--- a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/DefaultDocumentAdapter.java
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/DefaultDocumentAdapter.java
@@ -273,7 +273,7 @@ class DefaultDocumentAdapter implements IDocumentAdapter, IDocumentListener, IDo
} else {
if (event.getOffset() < fRememberedLengthOfFirstLine)
fLineDelimiter= null;
- fireTextChanged();
+ fireTextChanged(event.getOffset(), event.getLength(), event.getText());
}
}
@@ -319,12 +319,12 @@ class DefaultDocumentAdapter implements IDocumentAdapter, IDocumentListener, IDo
/**
* Sends a text changed event to all registered listeners.
*/
- private void fireTextChanged() {
+ private void fireTextChanged(int offset, int replaceLength, String newText) {
if (!fIsForwarding)
return;
- TextChangedEvent event = TextChangedEvent.textChanged(this);
+ TextChangedEvent event = TextChangedEvent.textChanged(this, offset, replaceLength, newText);
if (fTextChangeListeners != null && fTextChangeListeners.size() > 0) {
Iterator e= new ArrayList(fTextChangeListeners).iterator();
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/TextViewer.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/TextViewer.java
index 5f064753b..e3335294c 100644
--- a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/TextViewer.java
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/TextViewer.java
@@ -19,11 +19,10 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import javafx.scene.input.KeyCode;
-import javafx.scene.layout.AnchorPane;
-
import org.eclipse.core.runtime.Assert;
import org.eclipse.fx.core.Util;
+import org.eclipse.fx.text.ui.internal.InvisibleCharSupport;
+import org.eclipse.fx.text.ui.internal.LineNumberSupport;
import org.eclipse.fx.ui.controls.styledtext.StyleRange;
import org.eclipse.fx.ui.controls.styledtext.StyledTextArea;
import org.eclipse.fx.ui.controls.styledtext.VerifyEvent;
@@ -48,6 +47,9 @@ import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.projection.ChildDocument;
import org.eclipse.jface.text.projection.ChildDocumentManager;
+import javafx.scene.input.KeyCode;
+import javafx.scene.layout.AnchorPane;
+
public class TextViewer extends AnchorPane implements
ITextViewer, ITextViewerExtension, ITextViewerExtension2, ITextViewerExtension4, ITextViewerExtension6, ITextViewerExtension7, ITextViewerExtension8
/*,
@@ -108,6 +110,9 @@ public class TextViewer extends AnchorPane implements
getChildren().add(fTextWidget);
fTextWidget.addEventHandler(VerifyEvent.VERIFY, this::onVerify);
+
+ new LineNumberSupport(fTextWidget).install();
+ new InvisibleCharSupport(fTextWidget).install();
}
private void onVerify(VerifyEvent event) {
@@ -1006,4 +1011,5 @@ public class TextViewer extends AnchorPane implements
fStateMask= stateMask;
}
}
+
}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ContentAssistant.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ContentAssistant.java
index 65c80a573..046158db3 100644
--- a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ContentAssistant.java
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ContentAssistant.java
@@ -21,8 +21,9 @@ import org.eclipse.fx.ui.controls.styledtext.VerifyEvent;
public class ContentAssistant implements IContentAssistant {
private final Function<ContentAssistContextData, List<ICompletionProposal>> proposalComputer;
- private ITextViewer viewer;
- private ContentProposalPopup popuop;
+ private ITextViewer fViewer;
+ private ContentProposalPopup fProposalPopup;
+ private ContextInformationPopup fContextInfoPopup;
public ContentAssistant(Function<ContentAssistContextData, List<ICompletionProposal>> proposalComputer) {
this.proposalComputer = proposalComputer;
@@ -30,10 +31,12 @@ public class ContentAssistant implements IContentAssistant {
@Override
public void install(ITextViewer textViewer) {
- if( this.viewer == null ) {
- this.viewer = textViewer;
- this.popuop = new ContentProposalPopup(textViewer,proposalComputer);
+ if( this.fViewer == null ) {
+ this.fViewer = textViewer;
+ this.fProposalPopup = new ContentProposalPopup(this, textViewer,proposalComputer);
textViewer.getTextWidget().addEventHandler(VerifyEvent.VERIFY, this::handleVerify);
+
+ fContextInfoPopup = new ContextInformationPopup(this, textViewer);
}
}
@@ -43,18 +46,32 @@ public class ContentAssistant implements IContentAssistant {
}
event.consume();
- List<ICompletionProposal> proposals = proposalComputer.apply(new ContentAssistContextData(this.viewer.getTextWidget().getCaretOffset(),this.viewer.getDocument()/*,""*/));
+ final int offset = this.fViewer.getTextWidget().getCaretOffset();
+
+ List<ICompletionProposal> proposals = proposalComputer.apply(new ContentAssistContextData(offset, this.fViewer.getDocument()/*,""*/));
if( proposals.size() == 1) {
- proposals.get(0).apply(this.viewer.getDocument());
- this.viewer.getTextWidget().setSelection(proposals.get(0).getSelection(this.viewer.getDocument()));
+ ICompletionProposal completionProposal = proposals.get(0);
+
+ completionProposal.apply(this.fViewer.getDocument());
+
+ showContextInformation(completionProposal.getContextInformation(), offset);
+
+ //this.fViewer.getTextWidget().setSelection(proposals.get(0).getSelection(this.fViewer.getDocument()));
} else if( ! proposals.isEmpty() ) {
// System.err.println(this.viewer.getTextWidget().getCaretLocation());
System.err.println();
- Point2D p = this.viewer.getTextWidget().getLocationAtOffset(this.viewer.getTextWidget().getCaretOffset());
+ Point2D p = this.fViewer.getTextWidget().getLocationAtOffset(this.fViewer.getTextWidget().getCaretOffset());
System.err.println(p);
- this.popuop.displayProposals(proposals, this.viewer.getTextWidget().getCaretOffset(), this.viewer.getTextWidget().localToScreen(p));
+ this.fProposalPopup.displayProposals(proposals, this.fViewer.getTextWidget().getCaretOffset(), this.fViewer.getTextWidget().localToScreen(p));
+ }
+
+ }
+
+ void showContextInformation(IContextInformation contextInformation, int offset) {
+ if (this.fContextInfoPopup != null) {
+ this.fContextInfoPopup.showContextInformation(contextInformation, offset);
}
}
}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ContentProposalPopup.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ContentProposalPopup.java
index ff78bf6fd..2ef7522fb 100644
--- a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ContentProposalPopup.java
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ContentProposalPopup.java
@@ -12,35 +12,56 @@ package org.eclipse.fx.text.ui.contentassist;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.BrokenBarrierException;
import java.util.function.Function;
import org.eclipse.fx.core.Util;
import org.eclipse.fx.text.ui.ITextViewer;
import org.eclipse.fx.ui.controls.list.SimpleListCell;
+import org.eclipse.fx.ui.controls.styledtext.StyledTextContent.TextChangeListener;
+import org.eclipse.fx.ui.controls.styledtext.TextChangedEvent;
+import org.eclipse.fx.ui.controls.styledtext.TextChangingEvent;
+import org.eclipse.fx.ui.controls.styledtext.VerifyEvent;
import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentListener;
+
+import com.sun.javafx.collections.ImmutableObservableList;
import javafx.application.Platform;
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
+import javafx.event.Event;
+import javafx.event.EventDispatchChain;
+import javafx.event.EventDispatcher;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.control.ListView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
+import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
+import javafx.scene.web.WebView;
import javafx.stage.PopupWindow;
-public class ContentProposalPopup {
+public class ContentProposalPopup implements IContentAssistListener {
private ITextViewer viewer;
private PopupWindow stage;
private ListView<ICompletionProposal> proposalList;
+ private WebView documentationView;
private String prefix;
private int offset;
private Function<ContentAssistContextData, List<ICompletionProposal>> proposalComputer;
- public ContentProposalPopup(ITextViewer viewer, Function<ContentAssistContextData, List<ICompletionProposal>> proposalComputer) {
+ private ContentAssistant fContentAssistant;
+
+ public ContentProposalPopup(ContentAssistant assistant, ITextViewer viewer, Function<ContentAssistContextData, List<ICompletionProposal>> proposalComputer) {
this.viewer = viewer;
this.proposalComputer = proposalComputer;
+ this.fContentAssistant = assistant;
}
public void displayProposals(List<ICompletionProposal> proposalList, int offset, Point2D position) {
@@ -55,37 +76,80 @@ public class ContentProposalPopup {
this.stage.setHeight(200);
this.stage.show(this.viewer.getTextWidget().getScene().getWindow());
this.stage.requestFocus();
+
+ this.stage.setOnShowing(this::subscribe);
+ this.stage.setOnHidden(this::unsubscribe);
}
- private void handleKeyTyped(KeyEvent event) {
- if( event.getCharacter().length() == 0 ) {
- return;
+ private void subscribe(Event e) {
+ this.viewer.getTextWidget().getContent().addTextChangeListener(this.textChangeListener);
+ this.viewer.getTextWidget().selectionProperty().addListener(this::onSelectionChange);
+ }
+ private void unsubscribe(Event e) {
+ this.viewer.getTextWidget().getContent().removeTextChangeListener(this.textChangeListener);
+ this.viewer.getTextWidget().selectionProperty().removeListener(this::onSelectionChange);
+ }
+
+ // TODO calling updateProposals is way too slow
+
+ private void onSelectionChange(Observable x) {
+ System.err.println("update trigger selection change");
+ updateProposals();
+ }
+
+ private TextChangeListener textChangeListener = new TextChangeListener() {
+ @Override
+ public void textSet(TextChangedEvent event) {
+ System.err.println("update trigger textSet");
+ updateProposals();
}
- String character = event.getCharacter();
- if( character.length() == 0 ) {
- return;
+ @Override
+ public void textChanging(TextChangingEvent event) {
}
- if (event.isControlDown() || event.isAltDown() || (Util.isMacOS() && event.isMetaDown())) {
- if (!((event.isControlDown() || Util.isMacOS()) && event.isAltDown())) {
- return;
- }
+ @Override
+ public void textChanged(TextChangedEvent event) {
+ System.err.println("update trigger textChanged");
+ updateProposals();
}
+ };
- if (character.charAt(0) > 0x1F
- && character.charAt(0) != 0x7F ) {
-// try {
- this.prefix = this.prefix+character;
-// viewer.getDocument().replace(offset, 0, character);
- this.offset += event.getCharacter().length();
-// viewer.getTextWidget().setCaretOffset(offset);
- updateProposals();
-// } catch (BadLocationException e) {
-// // TODO Auto-generated catch block
-// e.printStackTrace();
+
+ private void handleKeyTyped(KeyEvent event) {
+// Event.fireEvent(this.viewer.getTextWidget(), event);
+ return;
+//
+//
+//
+// if( event.getCharacter().length() == 0 ) {
+// return;
+// }
+//
+// String character = event.getCharacter();
+// if( character.length() == 0 ) {
+// return;
+// }
+//
+// if (event.isControlDown() || event.isAltDown() || (Util.isMacOS() && event.isMetaDown())) {
+// if (!((event.isControlDown() || Util.isMacOS()) && event.isAltDown())) {
+// return;
// }
- }
+// }
+//
+// if (character.charAt(0) > 0x1F
+// && character.charAt(0) != 0x7F ) {
+//// try {
+// this.prefix = this.prefix+character;
+//// viewer.getDocument().replace(offset, 0, character);
+// this.offset += event.getCharacter().length();
+//// viewer.getTextWidget().setCaretOffset(offset);
+// updateProposals();
+//// } catch (BadLocationException e) {
+//// // TODO Auto-generated catch block
+//// e.printStackTrace();
+//// }
+// }
}
private void updateProposals() {
@@ -99,49 +163,80 @@ public class ContentProposalPopup {
}
}
- private void handleKeyPressed(KeyEvent event) {
- if( event.getCode() == KeyCode.ESCAPE ) {
- event.consume();
- this.stage.hide();
- } else if( event.getCode() == KeyCode.BACK_SPACE ) {
- event.consume();
- this.offset -= 1;
- try {
- this.viewer.getDocument().replace(this.offset, 1, ""); //$NON-NLS-1$
- this.viewer.getTextWidget().setCaretOffset(this.offset);
- updateProposals();
- } catch (BadLocationException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- } else if( event.getCode() == KeyCode.ENTER ) {
- event.consume();
- applySelection();
- } else if( event.getCode() == KeyCode.LEFT ) {
- event.consume();
- this.offset -= 1;
- this.offset = Math.max(0, this.offset);
- this.viewer.getTextWidget().setCaretOffset(this.offset);
- updateProposals();
- } else if( event.getCode() == KeyCode.RIGHT ) {
- event.consume();
- this.offset += 1;
- this.offset = Math.min(this.viewer.getDocument().getLength()-1, this.offset);
- this.viewer.getTextWidget().setCaretOffset(this.offset);
- updateProposals();
- }
+ private void cancelProposal() {
+ this.stage.hide();
}
- private void applySelection() {
+ private void applySelectedProposal() {
ICompletionProposal selectedItem = this.proposalList.getSelectionModel().getSelectedItem();
if( selectedItem != null ) {
IDocument document = this.viewer.getDocument();
selectedItem.apply(document);
+
this.viewer.getTextWidget().setSelection(selectedItem.getSelection(document));
this.stage.hide();
+
+ this.fContentAssistant.showContextInformation(selectedItem.getContextInformation(), offset);
+ }
+ }
+
+ private void handleMouseClicked(MouseEvent event) {
+ if(event.isStillSincePress() && event.getClickCount() == 2) {
+ applySelectedProposal();
}
}
+ private void handleKeyPressed(KeyEvent event) {
+ switch (event.getCode()) {
+ case ESCAPE:
+ event.consume();
+ cancelProposal();
+ break;
+ case ENTER:
+ event.consume();
+ applySelectedProposal();
+ }
+
+
+// if( event.getCode() == KeyCode.ESCAPE ) {
+// event.consume();
+// this.stage.hide();
+// } else if( event.getCode() == KeyCode.BACK_SPACE ) {
+// event.consume();
+// this.offset -= 1;
+// try {
+// this.viewer.getDocument().replace(this.offset, 1, ""); //$NON-NLS-1$
+// this.viewer.getTextWidget().setCaretOffset(this.offset);
+// updateProposals();
+// } catch (BadLocationException e) {
+// // TODO Auto-generated catch block
+// e.printStackTrace();
+// }
+// } else if( event.getCode() == KeyCode.ENTER ) {
+// event.consume();
+// applySelection();
+// } else if( event.getCode() == KeyCode.LEFT ) {
+// event.consume();
+// this.offset -= 1;
+// this.offset = Math.max(0, this.offset);
+// this.viewer.getTextWidget().setCaretOffset(this.offset);
+// updateProposals();
+// } else if( event.getCode() == KeyCode.RIGHT ) {
+// event.consume();
+// this.offset += 1;
+// this.offset = Math.min(this.viewer.getDocument().getLength()-1, this.offset);
+// this.viewer.getTextWidget().setCaretOffset(this.offset);
+// updateProposals();
+// }
+ }
+
+ private String getDocumentation(ICompletionProposal proposal) {
+ if (proposal != null) {
+ return "<html><body><pre>"+proposal.getHoverInfo()+"</pre></body></html>";
+ }
+ return "<html></html>";
+ }
+
private void setup() {
if( this.stage == null ) {
this.stage = new PopupWindow() {
@@ -152,16 +247,22 @@ public class ContentProposalPopup {
this.stage.setHeight(200);
BorderPane p = new BorderPane();
p.setPrefHeight(200);
- p.setPrefWidth(400);
+ p.setPrefWidth(600);
this.stage.getScene().addEventFilter(KeyEvent.KEY_TYPED, this::handleKeyTyped);
this.stage.getScene().addEventFilter(KeyEvent.KEY_PRESSED, this::handleKeyPressed);
+
this.stage.getScene().getStylesheets().addAll(this.viewer.getTextWidget().getScene().getStylesheets());
this.proposalList = new ListView<>();
+ this.proposalList.setMinWidth(300);
this.proposalList.getStyleClass().add("content-proposal-list"); //$NON-NLS-1$
- this.proposalList.setOnMouseClicked((e) -> {
- if(e.getClickCount() == 1) {
- applySelection();
- }
+ this.proposalList.setOnMouseClicked(this::handleMouseClicked);
+
+ this.documentationView = new WebView();
+ this.documentationView.setPrefWidth(300);
+ this.documentationView.getStyleClass().add("content-proposal-doc"); //$NON-NLS-1$
+
+ this.proposalList.getSelectionModel().selectedItemProperty().addListener((ChangeListener<ICompletionProposal>) (observable, oldValue, newValue) -> {
+ this.documentationView.getEngine().loadContent(getDocumentation(newValue));
});
Function<ICompletionProposal, CharSequence> label = (c) -> c.getLabel();
@@ -171,6 +272,7 @@ public class ContentProposalPopup {
this.proposalList.setCellFactory((v) -> new SimpleListCell<ICompletionProposal>(label,graphic,css));
p.setCenter(this.proposalList);
+ p.setRight(this.documentationView);
this.stage.getScene().setRoot(p);
this.stage.focusedProperty().addListener((o) -> {
if( this.stage != null && ! this.stage.isFocused() ) {
@@ -183,4 +285,13 @@ public class ContentProposalPopup {
});
}
}
+
+ @Override
+ public boolean verifyKey(VerifyEvent event) {
+
+ System.err.println("CONTENT PROPOSAL POPUP: VerifyEvent " + event);
+
+ return true;
+ }
+
}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ContextInformationPopup.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ContextInformationPopup.java
new file mode 100644
index 000000000..7c9c56143
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ContextInformationPopup.java
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.fx.text.ui.contentassist;
+
+import java.util.Iterator;
+import java.util.Stack;
+
+import javax.swing.text.html.HTMLDocument.HTMLReader.HiddenAction;
+
+import org.eclipse.fx.text.ui.ITextViewer;
+import org.eclipse.fx.ui.controls.styledtext.VerifyEvent;
+
+import javafx.geometry.Point2D;
+import javafx.scene.control.Label;
+import javafx.scene.layout.BorderPane;
+import javafx.stage.Popup;
+import javafx.stage.PopupWindow;
+
+
+/**
+ * This class is used to present context information to the user.
+ * If multiple contexts are valid at the current cursor location,
+ * a list is presented from which the user may choose one context.
+ * Once the user makes their choice, or if there was only a single
+ * possible context, the context information is shown in a tool tip like popup. <p>
+ * If the tool tip is visible and the user wants to see context information of
+ * a context embedded into the one for which context information is displayed,
+ * context information for the embedded context is shown. As soon as the
+ * cursor leaves the embedded context area, the context information for
+ * the embedding context is shown again.
+ *
+ * @see IContextInformation
+ * @see IContextInformationValidator
+ */
+class ContextInformationPopup implements IContentAssistListener {
+
+
+ /**
+ * Represents the state necessary for embedding contexts.
+ *
+ * @since 2.0
+ */
+ static class ContextFrame {
+
+ final int fBeginOffset;
+ final int fOffset;
+ final int fVisibleOffset;
+ final IContextInformation fInformation;
+ final IContextInformationValidator fValidator;
+ final IContextInformationPresenter fPresenter;
+
+ /*
+ * @since 3.1
+ */
+ public ContextFrame(IContextInformation information, int beginOffset, int offset, int visibleOffset, IContextInformationValidator validator, IContextInformationPresenter presenter) {
+ fInformation = information;
+ fBeginOffset = beginOffset;
+ fOffset = offset;
+ fVisibleOffset = visibleOffset;
+ fValidator = validator;
+ fPresenter = presenter;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ContextFrame) {
+ ContextFrame frame= (ContextFrame) obj;
+ return fInformation.equals(frame.fInformation) && fBeginOffset == frame.fBeginOffset;
+ }
+ return super.equals(obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return (fInformation.hashCode() << 16) | fBeginOffset;
+ }
+ }
+
+ private ITextViewer fViewer;
+ private ContentAssistant fContentAssistant;
+
+ private PopupWindow fContextSelectorStage;
+ private IContextInformation[] fContextSelectorInput;
+ private String fLineDelimiter= null;
+
+ private PopupWindow fContextInfoPopup;
+ private BorderPane fRoot;
+ private Label fContent;
+
+
+ private Stack<ContextFrame> fContextFrameStack= new Stack<>();
+
+ /**
+ * The last removed context frame is remembered in order to not re-query the
+ * user about which context should be used.
+ *
+ * @since 3.0
+ */
+ private ContextFrame fLastContext= null;
+
+ /**
+ * Creates a new context information popup.
+ *
+ * @param contentAssistant the content assist for computing the context information
+ * @param viewer the viewer on top of which the context information is shown
+ */
+ public ContextInformationPopup(ContentAssistant contentAssistant, ITextViewer viewer) {
+ fContentAssistant= contentAssistant;
+ fViewer= viewer;
+
+ this.fContextInfoPopup = new PopupWindow() {
+ };
+ this.fContextInfoPopup.setAutoFix(false);
+ this.fContextInfoPopup.setAutoHide(false);
+ viewer.getTextWidget().sceneProperty().addListener( e -> {
+ if( viewer.getTextWidget().getScene() != null ) {
+ fContextInfoPopup.getScene().getStylesheets().setAll(viewer.getTextWidget().getScene().getStylesheets());
+ }
+
+ });
+ fRoot = new BorderPane();
+ fRoot.getStyleClass().add("styled-text-hover");
+ fContent = new Label();
+ fRoot.setCenter(fContent);
+ fContent.getStyleClass().add("styled-text-hover-text");
+ fContextInfoPopup.getScene().setRoot(fRoot);
+ }
+
+ /**
+ * Shows all possible contexts for the given cursor position of the viewer.
+ *
+ * @param autoActivated <code>true</code> if auto activated
+ * @return a potential error message or <code>null</code> in case of no error
+ */
+ public String showContextProposals(final boolean autoActivated) {
+ System.err.println("showContextProposals(" + autoActivated + ") (TODO)");
+ return "TODO";
+ }
+
+ /**
+ * Displays the given context information for the given offset.
+ *
+ * @param info the context information
+ * @param offset the offset
+ * @since 2.0
+ */
+ public void showContextInformation(final IContextInformation info, final int offset) {
+ if (info != null && info.getInformationDisplayString() != null && !info.getInformationDisplayString().isEmpty()) {
+ fContent.setText(info.getInformationDisplayString());
+ Point2D locationAtOffset = fViewer.getTextWidget().getLocationAtOffset(offset);
+ locationAtOffset = fViewer.getTextWidget().localToScreen(locationAtOffset);
+ if (locationAtOffset != null) {
+ fContextInfoPopup.show(fViewer.getTextWidget().getScene().getWindow(), locationAtOffset.getX(), locationAtOffset.getY());
+ }
+ }
+ else {
+ fContextInfoPopup.hide();
+ }
+ }
+
+ @Override
+ public boolean verifyKey(VerifyEvent event) {
+
+ return false;
+ }
+
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ICompletionProposal.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ICompletionProposal.java
index 63aa1370e..ad9aa1c1d 100644
--- a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ICompletionProposal.java
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/ICompletionProposal.java
@@ -17,9 +17,40 @@ import org.eclipse.jface.text.IDocument;
public interface ICompletionProposal {
public CharSequence getLabel();
+
+ public String getHoverInfo();
+
public Node getGraphic();
// public List<String> getStyles();
+ /**
+ * Inserts the proposed completion into the given document.
+ *
+ * @param document the document into which to insert the proposed completion
+ */
public void apply(IDocument document);
+
+ /**
+ * Returns the new selection after the proposal has been applied to
+ * the given document in absolute document coordinates. If it returns
+ * <code>null</code>, no new selection is set.
+ *
+ * A document change can trigger other document changes, which have
+ * to be taken into account when calculating the new selection. Typically,
+ * this would be done by installing a document listener or by using a
+ * document position during {@link #apply(IDocument)}.
+ *
+ * @param document the document into which the proposed completion has been inserted
+ * @return the new selection
+ */
public TextSelection getSelection(IDocument document);
+
+ /**
+ * Returns optional context information associated with this proposal.
+ * The context information will automatically be shown if the proposal
+ * has been applied.
+ *
+ * @return the context information for this proposal or <code>null</code>
+ */
+ IContextInformation getContextInformation();
}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContentAssistListener.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContentAssistListener.java
new file mode 100644
index 000000000..055eac941
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContentAssistListener.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.fx.text.ui.contentassist;
+
+
+import org.eclipse.fx.ui.controls.styledtext.VerifyEvent;
+
+
+/**
+ * An interface whereby listeners can not only receive key events,
+ * but can also consume them to prevent subsequent listeners from
+ * processing the event.
+ */
+interface IContentAssistListener {
+
+ /**
+ * Verifies the key event.
+ *
+ * @param event the verify event
+ * @return <code>true</code> if processing should be continued by additional listeners
+ * @see org.eclipse.swt.custom.VerifyKeyListener#verifyKey(VerifyEvent)
+ */
+ public boolean verifyKey(VerifyEvent event);
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContentAssistProcessor.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContentAssistProcessor.java
new file mode 100644
index 000000000..bcf38a9a8
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContentAssistProcessor.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.fx.text.ui.contentassist;
+
+import org.eclipse.fx.text.ui.ITextViewer;
+
+
+/**
+ * A content assist processor proposes completions and
+ * computes context information for a particular content type.
+ * A content assist processor is a {@link org.eclipse.jface.text.contentassist.IContentAssistant}
+ * plug-in.
+ * <p>
+ * This interface must be implemented by clients. Implementers should be
+ * registered with a content assistant in order to get involved in the
+ * assisting process.
+ * </p>
+*/
+public interface IContentAssistProcessor {
+
+ /**
+ * Returns a list of completion proposals based on the
+ * specified location within the document that corresponds
+ * to the current cursor position within the text viewer.
+ *
+ * @param viewer the viewer whose document is used to compute the proposals
+ * @param offset an offset within the document for which completions should be computed
+ * @return an array of completion proposals or <code>null</code> if no proposals are possible
+ */
+ ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset);
+
+ /**
+ * Returns information about possible contexts based on the
+ * specified location within the document that corresponds
+ * to the current cursor position within the text viewer.
+ *
+ * @param viewer the viewer whose document is used to compute the possible contexts
+ * @param offset an offset within the document for which context information should be computed
+ * @return an array of context information objects or <code>null</code> if no context could be found
+ */
+ IContextInformation[] computeContextInformation(ITextViewer viewer, int offset);
+
+ /**
+ * Returns the characters which when entered by the user should
+ * automatically trigger the presentation of possible completions.
+ *
+ * @return the auto activation characters for completion proposal or <code>null</code>
+ * if no auto activation is desired
+ */
+ char[] getCompletionProposalAutoActivationCharacters();
+
+ /**
+ * Returns the characters which when entered by the user should
+ * automatically trigger the presentation of context information.
+ *
+ * @return the auto activation characters for presenting context information
+ * or <code>null</code> if no auto activation is desired
+ */
+ char[] getContextInformationAutoActivationCharacters();
+
+ /**
+ * Returns the reason why this content assist processor
+ * was unable to produce any completion proposals or context information.
+ *
+ * @return an error message or <code>null</code> if no error occurred
+ */
+ String getErrorMessage();
+
+ /**
+ * Returns a validator used to determine when displayed context information
+ * should be dismissed. May only return <code>null</code> if the processor is
+ * incapable of computing context information. <p>
+ *
+ * @return a context information validator, or <code>null</code> if the processor
+ * is incapable of computing context information
+ */
+ IContextInformationValidator getContextInformationValidator();
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContextInformation.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContextInformation.java
new file mode 100644
index 000000000..ac27014e6
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContextInformation.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.fx.text.ui.contentassist;
+
+import javafx.scene.Node;
+
+
+/**
+ * The interface of context information presented to the user and
+ * generated by content assist processors.
+ * <p>
+ * In order to provide backward compatibility for clients of
+ * <code>IContextInformation</code>, extension interfaces are used to
+ * provide a means of evolution. The following extension interfaces
+ * exist:
+ * <ul>
+ * <li>{@link org.eclipse.jface.text.contentassist.IContextInformationExtension}
+ * since version 2.0 introducing the ability to freely position the
+ * context information.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * The interface can be implemented by clients. By default, clients use
+ * {@link org.eclipse.jface.text.contentassist.ContextInformation} as
+ * the standard implementer of this interface.
+ * </p>
+ *
+ * @see IContentAssistProcessor
+ */
+public interface IContextInformation {
+
+ /**
+ * Returns the string to be displayed in the list of contexts.
+ * This method is used to supply a unique presentation for
+ * situations where the context is ambiguous. These strings are
+ * used to allow the user to select the specific context.
+ *
+ * @return the string to be displayed for the context
+ */
+ String getContextDisplayString();
+
+ /**
+ * Returns the image for this context information.
+ * The image will be shown to the left of the display string.
+ *
+ * @return the image to be shown or <code>null</code> if no image is desired
+ */
+ Node getGraphic();
+
+ /**
+ * Returns the string to be displayed in the tool tip like information popup.
+ *
+ * @return the string to be displayed
+ */
+ String getInformationDisplayString();
+
+ /**
+ * Compares the given object with this receiver. Two context informations are equal if there
+ * information display strings and their context display strings are equal.
+ * <p>
+ * <strong>Note:</strong> As specified in {@link Object#equals(Object)} clients will most likely
+ * also have to implement {@link Object#hashCode()}.
+ * </p>
+ *
+ * @see Object#equals(Object)
+ */
+ @Override
+ boolean equals(Object object);
+
+ /**
+ * Returns the start offset of the range for which this context
+ * information is valid or <code>-1</code> if unknown.
+ *
+ * @return the start offset of the range for which this context
+ * information is valid
+ */
+ int getContextInformationPosition();
+}
+
+
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContextInformationPresenter.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContextInformationPresenter.java
new file mode 100644
index 000000000..e8ed8da7d
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContextInformationPresenter.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.fx.text.ui.contentassist;
+
+import org.eclipse.fx.text.ui.ITextViewer;
+import org.eclipse.fx.text.ui.TextPresentation;
+
+
+/**
+ * A context information presenter determines the presentation
+ * of context information depending on a given document position.
+ * <p>
+ * The interface can be implemented by clients.
+ * </p>
+ *
+ * @since 2.0
+ */
+public interface IContextInformationPresenter {
+
+ /**
+ * Installs this presenter for the given context information.
+ *
+ * @param info the context information which this presenter should style
+ * @param viewer the text viewer on which the information is presented
+ * @param offset the document offset for which the information has been computed
+ */
+ void install(IContextInformation info, ITextViewer viewer, int offset);
+
+ /**
+ * Updates the given presentation of the given context information
+ * at the given document position. Returns whether update changed the
+ * presentation.
+ *
+ * @param offset the current offset within the document
+ * @param presentation the presentation to be updated
+ * @return <code>true</code> if the given presentation has been changed
+ */
+ boolean updatePresentation(int offset, TextPresentation presentation);
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContextInformationValidator.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContextInformationValidator.java
new file mode 100644
index 000000000..d34f415b1
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/contentassist/IContextInformationValidator.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.fx.text.ui.contentassist;
+
+import org.eclipse.fx.text.ui.ITextViewer;
+
+
+/**
+ * A context information validator is used to determine if
+ * a displayed context information is still valid or should
+ * be dismissed.
+ * <p>
+ * The interface can be implemented by clients.
+ * </p>
+ *
+ * @see IContextInformationPresenter
+ */
+public interface IContextInformationValidator {
+
+ /**
+ * Installs this validator for the given context information.
+ *
+ * @param info the context information which this validator should check
+ * @param viewer the text viewer on which the information is presented
+ * @param offset the document offset for which the information has been computed
+ */
+ void install(IContextInformation info, ITextViewer viewer, int offset);
+
+ /**
+ * Returns whether the information this validator is installed on is still valid
+ * at the given document position.
+ *
+ * @param offset the current offset within the document
+ * @return <code>true</code> if the information also valid at the given document position
+ */
+ boolean isContextInformationValid(int offset);
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/AnnotationModelSupport.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/AnnotationModelSupport.java
new file mode 100644
index 000000000..70767a407
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/AnnotationModelSupport.java
@@ -0,0 +1,198 @@
+package org.eclipse.fx.text.ui.internal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+import org.eclipse.fx.core.Subscription;
+import org.eclipse.fx.ui.controls.styledtext.StyledTextArea;
+import org.eclipse.fx.ui.controls.styledtext.model.AnnotationProvider;
+import org.eclipse.fx.ui.controls.styledtext.model.TextAnnotation;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.AnnotationModelEvent;
+import org.eclipse.jface.text.source.IAnnotationModel;
+import org.eclipse.jface.text.source.IAnnotationModelListener;
+import org.eclipse.jface.text.source.IAnnotationModelListenerExtension;
+
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+
+import javafx.application.Platform;
+
+public class AnnotationModelSupport {
+
+ private IAnnotationModel annotationModel;
+ private StyledTextArea control;
+
+ private Map<Integer, Set<MarkerAnnotation>> annotationCache = new HashMap<>();
+
+ public AnnotationModelSupport(IAnnotationModel model, StyledTextArea control) {
+ this.annotationModel = model;
+ this.control = control;
+ }
+
+ static class MarkerAnnotation implements TextAnnotation, WrappedAnnotation {
+ public final Range<Integer> range;
+ public final Annotation annotation;
+
+ private final String nfo;
+
+ public MarkerAnnotation(Annotation annotation, Range<Integer> range) {
+ this.annotation = annotation;
+ this.range = range;
+ this.nfo = annotation.getType() + annotation.getText();
+ }
+
+ @Override
+ public Annotation getAnnotation() {
+ return annotation;
+ }
+
+ @Override
+ public Range<Integer> getRange() {
+ return range;
+ }
+
+ @Override
+ public String toString() {
+ return "MarkerAnnotation("+range+", "+nfo+")";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((nfo == null) ? 0 : nfo.hashCode());
+ result = prime * result + ((range == null) ? 0 : range.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ MarkerAnnotation other = (MarkerAnnotation) obj;
+ if (nfo == null) {
+ if (other.nfo != null)
+ return false;
+ } else if (!nfo.equals(other.nfo))
+ return false;
+ if (range == null) {
+ if (other.range != null)
+ return false;
+ } else if (!range.equals(other.range))
+ return false;
+ return true;
+ }
+
+
+
+ }
+
+ class AnnotationModelAnnotationProvider implements AnnotationProvider {
+ @Override
+ public Set<? extends org.eclipse.fx.ui.controls.styledtext.model.Annotation> computeAnnotations(int index) {
+ return getAnnotations(index);
+ }
+
+
+ @Override
+ public Subscription registerChangeListener(Consumer<RangeSet<Integer>> onChange) {
+ AnnotationModelSupport.this.listener.add(onChange);
+ return new Subscription() {
+ @Override
+ public void dispose() {
+ AnnotationModelSupport.this.listener.remove(onChange);
+ }
+ };
+ }
+
+
+ }
+
+ private List<Consumer<RangeSet<Integer>>> listener = new ArrayList<>();
+
+ public void triggerChange(RangeSet<Integer> range) {
+ Platform.runLater(()-> {
+ this.listener.forEach(l->l.accept(range));
+ });
+ }
+
+ private Set<MarkerAnnotation> findAnnotations(int lineIndex) {
+ long now = System.currentTimeMillis();
+ Set<MarkerAnnotation> result = new HashSet<>();
+ Iterator<Annotation> annotationIterator = annotationModel.getAnnotationIterator();
+ while( annotationIterator.hasNext() ) {
+ Annotation a = annotationIterator.next();
+ Position position = annotationModel.getPosition(a);
+ int curLineIndex = control.getLineAtOffset(position.offset);
+ if (curLineIndex == lineIndex) {
+ int lineBegin = control.getOffsetAtLine(curLineIndex);
+ int lineLength = control.getContent().getLine(curLineIndex).length();
+ // TODO multiline = ????
+ int lower = position.offset - lineBegin;
+ int upper = lower + Math.min(position.length, lineLength);
+ Range<Integer> lineLocalRange = Range.closed(lower, upper);
+ MarkerAnnotation annotation = new MarkerAnnotation(a, lineLocalRange);
+ result.add(annotation);
+ }
+ }
+// System.err.println("AnnotationModelSupport: findAnnotations("+lineIndex+") needed " + (System.currentTimeMillis() - now) + "ms");
+ return result;
+ }
+
+
+
+ private Set<MarkerAnnotation> getAnnotations(int lineIndex) {
+ Set<MarkerAnnotation> set = annotationCache.get(lineIndex);
+ if (set == null) {
+ set = findAnnotations(lineIndex);
+ annotationCache.put(lineIndex, set);
+ }
+ return set;
+ }
+
+ private void onAnnotationModelChange() {
+// System.err.println("AnnotationModelSupport: clearing annotation cache");
+ annotationCache.clear();
+ RangeSet<Integer> range = TreeRangeSet.create();
+ range.add(com.google.common.collect.Range.closed(0, Integer.MAX_VALUE));
+ triggerChange(range);
+
+ }
+
+ class Listener implements IAnnotationModelListener, IAnnotationModelListenerExtension {
+ @Override
+ public void modelChanged(AnnotationModelEvent event) {
+ onAnnotationModelChange();
+// System.err.println("modelChanged");
+// System.err.println(" added: " + Arrays.toString(event.getAddedAnnotations()));
+// System.err.println(" removed: " + Arrays.toString(event.getRemovedAnnotations()));
+// System.err.println(" changed: " + Arrays.toString(event.getChangedAnnotations()));
+ }
+
+ @Override
+ public void modelChanged(IAnnotationModel model) {
+ // full
+ }
+
+ }
+
+
+ public void install() {
+ annotationModel.addAnnotationModelListener(new Listener());
+ control.getAnnotationProvider().add(new AnnotationModelAnnotationProvider());
+ }
+
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/InvisibleCharSupport.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/InvisibleCharSupport.java
new file mode 100644
index 000000000..49cc7285b
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/InvisibleCharSupport.java
@@ -0,0 +1,174 @@
+package org.eclipse.fx.text.ui.internal;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.fx.core.Subscription;
+import org.eclipse.fx.ui.controls.styledtext.StyledTextArea;
+import org.eclipse.fx.ui.controls.styledtext.StyledTextContent;
+import org.eclipse.fx.ui.controls.styledtext.model.Annotation;
+import org.eclipse.fx.ui.controls.styledtext.model.AnnotationProvider;
+import org.eclipse.fx.ui.controls.styledtext.model.TextAnnotation;
+import org.eclipse.fx.ui.controls.styledtext.model.TextAnnotationPresenter;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jface.text.source.IAnnotationModel;
+
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+
+import javafx.scene.Node;
+import javafx.scene.text.Text;
+
+public class InvisibleCharSupport {
+
+ private StyledTextArea control;
+
+ public InvisibleCharSupport(StyledTextArea control) {
+ this.control = control;
+ }
+
+
+ public class InvisibleCharAnnotation implements TextAnnotation {
+
+ private final Range range;
+ private final String symbol;
+
+ @Override
+ public Range getRange() {
+ return range;
+ }
+
+ public String getSymbol() {
+ return symbol;
+ }
+
+ public InvisibleCharAnnotation(String symbol, Range range) {
+ this.symbol = symbol;
+ this.range = range;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + getOuterType().hashCode();
+ result = prime * result + ((range == null) ? 0 : range.hashCode());
+ result = prime * result + ((symbol == null) ? 0 : symbol.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ InvisibleCharAnnotation other = (InvisibleCharAnnotation) obj;
+ if (!getOuterType().equals(other.getOuterType()))
+ return false;
+ if (range == null) {
+ if (other.range != null)
+ return false;
+ } else if (!range.equals(other.range))
+ return false;
+ if (symbol == null) {
+ if (other.symbol != null)
+ return false;
+ } else if (!symbol.equals(other.symbol))
+ return false;
+ return true;
+ }
+
+ private InvisibleCharSupport getOuterType() {
+ return InvisibleCharSupport.this;
+ }
+
+
+ }
+
+ public class InvisibleCharAnnotationPresenter implements TextAnnotationPresenter {
+
+ @Override
+ public boolean isApplicable(Annotation annotation) {
+ return annotation instanceof InvisibleCharAnnotation;
+ }
+
+ @Override
+ public Node createNode() {
+ Text n = new Text();
+ n.getStyleClass().add("invisible-char");
+ return n;
+ }
+
+ @Override
+ public boolean isVisible(Annotation annotation) {
+ return true;
+ }
+
+ @Override
+ public void updateNode(Node node, Annotation annotation) {
+ Text t = (Text) node;
+ InvisibleCharAnnotation a = (InvisibleCharAnnotation) annotation;
+ t.setText(a.getSymbol());
+ }
+
+ }
+
+ public class InvisibleCharAnnotationProvider implements AnnotationProvider {
+
+ @Override
+ public Set<? extends Annotation> computeAnnotations(int index) {
+ @NonNull
+ StyledTextContent content = control.getContent();
+
+ Set<InvisibleCharAnnotation> annotations = new HashSet<>();
+
+ int lineBegin = content.getOffsetAtLine(index);
+ int lineLength = content.getLine(index).length();
+
+ String line = content.getTextRange(lineBegin, lineLength);
+
+
+ int numOfLines = content.getLineCount();
+
+ // ADD TABS
+ Pattern tab = Pattern.compile("\\t");
+ Matcher matcher = tab.matcher(line);
+ while (matcher.find()) {
+ annotations.add(new InvisibleCharAnnotation("\u21E5", Range.closed(matcher.start(), matcher.start() + 1)));
+ }
+
+ // ADD NEWLINE
+ if (index < numOfLines-1) {
+ annotations.add(new InvisibleCharAnnotation("\u21B5", Range.closed(lineLength, lineLength +1)));
+ }
+
+ return annotations;
+ }
+
+ @Override
+ public Subscription registerChangeListener(Consumer<RangeSet<Integer>> onChange) {
+ return new Subscription() {
+ @Override
+ public void dispose() {
+
+ }
+ };
+ }
+
+ }
+
+
+
+ public void install() {
+ this.control.getAnnotationProvider().add(new InvisibleCharAnnotationProvider());
+ this.control.getAnnotationPresenter().add(new InvisibleCharAnnotationPresenter());
+ }
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/LineNumberSupport.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/LineNumberSupport.java
new file mode 100644
index 000000000..5f61a1c85
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/LineNumberSupport.java
@@ -0,0 +1,147 @@
+package org.eclipse.fx.text.ui.internal;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.function.Consumer;
+
+import org.eclipse.fx.core.Subscription;
+import org.eclipse.fx.ui.controls.styledtext.StyledTextArea;
+import org.eclipse.fx.ui.controls.styledtext.model.Annotation;
+import org.eclipse.fx.ui.controls.styledtext.model.AnnotationProvider;
+import org.eclipse.fx.ui.controls.styledtext.model.LineRulerAnnotationPresenter;
+
+import com.google.common.collect.RangeSet;
+
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.DoubleBinding;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import javafx.scene.Node;
+import javafx.scene.text.Text;
+
+public class LineNumberSupport {
+
+ private StyledTextArea control;
+
+ public LineNumberSupport(StyledTextArea control) {
+ this.control = control;
+ }
+
+
+ public class LineNrAnnotation implements Annotation {
+
+ private final int nr;
+
+ public LineNrAnnotation(int nr) {
+ this.nr = nr;
+ }
+ public int getNr() {
+ return nr;
+ }
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + getOuterType().hashCode();
+ result = prime * result + nr;
+ return result;
+ }
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ LineNrAnnotation other = (LineNrAnnotation) obj;
+ if (!getOuterType().equals(other.getOuterType()))
+ return false;
+ if (nr != other.nr)
+ return false;
+ return true;
+ }
+ private LineNumberSupport getOuterType() {
+ return LineNumberSupport.this;
+ }
+
+
+ }
+
+ public class LineNrAnnotationPresenter implements LineRulerAnnotationPresenter {
+
+ @Override
+ public LayoutHint getLayoutHint() {
+ return LayoutHint.ALIGN_RIGHT;
+ }
+
+ @Override
+ public boolean isApplicable(Annotation annotation) {
+ return annotation instanceof LineNrAnnotation;
+ }
+
+ @Override
+ public Node createNode() {
+ Text node = new Text();
+ node.getStyleClass().add("line-ruler-text");
+ return node;
+ }
+
+ private DoubleProperty w = new SimpleDoubleProperty(16);
+
+ @Override
+ public DoubleProperty getWidth() {
+ return w;
+ }
+
+ @Override
+ public int getOrder() {
+ return 10000;
+ }
+
+ @Override
+ public boolean isVisible(Set<Annotation> annotation) {
+ return true;
+ }
+
+ @Override
+ public void updateNode(Node node, Set<Annotation> annotation) {
+ Text n = (Text) node;
+ annotation.stream().findFirst().ifPresent(m->{
+ int nr = ((LineNrAnnotation)m).getNr();
+ n.setText("" + nr);
+ });
+ }
+
+
+
+ }
+
+ public class LineNrAnnotationProvider implements AnnotationProvider {
+
+ @Override
+ public Set<? extends Annotation> computeAnnotations(int index) {
+ return Collections.singleton(new LineNrAnnotation(index + 1));
+ }
+
+ @Override
+ public Subscription registerChangeListener(Consumer<RangeSet<Integer>> onChange) {
+ return new Subscription() {
+ @Override
+ public void dispose() {
+
+ }
+ };
+ }
+
+ }
+
+ public void install() {
+ LineNrAnnotationPresenter presenter = new LineNrAnnotationPresenter();
+ this.control.getAnnotationProvider().add(new LineNrAnnotationProvider());
+ this.control.getAnnotationPresenter().add(presenter);
+
+ DoubleBinding width = Bindings.createDoubleBinding(()->Integer.toString(this.control.lineCountProperty().get()).length() * 8d, this.control.lineCountProperty());
+ presenter.w.bind(width);
+ }
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/TextAnnotationPresenterWrapper.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/TextAnnotationPresenterWrapper.java
new file mode 100644
index 000000000..460bd42ed
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/TextAnnotationPresenterWrapper.java
@@ -0,0 +1,34 @@
+package org.eclipse.fx.text.ui.internal;
+
+import org.eclipse.fx.ui.controls.styledtext.model.Annotation;
+import org.eclipse.fx.ui.controls.styledtext.model.TextAnnotationPresenter;
+
+import javafx.scene.Node;
+
+public class TextAnnotationPresenterWrapper implements TextAnnotationPresenter {
+
+ @Override
+ public boolean isApplicable(Annotation annotation) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public Node createNode() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public boolean isVisible(Annotation annotation) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void updateNode(Node node, Annotation annotation) {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/WrappedAnnotation.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/WrappedAnnotation.java
new file mode 100644
index 000000000..1de9aaf5f
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/WrappedAnnotation.java
@@ -0,0 +1,9 @@
+package org.eclipse.fx.text.ui.internal;
+
+import org.eclipse.jface.text.source.Annotation;
+
+public interface WrappedAnnotation {
+
+ Annotation getAnnotation();
+
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/WrappedLineRulerAnnotationPresenter.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/WrappedLineRulerAnnotationPresenter.java
new file mode 100644
index 000000000..9d57b36a7
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/WrappedLineRulerAnnotationPresenter.java
@@ -0,0 +1,70 @@
+package org.eclipse.fx.text.ui.internal;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.fx.text.ui.source.ILineRulerAnnotationPresenter;
+import org.eclipse.fx.ui.controls.styledtext.model.Annotation;
+import org.eclipse.fx.ui.controls.styledtext.model.LineRulerAnnotationPresenter;
+
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import javafx.scene.Node;
+
+public class WrappedLineRulerAnnotationPresenter implements LineRulerAnnotationPresenter {
+
+ private ILineRulerAnnotationPresenter wrapped;
+
+ public WrappedLineRulerAnnotationPresenter(ILineRulerAnnotationPresenter wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public LayoutHint getLayoutHint() {
+ return LayoutHint.valueOf(wrapped.getLayoutHint().toString());
+ }
+
+ @Override
+ public int getOrder() {
+ return wrapped.getOrder();
+ }
+
+ private org.eclipse.jface.text.source.Annotation unwrap(Annotation annotation) {
+ return ((WrappedAnnotation) annotation).getAnnotation();
+ }
+
+ private Set<org.eclipse.jface.text.source.Annotation> unwrap(Set<Annotation> annotations) {
+ return annotations.stream().map(this::unwrap).collect(Collectors.toSet());
+ }
+
+ @Override
+ public boolean isApplicable(Annotation annotation) {
+ if (annotation instanceof WrappedAnnotation) {
+ return wrapped.isApplicable(unwrap(annotation));
+ }
+ return false;
+ }
+
+ @Override
+ public Node createNode() {
+ return wrapped.createNode();
+ }
+
+ DoubleProperty TODO = new SimpleDoubleProperty(16);
+ @Override
+ public DoubleProperty getWidth() {
+ // TODO
+ return TODO;
+ }
+
+ @Override
+ public boolean isVisible(Set<Annotation> annotation) {
+ return true;
+ }
+
+ @Override
+ public void updateNode(Node node, Set<Annotation> annotation) {
+ wrapped.updateNode(node, unwrap(annotation));
+ }
+
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/WrappedTextAnnotationPresenter.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/WrappedTextAnnotationPresenter.java
new file mode 100644
index 000000000..986f89e6e
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/internal/WrappedTextAnnotationPresenter.java
@@ -0,0 +1,51 @@
+package org.eclipse.fx.text.ui.internal;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.fx.text.ui.source.ITextAnnotationPresenter;
+import org.eclipse.fx.ui.controls.styledtext.model.Annotation;
+import org.eclipse.fx.ui.controls.styledtext.model.TextAnnotationPresenter;
+
+import javafx.scene.Node;
+
+public class WrappedTextAnnotationPresenter implements TextAnnotationPresenter {
+
+ private ITextAnnotationPresenter wrapped;
+
+ public WrappedTextAnnotationPresenter(ITextAnnotationPresenter wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ private org.eclipse.jface.text.source.Annotation unwrap(Annotation annotation) {
+ return ((WrappedAnnotation) annotation).getAnnotation();
+ }
+
+ private Set<org.eclipse.jface.text.source.Annotation> unwrap(Set<Annotation> annotations) {
+ return annotations.stream().map(this::unwrap).collect(Collectors.toSet());
+ }
+
+ @Override
+ public boolean isApplicable(Annotation annotation) {
+ if (annotation instanceof WrappedAnnotation) {
+ return wrapped.isApplicable(unwrap(annotation));
+ }
+ return false;
+ }
+
+ @Override
+ public Node createNode() {
+ return wrapped.createNode();
+ }
+
+ @Override
+ public boolean isVisible(Annotation annotation) {
+ return true;
+ }
+
+ @Override
+ public void updateNode(Node node, Annotation annotation) {
+ wrapped.updateNode(node, unwrap(annotation));
+ }
+
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/AnnotationPresenter.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/AnnotationPresenter.java
index 4391469b6..bf9fe8c63 100644
--- a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/AnnotationPresenter.java
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/AnnotationPresenter.java
@@ -10,13 +10,11 @@
*******************************************************************************/
package org.eclipse.fx.text.ui.source;
-import java.util.List;
+import java.util.Set;
-import org.eclipse.jface.text.source.Annotation;
+public interface AnnotationPresenter {
-import javafx.scene.Node;
+ Set<ITextAnnotationPresenter> getTextAnnotationPresenter();
+ Set<ILineRulerAnnotationPresenter> getLineRulerAnnotationPresenter();
-public interface AnnotationPresenter {
- public List<String> getTypes();
- public Node getPresentation(Annotation annotation);
}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/IAnnotationPresenter.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/IAnnotationPresenter.java
new file mode 100644
index 000000000..a75f1d036
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/IAnnotationPresenter.java
@@ -0,0 +1,11 @@
+package org.eclipse.fx.text.ui.source;
+
+import org.eclipse.jface.text.source.Annotation;
+
+import javafx.scene.Node;
+
+public interface IAnnotationPresenter {
+
+ boolean isApplicable(Annotation annotation);
+ Node createNode();
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/ILineRulerAnnotationPresenter.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/ILineRulerAnnotationPresenter.java
new file mode 100644
index 000000000..f992240b6
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/ILineRulerAnnotationPresenter.java
@@ -0,0 +1,22 @@
+package org.eclipse.fx.text.ui.source;
+
+import java.util.Set;
+
+import org.eclipse.jface.text.source.Annotation;
+
+import javafx.scene.Node;
+
+public interface ILineRulerAnnotationPresenter extends IAnnotationPresenter {
+
+ enum LayoutHint {
+ ALIGN_LEFT,
+ ALIGN_RIGHT,
+ ALIGN_CENTER;
+ }
+
+ int getOrder();
+
+ void updateNode(Node node, Set<Annotation> annotation);
+
+ LayoutHint getLayoutHint();
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/ITextAnnotationPresenter.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/ITextAnnotationPresenter.java
new file mode 100644
index 000000000..22ebb021f
--- /dev/null
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/ITextAnnotationPresenter.java
@@ -0,0 +1,18 @@
+package org.eclipse.fx.text.ui.source;
+
+import org.eclipse.jface.text.source.Annotation;
+
+import javafx.scene.Node;
+
+public interface ITextAnnotationPresenter extends IAnnotationPresenter {
+
+ /**
+ * <p>Performance Hint:
+ * try to prevent adding nodes here!
+ * </p>
+ * @param node
+ * @param annotation
+ */
+ void updateNode(Node node, Annotation annotation);
+
+}
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/SourceViewer.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/SourceViewer.java
index 454fec1df..7abb16027 100644
--- a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/SourceViewer.java
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/SourceViewer.java
@@ -12,16 +12,14 @@
*******************************************************************************/
package org.eclipse.fx.text.ui.source;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
import org.eclipse.fx.text.ui.ITextViewerExtension2;
import org.eclipse.fx.text.ui.TextViewer;
import org.eclipse.fx.text.ui.contentassist.IContentAssistant;
+import org.eclipse.fx.text.ui.internal.AnnotationModelSupport;
+import org.eclipse.fx.text.ui.internal.WrappedLineRulerAnnotationPresenter;
+import org.eclipse.fx.text.ui.internal.WrappedTextAnnotationPresenter;
import org.eclipse.fx.text.ui.presentation.IPresentationReconciler;
import org.eclipse.fx.text.ui.reconciler.IReconciler;
-import org.eclipse.fx.ui.controls.styledtext.StyledTextArea.StyledTextLine;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ISynchronizable;
@@ -31,10 +29,6 @@ import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.AnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
-import org.eclipse.jface.text.source.IAnnotationModelListener;
-
-import javafx.application.Platform;
-import javafx.scene.Node;
public class SourceViewer extends TextViewer implements ISourceViewer, ISourceViewerExtension, ISourceViewerExtension2, ISourceViewerExtension3, ISourceViewerExtension4 {
@@ -42,7 +36,7 @@ public class SourceViewer extends TextViewer implements ISourceViewer, ISourceVi
private IReconciler fReconciler;
private IAnnotationModel fVisualAnnotationModel;
protected final static Object MODEL_ANNOTATION_MODEL= new Object();
- private Map<String, AnnotationPresenter> presenterMap = new HashMap<>();
+
private Annotation fRangeIndicator;
@Override
@@ -50,6 +44,10 @@ public class SourceViewer extends TextViewer implements ISourceViewer, ISourceVi
if (getTextWidget() == null)
return;
+// installPlugin(new InvisibleCharacterPlugin());
+
+
+
setUndoManager(configuration.getUndoManager(this));
getTextWidget().getStyleClass().add(configuration.getStyleclassName());
@@ -86,27 +84,37 @@ public class SourceViewer extends TextViewer implements ISourceViewer, ISourceVi
//TODO This is complete different to JFace-Text
IAnnotationModel annotationModel = configuration.getAnnotationModel();
if( annotationModel != null ) {
- getTextWidget().setLineRulerGraphicNodeFactory(this::annotationFactory);
- annotationModel.addAnnotationModelListener(new IAnnotationModelListener() {
-
- private boolean scheduled = false;
-
- @Override
- public void modelChanged(IAnnotationModel model) {
- if( ! this.scheduled ) {
- this.scheduled = true;
- Platform.runLater(() -> {
- this.scheduled = false;
- getTextWidget().refreshLineRuler();
- });
- }
- }
+// getTextWidget().setLineRulerGraphicNodeFactory(this::annotationFactory);
- });
+ // NFO: the line ruler is updated by changes to the annotation model
+
+// annotationModel.addAnnotationModelListener(new IAnnotationModelListener() {
+//
+// private boolean scheduled = false;
+//
+// @Override
+// public void modelChanged(IAnnotationModel model) {
+// if( ! this.scheduled ) {
+// this.scheduled = true;
+// Platform.runLater(() -> {
+// this.scheduled = false;
+// getTextWidget().refreshLineRuler();
+// });
+// }
+// }
+//
+// });
}
- if( configuration.getAnnotationPresenters() != null ) {
- configuration.getAnnotationPresenters().stream().forEach(p -> p.getTypes().forEach( s -> this.presenterMap.put(s,p)));
+ if( configuration.getAnnotationPresenter() != null ) {
+ // install presenters
+ configuration.getAnnotationPresenter().getLineRulerAnnotationPresenter().forEach(p -> {
+ System.err.println("adding line ruler presenter " + p);
+ getTextWidget().getAnnotationPresenter().add(new WrappedLineRulerAnnotationPresenter(p));
+ });
+ configuration.getAnnotationPresenter().getTextAnnotationPresenter().forEach(p -> {
+ getTextWidget().getAnnotationPresenter().add(new WrappedTextAnnotationPresenter(p));
+ });
}
// presenterMap.putAll(configuration.getAnnotationPresenters().stream().collect(Collectors.toMap(p -> p.getType(), p -> p)));
@@ -123,9 +131,21 @@ public class SourceViewer extends TextViewer implements ISourceViewer, ISourceVi
setTextHover(configuration.getTextHover(this, t), t, ITextViewerExtension2.DEFAULT_HOVER_STATE_MASK);
}
-
// register annotation model with text widget
if (annotationModel != null) {
+
+ AnnotationModelSupport support = new AnnotationModelSupport(annotationModel, getTextWidget());
+ support.install();
+
+// MarkerAnnotationPlugin markerAnnotationPlugin = new MarkerAnnotationPlugin(annotationModel, getTextWidget(), presenterMap);
+// markerAnnotationPlugin.install();
+
+// new AnnotationIconLineAnnotationPlugin(annotationModel, getTextWidget(),
+//
+//
+// (a)->presenterMap.get(a.getType()).getPresentation(a));
+
+// installPlugin(new AnnotationPlugin(annotationModel));
// annotationModel.addAnnotationModelListener(new IAnnotationModelListener() {
// @Override
// public void modelChanged(IAnnotationModel model) {
@@ -192,24 +212,24 @@ public class SourceViewer extends TextViewer implements ISourceViewer, ISourceVi
}
- private Node annotationFactory(StyledTextLine l) {
- //TODO Should use IAnnotationExtension2
- @SuppressWarnings("unchecked")
- Iterator<Annotation> annotationIterator = this.fVisualAnnotationModel.getAnnotationIterator();
- while( annotationIterator.hasNext() ) {
- Annotation a = annotationIterator.next();
- Position position = this.fVisualAnnotationModel.getPosition(a);
-
- if( l.getLineIndex() == getTextWidget().getContent().getLineAtOffset(position.offset) ) {
- AnnotationPresenter annotationPresenter = this.presenterMap.get(a.getType());
- if( annotationPresenter != null ) {
- return annotationPresenter.getPresentation(a);
- }
- return null;
- }
- }
- return null;
- }
+// private Node annotationFactory(StyledTextLine l) {
+// //TODO Should use IAnnotationExtension2
+// @SuppressWarnings("unchecked")
+// Iterator<Annotation> annotationIterator = this.fVisualAnnotationModel.getAnnotationIterator();
+// while( annotationIterator.hasNext() ) {
+// Annotation a = annotationIterator.next();
+// Position position = this.fVisualAnnotationModel.getPosition(a);
+//
+// if( l.getLineIndex() == getTextWidget().getContent().getLineAtOffset(position.offset) ) {
+// AnnotationPresenter annotationPresenter = this.presenterMap.get(a.getType());
+// if( annotationPresenter != null ) {
+// return annotationPresenter.getPresentation(a);
+// }
+// return null;
+// }
+// }
+// return null;
+// }
/**
* Disposes the visual annotation model.
diff --git a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/SourceViewerConfiguration.java b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/SourceViewerConfiguration.java
index 997e9b5be..a41830772 100644
--- a/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/SourceViewerConfiguration.java
+++ b/bundles/code/org.eclipse.fx.text.ui/src/org/eclipse/fx/text/ui/source/SourceViewerConfiguration.java
@@ -49,8 +49,8 @@ public abstract class SourceViewerConfiguration {
return null;
}
- public List<AnnotationPresenter> getAnnotationPresenters() {
- return Collections.emptyList();
+ public AnnotationPresenter getAnnotationPresenter() {
+ return null;
}
public AnnotationPainter getAnnotationPainter(ISourceViewer sourceViewer) {
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/.settings/org.eclipse.pde.core.prefs b/bundles/runtime/org.eclipse.fx.ui.controls/.settings/org.eclipse.pde.core.prefs
new file mode 100644
index 000000000..e313d3c3b
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/.settings/org.eclipse.pde.core.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+resolve.requirebundle=false
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/META-INF/MANIFEST.MF b/bundles/runtime/org.eclipse.fx.ui.controls/META-INF/MANIFEST.MF
index 1456484f3..495004ff3 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/META-INF/MANIFEST.MF
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/META-INF/MANIFEST.MF
@@ -19,6 +19,7 @@ Export-Package: org.eclipse.fx.ui.controls;version="2.2.0",
org.eclipse.fx.ui.controls.styledtext;version="2.2.0",
org.eclipse.fx.ui.controls.styledtext.behavior;version="2.2.0";x-internal:=true,
org.eclipse.fx.ui.controls.styledtext.events,
+ org.eclipse.fx.ui.controls.styledtext.model,
org.eclipse.fx.ui.controls.styledtext.skin;version="2.2.0";x-internal:=true,
org.eclipse.fx.ui.controls.table,
org.eclipse.fx.ui.controls.tabpane;version="2.2.0",
@@ -28,7 +29,8 @@ Bundle-ActivationPolicy: lazy
Service-Component: OSGI-INF/services/org.eclipse.fx.ui.controls.styledtext.UnderlineStrategyFactory.xml,
OSGI-INF/services/org.eclipse.fx.ui.controls.image.fontawesome.FontAwesomeIconFontProvider.xml
Bundle-Vendor: Eclipse.org
-Import-Package: org.eclipse.fx.core;version="2.2.0",
+Import-Package: com.google.common.collect;version="15.0.0",
+ org.eclipse.fx.core;version="2.2.0",
org.eclipse.fx.core.text;version="2.2.0",
org.eclipse.fx.ui.panes;version="2.2.0",
org.osgi.service.component.annotations;resolution:=optional
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/build.properties b/bundles/runtime/org.eclipse.fx.ui.controls/build.properties
index 1e68d6ddd..c39a484ce 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/build.properties
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/build.properties
@@ -4,3 +4,4 @@ bin.includes = META-INF/,\
about.html,\
OSGI-INF/
source.. = src/
+additional.bundles = com.google.guava
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/DefaultContent.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/DefaultContent.java
index b6a9d3c58..379ceced4 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/DefaultContent.java
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/DefaultContent.java
@@ -880,7 +880,7 @@ class DefaultContent implements StyledTextContent {
// then insert the new text
insert(start, newText);
// inform listeners
- TextChangedEvent textChanged = TextChangedEvent.textChanged(this);
+ TextChangedEvent textChanged = TextChangedEvent.textChanged(this, start, replaceLength, newText);
for (TextChangeListener l : this.textListeners) {
l.textChanged(textChanged);
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/FXBindUtil.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/FXBindUtil.java
new file mode 100644
index 000000000..84ee9ea3f
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/FXBindUtil.java
@@ -0,0 +1,102 @@
+package org.eclipse.fx.ui.controls.styledtext;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.eclipse.fx.core.Subscription;
+
+import javafx.beans.InvalidationListener;
+import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
+
+public class FXBindUtil {
+
+ public static <A, B> Subscription uniMapBindList(ObservableList<A> a, ObservableList<B> b, Function<A, B> map) {
+ final ListChangeListener<A> aChange = (ListChangeListener.Change<? extends A> change) -> {
+ while (change.next()) {
+// System.err.println("NEXT");
+// if (change.wasAdded() && change.wasRemoved()) System.err.println("WTF?!?");
+ if (change.wasPermutated()) {
+// System.err.println("permutate");
+ List<B> beforePermutate = b.subList(change.getFrom(), change.getTo());
+ for (int i = 0; i < beforePermutate.size(); i++) {
+ b.set(change.getPermutation(change.getFrom() + i), beforePermutate.get(i));
+ }
+ }
+
+ if (change.wasRemoved()) {
+// System.err.println("removed");
+ b.remove(change.getFrom(), change.getFrom() + change.getRemovedSize());
+ }
+ if (change.wasAdded()) {
+// System.err.println("added");
+ List<? extends A> added = change.getAddedSubList();
+ List<B> addedMapped = added.stream().map(map).collect(Collectors.toList());
+
+ b.addAll(change.getFrom(), addedMapped);
+
+ }
+ else if (change.wasUpdated() || change.wasReplaced()) {
+// System.err.println("updated/replaced");
+ List<? extends A> updated = a.subList(change.getFrom(), change.getTo());
+ List<B> updatedMapped = updated.stream().map(map).collect(Collectors.toList());
+ for (int i = 0; i < updatedMapped.size(); i++) {
+ b.set(change.getFrom(), updatedMapped.get(i));
+ }
+ }
+ }
+ };
+
+ a.addListener(aChange);
+
+ return new Subscription() {
+ @Override
+ public void dispose() {
+ a.removeListener(aChange);
+ }
+ };
+ }
+
+ public static void main(String[] args) {
+ ObservableList<String> a = FXCollections.observableArrayList();
+ ObservableList<Integer> b = FXCollections.observableArrayList();
+
+ uniMapBindList(a, b, Integer::valueOf);
+
+ b.addListener((InvalidationListener)(x)->System.err.println(b));
+
+
+ a.add("1");
+ assert(b.get(0) == 1);
+
+ a.add("2");
+ assert b.get(1) == 2;
+
+ a.add("3");
+ assert b.get(2) == 3;
+
+ a.remove("2");
+ assert b.get(1) == 3;
+
+ a.addAll(1, Arrays.asList("5", "6"));
+ assert b.get(1) == 5;
+ assert b.get(2) == 6;
+ assert b.get(3) == 3;
+
+ ArrayList<String> copy = new ArrayList<>(a);
+ String string = copy.get(2);
+ copy.remove(2);
+ copy.add(1, string);
+
+ a.setAll(copy);
+ assert b.get(1) == 6;
+ assert b.get(2) == 5;
+ }
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/NodeCachePane.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/NodeCachePane.java
new file mode 100644
index 000000000..d9698bc55
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/NodeCachePane.java
@@ -0,0 +1,43 @@
+package org.eclipse.fx.ui.controls.styledtext;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+import java.util.function.Supplier;
+
+import javafx.scene.Node;
+import javafx.scene.layout.Pane;
+
+public class NodeCachePane extends Pane {
+
+ protected final ReuseCache<Node> cache;
+
+ public NodeCachePane(Supplier<Node> nodeFactory) {
+ cache = new ReuseCache<>(nodeFactory);
+ cache.addOnActivate(node->{
+ if (!getChildren().contains(node)) {
+ getChildren().add(node);
+ }
+ node.setVisible(true);
+ });
+ cache.addOnRelease( node -> getChildren().remove(node)); // FIXME modified by Tom leads to more and more text instances in AnnotationOverlay
+ cache.addOnClear( node -> getChildren().remove(node));
+ }
+
+ protected Node getNode() {
+ return cache.getElement();
+ }
+
+ protected void releaseNode(Node node) {
+ cache.releaseElement(node);
+ }
+
+ protected void cleanup() {
+
+ cache.clearFreeElements();
+ }
+
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/ReuseCache.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/ReuseCache.java
new file mode 100644
index 000000000..e32e5ba6f
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/ReuseCache.java
@@ -0,0 +1,66 @@
+package org.eclipse.fx.ui.controls.styledtext;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class ReuseCache<T> {
+
+ private Supplier<T> factory;
+
+ public ReuseCache(Supplier<T> factory) {
+ this.factory = factory;
+ }
+
+ private List<Consumer<T>> onActivate = new ArrayList<>();
+ private List<Consumer<T>> onRelease = new ArrayList<>();
+ private List<Consumer<T>> onClear = new ArrayList<>();
+
+ private Stack<T> free = new Stack<>();
+ private Set<T> active = new HashSet<>();
+
+ public void addOnActivate(Consumer<T> onActivate) {
+ this.onActivate.add(onActivate);
+ }
+
+ public void addOnRelease(Consumer<T> onRelease) {
+ this.onRelease.add(onRelease);
+ }
+
+ public void addOnClear(Consumer<T> onClear) {
+ this.onClear.add(onClear);
+ }
+
+ public T getElement() {
+ T element;
+ if (free.isEmpty()) {
+ element = factory.get();
+ }
+ else {
+ element = free.pop();
+ }
+ active.add(element);
+ onActivate.forEach(a->a.accept(element));
+ return element;
+ }
+
+ public void releaseElement(T element) {
+ active.remove(element);
+ onRelease.forEach(a->a.accept(element));
+// free.push(element); //FIXME Tom no caching as of NOW
+ }
+
+ public void clearFreeElements() {
+ free.clear();
+ }
+
+ public boolean isActive(T element) {
+ onClear.forEach(onClear->onClear.accept(element));
+ return active.contains(element);
+ }
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextArea.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextArea.java
index 28b6d4d4e..d787dbbb8 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextArea.java
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextArea.java
@@ -16,6 +16,8 @@ import java.lang.ref.WeakReference;
import java.util.Collections;
import org.eclipse.fx.ui.controls.styledtext.StyledTextContent.TextChangeListener;
+import org.eclipse.fx.ui.controls.styledtext.model.AnnotationPresenter;
+import org.eclipse.fx.ui.controls.styledtext.model.AnnotationProvider;
import org.eclipse.fx.ui.controls.styledtext.skin.StyledTextSkin;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
@@ -23,17 +25,19 @@ import org.eclipse.jdt.annotation.Nullable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyIntegerProperty;
+import javafx.beans.property.SetProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.property.SimpleSetProperty;
+import javafx.collections.FXCollections;
import javafx.geometry.Point2D;
-import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.input.Clipboard;
import javafx.scene.input.DataFormat;
import javafx.scene.paint.Color;
-import javafx.util.Callback;
/**
* Control which allows to implemented a code-editor
@@ -72,11 +76,6 @@ public class StyledTextArea extends Control {
@NonNull
final ObjectProperty<@NonNull StyledTextContent> contentProperty;
-// @NonNull
-// final SetProperty<@NonNull StyledTextAnnotation> annotationsProperty = new SimpleSetProperty<>(this, "annotations", FXCollections.observableSet()); //$NON-NLS-1$
-// public SetProperty<@NonNull StyledTextAnnotation> getAnnotations() {
-// return this.annotationsProperty;
-// }
TextChangeListener textChangeListener = new TextChangeListener() {
@Override
@@ -107,9 +106,6 @@ public class StyledTextArea extends Control {
@NonNull
private final ObjectProperty<TextSelection> currentSelection = new SimpleObjectProperty<>(this, "currentSelection"); //$NON-NLS-1$
- @NonNull
- private final ObjectProperty<@NonNull Callback<@NonNull StyledTextLine, @Nullable Node>> lineRulerGraphicNodeFactory = new SimpleObjectProperty<>(this, "lineRulerGraphicNodeFactory", e -> null); //$NON-NLS-1$
-
/**
* Separator for lines
*/
@@ -140,20 +136,16 @@ public class StyledTextArea extends Control {
@NonNull
private final ObjectProperty<@NonNull LineSeparator> lineSeparator = new SimpleObjectProperty<>(this, "lineSeparator", "\n".equals(System.getProperty("line.separator")) ? LineSeparator.NEW_LINE : LineSeparator.CARRIAGE_RETURN_NEW_LINE); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- /**
- * Represents a line shown in the control
- */
- public interface StyledTextLine {
- /**
- * @return the plain text value
- */
- public String getText();
- /**
- * @return the index of the line
- */
- public int getLineIndex();
+ private SetProperty<AnnotationPresenter> annotationPresenter = new SimpleSetProperty<>(this, "annotationPresenter", FXCollections.observableSet());
+ public SetProperty<AnnotationPresenter> getAnnotationPresenter() {
+ return annotationPresenter;
+ }
+
+ private SetProperty<AnnotationProvider> annotationProvider = new SimpleSetProperty<>(this, "annotationProvider", FXCollections.observableSet());
+ public SetProperty<AnnotationProvider> getAnnotationProvider() {
+ return annotationProvider;
}
private int anchor;
@@ -192,7 +184,15 @@ public class StyledTextArea extends Control {
return USER_AGENT_STYLESHEET;
}
+ private TextChangingEvent changingEvent;
+
+
void handleTextChanging(TextChangingEvent event) {
+ System.err.println("handleTextChanging");
+ changingEvent = event;
+
+ this.renderer.textChanging(event);
+
if (event.replaceCharCount < 0) {
event.offset += event.replaceCharCount;
event.replaceCharCount *= -1;
@@ -204,7 +204,7 @@ public class StyledTextArea extends Control {
// this.lastTextChangeReplaceLineCount = event.replaceLineCount;
this.lastTextChangeReplaceCharCount = event.replaceCharCount;
- this.renderer.textChanging(event);
+
// Update the caret offset if it is greater than the length of the
// content.
@@ -216,35 +216,40 @@ public class StyledTextArea extends Control {
setCaretOffset(newEndOfText/* , SWT.DEFAULT */);
}
- void handleTextSet(TextChangedEvent event) {
- int newCharCount = getCharCount();
- if( this.caretOffsetProperty.get() > newCharCount ) {
- this.caretOffsetProperty.set(newCharCount);
- }
+ void handleTextChanged(TextChangedEvent xxx) {
+ System.err.println("handleTextChanged");
- // in SWT this is done in reset()
- clearSelection();
+ if (changingEvent == null) {
+ // full text change
+ int newCharCount = getCharCount();
+ if( this.caretOffsetProperty.get() > newCharCount ) {
+ this.caretOffsetProperty.set(newCharCount);
+ }
+
+ // in SWT this is done in reset()
+ clearSelection();
- if( getSkin() instanceof StyledTextSkin ) {
- ((StyledTextSkin)getSkin()).recalculateItems();
+// if( getSkin() instanceof StyledTextSkin ) {
+// ((StyledTextSkin)getSkin()).computeModel();
+// }
}
- }
+ else {
+ // partial text change
+ TextChangingEvent event = changingEvent;
+ changingEvent = null;
- void handleTextChanged(TextChangedEvent event) {
- // int firstLine = getContent().getLineAtOffset(lastTextChangeStart);
- if (getSkin() instanceof StyledTextSkin) {
- ((StyledTextSkin) getSkin()).recalculateItems();
+// if (getSkin() instanceof StyledTextSkin) {
+// ((StyledTextSkin) getSkin()).computeModelDelta(event);
+// }
}
+ }
- // TODO We need to re enable this in the future.
- // For each change coming from outside (for example refactoring)
- // we need to update the caret
+ void handleTextSet(TextChangedEvent event) {
+ System.err.println("handleTextSet");
+ changingEvent = null;
+ }
-// updateSelection(this.lastTextChangeStart, this.lastTextChangeReplaceCharCount, this.lastTextChangeNewCharCount);
- // lastCharCount += lastTextChangeNewCharCount;
- // lastCharCount -= lastTextChangeReplaceCharCount;
- }
void updateSelection(int startOffset, int replacedLength, int newLength) {
if (getSelection().offset + getSelection().length > startOffset && getSelection().offset < startOffset + replacedLength) {
@@ -534,9 +539,10 @@ public class StyledTextArea extends Control {
this.renderer.setStyleRanges(ranges, styles);
}
- if (getSkin() instanceof StyledTextSkin) {
- ((StyledTextSkin) getSkin()).recalculateItems();
- }
+// if (getSkin() instanceof StyledTextSkin) {
+// ((StyledTextSkin) getSkin()).requestRedraw();
+// }
+
}
/**
@@ -1465,6 +1471,12 @@ public class StyledTextArea extends Control {
}
+ private IntegerProperty lineCount = new SimpleIntegerProperty(this, "lineCount", 0);
+ public ReadOnlyIntegerProperty lineCountProperty() {
+ return lineCount;
+ }
+
+
/**
* Paste the clipboard content
*/
@@ -1501,38 +1513,8 @@ public class StyledTextArea extends Control {
}
}
- /**
- * @return property holding the factory to create graphics in the ruler
- */
- @NonNull
- public final ObjectProperty<@NonNull Callback<@NonNull StyledTextLine, @Nullable Node>> lineRulerGraphicNodeFactoryProperty() {
- return this.lineRulerGraphicNodeFactory;
- }
-
- /**
- * @return the current factory to create graphics in the ruler
- */
- public final @NonNull Callback<@NonNull StyledTextLine, @Nullable Node> getLineRulerGraphicNodeFactory() {
- return this.lineRulerGraphicNodeFactoryProperty().get();
- }
-
- /**
- * Set a new factory to create graphics in the ruler
- *
- * @param lineRulerGraphicNodeFactory
- * the factory
- */
- public final void setLineRulerGraphicNodeFactory(final @NonNull Callback<@NonNull StyledTextLine, @Nullable Node> lineRulerGraphicNodeFactory) {
- this.lineRulerGraphicNodeFactoryProperty().set(lineRulerGraphicNodeFactory);
- }
-
- /**
- * Refresh the line ruler
- */
- public void refreshLineRuler() {
- if (getSkin() != null) {
- // TODO We need to send an event!
- ((StyledTextSkin) getSkin()).refreshLineRuler();
- }
+ public int getOffsetAtPosition(double x, double y) {
+ int result = ((StyledTextSkin) getSkin()).getOffsetAtPosition(x, y);
+ return result;
}
}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextLayoutContainer.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextLayoutContainer.java
deleted file mode 100644
index 9d41dc27d..000000000
--- a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextLayoutContainer.java
+++ /dev/null
@@ -1,487 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 BestSolution.at and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Tom Schindl<tom.schindl@bestsolution.at> - initial API and implementation
- * Christoph Caks <ccaks@bestsolution.at> - improved editor behavior
- *******************************************************************************/
-package org.eclipse.fx.ui.controls.styledtext;
-
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-
-import javafx.animation.Animation;
-import javafx.animation.FadeTransition;
-import javafx.beans.Observable;
-import javafx.beans.binding.Bindings;
-import javafx.beans.property.IntegerProperty;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.ReadOnlyBooleanProperty;
-import javafx.beans.property.SimpleBooleanProperty;
-import javafx.beans.property.SimpleIntegerProperty;
-import javafx.beans.property.SimpleObjectProperty;
-import javafx.collections.FXCollections;
-import javafx.collections.ObservableList;
-import javafx.geometry.Point2D;
-import javafx.scene.Node;
-import javafx.scene.layout.Region;
-import javafx.scene.shape.Line;
-import javafx.scene.text.TextFlow;
-import javafx.util.Duration;
-
-/**
- * A text layout container who is able to show selection
- *
- * @since 2.0
- */
-public class StyledTextLayoutContainer extends Region {
- @SuppressWarnings("null")
- @NonNull
- private final ObservableList<@NonNull StyledTextNode> textNodes = FXCollections.observableArrayList();
-
- @NonNull
- private final IntegerProperty caretIndex = new SimpleIntegerProperty(this, "caretIndex"); //$NON-NLS-1$
-
- @NonNull
- private final IntegerProperty startOffset = new SimpleIntegerProperty(this, "startOffset"); //$NON-NLS-1$
-
-
-// @NonNull
-// final SetProperty<@NonNull StyledTextAnnotation> annotationsProperty = new SimpleSetProperty<>(this, "annotations", FXCollections.observableSet()); //$NON-NLS-1$
-// public SetProperty<@NonNull StyledTextAnnotation> getAnnotations() {
-// return this.annotationsProperty;
-// }
-//
-// private Map<StyledTextAnnotation, Rectangle> annotationMarkers = new HashMap<>();
-
- /**
- * The start offset if used in a bigger context like {@link StyledTextArea}
- *
- * @return the offset property to observe
- */
- public final IntegerProperty startOffsetProperty() {
- return this.startOffset;
- }
-
- /**
- * the the caret index property.
- *
- * <p>the index or <code>-1</code> if caret is to be hidden</p>
- *
- * @return the caret index property
- */
- public final IntegerProperty caretIndexProperty() {
- return this.caretIndex;
- }
-
- /**
- * Set the caret index
- *
- * @param index
- * the index or <code>-1</code> if caret is to be hidden
- */
- public void setCaretIndex(int index) {
- this.caretIndex.set(index);
- }
-
- /**
- * Returns the caret index
- *
- * @return the index or <code>-1</code> if caret is to be hidden
- */
- public int getCaretIndex() {
- return this.caretIndex.get();
- }
-
-
- /**
- * The start offset if used in a bigger context like {@link StyledTextArea}
- *
- * @return the offset
- */
- public final int getStartOffset() {
- return this.startOffsetProperty().get();
- }
-
- /**
- * The start offset if used in a bigger context like {@link StyledTextArea}
- *
- * @param startOffset
- * the start offset
- */
- public final void setStartOffset(final int startOffset) {
- this.startOffsetProperty().set(startOffset);
- }
-
- @NonNull
- private final ObjectProperty<@NonNull TextSelection> selection = new SimpleObjectProperty<>(this, "selection", TextSelection.EMPTY); //$NON-NLS-1$
-
- /**
- * The selection currently shown
- *
- * @return the selection property to observe
- */
- public final ObjectProperty<@NonNull TextSelection> selectionProperty() {
- return this.selection;
- }
-
- /**
- * @return the text selection
- */
- public final @NonNull TextSelection getSelection() {
- return this.selectionProperty().get();
- }
-
- /**
- * The selection to be shown
- *
- * @param selection
- * the new selection
- */
- public final void setSelection(final @NonNull TextSelection selection) {
- this.selectionProperty().set(selection);
- }
-
- private final Region selectionMarker = new Region();
- final Line caret = new Line();
- private final TextFlow textLayoutNode = new TextFlow();
-
- private StyledTextNode selectionStartNode;
- private StyledTextNode selectionEndNode;
-
- private Animation caretAnimation;
-
- private final ReadOnlyBooleanProperty ownerFocusedProperty;
-
- /**
- * Create a container to layout text and allows to show e.g. a selection
- * range
- */
- public StyledTextLayoutContainer() {
- this(new SimpleBooleanProperty(true));
- }
-
- private static Animation createCaretAnimation(Node caret) {
- FadeTransition t = new FadeTransition(Duration.millis(400), caret);
- t.setAutoReverse(true);
- t.setFromValue(1);
- t.setToValue(0);
- t.setCycleCount(Animation.INDEFINITE);
- return t;
- }
-
- /**
- * Create a container to layout text and allows to show e.g. a selection
- * range
- *
- * @param ownerFocusedProperty
- * property identifing if the owner of the text container has the
- * input focus
- */
- @SuppressWarnings("null")
- public StyledTextLayoutContainer(ReadOnlyBooleanProperty ownerFocusedProperty) {
- this.ownerFocusedProperty = ownerFocusedProperty;
- getStyleClass().add("styled-text-layout-container"); //$NON-NLS-1$
- this.textNodes.addListener(this::recalculateOffset);
- this.selectionMarker.setVisible(false);
- this.selectionMarker.setManaged(false);
- this.selectionMarker.getStyleClass().add("selection-marker"); //$NON-NLS-1$
- this.caret.setVisible(false);
- this.caret.setStrokeWidth(2);
- this.caret.setManaged(false);
- this.caret.getStyleClass().add("text-caret"); //$NON-NLS-1$
-
- this.caretAnimation = createCaretAnimation(this.caret);
-
- Bindings.bindContent(this.textLayoutNode.getChildren(), this.textNodes);
- getChildren().setAll(this.selectionMarker, this.textLayoutNode, this.caret);
- selectionProperty().addListener(this::handleSelectionChange);
- this.textNodes.addListener(this::handleSelectionChange);
-
- this.ownerFocusedProperty.addListener(this::updateCaretVisibility);
- this.caretIndex.addListener(this::updateCaretVisibility);
-
- updateCaretVisibility(null);
-
-// this.annotationsProperty.addListener(new SetChangeListener<StyledTextAnnotation>() {
-// @Override
-// public void onChanged(javafx.collections.SetChangeListener.Change<? extends StyledTextAnnotation> change) {
-// System.err.println("ON ANNOTATION MARKER CHANGE");
-// if (change.getElementAdded() != null) {
-// StyledTextAnnotation a = change.getElementAdded();
-//
-// Rectangle child = new Rectangle();
-//
-// if (a.getType().contains("ERROR")) {
-// child.setFill(Color.RED);
-// }
-// else if (a.getType().contains("WARN")) {
-// child.setFill(Color.YELLOW);
-// }
-// child.setOpacity(0.3);
-//
-// Tooltip tt = new Tooltip();
-// tt.setText(a.getText());
-// getChildren().add(child);
-// Tooltip.install(child, tt);
-//
-// annotationMarkers.put(a, child);
-// }
-// if (change.getElementRemoved() != null) {
-// StyledTextAnnotation a = change.getElementRemoved();
-//
-// Rectangle child = annotationMarkers.remove(a);
-// if (child != null) {
-// getChildren().remove(child);
-// }
-// }
-//
-// requestLayout();
-// }
-// });
- }
-
- private void updateCaretVisibility(Observable o) {
- if (this.ownerFocusedProperty.get() && this.caretIndex.get() != -1) {
- showCaret();
- }
- else {
- hideCaret();
- }
- }
-
- private void showCaret() {
- this.caret.setVisible(true);
- this.caretAnimation.play();
- requestLayout();
- }
-
- private void hideCaret() {
- this.caret.setVisible(false);
- this.caretAnimation.stop();
- requestLayout();
- }
-
- private int getEndOffset() {
- return getStartOffset() + getText().length();
- }
-
- /**
- * Check if the offset is between the start and end
- *
- * @param start
- * the start
- * @param end
- * the end
- * @return <code>true</code> if intersects the offset
- */
- public boolean intersectOffset(int start, int end) {
- if (getStartOffset() > end) {
- return false;
- } else if (getEndOffset() < start) {
- return false;
- }
- return true;
- }
-
- private void recalculateOffset(Observable o) {
- int offset = 0;
- for (StyledTextNode t : this.textNodes) {
- t.setStartOffset(offset);
- offset = t.getEndOffset();
- }
- }
-
- private void handleSelectionChange(Observable o) {
- TextSelection selection = getSelection();
- if (selection.length == 0) {
- this.selectionMarker.setVisible(false);
- }
- else {
- this.selectionMarker.setVisible(true);
- int startOffset = selection.offset;
- int endOffset = selection.offset + selection.length;
-
- this.selectionStartNode = null;
- this.selectionEndNode = null;
- for (StyledTextNode t : this.textNodes) {
- if (t.intersectOffset(startOffset, endOffset)) {
- if (this.selectionStartNode == null) {
- this.selectionStartNode = t;
- }
- this.selectionEndNode = t;
- }
- }
- requestLayout();
- }
- }
-
- @Override
- protected double computePrefHeight(double width) {
- double d = super.computePrefHeight(width);
- return d;
- }
-
-// private double findX(int localOffset) {
-//
-// double len = 0;
-// for (StyledTextNode t : this.textNodes) {
-// if (t.getStartOffset() <= localOffset && t.getEndOffset() > localOffset || this.textNodes.get(this.textNodes.size() - 1) == t) {
-// return len + t.getCharLocation(localOffset - t.getStartOffset());
-// }
-// len += t.getWidth();
-// }
-// return -1;
-// }
-
- private void layoutAnnotations() {
-// for (Entry<StyledTextAnnotation, Rectangle> e : annotationMarkers.entrySet()) {
-// System.err.println("LAYOUTING MARKER: " + e.getKey().getText());
-// final int globalBeginIndex = e.getKey().getStartOffset();
-// final int globalEndIndex = e.getKey().getStartOffset() + e.getKey().getLength();
-//
-// System.err.println("global: " + globalBeginIndex + " - " + globalEndIndex);
-//
-// final int localBeginIndex = Math.max(0, globalBeginIndex - getStartOffset());
-// final int localEndIndex = Math.min(getText().length(), globalEndIndex - getStartOffset());
-//
-// System.err.println("local: " + localBeginIndex + " - " + localEndIndex);
-//
-// double xBegin = findX(localBeginIndex);
-// double xEnd = findX(localEndIndex);
-//
-//// System.err.println(xBegin + ", " + getInsets().getTop() + ", " + (xEnd - xBegin)+ ", " + textLayoutNode.prefHeight(-1));
-//// e.getValue().resizeRelocate(xBegin, getInsets().getTop(), xEnd - xBegin, textLayoutNode.prefHeight(-1));
-// e.getValue().setX(xBegin);
-// e.getValue().setY(getInsets().getTop());
-// e.getValue().setWidth(xEnd - xBegin);
-// e.getValue().setHeight(textLayoutNode.prefHeight(-1));
-// e.getValue().toFront();
-// System.err.println(" -> " + e.getValue());
-// }
- }
-
- private void layoutSelection() {
- if (this.selectionStartNode != null && this.selectionEndNode != null) {
- final TextSelection selection = getSelection();
- final double selectionStart = this.selectionStartNode.getLayoutX() + this.selectionStartNode.getCharLocation(selection.offset - this.selectionStartNode.getStartOffset());
- final double selectionEnd = this.selectionEndNode.getLayoutX() + this.selectionEndNode.getCharLocation(selection.offset + selection.length - this.selectionEndNode.getStartOffset());
- this.selectionMarker.resizeRelocate(selectionStart, 0, selectionEnd - selectionStart, getHeight());
- }
- }
-
- private void layoutCaret() {
- if (this.getCaretIndex() >= 0) {
- for (StyledTextNode t : this.textNodes) {
- // XXX do we really need to apply the css on the StyledTextNode here??
-// t.applyCss();
- if (t.getStartOffset() <= this.getCaretIndex() && (t.getEndOffset() > this.getCaretIndex() || this.textNodes.get(this.textNodes.size() - 1) == t)) {
- double caretX = t.getCharLocation(this.getCaretIndex() - t.getStartOffset());
- double x = this.textLayoutNode.localToParent(t.getBoundsInParent().getMinX(), 0).getX() + caretX;
-
- double h = getHeight();
-
- this.caret.setStartX(x);
- this.caret.setEndX(x);
- this.caret.setStartY(getInsets().getTop() + 1);
- this.caret.setEndY(h + getInsets().getTop() + 1);
- this.caret.toFront();
- return;
- }
- }
-
- this.caret.setStartX(0);
- this.caret.setEndX(0);
- this.caret.setStartY(0);
- this.caret.setEndY(15);
-
- } else {
- this.caret.setStartX(0);
- this.caret.setEndX(0);
- this.caret.setStartY(0);
- this.caret.setEndY(getHeight());
- }
- }
-
- @Override
- protected void layoutChildren() {
- super.layoutChildren();
-
- this.textLayoutNode.relocate(getInsets().getLeft(), getInsets().getTop());
-
- // we need to ensure that the text is layouted correctly before processing any kind of markers
- // since we need the correct letter positions to place them
- this.textLayoutNode.layout();
- this.textLayoutNode.applyCss();
-
- layoutSelection();
- layoutCaret();
-
- layoutAnnotations();
-
- }
-
- /**
- * @return list of text nodes rendered
- */
- public @NonNull ObservableList<@NonNull StyledTextNode> getTextNodes() {
- return this.textNodes;
- }
-
- /**
- * Find the caret index at the give point
- *
- * @param point
- * the point relative to coordinate system of this node
- * @return the index or <code>-1</code> if not found
- */
- public int getCaretIndexAtPoint(Point2D point) {
- Point2D scenePoint = localToScene(point);
- for (StyledTextNode t : this.textNodes) {
- if (t.localToScene(t.getBoundsInLocal()).contains(scenePoint)) {
- int idx = t.getCaretIndexAtPoint(t.sceneToLocal(scenePoint));
- if( idx != -1 ) {
- return idx + t.getStartOffset();
- }
- }
- }
-
- return -1;
- }
-
- private String getText() {
- StringBuilder b = new StringBuilder();
- for (StyledTextNode t : this.textNodes) {
- b.append(t.getText());
- }
- return b.toString();
- }
-
- /**
- * Find the position of a the caret at a given index
- *
- * @param index
- * the index
- * @return the location relative to this node or <code>null</code>
- */
- public @Nullable Point2D getCareLocation(int index) {
- for (StyledTextNode t : this.textNodes) {
- if (t.getStartOffset() <= index && t.getEndOffset() >= index) {
- double x = t.getCharLocation(index);
- return sceneToLocal(t.localToScene(x, 0));
- }
- }
- return null;
- }
-
- /**
- * Releases resources immediately
- */
- public void dispose() {
- this.caretAnimation.stop();
- }
-}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/TextChangedEvent.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/TextChangedEvent.java
index aa4372eab..b40e6c0ab 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/TextChangedEvent.java
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/TextChangedEvent.java
@@ -71,6 +71,11 @@ public class TextChangedEvent {
return new TextChangedEvent(source, 0, 0, 0, null, 0, 0);
}
+ public static TextChangedEvent textChanged(StyledTextContent source, int offset, int replaceLength, String newText) {
+ return new TextChangedEvent(source, offset, replaceLength, -1, newText, newText.length(), -1);
+ }
+
+
/**
* Create the text set event
*
@@ -82,4 +87,11 @@ public class TextChangedEvent {
return new TextChangedEvent(source, 0, 0, 0, null, 0, 0);
}
+
+ @Override
+ public String toString() {
+ return "TextChangedEvent(offset="+this.offset+", replaceCharCount="+this.replaceCharCount+", replaceText=" + (this.newText == null ? "null" : "\""+this.newText+"\"") + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$
+ }
+
+
}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/TextChangingEvent.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/TextChangingEvent.java
index 8c837812c..aa31ef73c 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/TextChangingEvent.java
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/TextChangingEvent.java
@@ -82,4 +82,9 @@ public class TextChangingEvent {
public static TextChangingEvent textChanging(StyledTextContent source, int offset, int replaceCharCount, int replaceLineCount, String newText, int newCharCount, int newLineCount) {
return new TextChangingEvent(source, offset, replaceCharCount, replaceLineCount, newText, newCharCount, newLineCount);
}
+
+ @Override
+ public String toString() {
+ return "TextChangingEvent(offset="+this.offset+", replaceCharCount="+this.replaceCharCount+", replaceText=" + (this.newText == null ? "null" : "\""+this.newText+"\"") + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$
+ }
}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/VerticalLineFlow.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/VerticalLineFlow.java
new file mode 100644
index 000000000..6dba57505
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/VerticalLineFlow.java
@@ -0,0 +1,158 @@
+package org.eclipse.fx.ui.controls.styledtext;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Stack;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import javafx.beans.InvalidationListener;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ListProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.property.SimpleListProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.collections.FXCollections;
+import javafx.scene.Node;
+import javafx.scene.layout.Pane;
+
+import com.google.common.collect.ContiguousSet;
+import com.google.common.collect.DiscreteDomain;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+
+public class VerticalLineFlow<M, N> extends NodeCachePane {
+
+ private Predicate<Set<N>> needsPresentation;
+
+ private BiConsumer<Node, Set<N>> nodePopulator;
+ private Function<Integer, Set<N>> converter;
+ protected Map<Integer, Double> yOffsetData = new HashMap<>();
+
+ private DoubleProperty lineHeigth = new SimpleDoubleProperty(this, "lineHeight", 16.0);
+ public DoubleProperty lineHeightProperty() {
+ return this.lineHeigth;
+ }
+ public double getLineHeight() {
+ return this.lineHeigth.get();
+ }
+ public void setLineHeight(double lineHeight) {
+ this.lineHeigth.set(lineHeight);
+ }
+
+// private ListProperty<M> model = new SimpleListProperty<>(this, "model", FXCollections.observableArrayList());
+// public ListProperty<M> getModel() {
+// return this.model;
+// }
+
+ private IntegerProperty numberOfLines = new SimpleIntegerProperty(this, "numberOfLines", 0);
+ public IntegerProperty numberOfLinesProperty() {
+ return this.numberOfLines;
+ }
+
+ private ObjectProperty<Range<Integer>> visibleLines = new SimpleObjectProperty<>(this, "visibleLines", Range.all()); //$NON-NLS-1$
+ public ObjectProperty<Range<Integer>> visibleLinesProperty() {
+ return this.visibleLines;
+ }
+ public RangeSet<Integer> getVisibleLines() {
+ RangeSet<Integer> visibleLines = TreeRangeSet.create();
+ visibleLines.add(this.visibleLines.get());
+ return visibleLines.subRangeSet(Range.closedOpen(0, numberOfLines.get()));
+ }
+
+ public void setVisibleLines(Range<Integer> visibleLines) {
+ this.visibleLines.set(visibleLines);
+ }
+
+ public VerticalLineFlow(Function<Integer, Set<N>> converter, Predicate<Set<N>> needsPresentation, Supplier<Node> nodeFactory, BiConsumer<Node, Set<N>> nodePopulator) {
+ super(nodeFactory);
+ this.needsPresentation = needsPresentation;
+ this.nodePopulator = nodePopulator;
+ this.converter = converter;
+
+ this.visibleLines.addListener((InvalidationListener)(x)->prepareNodes(getVisibleLines()));
+ this.numberOfLines.addListener((InvalidationListener)(x)->prepareNodes(getVisibleLines()));
+ }
+
+ protected Map<Integer, Node> activeNodes = new HashMap<>();
+
+ protected void releaseNode(int lineIndex) {
+ Node node = activeNodes.remove(lineIndex);
+ if (node != null) {
+ releaseNode(node);
+ }
+ }
+
+ protected Node getNode(int lineIndex) {
+ Node node = activeNodes.get(lineIndex);
+
+ if (node == null) {
+ node = getNode();
+ }
+
+ activeNodes.put(lineIndex, node);
+ return node;
+ }
+
+ private void prepareNodes(RangeSet<Integer> range) {
+ if (range == null) return;
+
+// System.err.println("VerticalLineFlow: prepareNodes " + range);
+
+ // release invisible nodes
+ Iterator<Entry<Integer, Node>> iterator = this.activeNodes.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry<Integer, Node> entry = iterator.next();
+ int index = entry.getKey();
+ if (!range.contains(index)) {
+ releaseNode(entry.getValue());
+ iterator.remove();
+ }
+ }
+
+ // prepare range nodes
+ range.asRanges().forEach(r->ContiguousSet.create(r, DiscreteDomain.integers()).forEach(index -> prepareNode(index)));
+
+ }
+
+ private void prepareNode(int lineIndex) {
+ Node node = getNode(lineIndex);
+ nodePopulator.accept(node, converter.apply(lineIndex));
+ }
+
+ public void setLineOffset(int lineIndex, double yOffset) {
+ yOffsetData.put(lineIndex, yOffset);
+ requestLayout();
+ }
+
+ @Override
+ protected void layoutChildren() {
+ this.activeNodes.entrySet().forEach(e -> {
+ if (!yOffsetData.containsKey(e.getKey())) {
+ System.err.println("NO DATA FOR " + e);
+ return;
+ }
+ double x = 0;
+ double y = yOffsetData.get(e.getKey());
+ double width = getWidth();
+ double height = getLineHeight();
+ e.getValue().resizeRelocate(x, y, width, height);
+ });
+ }
+
+
+
+ public void update(RangeSet<Integer> r) {
+ prepareNodes(r.subRangeSet(visibleLines.get()).subRangeSet(Range.closedOpen(0, numberOfLines.get())));
+ }
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/HoverSupport.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/HoverSupport.java
index 166d8cf0a..69bb1184b 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/HoverSupport.java
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/HoverSupport.java
@@ -12,15 +12,13 @@
package org.eclipse.fx.ui.controls.styledtext.behavior;
import org.eclipse.fx.ui.controls.Util;
-import org.eclipse.fx.ui.controls.styledtext.StyledTextLayoutContainer;
-import org.eclipse.fx.ui.controls.styledtext.StyledTextNode;
import org.eclipse.fx.ui.controls.styledtext.events.TextHoverEvent;
import javafx.event.Event;
+import javafx.event.EventTarget;
import javafx.scene.Node;
-import javafx.scene.Parent;
-import javafx.scene.control.Control;
import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.Region;
import javafx.util.Duration;
/**
@@ -30,10 +28,10 @@ import javafx.util.Duration;
*/
public class HoverSupport {
- private Control control;
+ private Region control;
private TextHoverEvent lastHover;
- public HoverSupport(Control control) {
+ public HoverSupport(Region control) {
this.control = control;
}
@@ -45,7 +43,7 @@ public class HoverSupport {
}
- public static HoverSupport install(Control control) {
+ public static HoverSupport install(Region control) {
HoverSupport support = new HoverSupport(control);
support.install();
return support;
@@ -59,6 +57,7 @@ public class HoverSupport {
}
private void onMouseMoved(MouseEvent event) {
+ System.err.println("moved " + event.getTarget());
if( this.lastHover != null ) {
TextHoverEvent hoverEvent = createHoverEvent(event);
if( this.lastHover.getOffsetTokenStart() != hoverEvent.getOffsetTokenStart() ) {
@@ -77,26 +76,34 @@ public class HoverSupport {
protected static TextHoverEvent createHoverEvent(MouseEvent e) {
- Parent parent = ((Node)e.getTarget()).getParent();
- if( parent instanceof StyledTextNode ) {
- StyledTextNode n = (StyledTextNode) parent;
- if( n.getParent() == null || n.getParent().getParent() == null ) {
- return new TextHoverEvent(e, -1, -1, -1, ""); //$NON-NLS-1$
- }
- StyledTextLayoutContainer lc = (StyledTextLayoutContainer) n.getParent().getParent();
- int start = lc.getStartOffset() + n.getStartOffset();
- int end = lc.getStartOffset() + n.getEndOffset();
- String text = n.getText();
-
- int offset = n.getCaretIndexAtPoint(n.sceneToLocal(e.getSceneX(), e.getSceneY()));
- return new TextHoverEvent(e, start, end, start + offset, text);
- } else {
- return new TextHoverEvent(e, -1, -1, -1, ""); //$NON-NLS-1$
- }
+
+ System.err.println("createHover for " + e.getTarget());
+
+ // TODO fix hover event
+// if (e.getTarget() instanceof Annotation)
+
+ return new TextHoverEvent(e, -1, -1, -1, "");
+// Parent parent = ((Node)e.getTarget()).getParent();
+// if( parent instanceof StyledTextNode ) {
+// StyledTextNode n = (StyledTextNode) parent;
+// if( n.getParent() == null || n.getParent().getParent() == null ) {
+// return new TextHoverEvent(e, -1, -1, -1, ""); //$NON-NLS-1$
+// }
+// StyledTextLayoutContainer lc = (StyledTextLayoutContainer) n.getParent().getParent();
+// int start = lc.getStartOffset() + n.getStartOffset();
+// int end = lc.getStartOffset() + n.getEndOffset();
+// String text = n.getText();
+//
+// int offset = n.getCaretIndexAtPoint(n.sceneToLocal(e.getSceneX(), e.getSceneY()));
+// return new TextHoverEvent(e, start, end, start + offset, text);
+// } else {
+// return new TextHoverEvent(e, -1, -1, -1, ""); //$NON-NLS-1$
+// }
}
protected void handleHover(MouseEvent e) {
TextHoverEvent event = createHoverEvent(e);
+ System.err.println("hoverEvent " + event);
if( this.lastHover == null || this.lastHover.getOffsetTokenStart() != event.getOffsetTokenStart() ) {
Event.fireEvent(control, event);
if( event.getOffset() == -1 ) {
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/StyledTextBehavior.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/StyledTextBehavior.java
index 4f3b53d19..cee4c505d 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/StyledTextBehavior.java
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/StyledTextBehavior.java
@@ -17,7 +17,6 @@ import static javafx.scene.input.KeyCode.C;
import static javafx.scene.input.KeyCode.D;
import static javafx.scene.input.KeyCode.DELETE;
import static javafx.scene.input.KeyCode.DOWN;
-import static javafx.scene.input.KeyCode.E;
import static javafx.scene.input.KeyCode.END;
import static javafx.scene.input.KeyCode.ENTER;
import static javafx.scene.input.KeyCode.HOME;
@@ -50,18 +49,15 @@ import org.eclipse.fx.ui.controls.styledtext.ActionEvent;
import org.eclipse.fx.ui.controls.styledtext.ActionEvent.ActionType;
import org.eclipse.fx.ui.controls.styledtext.StyledTextArea;
import org.eclipse.fx.ui.controls.styledtext.StyledTextContent;
-import org.eclipse.fx.ui.controls.styledtext.StyledTextLayoutContainer;
import org.eclipse.fx.ui.controls.styledtext.TextSelection;
import org.eclipse.fx.ui.controls.styledtext.VerifyEvent;
import org.eclipse.fx.ui.controls.styledtext.behavior.StyledTextBehavior.KeyMapping.InputAction;
import org.eclipse.fx.ui.controls.styledtext.behavior.StyledTextBehavior.KeyMapping.KeyCombo;
import org.eclipse.fx.ui.controls.styledtext.events.TextPositionEvent;
import org.eclipse.fx.ui.controls.styledtext.skin.StyledTextSkin;
-import org.eclipse.fx.ui.controls.styledtext.skin.StyledTextSkin.LineCell;
import org.eclipse.jdt.annotation.NonNull;
import javafx.event.Event;
-import javafx.geometry.Bounds;
import javafx.scene.control.Control;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
@@ -103,7 +99,7 @@ public class StyledTextBehavior {
}
// called from skin
- public void installContentListeners(final Control contentNode) {
+ public void installContentListeners(final javafx.scene.layout.Region contentNode) {
this.textPositionSupport = TextPositionSupport.install(contentNode, getControl());
this.hoverSupport = HoverSupport.install(contentNode);
}
@@ -1324,49 +1320,50 @@ public class StyledTextBehavior {
// action for insert tab support
keyMapping.mapKey(new KeyCombo(TAB), ()->getControl().insert("\t")); //$NON-NLS-1$
- }
-
- /**
- * computes the text offset under the mouse cursor.
- * @param event
- * @return the offset
- */
- private int computeCursorOffset(MouseEvent event) {
- List<LineCell> visibleCells = ((StyledTextSkin) getControl().getSkin()).getCurrentVisibleCells();
-
- LineCell lastCell = null;
-
- int result = getControl().getContent().getCharCount();
-
- for (LineCell tmp : visibleCells) {
- Bounds boundsInParent = tmp.getBoundsInParent();
- if (boundsInParent.getMinY() > event.getY()) {
- if (lastCell == null) {
- lastCell = tmp;
- }
- if (lastCell.getDomainElement() != null) {
- StyledTextLayoutContainer n = (StyledTextLayoutContainer) lastCell.getGraphic();
- if (n.localToScene(n.getBoundsInLocal()).contains(event.getSceneX(), event.getSceneY())) {
- int index = n.getCaretIndexAtPoint(n.sceneToLocal(event.getSceneX(), event.getSceneY()));
- if (index >= 0) {
- return n.getStartOffset() + index;
- }
- }
-
- final double minX = n.localToScene(n.getBoundsInLocal()).getMinX();
- final double mouseX = event.getSceneX();
- final boolean left = minX >= mouseX;
-
- result = lastCell.getDomainElement().getLineOffset() + (left ? 0 : lastCell.getDomainElement().getLineLength());
- }
- break;
- }
- lastCell = tmp;
- }
- return result;
}
+// /**
+// * computes the text offset under the mouse cursor.
+// * @param event
+// * @return the offset
+// */
+// private int x_computeCursorOffset(MouseEvent event) {
+// List<LineCell1> visibleCells = ((StyledTextSkin) getControl().getSkin()).getCurrentVisibleCells();
+//
+// LineCell lastCell = null;
+//
+// int result = getControl().getContent().getCharCount();
+//
+// for (LineCell tmp : visibleCells) {
+// Bounds boundsInParent = tmp.getBoundsInParent();
+// if (boundsInParent.getMinY() > event.getY()) {
+// if (lastCell == null) {
+// lastCell = tmp;
+// }
+//
+// if (lastCell.getDomainElement() != null) {
+// StyledTextLayoutContainer n = (StyledTextLayoutContainer) lastCell.getGraphic();
+// if (n.localToScene(n.getBoundsInLocal()).contains(event.getSceneX(), event.getSceneY())) {
+// int index = n.getCaretIndexAtPoint(n.sceneToLocal(event.getSceneX(), event.getSceneY()));
+// if (index >= 0) {
+// return n.getStartOffset() + index;
+// }
+// }
+//
+// final double minX = n.localToScene(n.getBoundsInLocal()).getMinX();
+// final double mouseX = event.getSceneX();
+// final boolean left = minX >= mouseX;
+//
+// result = lastCell.getDomainElement().getLineOffset() + (left ? 0 : lastCell.getDomainElement().getLineLength());
+// }
+// break;
+// }
+// lastCell = tmp;
+// }
+// return result;
+// }
+
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/TextPositionSupport.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/TextPositionSupport.java
index 1e3b0210e..952712e19 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/TextPositionSupport.java
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/TextPositionSupport.java
@@ -11,19 +11,12 @@
*******************************************************************************/
package org.eclipse.fx.ui.controls.styledtext.behavior;
-import java.util.List;
-
import org.eclipse.fx.ui.controls.styledtext.StyledTextArea;
-import org.eclipse.fx.ui.controls.styledtext.StyledTextLayoutContainer;
-import org.eclipse.fx.ui.controls.styledtext.events.TextHoverEvent;
import org.eclipse.fx.ui.controls.styledtext.events.TextPositionEvent;
-import org.eclipse.fx.ui.controls.styledtext.skin.StyledTextSkin;
-import org.eclipse.fx.ui.controls.styledtext.skin.StyledTextSkin.LineCell;
import javafx.event.Event;
-import javafx.geometry.Bounds;
-import javafx.scene.control.Control;
import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.Region;
/**
* manages text position support
@@ -33,9 +26,9 @@ import javafx.scene.input.MouseEvent;
public class TextPositionSupport {
private StyledTextArea textArea;
- private Control control;
+ private Region control;
- public TextPositionSupport(Control control, StyledTextArea textArea) {
+ public TextPositionSupport(Region control, StyledTextArea textArea) {
this.control = control;
this.textArea = textArea;
}
@@ -49,7 +42,7 @@ public class TextPositionSupport {
this.control.addEventFilter(MouseEvent.MOUSE_MOVED, this::onMouseEvent);
}
- public static TextPositionSupport install(Control control, StyledTextArea textArea) {
+ public static TextPositionSupport install(Region control, StyledTextArea textArea) {
TextPositionSupport support = new TextPositionSupport(control, textArea);
support.install();
return support;
@@ -65,39 +58,43 @@ public class TextPositionSupport {
* @return the offset
*/
protected int computeCursorOffset(MouseEvent event) {
- List<LineCell> visibleCells = ((StyledTextSkin) this.textArea.getSkin()).getCurrentVisibleCells();
-
- LineCell lastCell = null;
-
- int result = this.textArea.getContent().getCharCount();
-
- for (LineCell tmp : visibleCells) {
- Bounds boundsInParent = tmp.getBoundsInParent();
- if (boundsInParent.getMinY() > event.getY()) {
- if (lastCell == null) {
- lastCell = tmp;
- }
-
- if (lastCell.getDomainElement() != null) {
- StyledTextLayoutContainer n = (StyledTextLayoutContainer) lastCell.getGraphic();
- if (n.localToScene(n.getBoundsInLocal()).contains(event.getSceneX(), event.getSceneY())) {
- int index = n.getCaretIndexAtPoint(n.sceneToLocal(event.getSceneX(), event.getSceneY()));
- if (index >= 0) {
- return n.getStartOffset() + index;
- }
- }
-
- final double minX = n.localToScene(n.getBoundsInLocal()).getMinX();
- final double mouseX = event.getSceneX();
- final boolean left = minX >= mouseX;
-
- result = lastCell.getDomainElement().getLineOffset() + (left ? 0 : lastCell.getDomainElement().getLineLength());
- }
- break;
- }
- lastCell = tmp;
- }
- return result;
+ return textArea.getOffsetAtPosition(event.getX(), event.getY());
+
+
+// List<LineCell> visibleCells = ((StyledTextSkin) this.textArea.getSkin()).getCurrentVisibleCells();
+//
+// LineCell lastCell = null;
+//
+// int result = this.textArea.getContent().getCharCount();
+//
+// for (LineCell tmp : visibleCells) {
+// Bounds boundsInParent = tmp.getBoundsInParent();
+// if (boundsInParent.getMinY() > event.getY()) {
+// if (lastCell == null) {
+// lastCell = tmp;
+// }
+//
+// if (lastCell.getDomainElement() != null) {
+// LineNode n = (LineNode) lastCell.getGraphic();
+//// StyledTextLayoutContainer n = (StyledTextLayoutContainer) lastCell.getGraphic();
+// if (n.localToScene(n.getBoundsInLocal()).contains(event.getSceneX(), event.getSceneY())) {
+// int index = n.getCaretIndexAtPoint(n.sceneToLocal(event.getSceneX(), event.getSceneY()));
+// if (index >= 0) {
+// return n.getStartOffset() + index;
+// }
+// }
+//
+// final double minX = n.localToScene(n.getBoundsInLocal()).getMinX();
+// final double mouseX = event.getSceneX();
+// final boolean left = minX >= mouseX;
+//
+// result = lastCell.getDomainElement().getLineOffset() + (left ? 0 : lastCell.getDomainElement().getLineLength());
+// }
+// break;
+// }
+// lastCell = tmp;
+// }
+// return result;
}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/ContentView.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/ContentView.java
new file mode 100644
index 000000000..de0ede402
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/ContentView.java
@@ -0,0 +1,816 @@
+package org.eclipse.fx.ui.controls.styledtext.internal;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import org.eclipse.fx.ui.controls.styledtext.StyledTextContent;
+import org.eclipse.fx.ui.controls.styledtext.StyledTextContent.TextChangeListener;
+import org.eclipse.fx.ui.controls.styledtext.TextChangedEvent;
+import org.eclipse.fx.ui.controls.styledtext.TextChangingEvent;
+import org.eclipse.fx.ui.controls.styledtext.TextSelection;
+import org.eclipse.fx.ui.controls.styledtext.model.TextAnnotationPresenter;
+
+import com.google.common.collect.ContiguousSet;
+import com.google.common.collect.DiscreteDomain;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+
+import javafx.beans.Observable;
+import javafx.beans.binding.DoubleBinding;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyIntegerProperty;
+import javafx.beans.property.SetProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.property.SimpleSetProperty;
+import javafx.collections.FXCollections;
+import javafx.geometry.Point2D;
+import javafx.scene.control.ScrollBar;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.StackPane;
+
+public class ContentView extends Pane {
+
+ private static boolean debugOut = Boolean.getBoolean("styledtext.debugout");
+
+ private SetProperty<TextAnnotationPresenter> textAnnotationPresenter = new SimpleSetProperty<>(FXCollections.observableSet());
+ public SetProperty<TextAnnotationPresenter> textAnnotationPresenterProperty() {
+ return textAnnotationPresenter;
+ }
+
+ private class LineLayer extends VFlow<LineNode> {
+
+ public LineLayer(Supplier<LineNode> nodeFactory, BiConsumer<LineNode, Integer> nodePopulator) {
+ super(nodeFactory, nodePopulator);
+
+ setOnRelease(n->n.release());
+ setOnActivate((idx, n)->n.setIndex(idx));
+ }
+
+
+
+// protected void releaseNode(int lineIndex) {
+//
+// get(model.get(lineIndex)).ifPresent(n->{
+// n.setVisible(false);
+// n.release();
+// });
+// }
+
+// protected void releaseNode(StyledTextLine line) {
+// if (debugOut) System.err.println("RELEASE " + line);
+// release(line);
+//// get(line).ifPresent(n->{
+//// n.setVisible(false);
+//// n.release();
+//// });
+// }
+
+// private void updateNode(StyledTextLine line) {
+// if (debugOut) System.err.println("UPDATE " + line);
+// LineNode node = get(line);
+// node.update(line, textAnnotationPresenter);
+//// LineNode node = getCreate(m);
+//// node.setVisible(true);
+////// node.setModel(m);
+//// node.update(m, textAnnotationPresenter);
+// }
+
+// private void updateNode(int lineIndex, StyledTextLine m) {
+// LineNode node = getCreate(m);
+// node.setVisible(true);
+//// node.setModel(m);
+// node.update(m, textAnnotationPresenter);
+// }
+
+// @Override
+// public void requestLayout() {
+// super.requestLayout();
+// }
+
+// @Override
+// protected void layoutChildren() {
+// if (debugOut) System.err.println("layout LineLayer");
+// ContiguousSet.create(visibleLines.get(), DiscreteDomain.integers()).forEach(e -> {
+// if (!yOffsetData.containsKey(e)) {
+// if (debugOut) System.err.println("NO yOffset FOR " + e);
+// return;
+// }
+// double x = 0;
+// double y = yOffsetData.get(e);
+// double width = getWidth();
+// double height = getLineHeight();
+//
+// if (model.size() > e) {
+// StyledTextLine m = model.get(e);
+// LineNode lineNode = get(m);
+// lineNode.resizeRelocate(x, y, width, height);
+//
+// if (debugOut) System.err.println("layout " + e + ": y=" + y + " line= " + lineNode);
+// if (debugOut) System.err.println(" visible = " + lineNode.isVisible());
+// if (debugOut) System.err.println(" bounds = " + lineNode.getBoundsInParent());
+//// get(m).ifPresent(n->n.resizeRelocate(x, y, width, height));
+//
+// lineNode.layout();
+// }
+// });
+// }
+
+ @Override
+ protected void releaseNode(int lineIndex) {
+ super.releaseNode(lineIndex);
+
+ }
+
+ private Stream<LineNode> createVisibleLineNodesStream() {
+ ContiguousSet<Integer> visibleIndexes = ContiguousSet.create(visibleLines.get(), DiscreteDomain.integers());
+// return visibleIndexes.stream().filter(i->i<model.size()).map(idx->get(model.get(idx))).filter(n->n.isPresent()).map(n->n.get()).filter(n->n.isVisible());
+ return visibleIndexes.stream().filter(i->i<getNumberOfLines()).map(idx->getVisibleNode(idx)).filter(n->n.isPresent()).map(n->n.get());
+ }
+
+ private Optional<Integer> getLineIndex(javafx.geometry.Point2D point) {
+ final Optional<LineNode> hitLine = createVisibleLineNodesStream().filter(n->n.getBoundsInParent().contains(point)).findFirst();
+ final Optional<Integer> index = hitLine.map(n->{
+ int i = n.getCaretIndexAtPoint(new javafx.geometry.Point2D(point.getX(), n.getHeight()/2));
+ if (i >= 0 ) {
+ return n.getStartOffset() + i;
+ }
+ else {
+ return n.getEndOffset();
+ }
+ });
+ return index;
+ }
+
+
+// protected void permutate(int a, int b) {
+// LineNode nodeA = existingNodes.get(a);
+// LineNode nodeB = existingNodes.get(b);
+//
+// existingNodes.put(a, nodeB);
+// existingNodes.put(b, nodeA);
+//
+// System.err.println(" . permutate " + a + " -> " + b);
+// }
+ }
+
+ private StackPane contentBody = new StackPane();
+
+ private LineLayer lineLayer = new LineLayer(()->new LineNode(), (n, m)->{
+ n.setLineHelper(getLineHelper());
+ n.update(textAnnotationPresenter.get());
+ });
+
+ private Predicate<Set<LineNode>> needsPresentation;
+
+ private Map<Integer, Double> yOffsetData = new HashMap<>();
+
+ private boolean forceLayout = true;
+
+ private DoubleProperty lineHeigth = new SimpleDoubleProperty(this, "lineHeight", 16.0);
+ public DoubleProperty lineHeightProperty() {
+ return this.lineHeigth;
+ }
+ public double getLineHeight() {
+ return this.lineHeigth.get();
+ }
+ public void setLineHeight(double lineHeight) {
+ this.lineHeigth.set(lineHeight);
+ }
+
+ private IntegerProperty numberOfLines = new SimpleIntegerProperty(this, "numberOfLines", 0);
+ public ReadOnlyIntegerProperty numberOfLinesProperty() {
+ return this.numberOfLines;
+ }
+ public int getNumberOfLines() {
+ return this.numberOfLines.get();
+ }
+
+ private IntegerProperty caretOffset = new SimpleIntegerProperty(this, "caretOffset", 0);
+ public IntegerProperty caretOffsetProperty() {
+ return this.caretOffset;
+ }
+
+ private ObjectProperty<TextSelection> textSelection = new SimpleObjectProperty<>(this, "textSelection", new TextSelection(0, 0));
+ public ObjectProperty<TextSelection> textSelectionProperty() {
+ return this.textSelection;
+ }
+
+ private ObjectProperty<StyledTextContent> content = new SimpleObjectProperty<>(this, "content");
+ public ObjectProperty<StyledTextContent> contentProperty() {
+ return this.content;
+ }
+ public StyledTextContent getContent() {
+ return this.content.get();
+ }
+
+ private LineHelper lineHelper;
+
+ protected LineLayer getLineLayer() {
+ return this.lineLayer;
+ }
+
+ protected LineHelper getLineHelper() {
+ return this.lineHelper;
+ }
+
+// private ObservableList<StyledTextLine> model;
+// private IntegerBinding modelSize;
+
+// public void setModel(ObservableList<StyledTextLine> model) {
+// this.model = model;
+//
+//
+//// model.addListener((InvalidationListener)(x)->prepareNodes(getVisibleLines()));
+//
+//// model.addListener((ListChangeListener<StyledTextLine>)(c)->{
+//// RangeSet<Integer> updateNodes = TreeRangeSet.create();
+////
+//// while (c.next()) {
+//// if (debugOut) System.err.println("CHANGE [");
+//// if (c.wasPermutated()) {
+//// if (debugOut) System.err.println("-> permutation " + c);
+////// for (int i = c.getFrom(); i < c.getTo(); i++) {
+////// lineLayer.permutate(i, c.getPermutation(i));
+////// }
+////// lineLayer.requestLayout();
+//// }
+//// if (c.wasUpdated() || c.wasReplaced()) {
+//// if (debugOut) System.err.println("-> updated or replaced: " + c.getFrom() + " - " + c.getTo());
+//// updateNodes.add(Range.closedOpen(c.getFrom(), c.getTo()));
+////
+////
+//// }
+//// if (c.wasAdded()) {
+//// if (debugOut) System.err.println("-> added: " + c.getFrom() + " - " + c.getTo());
+//// updateNodes.add(Range.closedOpen(c.getFrom(), model.size()));
+//// }
+//// if (c.wasRemoved()) {
+//// if (debugOut) System.err.println("-> removed: " + c.getFrom() + " - " + c.getTo());
+////
+//// c.getRemoved().forEach(line->lineLayer.releaseNode(line));
+////
+//// updateNodes.add(Range.closedOpen(c.getFrom(), model.size()));
+//// }
+//// if (debugOut) System.err.println("]");
+//// }
+////
+//// updateNodesNow(updateNodes);
+//// });
+////
+//// modelSize = Bindings.size(model);
+////
+//// modelSize.addListener((x, o, n)-> {
+//// int newSize = n.intValue();
+//// int oldSize = o.intValue();
+//// if (newSize < oldSize) {
+////// for (int lineIdx = newSize; lineIdx < oldSize; lineIdx++) {
+////// lineLayer.releaseNode(lineIdx);
+////// }
+//// }
+//// });
+// }
+
+// private ListProperty<StyledTextLine> model = new SimpleListProperty<>(this, "model", FXCollections.observableArrayList());
+// public ListProperty<StyledTextLine> getModel() {
+// return this.model;
+// }
+
+ private ObjectProperty<Range<Integer>> visibleLines = new SimpleObjectProperty<>(this, "visibleLines", Range.closed(0, 0));
+ public ObjectProperty<Range<Integer>> visibleLinesProperty() {
+ return this.visibleLines;
+ }
+ public com.google.common.collect.Range<Integer> getVisibleLines() {
+ return this.visibleLines.get();
+ }
+ public void setVisibleLines(Range<Integer> visibleLines) {
+ this.visibleLines.set(visibleLines);
+ }
+
+ private Range<Integer> curVisibleLines;
+
+ public ContentView(LineHelper lineHelper) {
+ this.lineHelper = lineHelper;
+// setStyle("-fx-border-color: green; -fx-border-width:2px; -fx-border-style: dashed;");
+
+ this.contentBody.getChildren().setAll(this.lineLayer);
+
+// this.lineLayer.setStyle("-fx-border-color: orange; -fx-border-width:2px; -fx-border-style: solid;");
+
+
+// this.contentBody.setStyle("-fx-border-color: blue; -fx-border-width:2px; -fx-border-style: dotted;");
+
+
+ this.getChildren().setAll(this.contentBody);
+
+
+
+// AnimationTimer t = new AnimationTimer() {
+// @Override
+// public void handle(long now) {
+// updatePulse(now);
+// }
+// };
+//
+// visibleProperty().addListener((x, o, n)->{
+// if (n) {
+// t.start();
+// }
+// else {
+// t.stop();
+// }
+// });
+//
+// t.start();
+// sceneProperty().addListener((x, o, n) -> {
+// if (n == null) {
+// t.stop();
+// }
+// else {
+// t.start();
+// }
+// });
+
+
+ setMinWidth(200);
+ setMinHeight(200);
+
+ visibleLines.addListener(this::onLineChange);
+ offsetX.addListener(this::onLineChange);
+
+
+
+ this.lineLayer.lineHeightProperty().bind(this.lineHeigth);
+ this.lineLayer.yOffsetProperty().bind(this.offsetY);
+ this.lineLayer.visibleLinesProperty().bind(this.visibleLines);
+ this.lineLayer.numberOfLinesProperty().bind(this.numberOfLines);
+
+ bindContentListener();
+ bindCaretListener();
+ bindSelectionListener();
+ }
+
+ private void bindCaretListener() {
+ caretOffset.addListener((x, o, n)-> {
+ int oldCaretLine = getContent().getLineAtOffset(o.intValue());
+ int newCaretLine = getContent().getLineAtOffset(n.intValue());
+
+ RangeSet<Integer> toUpdate = TreeRangeSet.create();
+ toUpdate.add(Range.closed(oldCaretLine, oldCaretLine));
+ toUpdate.add(Range.closed(newCaretLine, newCaretLine));
+
+ updateNodesNow(toUpdate);
+ });
+ }
+
+ private Range<Integer> getAffectedLines(TextSelection selection) {
+ int firstLine = getContent().getLineAtOffset(selection.offset);
+ int lastLine = getContent().getLineAtOffset(selection.offset + selection.length);
+ System.err.println("getAffectedLines " + selection + " -> " + firstLine + " - " + lastLine);
+ if (lastLine == -1) {
+ lastLine = numberOfLines.get();
+ }
+ return Range.closed(firstLine, Math.min(lastLine, numberOfLines.get()));
+ }
+
+ private Range<Integer> toRange(TextSelection s) {
+ return Range.closedOpen(s.offset, s.offset + s.length);
+ }
+
+ private Range<Integer> toLineRange(Range<Integer> globalOffsetRange) {
+ int lower = getContent().getLineAtOffset(globalOffsetRange.lowerEndpoint());
+ int upper = getContent().getLineAtOffset(globalOffsetRange.upperEndpoint());
+ return Range.closed(lower, upper);
+ }
+
+ private void bindSelectionListener() {
+ textSelection.addListener((x, o, n) -> {
+ RangeSet<Integer> toUpdate = TreeRangeSet.create();
+ if (o != null) toUpdate.add(getAffectedLines(o));
+ if (n != null) toUpdate.add(getAffectedLines(n));
+ System.err.println("ContentView: onSelectionChange " + o + " -> " + n + " triggered " + toUpdate);
+ updateNodesNow(toUpdate);
+ });
+ }
+
+ private void bindContentListener() {
+ this.content.addListener((x, o, n)->{
+ if (o != null) {
+ o.removeTextChangeListener(this.textChangeListener);
+ }
+ if (n != null) {
+ n.addTextChangeListener(this.textChangeListener);
+ System.err.println("init #ofLines with " + n.getLineCount());
+ this.numberOfLines.set(n.getLineCount());
+ }
+ });
+ StyledTextContent current = this.content.get();
+ if (current != null) {
+ current.addTextChangeListener(this.textChangeListener);
+
+ // set inital values
+ System.err.println("init #ofLines with " + current.getLineCount());
+ numberOfLines.set(current.getLineCount());
+
+ }
+ }
+
+ private TextChangeListener textChangeListener = new TextChangeListener() {
+
+ private Function<Integer, Integer> mapping;
+ private RangeSet<Integer> toUpdate = TreeRangeSet.create();
+ private RangeSet<Integer> toRelease = TreeRangeSet.create();
+
+ @Override
+ public void textSet(TextChangedEvent event) {
+ System.err.println("ContentView textSet");
+
+ // update number of lines
+ ContentView.this.numberOfLines.set(getContent().getLineCount());
+
+ getLineLayer().requestLayout();
+ }
+
+ private int computeFirstUnchangedLine(TextChangingEvent event) {
+
+ int endOffset = event.offset + event.replaceCharCount;
+ int endLineIndex = getContent().getLineAtOffset(endOffset);
+ int endLineBegin = getContent().getOffsetAtLine(endLineIndex);
+ int endLineLength = lineHelper.getLength(endLineIndex);
+
+ int firstSafeLine;
+
+ if (endLineBegin == event.offset) {
+ // offset at beginning of line
+ firstSafeLine = endLineIndex;
+ }
+ else {
+ // offset in middle or at end of line
+ firstSafeLine = endLineIndex + 1;
+ }
+
+ return firstSafeLine;
+ }
+
+ @Override
+ public void textChanging(TextChangingEvent event) {
+ System.err.println("ContentView textChanging");
+ System.err.println(" current doc len = " + getContent().getLineCount());
+ System.err.println("Event: "+ event);
+
+ final int changeBeginLine = getContent().getLineAtOffset(event.offset);
+
+ // determine first unchanged line
+ int firstUnchangedLine = computeFirstUnchangedLine(event);
+
+ System.err.println("FIRST UNCHANGEDLINE: " + firstUnchangedLine);
+
+ int deltaLines = event.newLineCount - event.replaceLineCount;
+
+ if (deltaLines < 0) {
+ this.toRelease.add(Range.closedOpen(firstUnchangedLine + deltaLines, firstUnchangedLine));
+ }
+
+ // prepare permutation
+ this.mapping = (idx) -> {
+ if (idx >= firstUnchangedLine) {
+ return idx + deltaLines;
+ }
+ return idx;
+ };
+
+ this.toUpdate.add(Range.closedOpen(changeBeginLine, firstUnchangedLine + deltaLines));
+
+
+//
+// // simple insert
+// if (event.replaceCharCount == 0) {
+// System.err.println("# Simple Insert");
+// if (event.newLineCount > 0) {
+// System.err.println("# We have new lines");
+//
+// int lineIndex = getContent().getLineAtOffset(event.offset);
+// int lineBegin = getContent().getOffsetAtLine(lineIndex);
+// int lineLength = lineHelper.getLength(lineIndex);
+//
+// int firstSafeLine;
+// Range<Integer> updateRange;
+//
+// if (lineBegin == event.offset) {
+// // at beginning of line
+// firstSafeLine = lineIndex;
+// updateRange = Range.closedOpen(lineIndex, lineIndex + event.newLineCount);
+// }
+// else if (lineBegin == event.offset + lineLength) {
+// // insert was at end of line
+// firstSafeLine= lineIndex + 1;
+// updateRange = Range.closedOpen(lineIndex, lineIndex + event.newLineCount);
+// }
+// else {
+// // insert was in middle of line
+// firstSafeLine = lineIndex + 2;
+// updateRange = Range.closedOpen(lineIndex, lineIndex + 1 + event.newLineCount);
+// }
+// System.err.println("# firstSafeLine = " + firstSafeLine + " / updateRange " + updateRange);
+//
+// // prepare update
+// toUpdate.add(updateRange);
+//
+// // prepare permutation
+// this.mapping = (idx) -> {
+// if (idx >= firstSafeLine) {
+// return idx + event.newLineCount;
+// }
+// return idx;
+// };
+//
+// }
+// }
+
+
+
+// int firstUnchangedLine = changeBeginLine + replaceLines;
+//
+// int newFirstUnchnagedLine = changeBeginLine + newLines;
+//
+// System.err.println(" changeBeginLine = " + changeBeginLine);
+// System.err.println(" firstUnchangedLine = " + firstUnchangedLine);
+// System.err.println(" newFirstUnchangedLine = " + newFirstUnchnagedLine);
+//
+// // prepare updates
+// toUpdate.add(Range.closedOpen(changeBeginLine, changeBeginLine + newLines));
+// // prepare permutation
+// this.mapping = (idx) -> {
+// if (idx >= firstUnchangedLine) {
+// return idx + firstUnchangedLine - newFirstUnchnagedLine;
+// }
+// return idx;
+// };
+
+ }
+
+ @Override
+ public void textChanged(TextChangedEvent event) {
+ System.err.println("ContentView textChanged");
+
+
+ if (!toRelease.isEmpty()) {
+ releaseNodesNow(toRelease);
+ toRelease.clear();
+ }
+
+ // execute permutation
+ if (this.mapping != null) {
+ getLineLayer().permutateNodes(mapping);
+ this.mapping = null;
+ }
+
+
+ // execute updates
+ if (!toUpdate.isEmpty()) {
+
+ updateNodesNow(toUpdate);
+ toUpdate.clear();
+ }
+
+ // update number of lines
+ ContentView.this.numberOfLines.set(getContent().getLineCount());
+
+
+ getLineLayer().requestLayout();
+ }
+ };
+
+// Timer t = new Timer();
+// volatile boolean scheduled = false;
+// private void scheduleUpdate() {
+// if (true) return;
+//
+// try {
+// if (!scheduled) {
+// scheduled = true;
+// t.schedule(new TimerTask() {
+// @Override
+// public void run() {
+// Platform.runLater(()->doUpdate());
+// scheduled = false;
+// }
+// }, 16);
+// }
+// }
+// catch (Exception e) {
+// e.printStackTrace();
+// }
+//
+// }
+
+ private void onLineChange(Observable o) {
+ RangeSet<Integer> toUpdate = TreeRangeSet.create();
+ RangeSet<Integer> toRelease = TreeRangeSet.create();
+
+ double offsetY = offsetYProperty().get();
+ com.google.common.collect.Range<Integer> visibleLines = visibleLinesProperty().get();
+ ContiguousSet<Integer> set = ContiguousSet.create(visibleLines, DiscreteDomain.integers());
+ double lineHeight = lineHeightProperty().get();
+
+// System.err.println("onLineChange " + offsetY + " " + visibleLines);
+
+
+ // schedule visible line updates
+ if (curVisibleLines == null) {
+ toUpdate.add(visibleLines);
+ }
+ else {
+ RangeSet<Integer> hiddenLines = TreeRangeSet.create();
+ hiddenLines.add(curVisibleLines);
+ hiddenLines.remove(visibleLines);
+
+ RangeSet<Integer> shownLines = TreeRangeSet.create();
+ shownLines.add(visibleLines);
+ shownLines.remove(curVisibleLines);
+
+ toUpdate.addAll(shownLines);
+ toRelease.addAll(hiddenLines);
+ }
+ this.curVisibleLines = visibleLines;
+
+ // store precomputed y data
+ for (int index : set) {
+ double y = index * lineHeight - offsetY;
+ yOffsetData.put(index, y);
+ forceLayout = true;
+ }
+
+ releaseNodesNow(toRelease);
+ updateNodesNow(toUpdate);
+
+ lineLayer.requestLayout();
+// scheduleUpdate();
+ }
+
+ private double computeLongestLine() {
+ System.err.println("compute longest line til " + getNumberOfLines());
+ System.err.println(" while content has " + content.get().getLineCount());
+ Optional<Integer> longestLine = IntStream.range(0, getNumberOfLines()).mapToObj(index->lineHelper.getLengthCountTabsAsChars(index)).collect(Collectors.maxBy(Integer::compare));
+// Optional<Integer> longestLine = model.stream().map(m->m.getLineLength() + countTabs(m.getText()) * 3).collect(Collectors.maxBy(Integer::compare));
+ longestLine = longestLine.map(s->s+2); // extra space
+ Optional<Double> longestLineWidth = longestLine.map(i->i*8d);
+ longestLineWidth = longestLineWidth.map(l->Math.max(l, getWidth()));
+ return longestLineWidth.orElse(getWidth());
+ }
+
+ @Override
+ protected void layoutChildren() {
+ double scrollX = -offsetX.get();
+ this.contentBody.resizeRelocate(scrollX, 0, computeLongestLine(), getHeight());
+ }
+
+
+ private DoubleProperty offsetY = new SimpleDoubleProperty();
+ private DoubleProperty offsetX = new SimpleDoubleProperty();
+
+ public void bindHorizontalScrollbar(ScrollBar bar) {
+ bar.setMin(0);
+ DoubleBinding max = this.contentBody.widthProperty().subtract(widthProperty());
+ DoubleBinding factor = this.contentBody.widthProperty().divide(max);
+ bar.maxProperty().bind(contentBody.widthProperty().divide(factor));
+ bar.visibleAmountProperty().bind(widthProperty().divide(factor));
+ this.offsetX.bind(bar.valueProperty());
+ }
+
+ public DoubleProperty offsetYProperty() {
+ return this.offsetY;
+ }
+
+
+// private RangeSet<Integer> toRelease = TreeRangeSet.create();
+// private RangeSet<Integer> toUpdate = TreeRangeSet.create();
+
+
+ private void updateNodesNow(com.google.common.collect.RangeSet<Integer> rs) {
+ System.err.println("updateNodesNow nr of lines = " + getNumberOfLines());
+ RangeSet<Integer> subRangeSet = rs.subRangeSet(getVisibleLines()).subRangeSet(Range.closedOpen(0, getNumberOfLines()));
+ System.err.println("updateNodesNow: " + subRangeSet);
+ subRangeSet.asRanges().forEach(r-> {
+ ContiguousSet.create(r, DiscreteDomain.integers()).forEach(index-> {
+ getLineLayer().updateNode(index);
+// StyledTextLine m = this.model.get(index);
+// lineLayer.updateNode(m);
+ });
+ });
+ }
+
+ private void releaseNodesNow(com.google.common.collect.RangeSet<Integer> rs) {
+ RangeSet<Integer> subRangeSet = rs.subRangeSet(Range.closedOpen(0, getNumberOfLines()));
+// System.err.println("releaseNodesNow " + subRangeSet);
+ if (debugOut) System.err.println("releasing " + rs);
+ subRangeSet.asRanges().forEach(r-> {
+ ContiguousSet.create(r, DiscreteDomain.integers()).forEach(index-> {
+ getLineLayer().releaseNode(index);
+// StyledTextLine m = this.model.get(index);
+// System.err.println("RELEASE " + m);
+// lineLayer.releaseNode(m);
+ });
+ });
+ }
+
+// private void updateNodes(com.google.common.collect.Range<Integer> range) {
+// if (debugOut) System.err.println("updateNodes(" + range + ")");
+// toUpdate.add(range);
+// scheduleUpdate();
+////
+//// if (range.intersects(getVisibleLines())) {
+//// Range intersection = range.intersect(getVisibleLines());
+//// for (int index = intersection.getOffset(); index <intersection.getEndOffset(); index++) {
+//// toUpdate.add(index);
+////// StyledTextLine m = this.model.get(index);
+////// prepareNode(index, m);
+//// }
+//// }
+// }
+
+// private boolean doUpdate() {
+// try {
+// long now = -System.nanoTime();
+// if (!toRelease.isEmpty()) {
+// toRelease.asRanges().forEach(r-> {
+// ContiguousSet.create(r, DiscreteDomain.integers()).forEach(this.lineLayer::releaseNode);
+// });
+// toRelease.clear();
+// }
+//
+// if (!toUpdate.isEmpty()) {
+// toUpdate.subRangeSet(getVisibleLines()).subRangeSet(Range.closedOpen(0, this.model.size())).asRanges().forEach(r-> {
+// ContiguousSet.create(r, DiscreteDomain.integers()).forEach(index-> {
+// StyledTextLine m = this.model.get(index);
+// lineLayer.updateNode(index, m);
+// });
+// });
+// toUpdate.clear();
+// }
+//
+//
+// now += System.nanoTime();
+//
+// if (now > 1000_000 * 5) {
+// System.err.println("update needed " + (now/1000000) + "ms");
+// }
+//
+// if (!toRelease.isEmpty() || !toUpdate.isEmpty() || forceLayout) {
+//// System.err.println("releasing " + toRelease + " and updating " + toUpdate + " lines");
+//
+// lineLayer.requestLayout();
+//
+// forceLayout = false;
+//
+// return true;
+// }
+// }
+// catch (Exception e) {
+// e.printStackTrace();
+// }
+// return false;
+//
+// }
+//
+// private void updatePulse(long now) {
+// doUpdate();
+// }
+
+
+ public Optional<Point2D> getLocationInScene(int globalOffset) {
+ int lineIndex = getContent().getLineAtOffset(globalOffset);
+ Optional<LineNode> node = lineLayer.getVisibleNode(lineIndex);
+
+ return node.map(n->{
+ double x = n.getCharLocation(globalOffset - n.getStartOffset());
+ Point2D p = new Point2D(x, 0);
+ return n.localToScene(p);
+ });
+ }
+
+ public Optional<Integer> getLineIndex(Point2D point) {
+ // transform point to respect horizontal scrolling
+ point = this.lineLayer.sceneToLocal(this.localToScene(point));
+ Optional<Integer> result = this.lineLayer.getLineIndex(point);
+ return result;
+
+ }
+ public void updateAnnotations(RangeSet<Integer> r) {
+ updateNodesNow(r);
+ }
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/DebugMarker.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/DebugMarker.java
new file mode 100644
index 000000000..a75134620
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/DebugMarker.java
@@ -0,0 +1,40 @@
+package org.eclipse.fx.ui.controls.styledtext.internal;
+
+import javafx.animation.FillTransition;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+import javafx.util.Duration;
+
+public class DebugMarker extends Rectangle {
+
+ private FillTransition transition;
+
+
+ public DebugMarker(Color color, long millis) {
+ this.setFill(Color.TRANSPARENT);
+
+ this.setMouseTransparent(true);
+
+ this.transition = new FillTransition();
+ this.transition.setFromValue(color);
+ this.transition.setToValue(Color.TRANSPARENT);
+ this.transition.setShape(this);
+ this.transition.setDuration(Duration.millis(millis));
+
+ this.setOpacity(0.5);
+
+
+ }
+
+
+ public void play() {
+ this.transition.playFromStart();
+ }
+
+ @Override
+ public void resize(double width, double height) {
+ this.setWidth(width);
+ this.setHeight(height);
+ }
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/DynCachePane.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/DynCachePane.java
new file mode 100644
index 000000000..51560e250
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/DynCachePane.java
@@ -0,0 +1,102 @@
+package org.eclipse.fx.ui.controls.styledtext.internal;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Queue;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import javafx.scene.Node;
+import javafx.scene.layout.Pane;
+
+public class DynCachePane<K, N extends Node> extends Pane {
+
+ int maxCache = 100;
+
+ protected Map<K, N> existingNodes = new HashMap<>();
+
+ protected Map<K, N> usedNodes = new HashMap<>();
+
+ protected Queue<N> cachedNodes = new LinkedList<>();
+
+ private Supplier<N> nodeFactory;
+
+ long count = 0;
+
+ public DynCachePane(Supplier<N> nodeFactory) {
+ this.nodeFactory = nodeFactory;
+ }
+
+
+ protected N get(K key) {
+ N node = usedNodes.get(key);
+ if (node == null) {
+ node = existingNodes.get(key);
+ }
+ if (node == null) {
+ if (!cachedNodes.isEmpty()) {
+ node = cachedNodes.poll();
+ }
+ }
+ if (node == null) {
+
+ node = this.nodeFactory.get();
+ existingNodes.put(key, node);
+ }
+ usedNodes.put(key, node);
+ node.setVisible(true);
+ return node;
+ }
+
+ protected void release(K key) {
+ N node = existingNodes.get(key);
+ if (node != null) {
+ node.setVisible(false);
+ usedNodes.remove(node);
+ cachedNodes.add(node);
+ }
+ }
+
+
+// protected N create(K key) {
+// N node = this.nodeFactory.get();
+// this.existingNodes.put(key, node);
+// this.getChildren().add(node);
+// count++;
+// if (count > 1000 ) {
+// System.err.println("ALLOCATED "+ count + " nodes!!!");
+// }
+// return node;
+// }
+//
+// protected void destroy(K key) {
+// N removed = this.existingNodes.remove(key);
+// getChildren().remove(removed);
+// }
+//
+// protected void release(K key) {
+// N removed = this.existingNodes.remove(key);
+// removed.setVisible(false);
+// cachedNodes.add(removed);
+// }
+//
+// protected Optional<N> get(K key) {
+// return Optional.ofNullable(this.existingNodes.get(key));
+// }
+//
+// protected N getCreate(K key) {
+// N n = this.existingNodes.get(key);
+// if (n == null) {
+// return create(key);
+// }
+// return n;
+// }
+
+
+
+
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/LineHelper.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/LineHelper.java
new file mode 100644
index 000000000..585b78d5e
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/LineHelper.java
@@ -0,0 +1,242 @@
+package org.eclipse.fx.ui.controls.styledtext.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.eclipse.fx.ui.controls.styledtext.StyleRange;
+import org.eclipse.fx.ui.controls.styledtext.StyledTextArea;
+import org.eclipse.fx.ui.controls.styledtext.StyledTextContent;
+import org.eclipse.fx.ui.controls.styledtext.TextSelection;
+import org.eclipse.fx.ui.controls.styledtext.model.Annotation;
+import org.eclipse.fx.ui.controls.styledtext.model.AnnotationProvider;
+import org.eclipse.fx.ui.controls.styledtext.model.TextAnnotation;
+import org.eclipse.jdt.annotation.NonNull;
+
+import com.google.common.collect.Range;
+
+public class LineHelper {
+
+ private StyledTextArea control;
+
+ public LineHelper(StyledTextArea control) {
+ this.control = control;
+ }
+
+ private StyledTextContent getContent() {
+ return this.control.getContent();
+ }
+
+ private Range<Integer> getSelection() {
+ @NonNull
+ TextSelection selection = this.control.getSelection();
+ return Range.closedOpen(selection.offset, selection.offset + selection.length);
+ }
+
+ private int getCaretOffset() {
+ return this.control.getCaretOffset();
+ }
+
+ private StyleRange[] getStyleRanges(int start, int length, boolean includeRanges) {
+ return this.control.getStyleRanges(start, length, includeRanges);
+ }
+
+ private Set<AnnotationProvider> getAnnotationProvider() {
+ return this.control.getAnnotationProvider();
+ }
+
+
+
+ public int getOffset(int index) {
+ return getContent().getOffsetAtLine(index);
+ }
+
+ public int getLength(int index) {
+ return getText(index).length();
+ }
+
+ public int getLengthCountTabsAsChars(int index) {
+ String t = getText(index);
+ return t.length() + countTabs(t) * 3;
+ }
+
+ public String getText(int index) {
+ return getContent().getLine(index);
+ }
+
+ public Range<Integer> getRange(int index) {
+ int lower = getOffset(index);
+ int upper = lower + getLength(index);
+ return Range.closed(lower, upper);
+ }
+
+ private int mapToLocal(int index, int globalOffset) {
+ return globalOffset - getOffset(index);
+ }
+
+ private com.google.common.collect.Range<Integer> mapToLocal(int index, com.google.common.collect.Range<Integer> global) {
+ return com.google.common.collect.Range.range(global.lowerEndpoint() - getOffset(index), global.lowerBoundType(), global.upperEndpoint() - getOffset(index), global.upperBoundType());
+ }
+
+ public Range<Integer> getSelection(int index) {
+ Range<Integer> selection = getSelection();
+ if (selection.isEmpty()) {
+ return null;
+ }
+
+ Range<Integer> range = getRange(index);
+
+ Range<Integer> localSelection = null;
+
+ if (range.isConnected(selection)) {
+ Range<Integer> intersection = selection.intersection(range);
+ localSelection = mapToLocal(index, intersection);
+ }
+
+ if (localSelection != null) {
+ System.err.println(index + " linerange - " + range);
+ System.err.println(index + " globalSelection - " + selection);
+ System.err.println(index + " localSelection - " + localSelection);
+ }
+
+ return localSelection;
+ }
+
+ public int getCaret(int index) {
+ int globalCaret = getCaretOffset();
+ Range<Integer> range = getRange(index);
+ if (range.contains(globalCaret)) {
+ return mapToLocal(index, globalCaret);
+ }
+ else {
+ return -1;
+ }
+ }
+
+ private Segment createSegement(String text, StyleRange style) {
+ List<String> styleClasses = new ArrayList<>();
+ if (style.stylename != null) {
+ if (style.stylename.contains(".")) { //$NON-NLS-1$
+ List<String> styles = new ArrayList<String>(Arrays.asList(style.stylename.split("\\."))); //$NON-NLS-1$
+ styles.add(0, "source-segment"); //$NON-NLS-1$
+ styleClasses.addAll(styles);
+ } else {
+ styleClasses.add("source-segment"); //$NON-NLS-1$
+ styleClasses.add(style.stylename);
+ }
+
+ } else {
+ if (style.foreground != null) {
+ styleClasses.add("plain-source-segment"); //$NON-NLS-1$
+ } else {
+ styleClasses.add("source-segment"); //$NON-NLS-1$
+ }
+ }
+ return new Segment(text, styleClasses);
+ }
+
+ static String removeLineending(String s) {
+ return s.replace("\n", "").replace("\r", ""); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+
+ public List<Segment> getSegments(int index) {
+ List<Segment> segments = new ArrayList<>();
+
+ String line = getContent().getLine(index);
+ if (line != null) {
+ int start = getContent().getOffsetAtLine(index);
+ int length = line.length();
+
+ StyleRange[] ranges = getStyleRanges(start, length, true);
+ if (ranges == null) {
+ return Collections.emptyList();
+ }
+
+ if (ranges.length == 0 && line.length() > 0) {
+ StyleRange styleRange = new StyleRange((String) null);
+ styleRange.start = start;
+ styleRange.length = line.length();
+
+ String text = removeLineending(line.substring(0, line.length()));
+
+ segments.add(createSegement(text, styleRange));
+ } else {
+ int lastIndex = -1;
+
+ if (ranges.length > 0) {
+ if (ranges[0].start - start > 0) {
+ StyleRange styleRange = new StyleRange((String) null);
+ styleRange.start = start;
+ styleRange.length = ranges[0].start - start;
+
+ String text = removeLineending(line.substring(0, ranges[0].start - start));
+
+ segments.add(createSegement(text, styleRange));
+ }
+ }
+
+ for (StyleRange r : ranges) {
+ int begin = r.start - start;
+ int end = r.start - start + r.length;
+
+ if (lastIndex != -1 && lastIndex != begin) {
+ StyleRange styleRange = new StyleRange((String) null);
+ styleRange.start = start + lastIndex;
+ styleRange.length = begin - lastIndex;
+
+ String text = removeLineending(line.substring(lastIndex, begin));
+
+ segments.add(createSegement(text, styleRange));
+ }
+
+ String text = removeLineending(line.substring(begin, end));
+
+ segments.add(createSegement(text, r));
+ lastIndex = end;
+ }
+
+ if (lastIndex > 0 && lastIndex < line.length()) {
+ StyleRange styleRange = new StyleRange((String) null);
+ styleRange.start = start + lastIndex;
+ styleRange.length = line.length() - lastIndex;
+
+ String text = removeLineending(line.substring(lastIndex, line.length()));
+
+ segments.add(createSegement(text, styleRange));
+ }
+ }
+ }
+
+ return segments;
+ }
+
+ public Set<TextAnnotation> getTextAnnotations(int index) {
+ return getAnnotations(index).stream().filter(m->m instanceof TextAnnotation).map(m->(TextAnnotation)m).collect(Collectors.toSet());
+ }
+
+ public Set<Annotation> getAnnotations(int index) {
+ // collect all the annotations for this line
+ Set<Annotation> result = getAnnotationProvider()
+ .stream().map(p->p.computeAnnotations(index))
+ .flatMap(Collection::stream)
+ .collect(Collectors.toSet());
+ return result;
+ }
+
+ private int countTabs(String s) {
+ Matcher matcher = Pattern.compile("\t").matcher(s);
+ int count = 0;
+ while (matcher.find()) {
+ count++;
+ }
+ return count;
+ }
+
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/LineNode.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/LineNode.java
new file mode 100644
index 000000000..9d4032051
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/LineNode.java
@@ -0,0 +1,718 @@
+package org.eclipse.fx.ui.controls.styledtext.internal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.fx.ui.controls.styledtext.NodeCachePane;
+import org.eclipse.fx.ui.controls.styledtext.ReuseCache;
+import org.eclipse.fx.ui.controls.styledtext.model.TextAnnotation;
+import org.eclipse.fx.ui.controls.styledtext.model.TextAnnotationPresenter;
+
+import javafx.animation.Animation;
+import javafx.animation.Animation.Status;
+import javafx.animation.FadeTransition;
+import javafx.animation.Interpolator;
+import javafx.geometry.Insets;
+import javafx.geometry.Point2D;
+import javafx.scene.Node;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Line;
+import javafx.util.Duration;
+
+public class LineNode extends StackPane {
+
+ int leftPadding = 0;
+
+ private static boolean debugAnimation = Boolean.getBoolean("styledtext.debuganimation");
+ private static boolean debugOut = Boolean.getBoolean("styledtext.debugout");
+
+ private DebugMarker debugUpdateText;
+ private DebugMarker debugUpdateAnnotations;
+ private DebugMarker debugUpdateSelection;
+ private DebugMarker debugUpdateCaret;
+ private HBox debugBox;
+
+ private int index;
+ private LineHelper lineHelper;
+
+
+ private int getLineIndex() {
+ return index;
+ }
+
+ private static TextNode createNode() {
+ TextNode textNode = new TextNode("");
+ return textNode;
+ }
+
+ public class TextLayer extends HBox {
+
+ protected final ReuseCache<TextNode> cache;
+
+ public TextLayer() {
+ getStyleClass().add("source-segment-container");
+// setCache(true);
+// setCacheHint(CacheHint.QUALITY);
+ setMinWidth(HBox.USE_COMPUTED_SIZE);
+
+ cache = new ReuseCache<>(LineNode::createNode);
+ cache.addOnActivate(node->{
+ getChildren().add(node);
+// if (!getChildren().contains(node)) {
+// getChildren().add(node);
+// }
+// node.setVisible(true);
+// node.setManaged(true);
+ });
+ cache.addOnRelease(node->{
+ getChildren().remove(node);
+// node.setVisible(false);
+// node.setManaged(false);
+ });
+// cache.addOnClear(node->getChildren().remove(node));
+ }
+
+ private List<Segment> currentContent = new ArrayList<>();
+ private List<TextNode> currentTextNodes = new ArrayList<>();
+
+ public boolean updateContent(List<Segment> content) {
+ if (currentContent.equals(content)) {
+ return false;
+ }
+ if (debugOut) System.err.println("update " + getLineIndex() + ": TextLayer");
+
+ for (TextNode c : currentTextNodes) {
+ cache.releaseElement(c);
+ }
+ currentTextNodes.clear();
+
+ for (Segment segment : content) {
+ TextNode node = cache.getElement();
+ node.updateText(segment.text);
+ node.getStyleClass().setAll(segment.styleClasses);
+ currentTextNodes.add(node);
+ }
+
+ if (content.isEmpty()) {
+ TextNode node = cache.getElement();
+ node.updateText("");
+ currentTextNodes.add(node);
+ }
+
+ currentContent = content;
+
+// applyCss();
+// layout();
+ return true;
+ }
+
+ public int getCaretIndexAtPoint(Point2D point) {
+ Point2D scenePoint = localToScene(point);
+ int offset = 0;
+ for (TextNode t : this.currentTextNodes) {
+ if (t.localToScene(t.getBoundsInLocal()).contains(scenePoint)) {
+ int idx = t.getCaretIndexAtPoint(t.sceneToLocal(scenePoint));
+ if( idx != -1 ) {
+// System.err.println("returning " + idx + " + " + offset);
+ int result = idx + offset;
+// System.err.println("==> (LineNode)" + result);
+ return result;
+ }
+ }
+ offset += t.getText().length();
+ }
+
+ return -1;
+ }
+
+ public double getCharLocation(int charOffset) {
+ double result = 0;
+
+ int startOffset = 0;
+ for (TextNode t : this.currentTextNodes) {
+ int len = t.getText().length();
+
+ if (charOffset >= startOffset && charOffset <= startOffset + len) {
+ // hit
+ int textNodeOffset = charOffset - startOffset;
+ result = t.getCharLocation(textNodeOffset);
+ break;
+ }
+ else if (charOffset > startOffset + len) {
+ result = t.getLayoutX() + t.getWidth();
+ }
+ startOffset += len;
+ }
+ return result;
+
+
+// double location = 0;
+// int offset = 0;
+// for (TextNode t : this.currentTextNodes) {
+// int length = t.getText().length();
+// int endOffset = offset + length;
+//
+// if (offset <= charOffset && charOffset <= endOffset ) {
+// int localCharOffset = charOffset - offset;
+// location = t.getLayoutX() + t.getCharLocation(localCharOffset);
+// break;
+// }
+// else if (charOffset > endOffset) {
+// location = t.getLayoutX() + t.getWidth();
+// }
+// offset += length;
+// }
+// System.err.println("getCharLocation(" + charOffset + ") -> " + location);
+// return location;
+ }
+
+ @Override
+ protected void layoutChildren() {
+ super.layoutChildren();
+ if (debugOut) System.err.println("layout Line "+getLineIndex()+": TextLayer");
+ }
+
+ protected String getText() {
+ StringBuilder b = new StringBuilder();
+ for (TextNode t : this.currentTextNodes) {
+ b.append(t.getText());
+ }
+ return b.toString();
+ }
+
+ }
+
+ public class SelectionLayer extends Region {
+ private Region selectionMarker = new Region();
+
+ private com.google.common.collect.Range<Integer> selection;
+
+ public SelectionLayer() {
+ this.selectionMarker.getStyleClass().add("selection-marker");
+ this.selectionMarker.setManaged(false);
+
+ this.getChildren().setAll(this.selectionMarker);
+ }
+
+ private boolean isSelectionChange(com.google.common.collect.Range<Integer> localSelection) {
+// System.err.println("change? " + localSelection + " vs " + selection);
+ if (localSelection == null && this.selection == null) {
+ return false;
+ }
+ return this.selection == null || !this.selection.equals(localSelection);
+ }
+
+ public void updateSelection(com.google.common.collect.Range<Integer> localSelection) {
+ if (isSelectionChange(localSelection)) {
+ System.err.println("updating selection for " + index);
+ if (localSelection == null) {
+ this.selectionMarker.setVisible(false);
+ }
+ else {
+ this.selectionMarker.setVisible(true);
+ }
+
+ this.selection = localSelection;
+ requestLayout();
+ if (debugAnimation) {
+ debugUpdateSelection.play();
+ }
+ }
+ }
+
+ @Override
+ protected void layoutChildren() {
+ //if (model == null) return;
+
+ if (selection != null) {
+ textLayer.layout();
+
+ double begin = textLayer.getCharLocation(selection.lowerEndpoint());
+ double end = textLayer.getCharLocation(selection.upperEndpoint());
+ if (selection.upperEndpoint() == lineHelper.getLength(index)) {
+ end = getWidth();
+ }
+
+
+ if (debugOut) System.err.println("layout Line "+getLineIndex()+": SelectionLayer");
+ // System.err.println("SelectionLayer: layoutChildren()");
+ // System.err.println(" setting selection to " + begin + " - " + end);
+ this.selectionMarker.resizeRelocate(begin, 0, end - begin, getHeight());
+ }
+ }
+ }
+
+ private static Animation createCaretAnimation(Node caret) {
+// Timeline t = new Timeline(
+// new KeyFrame(Duration.millis(200), new KeyValue(caret.opacityProperty(), 1, Interpolator.DISCRETE)),
+// new KeyFrame(Duration.millis(200), new KeyValue(caret.opacityProperty(), 0))
+// );
+// t.setCycleCount(Animation.INDEFINITE);
+// return t;
+
+ FadeTransition t = new FadeTransition(Duration.millis(200), caret);
+ t.setInterpolator(new Interpolator() {
+ @Override
+ protected double curve(double t) {
+ if (t < 0.5) {
+ return 0;
+ }
+ else {
+ return 1;
+ }
+ }
+ });
+ t.setAutoReverse(true);
+ t.setFromValue(1);
+ t.setToValue(0);
+ t.setCycleCount(Animation.INDEFINITE);
+
+ return t;
+ }
+
+ public class CaretLayer extends Region {
+ private int caretIndex = -1;
+
+ private Line caret = new Line();
+
+ private Animation caretAnimation;
+
+ public CaretLayer() {
+ this.caret.setVisible(false);
+ this.caret.setStrokeWidth(2);
+ this.caret.getStyleClass().add("text-caret"); //$NON-NLS-1$
+ this.caret.setVisible(false);
+ this.getChildren().add(this.caret);
+
+ this.caretAnimation = createCaretAnimation(this.caret);
+
+ this.caret.visibleProperty().addListener((x, o, n)->{
+ if (n) {
+ if (this.caretAnimation.getStatus() != Status.RUNNING) {
+ this.caretAnimation.playFromStart();
+ }
+ }
+ else {
+ this.caretAnimation.stop();
+ }
+ });
+ }
+
+ private void hideCaret() {
+ this.caret.setVisible(false);
+ }
+
+ private void showCaret() {
+ this.caret.setVisible(true);
+ }
+
+ public void updateCaret(int index) {
+ if (index != this.caretIndex) {
+ if (index == -1) {
+ hideCaret();
+ }
+ else {
+ showCaret();
+ }
+
+ this.caretIndex = index;
+ requestLayout();
+ if (debugAnimation) {
+ debugUpdateCaret.play();
+ }
+ }
+ }
+
+ public void layoutChildren() {
+ double caretOffset = getCharLocation(caretIndex);
+ if (debugOut) System.err.println("layout Line "+getLineIndex()+": CaretLayer caretIndex=" + caretIndex + " = " + caretOffset);
+ this.caret.setStartX(caretOffset);
+ this.caret.setEndX(caretOffset);
+ this.caret.setStartY(0);
+ this.caret.setEndY(getHeight());
+ this.caret.toFront();
+ }
+ }
+
+ public class AnnotationLayer extends StackPane {
+
+ private Set<TextAnnotation> currentAnnotations;
+ private Set<TextAnnotationPresenter> currentPresenters;
+
+ public AnnotationLayer() {
+ //setStyle("-fx-border-color: red; -fx-border-style: dashed; -fx-border-width: 1;");
+
+
+// setCache(true);
+// setCacheHint(CacheHint.SPEED);
+
+ }
+
+ private class AnnotationOverlay extends NodeCachePane {
+ private TextAnnotationPresenter presenter;
+
+ public AnnotationOverlay(TextAnnotationPresenter presenter) {
+ super(presenter::createNode);
+ this.presenter = presenter;
+
+// setStyle("-fx-border-color: red; -fx-border-style: dashed; -fx-border-width: 1;");
+// setBackground(new Background(new BackgroundFill(Color.LIGHTGREEN, CornerRadii.EMPTY, Insets.EMPTY)));
+// setBorder(new Border(new BorderStroke(Color.CADETBLUE, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT)));
+ setOpacity(0.5);
+
+// setPrefHeight(10);
+// cache.addOnActivate(node->node.setManaged(false));
+
+
+ }
+
+ private Map<TextAnnotation, Node> usedNodes = new HashMap<>();
+
+ private Node getNode(TextAnnotation a) {
+ Node node = this.usedNodes.get(a);
+ if (node == null) {
+ node = getNode();
+ this.usedNodes.put(a, node);
+ }
+ return node;
+ }
+
+ private Set<TextAnnotation> current = new HashSet<>();
+
+ public void prepareNodes(Set<TextAnnotation> annotations) {
+ boolean same = current.equals(annotations);
+ System.err.println("annotation check: " + current +"\n"
+ + " versus: " + annotations + "\n"
+ + " -> " + same);
+
+ if (same) {
+ return;
+ }
+
+ current = annotations;
+
+ // release invisible nodes
+ int released = 0;
+ Iterator<Entry<TextAnnotation, Node>> iterator = this.usedNodes.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry<TextAnnotation, Node> entry = iterator.next();
+ if (!annotations.contains(entry.getKey())) {
+ releaseNode(entry.getValue());
+ iterator.remove();
+ released ++;
+ }
+ else if (!presenter.isVisible(entry.getKey())) {
+ releaseNode(entry.getValue());
+ iterator.remove();
+ released++;
+ }
+ }
+
+ // release non used nodes
+// HashSet<Node> all = new HashSet<>(getChildren());
+// all.removeAll(usedNodes.values());
+// System.err.println("FORCE RELEASE OF " + all);
+// all.forEach(this::releaseNode);
+
+ int presented = 0;
+ // prepare nodes
+ for (TextAnnotation a : annotations) {
+ Node n = getNode(a);
+ this.presenter.updateNode(n, a);
+ presented ++;
+ }
+// iterator = this.usedNodes.entrySet().iterator();
+// while (iterator.hasNext()) {
+// Entry<TextAnnotation, Node> entry = iterator.next();
+// presenter.updateNode(entry.getValue(), entry.getKey());
+// presented ++;
+// }
+
+ if (debugOut) System.err.println("update "+getLineIndex()+": AnnotationOverlay: released " + released + " and presented " + presented + " annotation nodes");
+ if (debugOut) System.err.println("update "+getLineIndex()+": AnnotationOverlay: active " + annotations);
+ }
+
+ @Override
+ protected void layoutChildren() {
+ if (debugOut) System.err.println("layout Line "+getLineIndex()+": AnnotationLayer -> AnnotationOverlay");
+ Iterator<Entry<TextAnnotation, Node>> iterator = this.usedNodes.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry<TextAnnotation, Node> entry = iterator.next();
+ com.google.common.collect.Range<Integer> range = entry.getKey().getRange();
+ double x = getCharLocation(range.lowerEndpoint());
+ double width = getCharLocation(range.upperEndpoint()) -x;
+ if (debugOut) System.err.println("layout " + entry.getKey() + "-> " + x + " " + width);
+ entry.getValue().resizeRelocate(x, 0, width, getHeight());
+ }
+ }
+
+ }
+
+ Map<TextAnnotationPresenter, AnnotationOverlay> overlays = new HashMap<>();
+
+ public void updateAnnoations(Set<TextAnnotation> annotations, Set<TextAnnotationPresenter> presenters) {
+ if (currentAnnotations != null && currentAnnotations.equals(annotations) &&
+ currentPresenters != null && currentPresenters.equals(presenters)) {
+ // nothing to updated
+
+ return;
+ }
+
+ if (debugOut) System.err.println("ANNOTATIONS: " + annotations);
+ if (debugOut) System.err.println("update " + getLineIndex() + ": AnnotationLayer " + annotations.size() + " / " + presenters.size());
+ Iterator<Entry<TextAnnotationPresenter, AnnotationOverlay>> iterator = overlays.entrySet().iterator();
+ // cleanup presenters
+ while (iterator.hasNext()) {
+ Entry<TextAnnotationPresenter, AnnotationOverlay> entry = iterator.next();
+ if (!presenters.contains(entry.getKey())) {
+ getChildren().remove(entry.getValue());
+ iterator.remove();
+ }
+ }
+ // update presenters
+ for (TextAnnotationPresenter presenter : presenters) {
+ Set<TextAnnotation> applicableAnnotations = annotations.stream().filter(presenter::isApplicable).collect(Collectors.toSet());
+ AnnotationOverlay overlay = this.overlays.get(presenter);
+ if (overlay == null) {
+ overlay = new AnnotationOverlay(presenter);
+ getChildren().add(overlay);
+ this.overlays.put(presenter, overlay);
+ }
+ overlay.prepareNodes(applicableAnnotations);
+ overlay.requestLayout();
+ }
+
+ if (debugOut) System.err.println("update Line " + getLineIndex() + getChildren());
+ requestLayout();
+
+ if (debugAnimation) {
+ debugUpdateAnnotations.play();
+ }
+
+ currentAnnotations = annotations;
+ currentPresenters = presenters;
+ }
+
+
+ @Override
+ protected void layoutChildren() {
+ super.layoutChildren();
+ if (debugOut) System.err.println("layout Line " + getLineIndex() + ": AnnotationLayer ");
+ }
+
+ }
+
+ private TextLayer textLayer = new TextLayer();
+ private SelectionLayer selectionLayer = new SelectionLayer();
+ private CaretLayer caretLayer = new CaretLayer();
+ private AnnotationLayer annotationLayer = new AnnotationLayer();
+
+
+ public LineNode() {
+// this.model = model;
+ getStyleClass().add("styled-text-line");
+
+ setPadding(new Insets(0, 0, 0, leftPadding));
+
+
+// setStyle("-fx-border-width: 0.1px; -fx-border-color: red");
+
+ getChildren().setAll(this.selectionLayer, this.textLayer, this.caretLayer, this.annotationLayer);
+
+ if (debugAnimation) {
+ this.debugUpdateAnnotations = new DebugMarker(Color.RED, 400);
+ this.debugUpdateText = new DebugMarker(Color.AQUAMARINE, 300);
+ this.debugUpdateSelection = new DebugMarker(Color.BLUE, 150);
+ this.debugUpdateCaret = new DebugMarker(Color.GRAY, 150);
+
+ this.debugUpdateAnnotations.setWidth(10);
+ this.debugUpdateText.setWidth(10);
+ this.debugUpdateSelection.setWidth(10);
+ this.debugUpdateCaret.setWidth(10);
+
+ this.debugBox = new HBox();
+ this.debugBox.setManaged(false);
+ this.debugBox.getChildren().addAll(this.debugUpdateAnnotations, this.debugUpdateSelection, this.debugUpdateCaret, this.debugUpdateText);
+
+ this.getChildren().add(debugBox);
+ }
+
+ }
+
+ @Override
+ protected void layoutChildren() {
+ if (debugOut) System.err.println("layout Line " + index);
+ super.layoutChildren();
+ if (debugAnimation) {
+ debugBox.resizeRelocate(0, 0, 40, getHeight());
+ debugUpdateAnnotations.resize(getHeight(), getHeight());
+ debugUpdateText.resize(getHeight(), getHeight());
+ debugUpdateSelection.resize(getHeight(), getHeight());
+ debugUpdateCaret.resize(getHeight(), getHeight());
+// debugUpdateAnnotations.resizeRelocate(0, 0, getWidth(), getHeight());
+// debugUpdateText.resizeRelocate(0, 0, getWidth(), getHeight());
+// debugUpdateSelection.resizeRelocate(0, 0, getWidth(), getHeight());
+// debugUpdateCaret.resizeRelocate(0, 0, getWidth(), getHeight());
+ }
+
+ }
+
+ public void setLineHelper(LineHelper helper) {
+ this.lineHelper = helper;
+ }
+
+ public void update(Set<TextAnnotationPresenter> presenters) {
+ requestLayout();
+ updateContent(lineHelper.getSegments(index));
+ updateSelection(lineHelper.getSelection(index));
+ updateCaret(lineHelper.getCaret(index));
+ updateAnnotations(lineHelper.getTextAnnotations(index), presenters);
+ }
+
+
+// public void update(StyledTextLine model, Set<TextAnnotationPresenter> annotationPresenter) {
+//// System.err.println("update " + model.getLineIndex());
+//
+// this.model = model;
+//
+// updateContent(model.getSegments());
+// updateSelection(model.getSelectionRange());
+// updateCaret(model.getCaretIndex());
+//
+// Set<TextAnnotation> textAnnotations = model.getAnnotations().stream().filter(m->m instanceof TextAnnotation).map(m->(TextAnnotation)m).collect(Collectors.toSet());
+// this.annotationLayer.updateAnnoations(textAnnotations, annotationPresenter);
+// }
+
+ public void updateSelection(com.google.common.collect.Range<Integer> lineSelection) {
+// System.err.println("LineNode: updateSelection " + lineSelection);
+
+ if (lineSelection != null && lineSelection.isEmpty()) {
+ this.selectionLayer.updateSelection(null);
+ }
+ else {
+ this.selectionLayer.updateSelection(lineSelection);
+ }
+ }
+
+ boolean currentLine = false;
+
+ public void updateCaret(int caret) {
+ this.caretLayer.updateCaret(caret);
+
+ updateCurrentLine(caret != -1);
+ }
+
+ public void updateCurrentLine(boolean current) {
+ if (this.currentLine != current) {
+
+ if (current) {
+ getStyleClass().add("current-line");
+ }
+ else {
+ getStyleClass().remove("current-line");
+ }
+
+ this.currentLine = current;
+ requestLayout();
+ }
+ }
+
+ public void updateContent(List<Segment> content) {
+ boolean updated = this.textLayer.updateContent(content);
+ if (updated) {
+ if (debugAnimation) {
+ debugUpdateText.play();
+ }
+ }
+ }
+
+ public void updateAnnotations(Set<TextAnnotation> annotations, Set<TextAnnotationPresenter> presenters) {
+ this.annotationLayer.updateAnnoations(annotations, presenters);
+ }
+
+
+ public int getCaretIndexAtPoint(Point2D p) {
+ return textLayer.getCaretIndexAtPoint(p);
+ }
+
+ public int getStartOffset() {
+ return lineHelper.getOffset(index);
+ }
+
+ public int getEndOffset() {
+ return lineHelper.getOffset(index) + lineHelper.getLength(index);
+ }
+
+ public double getCharLocation(int charOffset) {
+ return this.textLayer.getCharLocation(charOffset);
+ }
+
+ /**
+ * Check if the offset is between the start and end
+ *
+ * @param start
+ * the start
+ * @param end
+ * the end
+ * @return <code>true</code> if intersects the offset
+ */
+// public boolean intersectOffset(int start, int end) {
+// if (getStartOffset() > end) {
+// return false;
+// } else if (getEndOffset() < start) {
+// return false;
+// }
+// return true;
+// }
+//
+// public boolean intersects(Range globalRange) {
+// return intersectOffset(globalRange.getOffset(), globalRange.getEndOffset());
+// }
+//
+// public Range computeLineLocalIntersection(Range globalRange) {
+// Range result;
+// if (intersects(globalRange)) {
+// int begin = Math.max(0, globalRange.getOffset() - getStartOffset());
+// int end = Math.min(getEndOffset(), globalRange.getEndOffset() - getStartOffset());
+// result = new Range(begin, end - begin);
+// }
+// else {
+// result = new Range(0, 0);
+// }
+// System.err.println("Line local intersection " + this + ": " + globalRange + " -> " + result);
+// return result;
+// }
+
+ @Override
+ public String toString() {
+ return "LineNode(idx: "+getLineIndex() + ")@" + hashCode();
+ }
+
+
+ public int getLineLength() {
+ return lineHelper.getLength(index);
+ }
+
+ public void release() {
+ this.index = -1;
+ this.caretLayer.hideCaret();
+ this.selectionLayer.selection = null;
+ this.selectionLayer.selectionMarker.resize(0, 0);
+ }
+
+ public void setIndex(int idx) {
+// System.err.println("LineNode#setIndex " + index + " -> " + idx);
+ this.index = idx;
+ }
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/LineRuler.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/LineRuler.java
new file mode 100644
index 000000000..eaf9ae0c8
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/LineRuler.java
@@ -0,0 +1,57 @@
+package org.eclipse.fx.ui.controls.styledtext.internal;
+
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import org.eclipse.fx.ui.controls.styledtext.VerticalLineFlow;
+import org.eclipse.fx.ui.controls.styledtext.model.Annotation;
+import org.eclipse.fx.ui.controls.styledtext.model.LineRulerAnnotationPresenter;
+import org.eclipse.fx.ui.controls.styledtext.model.LineRulerAnnotationPresenter.LayoutHint;
+
+import javafx.scene.Node;
+
+public class LineRuler extends VerticalLineFlow<Integer, Annotation>{
+
+ private LineRulerAnnotationPresenter.LayoutHint h;
+
+ public LineRuler(LineRulerAnnotationPresenter.LayoutHint h, Function<Integer, Set<Annotation>> converter, Predicate<Set<Annotation>> needsPresentation, Supplier<Node> nodeFactory, BiConsumer<Node, Set<Annotation>> nodePopulator) {
+ super(converter, needsPresentation, nodeFactory, nodePopulator);
+ this.h = h;
+ }
+
+ @Override
+ protected void layoutChildren() {
+ this.activeNodes.entrySet().forEach(e -> {
+ if (!yOffsetData.containsKey(e.getKey())) {
+ System.err.println("NO DATA FOR " + e);
+ return;
+ }
+ double x = 0;
+ double y = yOffsetData.get(e.getKey());
+ double width = getWidth();
+ double height = getLineHeight();
+
+ if (h == LayoutHint.ALIGN_RIGHT) {
+ e.getValue().autosize();
+ double w = e.getValue().getBoundsInLocal().getWidth();
+
+ x = width - w;
+ width = w;
+ }
+ else if (h == LayoutHint.ALIGN_CENTER) {
+ e.getValue().autosize();
+ double w = e.getValue().getBoundsInLocal().getWidth();
+
+ x = width / 2 - w / 2;
+ width = w;
+ }
+
+ e.getValue().resizeRelocate(x, y, width, height);
+ });
+ }
+
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/ScrollbarPane.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/ScrollbarPane.java
new file mode 100644
index 000000000..bd19b6de7
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/ScrollbarPane.java
@@ -0,0 +1,61 @@
+package org.eclipse.fx.ui.controls.styledtext.internal;
+
+import javafx.geometry.Orientation;
+import javafx.scene.Node;
+import javafx.scene.control.ScrollBar;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.Region;
+import javafx.scene.shape.Rectangle;
+
+public class ScrollbarPane<N extends Node> extends Region {
+
+ public final ScrollBar horizontal;
+ public final ScrollBar vertical;
+ private Rectangle clip;
+ private N center;
+
+ public ScrollbarPane() {
+ this.horizontal = new ScrollBar();
+ this.horizontal.setOrientation(Orientation.HORIZONTAL);
+
+ this.vertical = new ScrollBar();
+ this.vertical.setOrientation(Orientation.VERTICAL);
+
+ clip = new Rectangle();
+
+ getChildren().setAll(horizontal, vertical);
+ }
+
+ public void setCenter(N center) {
+ if (this.center != null) {
+ this.center.setClip(null);
+ getChildren().remove(this.center);
+ }
+
+ this.center = center;
+ if (this.center != null) {
+ this.center.setClip(clip);
+ getChildren().add(0, this.center);
+ }
+ }
+
+ @Override
+ protected void layoutChildren() {
+ int space = 0;
+ int w = 16;
+ this.horizontal.resizeRelocate(0, getHeight() - w, getWidth() - w , w);
+ this.vertical.resizeRelocate(getWidth() - w, 0, w, getHeight()- w);
+
+ if (this.center != null) {
+ this.clip.setX(0);
+ this.clip.setY(0);
+ this.clip.setWidth(getWidth() - w - space);
+ this.clip.setHeight(getHeight() - w - space);
+ this.center.resizeRelocate(0, 0, getWidth() - w - space, getHeight() - w - space);
+ }
+
+
+ }
+
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/Scroller.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/Scroller.java
new file mode 100644
index 000000000..c6aae79ca
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/Scroller.java
@@ -0,0 +1,163 @@
+package org.eclipse.fx.ui.controls.styledtext.internal;
+
+
+import com.google.common.collect.Range;
+
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyDoubleProperty;
+import javafx.beans.property.ReadOnlyObjectProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.scene.control.ScrollBar;
+
+public class Scroller {
+
+ private DoubleProperty lineHeight = new SimpleDoubleProperty(this, "lineHeight", 16); //$NON-NLS-1$
+ public DoubleProperty lineHeightProperty() {
+ return this.lineHeight;
+ }
+
+ private DoubleProperty lineCount = new SimpleDoubleProperty(this, "lineCount", 0); //$NON-NLS-1$
+ public DoubleProperty lineCountProperty() {
+ return lineCount;
+ }
+
+ private IntegerProperty visibleLineCount = new SimpleIntegerProperty(this, "visibleLineCount", 0); //$NON-NLS-1$
+
+ private DoubleProperty contentHeight = new SimpleDoubleProperty(this, "contentHeight", 0); //$NON-NLS-1$
+ public ReadOnlyDoubleProperty contentHeightProperty() {
+ return this.contentHeight;
+ }
+
+ private DoubleProperty contentAreaHeight = new SimpleDoubleProperty(this, "contentAreaHeight", 0); //$NON-NLS-1$
+ public DoubleProperty contentAreaHeightProperty() {
+ return this.contentAreaHeight;
+ }
+
+ private ObjectProperty<Range<Integer>> visibleLines = new SimpleObjectProperty<>(this, "visibleLines", Range.all()); //$NON-NLS-1$
+ public ReadOnlyObjectProperty<Range<Integer>> visibleLinesProperty() {
+ return this.visibleLines;
+ }
+
+ private DoubleProperty offset = new SimpleDoubleProperty(this, "offset", 0.01); //$NON-NLS-1$
+ public DoubleProperty offsetProperty() {
+ return this.offset;
+ }
+
+ private DoubleProperty min = new SimpleDoubleProperty(this, "min", 0); //$NON-NLS-1$
+ private DoubleProperty max = new SimpleDoubleProperty(this, "max", 0); //$NON-NLS-1$
+
+ public Scroller() {
+
+ install();
+ }
+
+ private void recomputeContentHeight(Observable x) {
+ double contentHeight = this.lineCount.get() * this.lineHeight.get();
+ this.contentHeight.set(contentHeight);
+// System.err.println("Scroller: contentHeight = " + contentHeight);
+ }
+
+ private void recomputeMax(Observable x) {
+// System.err.println("Scroller: max = " + contentHeight.get() + " - " + contentAreaHeight.get());
+ double max = Math.max(0, this.contentHeight.get() - this.contentAreaHeight.get());
+ this.max.set(max);
+// System.err.println("Scroller: max = " + max);
+ }
+
+ private void recomputeVisibleLineCount(Observable x) {
+ int visibleLineCount = (int) Math.ceil(this.contentAreaHeight.get() / this.lineHeight.get());
+ this.visibleLineCount.set(visibleLineCount);
+// System.err.println("Scroller: visibleLineCount = " + visibleLineCount);
+ }
+
+ private void recomputeVisibleLines(Observable x) {
+ int lower = (int) Math.floor(this.offset.get() / this.lineHeight.get());
+ int upper = lower + this.visibleLineCount.get();
+ Range<Integer> visibleLines = Range.closed(lower, upper);
+ this.visibleLines.set(visibleLines);
+// System.err.println("Scroller: visibleLines = " + visibleLines);
+ }
+
+ private void recomputeAll() {
+ recomputeContentHeight(null);
+ recomputeMax(null);
+ recomputeVisibleLineCount(null);
+ recomputeVisibleLines(null);
+ }
+
+ private void install() {
+ this.lineCount.addListener(this::recomputeContentHeight);
+ this.lineHeight.addListener(this::recomputeContentHeight);
+
+
+ this.contentHeight.addListener(this::recomputeMax);
+ this.contentAreaHeight.addListener(this::recomputeMax);
+
+ this.lineHeight.addListener(this::recomputeVisibleLineCount);
+ this.contentAreaHeight.addListener(this::recomputeVisibleLineCount);
+
+ this.offset.addListener(this::recomputeVisibleLines);
+ this.lineHeight.addListener(this::recomputeVisibleLines);
+ this.visibleLineCount.addListener(this::recomputeVisibleLines);
+ }
+
+ public void bind(ScrollBar bar) {
+ bar.minProperty().bind(this.min);
+
+ bar.maxProperty().bind(this.max);
+
+ bar.visibleAmountProperty().bind(this.contentAreaHeight.divide(contentHeight.divide(max)));
+
+ bar.valueProperty().bindBidirectional(this.offset);
+
+ recomputeAll();
+ }
+
+
+ public void scrollBy(int lines) {
+ scrollBy(lineHeightProperty().get() * lines);
+ }
+
+ public void scrollBy(double units) {
+
+ double newVal = this.offset.get() + units;
+ newVal = Math.min(max.get(), newVal);
+ newVal = Math.max(min.get(), newVal);
+
+ this.offset.set(newVal);
+ }
+
+ public void scrollIntoView(int line) {
+ System.err.println("scrollIntoView " + line);
+
+ double lineOffset = line * this.lineHeight.get();
+
+ if (lineOffset < this.offset.get()) {
+ // we need to scroll up
+ this.offset.set(lineOffset);
+ }
+
+ double lastLineDelta = this.contentAreaHeight.get() - this.lineHeight.get();
+ if (lineOffset > this.offset.get() + lastLineDelta) {
+ // we need to scroll down
+ this.offset.set(lineOffset - lastLineDelta);
+ }
+ }
+
+ public void scrollTo(int line) {
+ this.offset.set(line * this.lineHeight.get());
+ }
+
+ public void refresh() {
+ recomputeAll();
+ }
+
+
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/Segment.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/Segment.java
new file mode 100644
index 000000000..231617b9f
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/Segment.java
@@ -0,0 +1,60 @@
+package org.eclipse.fx.ui.controls.styledtext.internal;
+
+import java.util.List;
+
+public class Segment {
+ String text;
+ List<String> styleClasses;
+
+ public String getText() {
+ return text;
+ }
+
+ public List<String> getStyleClasses() {
+ return styleClasses;
+ }
+
+ public Segment(String text, List<String> styleClasses) {
+ this.text = text;
+ this.styleClasses = styleClasses;
+ }
+
+ @Override
+ public String toString() {
+ return "Segment(" + this.text + ", " + this.styleClasses + ")"; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((styleClasses == null) ? 0 : styleClasses.hashCode());
+ result = prime * result + ((text == null) ? 0 : text.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Segment other = (Segment) obj;
+ if (styleClasses == null) {
+ if (other.styleClasses != null)
+ return false;
+ } else if (!styleClasses.equals(other.styleClasses))
+ return false;
+ if (text == null) {
+ if (other.text != null)
+ return false;
+ } else if (!text.equals(other.text))
+ return false;
+ return true;
+ }
+
+
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/TextNode.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/TextNode.java
new file mode 100644
index 000000000..a5d9a356e
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/TextNode.java
@@ -0,0 +1,543 @@
+package org.eclipse.fx.ui.controls.styledtext.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.fx.ui.controls.styledtext.ReuseCache;
+import org.eclipse.jdt.annotation.NonNull;
+
+import com.sun.javafx.css.converters.PaintConverter;
+
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.collections.ObservableList;
+import javafx.css.CssMetaData;
+import javafx.css.SimpleStyleableIntegerProperty;
+import javafx.css.SimpleStyleableObjectProperty;
+import javafx.css.StyleConverter;
+import javafx.css.Styleable;
+import javafx.css.StyleableProperty;
+import javafx.geometry.Bounds;
+import javafx.geometry.Point2D;
+import javafx.scene.Node;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextBoundsType;
+
+public class TextNode extends HBox {
+
+ /**
+ * A strategy to decorate the text
+ */
+ public interface DecorationStrategy {
+ /**
+ * Attach the decoration on the text
+ *
+ * @param node
+ * the node the decoration is attached to
+ * @param textNode
+ * the text node decorated
+ */
+ public void attach(TextNode node, Node textNode);
+
+ /**
+ * Remove the decoration from the text
+ *
+ * @param node
+ * the node the decoration is attached to
+ * @param textNode
+ * the text node decorated
+ */
+ public void unattach(TextNode node, Node textNode);
+
+ /**
+ * Layout the decoration
+ *
+ * @param node
+ * the node the layout pass is done on
+ * @param textNode
+ * the text node decorated
+ */
+ public void layout(TextNode node, Node textNode);
+ }
+
+ @SuppressWarnings("null")
+ @NonNull
+ private static final CssMetaData<TextNode, @NonNull Paint> FILL = new CssMetaData<TextNode, @NonNull Paint>("-fx-fill", PaintConverter.getInstance(), Color.BLACK) { //$NON-NLS-1$
+
+ @Override
+ public boolean isSettable(TextNode styleable) {
+ return !styleable.fillProperty().isBound();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public StyleableProperty<@NonNull Paint> getStyleableProperty(TextNode styleable) {
+ return (StyleableProperty<@NonNull Paint>) styleable.fill;
+ }
+
+ };
+ @SuppressWarnings("null")
+ @NonNull
+ final ObjectProperty<@NonNull Paint> fill = new SimpleStyleableObjectProperty<>(FILL, this, "fill", Color.BLACK); //$NON-NLS-1$
+
+ private static String[] BASIC_STRING_CACHE = new String[256];
+
+ private static String toString(char c) {
+ String rv = null;
+ if( c < BASIC_STRING_CACHE.length ) {
+ rv = BASIC_STRING_CACHE[c];
+ if( rv == null ) {
+ BASIC_STRING_CACHE[c] = String.valueOf(c);
+ }
+ }
+ if( rv == null ) {
+ rv = String.valueOf(c);
+ }
+ return rv;
+ }
+
+ /**
+ * The paint used to fill the text
+ *
+ * @return the property to observe
+ */
+ public final @NonNull ObjectProperty<@NonNull Paint> fillProperty() {
+ return (ObjectProperty<@NonNull Paint>) this.fill;
+ }
+
+ /**
+ * @return the paint used to fill the text
+ */
+ public final @NonNull Paint getFill() {
+ return this.fillProperty().get();
+ }
+
+ /**
+ * Set a new paint
+ *
+ * @param color
+ * the paint used to fill the text
+ */
+ public final void setFill(final @NonNull Paint color) {
+ this.fillProperty().set(color);
+ }
+
+// static class DecorationStyleConverter extends StyleConverter<ParsedValue<?, DecorationStrategy>, DecorationStrategy> {
+// private static Map<String, DecorationStrategyFactory> FACTORIES;
+//
+// @SuppressWarnings("null")
+// @Override
+// public DecorationStrategy convert(ParsedValue<ParsedValue<?, DecorationStrategy>, DecorationStrategy> value, Font font) {
+// String definition = value.getValue() + ""; //$NON-NLS-1$
+//
+// if (FACTORIES == null) {
+// FACTORIES = Util.lookupServiceList(getClass(), DecorationStrategyFactory.class).stream().sorted((f1, f2) -> -1 * Integer.compare(f1.getRanking(), f2.getRanking())).collect(Collectors.toMap(f -> f.getDecorationStrategyName(), f -> f));
+// }
+//
+// String type;
+// if (definition.contains("(")) { //$NON-NLS-1$
+// type = definition.substring(0, definition.indexOf('('));
+// } else {
+// type = definition + ""; //$NON-NLS-1$
+// }
+//
+// DecorationStrategyFactory strategy = FACTORIES.get(type);
+// if (strategy != null) {
+// return strategy.create(definition.contains("(") ? definition.substring(definition.indexOf('(') + 1, definition.lastIndexOf(')')) : null); //$NON-NLS-1$
+// }
+//
+// return null;
+// }
+// }
+
+// @NonNull
+// private static final DecorationStyleConverter CONVERTER = new DecorationStyleConverter();
+
+// @SuppressWarnings("null")
+// @NonNull
+// private static final CssMetaData<TextNode, @Nullable DecorationStrategy> DECORATIONSTRATEGY = new CssMetaData<TextNode, @Nullable DecorationStrategy>("-efx-decoration", CONVERTER, null) { //$NON-NLS-1$
+//
+// @Override
+// public boolean isSettable(TextNode node) {
+// return !node.decorationStrategyProperty().isBound();
+// }
+//
+// @SuppressWarnings("unchecked")
+// @Override
+// public StyleableProperty<@Nullable DecorationStrategy> getStyleableProperty(TextNode node) {
+// return (StyleableProperty<@Nullable DecorationStrategy>) node.decorationStrategyProperty();
+// }
+//
+// };
+
+ @SuppressWarnings("null")
+ @NonNull
+ private static final CssMetaData<TextNode, @NonNull Number> TABCHARADANCE = new CssMetaData<TextNode, @NonNull Number>("-efx-tab-char-advance", StyleConverter.getSizeConverter(), Integer.valueOf(4)) { //$NON-NLS-1$
+
+ @Override
+ public boolean isSettable(TextNode styleable) {
+ return !styleable.tabCharAdvanceProperty().isBound();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public StyleableProperty<@NonNull Number> getStyleableProperty(TextNode styleable) {
+ return (StyleableProperty<@NonNull Number>) styleable.tabCharAdvance;
+ }
+
+ };
+
+ private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
+
+ static {
+ final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData());
+// styleables.add(DECORATIONSTRATEGY);
+ styleables.add(FILL);
+ STYLEABLES = Collections.unmodifiableList(styleables);
+ }
+
+ public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
+ return STYLEABLES;
+ }
+
+ @Override
+ public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
+ return getClassCssMetaData();
+ }
+
+// private @NonNull ObjectProperty<@Nullable DecorationStrategy> decorationStrategy = new SimpleStyleableObjectProperty<@Nullable DecorationStrategy>(DECORATIONSTRATEGY, this, "decorationStrategy"); //$NON-NLS-1$
+
+// /**
+// * The current strategy used for decoration
+// *
+// * @return the property to observe
+// */
+// public final @NonNull ObjectProperty<@Nullable DecorationStrategy> decorationStrategyProperty() {
+// return this.decorationStrategy;
+// }
+
+// /**
+// * @return strategy used for decoration
+// */
+// public final @Nullable DecorationStrategy getDecorationStrategy() {
+// return this.decorationStrategyProperty().get();
+// }
+
+// /**
+// * Set a new strategy used for decoration
+// *
+// * @param strategy
+// * the strategy
+// */
+// public final void setDecorationStrategy(final @Nullable DecorationStrategy strategy) {
+// this.decorationStrategyProperty().set(strategy);
+// }
+
+ @NonNull
+ IntegerProperty tabCharAdvance = new SimpleStyleableIntegerProperty(TABCHARADANCE, this, "tabCharAdvance", Integer.valueOf(4)); //$NON-NLS-1$
+
+ /**
+ * Number of chars to use for tab advance (default is 4)
+ *
+ * @return the property to observe
+ */
+ public final IntegerProperty tabCharAdvanceProperty() {
+ return this.tabCharAdvance;
+ }
+
+ /**
+ * @return the number of chars to use for tab advance (default is 4)
+ */
+ public final int getTabCharAdvance() {
+ return this.tabCharAdvanceProperty().get();
+ }
+
+ /**
+ * Set a new number for chars to advance for a tab
+ *
+ * @param tabCharAdvance
+ * the number of chars to use for tab advance (default is 4)
+ */
+ public final void setTabCharAdvance(final int tabCharAdvance) {
+ this.tabCharAdvanceProperty().set(tabCharAdvance);
+ }
+
+
+// private ReuseCache<Text> letterCache = new ReuseCache<Text>(this::createText);
+
+ private int startOffset;
+ private List<Integer> tabPositions = new ArrayList<>();
+ private String originalText;
+// private final HBox textNode;
+// private final Text textNode;
+
+ private List<Text> activeLetters = new ArrayList<>();
+ private ReuseCache<Text> cache;
+
+
+ /**
+ * Create a new styled text node
+ *
+ * @param text
+ * the text
+ */
+ public TextNode(String text) {
+ setMinWidth(HBox.USE_COMPUTED_SIZE);
+ cache = new ReuseCache<>(()->{
+ Text letter = new Text();
+ letter.setBoundsType(TextBoundsType.LOGICAL_VERTICAL_CENTER);
+ return letter;
+ });
+ cache.addOnActivate(node->{
+ getChildren().add(node);
+ });
+ cache.addOnRelease(node-> {
+ getChildren().remove(node);
+ });
+// cache.addOnClear(node->getChildren().remove(node));
+
+ getStyleClass().add("styled-text-node"); //$NON-NLS-1$
+ this.originalText = text;
+
+ rebuildText(text);
+
+ this.tabCharAdvance.addListener(o -> {
+ rebuildText(text);
+ });
+ cache.addOnActivate((n)->{
+ ((Text)n).fillProperty().bind(fillProperty());
+
+ });
+ cache.addOnRelease(n->{
+ ((Text)n).fillProperty().unbind();
+ });
+
+// setStyle("-fx-border-width: 0.1px; -fx-border-color: blue");
+ }
+
+ public void updateText(String text) {
+ rebuildText(text);
+ this.originalText = text;
+ }
+
+ private void rebuildText(String text) {
+ for (Text t : this.activeLetters) {
+ cache.releaseElement(t);
+ }
+ this.activeLetters.clear();
+ for( char c : processText(text).toCharArray() ) {
+ Text textNode = cache.getElement();
+ textNode.setText(toString(c));
+ this.activeLetters.add(textNode);
+ }
+ }
+
+ private String processText(String text) {
+ String tmp = text;
+ StringBuilder b = new StringBuilder();
+ for (int i = 0; i < this.tabCharAdvance.get(); i++) {
+ b.append(" "); //$NON-NLS-1$
+ }
+ int position = -1;
+ this.tabPositions.clear();
+ while ((position = tmp.indexOf('\t')) != -1) {
+ tmp = tmp.replaceFirst("\t", b.toString()); //$NON-NLS-1$
+ this.tabPositions.add(Integer.valueOf(position));
+ }
+ return tmp;
+ }
+
+// private void handleDecorationChange(ObservableValue<? extends DecorationStrategy> observable, DecorationStrategy oldValue, DecorationStrategy newValue) {
+// if (oldValue != null) {
+// oldValue.unattach(this, this.textNode);
+// }
+//
+// if (newValue != null) {
+// newValue.attach(this, this.textNode);
+// }
+// }
+
+ void setStartOffset(int startOffset) {
+ this.startOffset = startOffset;
+ }
+
+ /**
+ * @return the start offset in the file
+ */
+ @Deprecated
+ public int getStartOffset() {
+ return this.startOffset;
+ }
+
+ /**
+ * @return the end offset in the file
+ */
+ @Deprecated
+ public int getEndOffset() {
+ return this.startOffset + getText().length();
+ }
+
+ /**
+ * Check if the text node intersects with the start and end locations
+ *
+ * @param start
+ * the start
+ * @param end
+ * the end
+ * @return <code>true</code> if intersects
+ */
+ public boolean intersectOffset(int start, int end) {
+ if (getStartOffset() > end) {
+ return false;
+ } else if (getEndOffset() < start) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "'" + this.originalText + "'"; //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ @Override
+ public ObservableList<Node> getChildren() {
+ return super.getChildren();
+ }
+
+ /**
+ * @return the text
+ */
+ public String getText() {
+ return this.originalText;
+ }
+
+ protected double getFixedLetterWidth() {
+ return 8;
+ }
+
+ @Override
+ protected void layoutChildren() {
+ super.layoutChildren();
+
+// double off = 0;
+//
+// for (Text letter : this.activeLetters) {
+// letter.setLayoutX(off);
+// off += getFixedLetterWidth();
+// }
+
+// this.textNode.relocate(0, 0);
+// DecorationStrategy decorationStrategy2 = this.decorationStrategy.get();
+// if (decorationStrategy2 != null) {
+// decorationStrategy2.layout(this, this.textNode);
+// }
+ }
+
+ /**
+ * Get the caret index at the given point
+ *
+ * @param point
+ * the point relative to this node
+ * @return the index or <code>-1</code>
+ */
+ public int getCaretIndexAtPoint(Point2D point) {
+ Point2D local = this.sceneToLocal(localToScene(point));
+// System.err.println(local);
+
+ Optional<Node> charNode = this.getChildren().stream().filter( n -> n.getBoundsInParent().contains(local)).findFirst();
+ if( charNode.isPresent() ) {
+ Node node = charNode.get();
+ int idx = this.getChildren().indexOf(node);
+ Bounds bounds = node.getBoundsInParent();
+
+ // if it is the 2nd half of the character
+ if( bounds.getMinX() + bounds.getWidth() / 2 < local.getX() ) {
+ idx += 1;
+ }
+
+ int toRemove = 0;
+ for (Integer i : this.tabPositions) {
+ if (i.intValue() <= idx && idx < i.intValue() + this.tabCharAdvance.get()) {
+ toRemove += idx - i.intValue();
+ // If we are in the 2nd half of the tab we
+ // simply move one past the value
+ if ((idx - i.intValue()) % this.tabCharAdvance.get() >= this.tabCharAdvance.get() / 2) {
+ idx += 1;
+ }
+ break;
+ } else if (i.intValue() < idx) {
+ toRemove += this.tabCharAdvance.get() - 1;
+ }
+ }
+ idx -= toRemove;
+// System.err.println("==> (TextNode)" + idx);
+ return idx;
+ } else {
+// System.err.println("COULD NOT FIND NODE AT: " + local);
+// this.textNode.getChildren().stream().map( n -> n.getBoundsInParent()).forEach(System.err::println);
+ }
+
+// @SuppressWarnings("deprecation")
+// HitInfo info = this.textNode.impl_hitTestChar(this.textNode.sceneToLocal(localToScene(point)));
+// if (info != null) {
+// int idx = info.getInsertionIndex();
+// int toRemove = 0;
+// for (Integer i : this.tabPositions) {
+// if (i.intValue() <= idx && idx < i.intValue() + this.tabCharAdvance.get()) {
+// toRemove += idx - i.intValue();
+// // If we are in the 2nd half of the tab we
+// // simply move one past the value
+// if ((idx - i.intValue()) % this.tabCharAdvance.get() >= this.tabCharAdvance.get() / 2) {
+// idx += 1;
+// }
+// break;
+// } else if (i.intValue() < idx) {
+// toRemove += this.tabCharAdvance.get() - 1;
+// }
+// }
+// idx -= toRemove;
+// return idx;
+// }
+ return -1;
+ }
+
+ /**
+ * Find the x coordinate where we the character for the given index starts
+ *
+ * @param index
+ * the index
+ * @return the location or <code>0</code> if not found
+ */
+ public double getCharLocation(int index) {
+ int realIndex = index;
+ for (Integer i : this.tabPositions) {
+ if (i.intValue() < realIndex) {
+ realIndex += this.tabCharAdvance.get() - 1;
+ }
+ }
+
+ if( realIndex >= 0 && realIndex < this.getChildren().size() ) {
+ return this.localToParent(this.getChildren().get(realIndex).getBoundsInParent()).getMinX();
+ } else if( ! this.getChildren().isEmpty() ) {
+ return this.localToParent(this.getChildren().get(this.getChildren().size()-1).getBoundsInParent()).getMaxX();
+ }
+
+ return 0.0;
+
+// this.textNode.setImpl_caretPosition(realIndex);
+// PathElement[] pathElements = this.textNode.getImpl_caretShape();
+// for (PathElement e : pathElements) {
+// if (e instanceof MoveTo) {
+// return this.textNode.localToParent(((MoveTo) e).getX(), 0).getX();
+// }
+// }
+
+ }
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/VFlow.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/VFlow.java
new file mode 100644
index 000000000..c27eb0f23
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/internal/VFlow.java
@@ -0,0 +1,323 @@
+package org.eclipse.fx.ui.controls.styledtext.internal;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Queue;
+import java.util.Set;
+import java.util.Stack;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.IntStream;
+
+import org.eclipse.fx.ui.controls.styledtext.NodeCachePane;
+
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.IntegerBinding;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ListProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.property.SimpleListProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
+import javafx.scene.Node;
+import javafx.scene.layout.Pane;
+
+import com.google.common.collect.ContiguousSet;
+import com.google.common.collect.DiscreteDomain;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
+
+public class VFlow<N extends Node> extends Pane {
+
+ private Object sync = new Object();
+
+ private int overload = 100;
+
+ private Supplier<N> nodeFactory;
+ private BiConsumer<N, Integer> nodePopulator;
+
+ private Map<Integer, N> activeNodes = new HashMap<>();
+ private Queue<N> freeNodes = new LinkedList<>();
+
+ private DoubleProperty lineHeigth = new SimpleDoubleProperty(this, "lineHeight", 16.0);
+ public DoubleProperty lineHeightProperty() {
+ return this.lineHeigth;
+ }
+ public double getLineHeight() {
+ return this.lineHeigth.get();
+ }
+ public void setLineHeight(double lineHeight) {
+ this.lineHeigth.set(lineHeight);
+ }
+
+ private IntegerProperty numberOfLines = new SimpleIntegerProperty(this, "numberOfLines", 0);
+ public IntegerProperty numberOfLinesProperty() {
+ return this.numberOfLines;
+ }
+ public int getNumberOfLines() {
+ return this.numberOfLines.get();
+ }
+ public void setNumberOfLines(int lines) {
+ this.numberOfLines.set(lines);
+ }
+
+ private DoubleProperty yOffset = new SimpleDoubleProperty(this, "yOffset", 0);
+ public DoubleProperty yOffsetProperty() {
+ return this.yOffset;
+ }
+ public double getYOffset() {
+ return this.yOffset.get();
+ }
+ public void setYOffset(double yOffset) {
+ this.yOffset.set(yOffset);
+ }
+
+ private ObjectProperty<Range<Integer>> visibleLines = new SimpleObjectProperty<>(this, "visibleLines", Range.all()); //$NON-NLS-1$
+ public ObjectProperty<Range<Integer>> visibleLinesProperty() {
+ return this.visibleLines;
+ }
+ public Range<Integer> getVisibleLines() {
+ return this.visibleLines.get();
+ }
+ public void setVisibleLines(Range<Integer> visibleLines) {
+ this.visibleLines.set(visibleLines);
+ }
+
+ private Consumer<N> onRelease;
+ private BiConsumer<Integer, N> onActivate;
+
+ protected void setOnRelease(Consumer<N> onRelease) {
+ this.onRelease = onRelease;
+ }
+
+ protected void setOnActivate(BiConsumer<Integer, N> onActivate) {
+ this.onActivate = onActivate;
+ }
+
+ public VFlow(Supplier<N> nodeFactory, BiConsumer<N, Integer> nodePopulator) {
+ this.nodeFactory = nodeFactory;
+ this.nodePopulator = nodePopulator;
+ this.visibleLines.addListener(this::onVisibleLinesChange);
+ this.yOffset.addListener((x)->requestLayout());
+ this.numberOfLines.addListener(this::onNumberOfLinesChange);
+ }
+
+
+// private void onModelChange(ListChangeListener.Change<? extends M> change) {
+// System.err.println("ON MODEL CHANGE " + change);
+// RangeSet<Integer> toUpdate = TreeRangeSet.create();
+// RangeSet<Integer> toRelease = TreeRangeSet.create();
+////
+// while (change.next()) {
+// if (change.wasUpdated()) {
+// toUpdate.add(Range.closedOpen(change.getFrom(), change.getTo()));
+// }
+// if (change.wasAdded()) {
+// toUpdate.add(Range.closedOpen(change.getFrom(), model.size()));
+// }
+// if (change.wasRemoved()) {
+// toUpdate.add(Range.closedOpen(change.getFrom(), model.size()));
+// }
+// }
+////
+////
+////
+//// triggerUpdate(toUpdate);
+//// triggerRelease(toRelease);
+//// toUpdate.add(visibleLines.get());
+//
+//
+// Range<Integer> all = Range.all();
+// RangeSet<Integer> notAll = TreeRangeSet.create();
+// notAll.add(all);
+// // always release not existing nodes
+// toRelease.addAll(notAll.complement());
+//
+// triggerRelease(toRelease);
+// triggerUpdate(toUpdate);
+//
+// requestLayout();
+// }
+
+ public void permutateNodes(Function<Integer, Integer> mapping) {
+ String permutate = "permutateNodes(): ";
+ synchronized (this.sync) {
+ Map<Integer, N> newActiveNodes = new HashMap<>();
+ Set<Integer> changedIndexes = new HashSet<>();
+ for (Entry<Integer, N> entry : this.activeNodes.entrySet()) {
+ final int curIndex = entry.getKey();
+ final int newIndex = mapping.apply(entry.getKey());
+ permutate += "\n * " + curIndex + " -> " + newIndex;
+ if (curIndex != newIndex) changedIndexes.add(newIndex);
+ newActiveNodes.put(newIndex, entry.getValue());
+ }
+ this.activeNodes = newActiveNodes;
+
+ for (Integer index : changedIndexes) {
+ // apply indices
+ if (this.onActivate != null) this.onActivate.accept(index, this.activeNodes.get(index));
+ }
+
+ }
+// System.err.println(permutate);
+ }
+
+ private void onNumberOfLinesChange(Observable x, Number o, Number n) {
+ if (n.intValue() > o.intValue()) {
+ RangeSet<Integer> toUpdate = TreeRangeSet.create();
+ toUpdate.add(Range.closedOpen(o.intValue(), n.intValue()));
+ triggerUpdate(toUpdate);
+ }
+ else if (n.intValue() < o.intValue()) {
+ RangeSet<Integer> toRelease = TreeRangeSet.create();
+ toRelease.add(Range.closed(n.intValue(), o.intValue()));
+ triggerRelease(toRelease);
+ }
+ }
+
+ private void onVisibleLinesChange(Observable x, Range<Integer> o, Range<Integer> n) {
+ RangeSet<Integer> toUpdate = TreeRangeSet.create();
+ RangeSet<Integer> toRelease = TreeRangeSet.create();
+
+ if (o != null && n != null) {
+ RangeSet<Integer> hidden = TreeRangeSet.create();
+ hidden.add(o);
+ hidden.remove(n);
+
+ toRelease.addAll(hidden);
+ }
+
+ if (n != null) {
+ toUpdate.add(getVisible());
+ }
+
+ triggerUpdate(toUpdate);
+ triggerRelease(toRelease);
+ requestLayout();
+ }
+
+ private Range<Integer> getVisible() {
+ return visibleLines.get();
+// Range<Integer> r = visibleLines.get();
+// return Range.closedOpen(r.lowerEndpoint() - overload, r.upperEndpoint() + overload);
+ }
+
+ private void triggerUpdate(RangeSet<Integer> range) {
+ Range<Integer> all = Range.closedOpen(0, getNumberOfLines());
+// System.err.println("all = " + all);
+ RangeSet<Integer> onlyExisting = range.subRangeSet(all);
+ RangeSet<Integer> onlyVisible = onlyExisting.subRangeSet(getVisible());
+// System.err.println("triggerUpdate: " + range + " -> " + onlyExisting + " -> " + onlyVisible);
+
+ onlyVisible.asRanges().stream()
+ .flatMapToInt(this::toIntStream)
+ .forEach(index -> updateNode(index));
+ }
+
+ private void triggerRelease(RangeSet<Integer> range) {
+// System.err.println("triggerRelease: " + range );
+ if (range.contains(Integer.MAX_VALUE) || range.contains(Integer.MIN_VALUE)) {
+ System.err.println(" -> infinite -.-");
+ return;
+ }
+ range.asRanges().stream()
+ .flatMapToInt(this::toIntStream)
+ .forEach(index -> releaseNode(index));
+ }
+
+ @Override
+ protected void layoutChildren() {
+ synchronized (this.sync) {
+ this.activeNodes.entrySet().forEach(e -> {
+ double x = 0;
+ double y = e.getKey() * getLineHeight() - getYOffset();
+ double width = getWidth();
+ double height = getLineHeight();
+// System.err.println("VFlow#layout " + e.getValue());
+// System.err.println("VFlow layoutChildren " + e.getKey() + ": " + x +", " + y+ ", " + width + ", " + height);
+// System.err.println(" * isVisible: " + e.getValue().isVisible());
+// System.err.println(" * isChild: " + (e.getValue().getParent() == this));
+ e.getValue().resizeRelocate(x, y, width, height);
+ });
+ }
+ }
+
+
+
+ protected void releaseNode(int lineIndex) {
+ synchronized (this.sync) {
+ N node = this.activeNodes.remove(lineIndex);
+ if (node != null) {
+// System.err.println("releasing " + lineIndex + "("+node+")");
+ node.setVisible(false);
+ this.freeNodes.offer(node);
+ if (this.onRelease != null) {
+ this.onRelease.accept(node);
+ }
+ }
+ }
+ }
+
+ protected void updateNode(int lineIndex) {
+// System.err.println("VFlow#updateNode " + lineIndex);
+// Thread.dumpStack();
+ N node = getNode(lineIndex);
+// System.err.println("VFlow#updateNode " + node);
+ node.setVisible(true);
+ this.nodePopulator.accept(node, lineIndex);
+ }
+
+ public Optional<N> getVisibleNode(int lineIndex) {
+ synchronized (this.sync) {
+ N node = this.activeNodes.get(lineIndex);
+ return Optional.ofNullable(node);
+ }
+ }
+
+ protected N getNode(int lineIndex) {
+ synchronized (this.sync) {
+// System.err.println("VFlow#getNode " + lineIndex);
+ N node = this.activeNodes.remove(lineIndex);
+// if (node != null) System.err.println(" from active");
+
+ if (node == null) {
+ if (!this.freeNodes.isEmpty()) {
+ node = this.freeNodes.poll();
+// if (node != null) System.err.println(" from free ("+node+")");
+ }
+ }
+
+ if (node == null) {
+ node = this.nodeFactory.get();
+// System.err.println(" from factory");
+ getChildren().add(node);
+ }
+
+ if (node != null) if (this.onActivate != null) this.onActivate.accept(lineIndex, node);
+ this.activeNodes.put(lineIndex, node);
+
+ return node;
+ }
+ }
+
+ IntStream toIntStream(Range<Integer> range) {
+ return ContiguousSet.create(range, DiscreteDomain.integers()).stream().mapToInt(i->i.intValue());
+ }
+
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/Annotation.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/Annotation.java
new file mode 100644
index 000000000..c847402f5
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/Annotation.java
@@ -0,0 +1,4 @@
+package org.eclipse.fx.ui.controls.styledtext.model;
+
+public interface Annotation {
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/AnnotationPresenter.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/AnnotationPresenter.java
new file mode 100644
index 000000000..0156c84ff
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/AnnotationPresenter.java
@@ -0,0 +1,13 @@
+package org.eclipse.fx.ui.controls.styledtext.model;
+
+import java.util.Set;
+
+import javafx.scene.Node;
+
+public interface AnnotationPresenter {
+
+ boolean isApplicable(Annotation annotation);
+
+
+ Node createNode();
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/AnnotationProvider.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/AnnotationProvider.java
new file mode 100644
index 000000000..ea5c27cab
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/AnnotationProvider.java
@@ -0,0 +1,15 @@
+package org.eclipse.fx.ui.controls.styledtext.model;
+
+import java.util.Set;
+import java.util.function.Consumer;
+
+import org.eclipse.fx.core.Subscription;
+
+import com.google.common.collect.RangeSet;
+
+public interface AnnotationProvider {
+
+ Set<? extends Annotation> computeAnnotations(int index);
+
+ Subscription registerChangeListener(Consumer<RangeSet<Integer>> onChange);
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/LineRulerAnnotationPresenter.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/LineRulerAnnotationPresenter.java
new file mode 100644
index 000000000..cf9b326b4
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/LineRulerAnnotationPresenter.java
@@ -0,0 +1,28 @@
+package org.eclipse.fx.ui.controls.styledtext.model;
+
+import java.util.Set;
+
+import javafx.beans.property.DoubleProperty;
+import javafx.scene.Node;
+
+// TODO add some kind of layout hinting to position node for example on the right side
+public interface LineRulerAnnotationPresenter extends AnnotationPresenter {
+
+ enum LayoutHint {
+ ALIGN_RIGHT,
+ ALIGN_LEFT,
+ ALIGN_CENTER;
+ }
+
+ LayoutHint getLayoutHint();
+
+ DoubleProperty getWidth();
+
+ int getOrder();
+
+ // TODO use me or remove me
+ boolean isVisible(Set<Annotation> annotation);
+
+
+ void updateNode(Node node, Set<Annotation> annotation);
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/TextAnnotation.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/TextAnnotation.java
new file mode 100644
index 000000000..7285ef51e
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/TextAnnotation.java
@@ -0,0 +1,7 @@
+package org.eclipse.fx.ui.controls.styledtext.model;
+
+import com.google.common.collect.Range;
+
+public interface TextAnnotation extends Annotation {
+ Range<Integer> getRange();
+} \ No newline at end of file
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/TextAnnotationPresenter.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/TextAnnotationPresenter.java
new file mode 100644
index 000000000..5247c21f1
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/model/TextAnnotationPresenter.java
@@ -0,0 +1,9 @@
+package org.eclipse.fx.ui.controls.styledtext.model;
+
+import javafx.scene.Node;
+
+public interface TextAnnotationPresenter extends AnnotationPresenter {
+
+ boolean isVisible(Annotation annotation);
+ void updateNode(Node node, Annotation annotation);
+}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/skin/StyledTextSkin.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/skin/StyledTextSkin.java
index 587e5b164..3ef956899 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/skin/StyledTextSkin.java
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/skin/StyledTextSkin.java
@@ -11,63 +11,81 @@
*******************************************************************************/
package org.eclipse.fx.ui.controls.styledtext.skin;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
-
-import org.eclipse.fx.ui.controls.styledtext.StyleRange;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.eclipse.fx.core.Subscription;
+import org.eclipse.fx.ui.controls.styledtext.FXBindUtil;
import org.eclipse.fx.ui.controls.styledtext.StyledTextArea;
-import org.eclipse.fx.ui.controls.styledtext.StyledTextArea.StyledTextLine;
-import org.eclipse.fx.ui.controls.styledtext.StyledTextLayoutContainer;
-import org.eclipse.fx.ui.controls.styledtext.StyledTextNode;
-import org.eclipse.fx.ui.controls.styledtext.TextSelection;
+import org.eclipse.fx.ui.controls.styledtext.StyledTextContent.TextChangeListener;
+import org.eclipse.fx.ui.controls.styledtext.TextChangedEvent;
+import org.eclipse.fx.ui.controls.styledtext.TextChangingEvent;
+import org.eclipse.fx.ui.controls.styledtext.VerticalLineFlow;
import org.eclipse.fx.ui.controls.styledtext.behavior.StyledTextBehavior;
-import org.eclipse.jdt.annotation.NonNull;
-
-import com.sun.javafx.scene.control.skin.ListViewSkin;
-import com.sun.javafx.scene.control.skin.VirtualFlow;
-
-import javafx.application.Platform;
+import org.eclipse.fx.ui.controls.styledtext.internal.ContentView;
+import org.eclipse.fx.ui.controls.styledtext.internal.LineHelper;
+import org.eclipse.fx.ui.controls.styledtext.internal.LineRuler;
+import org.eclipse.fx.ui.controls.styledtext.internal.ScrollbarPane;
+import org.eclipse.fx.ui.controls.styledtext.internal.Scroller;
+import org.eclipse.fx.ui.controls.styledtext.model.Annotation;
+import org.eclipse.fx.ui.controls.styledtext.model.AnnotationPresenter;
+import org.eclipse.fx.ui.controls.styledtext.model.AnnotationProvider;
+import org.eclipse.fx.ui.controls.styledtext.model.LineRulerAnnotationPresenter;
+import org.eclipse.fx.ui.controls.styledtext.model.TextAnnotationPresenter;
+
+import com.google.common.collect.ContiguousSet;
+import com.google.common.collect.DiscreteDomain;
+import com.google.common.collect.RangeSet;
+
+import javafx.beans.InvalidationListener;
+import javafx.beans.property.IntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
+import javafx.collections.SetChangeListener;
+import javafx.collections.transformation.SortedList;
import javafx.geometry.Point2D;
-import javafx.geometry.Pos;
import javafx.scene.Node;
-import javafx.scene.control.Label;
-import javafx.scene.control.ListCell;
-import javafx.scene.control.ListView;
-import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.HBox;
-import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
-import javafx.util.Callback;
/**
* Styled text skin
*/
@SuppressWarnings("restriction")
public class StyledTextSkin extends SkinBase<StyledTextArea> {
- ListView<Line> contentView;
- LineRuler lineRuler;
- ObservableList<Line> lineList = FXCollections.observableArrayList();
+ private ScrollbarPane<ContentView> contentArea;
+ private ContentView content;
+
+ private Scroller scroller;
+
+ private HBox lineRulerArea;
- // private Set<LineCell> visibleCells = new HashSet<>();
- Map<LineCell, LineInfo> lineInfoMap = new HashMap<>();
+// private ObservableList<VerticalLineFlow<Integer, Annotation>> sortedLineRulerFlows;
- HBox rootContainer;
+ private ObservableList<LineRuler> sortedLineRulerFlows;
+
+
+ private HBox rootContainer;
private final StyledTextBehavior behavior;
+ private LineHelper lineHelper;
+
/**
* Create a new skin
*
@@ -90,209 +108,269 @@ public class StyledTextSkin extends SkinBase<StyledTextArea> {
super(styledText);
this.behavior = behavior;
this.rootContainer = new HBox();
+
this.rootContainer.setSpacing(0);
- this.lineRuler = new LineRuler();
- this.lineRuler.visibleProperty().bind(getSkinnable().lineRulerVisibleProperty());
- this.lineRuler.managedProperty().bind(getSkinnable().lineRulerVisibleProperty());
- this.rootContainer.getChildren().add(this.lineRuler);
+ this.lineRulerArea = new HBox();
+ this.rootContainer.getChildren().add(this.lineRulerArea);
- this.contentView = new ListView<Line>() {
- @Override
- protected Skin<?> createDefaultSkin() {
- return new MyListViewSkin(this);
- }
+
+ Region spacer = new Region();
+ spacer.getStyleClass().addAll("line-ruler", "spacer");
+ spacer.setMinWidth(2);
+ spacer.setMaxWidth(2);
+ this.rootContainer.getChildren().add(spacer);
+
+
+ lineHelper = new LineHelper(getSkinnable());
+ this.content = new ContentView(lineHelper);
+
+ this.contentArea = new ScrollbarPane<>();
+
+ this.contentArea.setCenter(this.content);
+
+
+ Map<AnnotationProvider, Subscription> subscriptions = new HashMap<>();
+ Consumer<RangeSet<Integer>> onAnnotationChange = r-> {
+ System.err.println("onAnnotationChange " + r);
+ content.updateAnnotations(r);
+ sortedLineRulerFlows.forEach(f->f.update(r));
};
- initializeContentViewer(this.contentView);
+ getSkinnable().getAnnotationProvider().addListener((SetChangeListener<? super AnnotationProvider>)(c) -> {
+ if (c.wasAdded()) {
+ System.err.println("register for2 " + c.getElementAdded());
+ Subscription s = c.getElementAdded().registerChangeListener(onAnnotationChange);
+ subscriptions.put(c.getElementAdded(), s);
+ }
+ if (c.wasRemoved()) {
+ Subscription s = subscriptions.remove(c.getElementRemoved());
+ if (s != null) s.dispose();
+ }
+ });
+ for (AnnotationProvider p : getSkinnable().getAnnotationProvider()) {
+ System.err.println("register for " + p);
+ if (!subscriptions.containsKey(p)) {
+ Subscription s = p.registerChangeListener(onAnnotationChange);
+ subscriptions.put(p, s);
+ }
+ }
- recalculateItems();
+ this.content.getStyleClass().addAll("list-view");
- this.contentView.setItems(this.lineList);
+ // focus delegation
+ this.content.focusedProperty().addListener((x, o, n)->{
+ if (n) {
+ getSkinnable().requestFocus();
+ }
+ });
- HBox.setHgrow(this.contentView, Priority.ALWAYS);
+ getBehavior().installContentListeners(this.content);
- this.rootContainer.getChildren().addAll(this.contentView);
+ this.content.contentProperty().bind(getSkinnable().contentProperty());
+
+ // scroll support
+ this.content.setOnScroll((e)-> {
+ this.scroller.scrollBy(Math.round(-e.getDeltaY()));
+ });
+
+// HBox.setHgrow(this.contentView, Priority.ALWAYS);
+
+ HBox.setHgrow(this.contentArea, Priority.ALWAYS);
+ this.rootContainer.getChildren().addAll(this.contentArea);
getChildren().addAll(this.rootContainer);
-// styledText.getAnnotations().addListener(new SetChangeListener<StyledTextAnnotation>() {
-// @Override
-// public void onChanged(javafx.collections.SetChangeListener.Change<? extends StyledTextAnnotation> change) {
-// if (change.getElementAdded() != null) {
-// StyledTextAnnotation a = change.getElementAdded();
-// addAnnotation(a);
-// System.err.println("removed " + a.getId() + " " + a.getType() + ": " + a.getText() + " @ " + a.getStartOffset() + ", " + a.getLength());
-// }
-// if (change.getElementRemoved() != null) {
-// StyledTextAnnotation a = change.getElementRemoved();
-// removeAnnotation(a);
-// System.err.println("added " + a.getId() + " " + a.getType() + ": " + a.getText() + " @ " + a.getStartOffset() + ", " + a.getLength());
-// }
-// }
-// });
- styledText.caretOffsetProperty().addListener(new ChangeListener<Number>() {
+ // scroll stuff
+ this.scroller = new Scroller();
+ this.scroller.contentAreaHeightProperty().bind(content.heightProperty());
- @Override
- public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
- int lineIndex = getSkinnable().getContent().getLineAtOffset(newValue.intValue());
- Line lineObject = StyledTextSkin.this.lineList.get(lineIndex);
- getFlow().show(lineIndex);
-
- for (LineCell c : getCurrentVisibleCells()) {
- if (c.domainElement == lineObject) {
- // Adjust the selection
- if (StyledTextSkin.this.contentView.getSelectionModel().getSelectedItem() != c.domainElement) {
- StyledTextSkin.this.contentView.getSelectionModel().select(lineObject);
- }
-
- StyledTextLayoutContainer p = (StyledTextLayoutContainer) c.getGraphic();
- p.setCaretIndex(newValue.intValue() - p.getStartOffset());
- p.requestLayout();
-
- updateCurrentCursorNode(p);
-
- return;
- }
- }
+ this.scroller.lineHeightProperty().bind(content.lineHeightProperty());
+
+ this.content.lineHeightProperty().set(16);
+
+ this.content.bindHorizontalScrollbar(this.contentArea.horizontal);
+
+// getSkinnable().lineCountProperty().addListener((x, o, n)-> {/* for the quantum! */});
+ ((IntegerProperty)getSkinnable().lineCountProperty()).bind(content.numberOfLinesProperty());
+
+// content.numberOfLinesProperty().addListener((x, o, n)-> System.err.println("FUCKING LINE COUNT CHANGE: " + n));
+// getSkinnable().lineCountProperty().addListener((x, o, n)-> System.err.println("FUCKING LINE COUNT CHANGE2: " + n));
+
+
+
+ scroller.lineCountProperty().bind(content.numberOfLinesProperty());
+
+ this.scroller.bind(this.contentArea.vertical);
+
+
+ this.content.textSelectionProperty().bind(getSkinnable().selectionProperty());
+ this.content.caretOffsetProperty().bind(getSkinnable().caretOffsetProperty());
+
+ this.content.visibleLinesProperty().bind(this.scroller.visibleLinesProperty());
+
+ Consumer<Double> updateOffset = (offset) -> {
+ com.google.common.collect.Range<Integer> visibleLines = scroller.visibleLinesProperty().get();
+ ContiguousSet<Integer> set = ContiguousSet.create(visibleLines, DiscreteDomain.integers());
+ double lineHeight = scroller.lineHeightProperty().get();
+ for (int index : set) {
+
+ double y = index * lineHeight - offset.doubleValue();
+
+ for (VerticalLineFlow<Integer, Annotation> flow : sortedLineRulerFlows) {
+ flow.setLineOffset(index, y);
+ }
}
+ };
+
+ content.offsetYProperty().bind(scroller.offsetProperty());
+
+ scroller.offsetProperty().addListener((x, o, offset)-> {
+ updateOffset.accept(offset.doubleValue());
+ });
+
+ scroller.visibleLinesProperty().addListener(x->{
+ updateOffset.accept(scroller.offsetProperty().get());
});
- styledText.selectionProperty().addListener(new ChangeListener<TextSelection>() {
+
+ getSkinnable().getContent().addTextChangeListener(new TextChangeListener() {
@Override
- public void changed(ObservableValue<? extends TextSelection> observable, TextSelection oldValue, TextSelection newValue) {
- if (newValue == null || newValue.length == 0) {
- for (LineCell c : getCurrentVisibleCells()) {
- if (c.getGraphic() != null) {
- StyledTextLayoutContainer block = (StyledTextLayoutContainer) c.getGraphic();
- block.setSelection(new TextSelection(0, 0));
- }
- }
- } else {
- TextSelection selection = newValue;
- for (LineCell c : getCurrentVisibleCells()) {
- if (c.getGraphic() != null) {
- Line arg0 = c.domainElement;
- StyledTextLayoutContainer block = (StyledTextLayoutContainer) c.getGraphic();
- if (selection.length > 0 && block.intersectOffset(selection.offset, selection.offset + selection.length)) {
- int start = Math.max(0, selection.offset - arg0.getLineOffset());
-
- if (arg0.getLineOffset() + arg0.getLineLength() > selection.offset + selection.length) {
- block.setSelection(new TextSelection(start, selection.offset + selection.length - arg0.getLineOffset() - start));
- } else {
- block.setSelection(new TextSelection(start, arg0.getLineLength() - start));
- }
- } else {
- block.setSelection(new TextSelection(0, 0));
- }
- }
- }
- }
+ public void textSet(TextChangedEvent event) {
+ scroller.refresh();
+ }
+
+ @Override
+ public void textChanging(TextChangingEvent event) {
+
+ }
+
+ @Override
+ public void textChanged(TextChangedEvent event) {
+
}
});
- }
-// private void addAnnotation(StyledTextAnnotation annotation) {
-// int startOffset = annotation.getStartOffset();
-// int endOffset = annotation.getStartOffset() + annotation.getLength();
-//
-// int startLineIndex = getSkinnable().getContent().getLineAtOffset(startOffset);
-// int endLineIndex = getSkinnable().getContent().getLineAtOffset(endOffset);
-//
-// Set<Line> affectedLines = IntStream.range(startLineIndex, endLineIndex + 1).mapToObj(StyledTextSkin.this.lineList::get).collect(Collectors.toSet());
-//
-// for (LineCell c : getCurrentVisibleCells()) {
-// if (affectedLines.contains(c.domainElement)) {
-// StyledTextLayoutContainer p = (StyledTextLayoutContainer) c.getGraphic();
-// p.getAnnotations().add(annotation);
-// }
-// }
-// }
-//
-// private void removeAnnotation(StyledTextAnnotation annotation) {
-// int startOffset = annotation.getStartOffset();
-// int endOffset = annotation.getStartOffset() + annotation.getLength();
-//
-// int startLineIndex = getSkinnable().getContent().getLineAtOffset(startOffset);
-// int endLineIndex = getSkinnable().getContent().getLineAtOffset(endOffset);
-//
-// Set<Line> affectedLines = IntStream.range(startLineIndex, endLineIndex + 1).mapToObj(StyledTextSkin.this.lineList::get).collect(Collectors.toSet());
-//
-// for (LineCell c : getCurrentVisibleCells()) {
-// if (affectedLines.contains(c.domainElement)) {
-// StyledTextLayoutContainer p = (StyledTextLayoutContainer) c.getGraphic();
-// p.getAnnotations().remove(annotation);
-// }
-// }
-// }
+ ObservableList<LineRulerAnnotationPresenter> lineRulerPresenters = FXCollections.observableArrayList();
+ SortedList<LineRulerAnnotationPresenter> sortedLineRulerPresenters = new SortedList<>(lineRulerPresenters, (a, b)->a.getOrder() - b.getOrder());
+ Function<LineRulerAnnotationPresenter, LineRuler> map = (ap) -> {
+ Function<Integer, Set<Annotation>> converter = (index)->
+ lineHelper.getAnnotations(index).stream().filter(ap::isApplicable).collect(Collectors.toSet());
- StyledTextBehavior getBehavior() {
- return this.behavior;
- }
+ Predicate<Set<Annotation>> needsPresentation = ap::isVisible;
+ Supplier<Node> nodeFactory = ap::createNode;
+ BiConsumer<Node, Set<Annotation>> populator = ap::updateNode;
- /**
- * Set up the content viewer
- *
- * @param contentView
- * the content viewer
- */
- protected void initializeContentViewer(ListView<Line> contentView) {
- // this.contentView.getStyleClass().add("styled-text-area");
- // //$NON-NLS-1$
- // listView.setFocusTraversable(false);
- contentView.focusedProperty().addListener(new ChangeListener<Boolean>() {
+ LineRuler flow = new LineRuler(ap.getLayoutHint(), converter, needsPresentation, nodeFactory, populator);
+// VerticalLineFlow<Integer, Annotation> flow = new VerticalLineFlow<Integer, Annotation>(converter, needsPresentation, nodeFactory, populator);
- @Override
- public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
- if (newValue.booleanValue()) {
- getSkinnable().requestFocus();
+ flow.visibleLinesProperty().bind(this.scroller.visibleLinesProperty());
+ flow.numberOfLinesProperty().bind(this.content.numberOfLinesProperty());
+// flow.getModel().bindContent(this.getModel());
+
+ flow.minWidthProperty().bind(ap.getWidth());
+ flow.prefWidthProperty().bind(ap.getWidth());
+
+ flow.prefWidthProperty().addListener((x, o, n)->{
+ System.err.println("width change! " + o + " -> " + n);
+ this.rootContainer.requestLayout();
+ });
+ return flow;
+ };
+
+ sortedLineRulerFlows = FXCollections.observableArrayList();
+ sortedLineRulerFlows.addListener((InvalidationListener)x-> {
+ for (VerticalLineFlow<Integer, Annotation> flow : sortedLineRulerFlows) {
+ flow.getStyleClass().setAll("line-ruler");
+ }
+ });
+ FXBindUtil.uniMapBindList(sortedLineRulerPresenters, sortedLineRulerFlows, map);
+ FXBindUtil.uniMapBindList(sortedLineRulerFlows, lineRulerArea.getChildren(), (flow)->(Node)flow);
+ sortedLineRulerFlows.addListener((ListChangeListener<? super VerticalLineFlow<Integer, Annotation>>)(c)-> {
+ while (c.next()) {
+ if (c.wasRemoved()) {
+ c.getRemoved().forEach((f)-> {
+ f.visibleLinesProperty().unbind();
+ f.numberOfLinesProperty().unbind();
+// f.getModel().unbind();
+ f.prefWidthProperty().unbind();
+ });
}
}
});
- // listView.addEventHandler(KeyEvent.KEY_PRESSED, new
- // EventHandler<KeyEvent>() {
- // });
- contentView.setCellFactory(new Callback<ListView<Line>, ListCell<Line>>() {
- @Override
- public ListCell<Line> call(ListView<Line> arg0) {
- return new LineCell();
+
+
+ Consumer<AnnotationPresenter> installPresenter = (p) -> {
+ if (p instanceof LineRulerAnnotationPresenter) {
+ LineRulerAnnotationPresenter lrp = (LineRulerAnnotationPresenter) p;
+ lineRulerPresenters.add(lrp);
+// installLineRulerAnnotationPresenter.accept(lrp);
+ }
+ else if (p instanceof TextAnnotationPresenter) {
+ TextAnnotationPresenter tp = (TextAnnotationPresenter) p;
+ this.content.textAnnotationPresenterProperty().add(tp);
+ }
+ };
+ Consumer<AnnotationPresenter> uninstallPresenter = (p) -> {
+ if (p instanceof LineRulerAnnotationPresenter) {
+ LineRulerAnnotationPresenter lrp = (LineRulerAnnotationPresenter) p;
+ lineRulerPresenters.remove(lrp);
+ }
+ else if (p instanceof TextAnnotationPresenter) {
+ TextAnnotationPresenter tp = (TextAnnotationPresenter) p;
+ this.content.textAnnotationPresenterProperty().remove(tp);
+ }
+ };
+
+ getSkinnable().getAnnotationPresenter().addListener((SetChangeListener<? super AnnotationPresenter>)(c)-> {
+ if (c.wasAdded()) {
+ installPresenter.accept(c.getElementAdded());
+ }
+ if (c.wasRemoved()) {
+ uninstallPresenter.accept(c.getElementRemoved());
}
});
- contentView.setMinHeight(0);
- contentView.setMinWidth(0);
- // this.contentView.setFixedCellSize(value);
- // this.contentView.setFixedCellSize(15);
+ getSkinnable().getAnnotationPresenter().forEach(installPresenter);
- getBehavior().installContentListeners(contentView);
}
- private StyledTextLayoutContainer currentActiveNode;
+ private <O> void pluginBinding(ObservableValue<O> property, Consumer<O> consumer, List<Subscription> subscriptions) {
+ // init
+ consumer.accept(property.getValue());
+ ChangeListener<? super O> l = (x, o, n)->consumer.accept(n);
+ // bind
+ property.addListener(l);
+ subscriptions.add(()->property.removeListener(l));
+ }
- void updateCurrentCursorNode(StyledTextLayoutContainer node) {
- if (this.currentActiveNode != node) {
- if (this.currentActiveNode != null) {
- this.currentActiveNode.setCaretIndex(-1);
- }
- this.currentActiveNode = node;
+ private void scrollColumnIntoView(int colIndex) {
+
+ double w = 8;
+
+ double curOffset = contentArea.horizontal.getValue();
+ double colOffset = w * colIndex;
+
+ if (curOffset > colOffset) {
+ contentArea.horizontal.setValue(colOffset);
+ }
+
+ double lastColDiff = content.getWidth() - w;
+ if (curOffset + lastColDiff < colOffset) {
+ contentArea.horizontal.setValue(colOffset - lastColDiff);
}
+
}
- /**
- * Refresh the line ruler
- */
- public void refreshLineRuler() {
- this.lineRuler.refresh();
+ private void scrollLineIntoView(int lineIndex) {
+ this.scroller.scrollIntoView(lineIndex);
}
- MyVirtualFlow getFlow() {
- if (this.contentView == null || this.contentView.getSkin() == null) {
- return null;
- }
- return ((MyListViewSkin) this.contentView.getSkin()).getFlow();
+ StyledTextBehavior getBehavior() {
+ return this.behavior;
}
/**
@@ -303,15 +381,17 @@ public class StyledTextSkin extends SkinBase<StyledTextArea> {
* @return the line height
*/
public double getLineHeight(int caretPosition) {
- int lineIndex = getSkinnable().getContent().getLineAtOffset(caretPosition);
- Line lineObject = this.lineList.get(lineIndex);
+ return 16;
- for (LineCell c : getCurrentVisibleCells()) {
- if (c.domainElement == lineObject) {
- return c.getHeight();
- }
- }
- return 0;
+// int lineIndex = getSkinnable().getContent().getLineAtOffset(caretPosition);
+// Line lineObject = (Line) this.lineList.get(lineIndex);
+//
+// for (LineCell c : getCurrentVisibleCells()) {
+// if (c.getDomainElement() == lineObject) {
+// return c.getHeight();
+// }
+// }
+// return 0;
}
/**
@@ -326,18 +406,28 @@ public class StyledTextSkin extends SkinBase<StyledTextArea> {
return null;
}
- int lineIndex = getSkinnable().getContent().getLineAtOffset(caretPosition);
- Line lineObject = this.lineList.get(lineIndex);
- for (LineCell c : getCurrentVisibleCells()) {
- if (c.domainElement == lineObject) {
- StyledTextLayoutContainer b = (StyledTextLayoutContainer) c.getGraphic();
- Point2D careLocation = b.getCareLocation(caretPosition - b.getStartOffset());
- Point2D tmp = getSkinnable().sceneToLocal(b.localToScene(careLocation));
- return new Point2D(tmp.getX(), getSkinnable().sceneToLocal(b.localToScene(0, b.getHeight())).getY());
- }
- }
+ Optional<Point2D> location = this.content.getLocationInScene(caretPosition);
- return null;
+ return location.map(l -> this.rootContainer.sceneToLocal(l))
+ .map(l -> new Point2D(l.getX(), l.getY() + this.content.getLineHeight()))
+ .orElse(null);
+
+
+// int lineIndex = getSkinnable().getContent().getLineAtOffset(caretPosition);
+//
+//// Line lineObject = (Line) this.getModel().get(lineIndex);
+//
+// // TODO =?
+//// for (LineCell c : getCurrentVisibleCells()) {
+//// if (c.getDomainElement() == lineObject) {
+//// StyledTextLayoutContainer b = (StyledTextLayoutContainer) c.getGraphic();
+//// Point2D careLocation = b.getCareLocation(caretPosition - b.getStartOffset());
+//// Point2D tmp = getSkinnable().sceneToLocal(b.localToScene(careLocation));
+//// return new Point2D(tmp.getX(), getSkinnable().sceneToLocal(b.localToScene(0, b.getHeight())).getY());
+//// }
+//// }
+//
+// return null;
}
/**
@@ -348,7 +438,7 @@ public class StyledTextSkin extends SkinBase<StyledTextArea> {
* @return the min height
*/
protected double computeMinHeight(double width) {
- return this.contentView.minHeight(width);
+ return 100; //this.contentView.minHeight(width);
}
/**
@@ -359,556 +449,24 @@ public class StyledTextSkin extends SkinBase<StyledTextArea> {
* @return the min width
*/
protected double computeMinWidth(double height) {
- return this.contentView.minWidth(height);
- }
-
- /**
- * recalculate the line items
- */
- public void recalculateItems() {
- if (this.lineList.size() != getSkinnable().getContent().getLineCount()) {
- if (this.lineList.size() > getSkinnable().getContent().getLineCount()) {
- this.lineList.remove(getSkinnable().getContent().getLineCount(), this.lineList.size());
- } else {
- List<Line> tmp = new ArrayList<>(getSkinnable().getContent().getLineCount() - this.lineList.size());
- for (int i = this.lineList.size(); i < getSkinnable().getContent().getLineCount(); i++) {
- tmp.add(new Line());
- }
- // System.err.println("DOING AN ADD!!!!!");
- this.lineList.addAll(tmp);
- // System.err.println("ADD IS DONE!");
- }
- }
-
- redraw();
- }
-
- /**
- * Redraw the lines
- */
- public void redraw() {
- for (LineCell l : getCurrentVisibleCells()) {
- if (l != null)
- l.update();
- }
- }
-
- public List<LineCell> getCurrentVisibleCells() {
- if (this.contentView == null || this.contentView.getSkin() == null) {
- return Collections.emptyList();
- }
- return ((MyListViewSkin) this.contentView.getSkin()).getFlow().getCells();
+ return 100; //this.contentView.minWidth(height);
}
public void scrollLineUp() {
- MyListViewSkin s = ((MyListViewSkin) this.contentView.getSkin());
-
- LineCell top = s.getFlow().getFirstVisibleCellWithinViewPort();
-
- this.contentView.scrollTo(top.getIndex() - 1);
+ this.scroller.scrollBy(-1);
}
public void scrollLineDown() {
- MyListViewSkin s = ((MyListViewSkin) this.contentView.getSkin());
-
- LineCell top = s.getFlow().getFirstVisibleCellWithinViewPort();
-
- this.contentView.scrollTo(top.getIndex() + 1);
- }
-
- /**
- * A line cell
- */
- public class LineCell extends ListCell<Line> {
- Line domainElement;
- List<Segment> currentSegments;
-
- /**
- * A line cell instance
- */
- public LineCell() {
- getStyleClass().add("styled-text-line"); //$NON-NLS-1$
- }
-
- /**
- * @return the domain element
- */
- public Line getDomainElement() {
- return this.domainElement;
- }
-
- /**
- * Update the item
- */
- public void update() {
- if (this.domainElement != null) {
- updateItem(this.domainElement, false);
- }
- }
-
- /**
- * Update the caret
- */
- public void updateCaret() {
- int caretPosition = getSkinnable().getCaretOffset();
-
- if (caretPosition < 0) {
- return;
- }
- }
-
- @Override
- protected void updateItem(Line arg0, boolean arg1) {
- if (arg0 != null && !arg1) {
- this.domainElement = arg0;
- LineInfo lineInfo = StyledTextSkin.this.lineInfoMap.get(this);
- if (lineInfo == null) {
- lineInfo = new LineInfo();
- lineInfo.setDomainElement(this.domainElement);
- StyledTextSkin.this.lineInfoMap.put(this, lineInfo);
- StyledTextSkin.this.lineRuler.getChildren().add(lineInfo);
- StyledTextSkin.this.lineRuler.requestLayout();
- } else {
- lineInfo.setDomainElement(this.domainElement);
- StyledTextSkin.this.lineRuler.requestLayout();
- }
- lineInfo.setLayoutY(getLayoutY());
-
- StyledTextLayoutContainer block = (StyledTextLayoutContainer) getGraphic();
-
- if (block == null) {
- // System.err.println("CREATING NEW GRAPHIC BLOCK: " + this
- // + " => " + this.domainElement);
- block = new StyledTextLayoutContainer(getSkinnable().focusedProperty());
- block.getStyleClass().add("source-segment-container"); //$NON-NLS-1$
- setGraphic(block);
- // getSkinnable().requestLayout();
- }
- block.setStartOffset(arg0.getLineOffset());
-
- List<Segment> segments = arg0.getSegments();
- if (segments.equals(this.currentSegments)) {
- // System.err.println("EQUAL: " + this.currentSegments + "
- // vs " + segments); //$NON-NLS-1$
- return;
- } else {
- // System.err.println("MODIFIED: " + this.currentSegments +
- // " vs " + segments);
- }
-
- this.currentSegments = segments;
-
- List<@NonNull StyledTextNode> texts = new ArrayList<>();
-
- for (final Segment seg : this.currentSegments) {
- StyledTextNode t = new StyledTextNode(seg.text);
- if (seg.style.stylename != null) {
- if (seg.style.stylename.contains(".")) { //$NON-NLS-1$
- List<String> styles = new ArrayList<String>(Arrays.asList(seg.style.stylename.split("\\."))); //$NON-NLS-1$
- styles.add(0, "source-segment"); //$NON-NLS-1$
- t.getStyleClass().setAll(styles);
- } else {
- t.getStyleClass().setAll("source-segment", seg.style.stylename); //$NON-NLS-1$
- }
-
- } else {
- if (seg.style.foreground != null) {
- t.getStyleClass().setAll("plain-source-segment"); //$NON-NLS-1$
- } else {
- t.getStyleClass().setAll("source-segment"); //$NON-NLS-1$
- }
- }
- texts.add(t);
- }
-
- if (segments.isEmpty()) {
- StyledTextNode t = new StyledTextNode(""); //$NON-NLS-1$
- t.getStyleClass().setAll("source-segment"); //$NON-NLS-1$
- block.getTextNodes().setAll(t);
- } else {
- block.getTextNodes().setAll(texts);
- }
-
- TextSelection selection = getSkinnable().getSelection();
-
- if (selection.length > 0 && block.intersectOffset(selection.offset, selection.offset + selection.length)) {
- int start = Math.max(0, selection.offset - arg0.getLineOffset());
-
- if (arg0.getLineOffset() + arg0.getLineLength() > selection.offset + selection.length) {
- block.setSelection(new TextSelection(start, selection.offset + selection.length - arg0.getLineOffset() - start));
- } else {
- block.setSelection(new TextSelection(start, arg0.getLineLength() - start));
- }
- } else {
- block.setSelection(new TextSelection(0, 0));
- }
-
- if (arg0.getLineOffset() <= getSkinnable().getCaretOffset() && arg0.getLineOffset() + arg0.getText().length() >= getSkinnable().getCaretOffset()) {
- block.setCaretIndex(getSkinnable().getCaretOffset() - arg0.getLineOffset());
- updateCurrentCursorNode(block);
- } else {
- block.setCaretIndex(-1);
- }
- } else {
- // FIND OUT WHY WE CLEAR SO OFTEN
- // System.err.println("CLEARING GRAPHICS: " + this + " => " +
- // this.domainElement);
-
- setGraphic(null);
- this.domainElement = null;
- this.currentSegments = null;
- LineInfo lineInfo = StyledTextSkin.this.lineInfoMap.remove(this);
- if (lineInfo != null) {
- lineInfo.setDomainElement(null);
- }
- }
-
- super.updateItem(arg0, arg1);
- }
- }
-
- static class RegionImpl extends Region {
- public RegionImpl(Node... nodes) {
- getChildren().addAll(nodes);
- }
-
- @Override
- public ObservableList<Node> getChildren() {
- return super.getChildren();
- }
+ this.scroller.scrollBy(1);
}
- /**
- * The line domain object
- */
- public class Line implements StyledTextLine {
- /**
- * @return the current text
- */
- @Override
- public String getText() {
- return removeLineending(getSkinnable().getContent().getLine(StyledTextSkin.this.lineList.indexOf(this)));
- }
-
- @Override
- public int getLineIndex() {
- return StyledTextSkin.this.lineList.indexOf(this);
- }
-
- /**
- * @return the line offset
- */
- public int getLineOffset() {
- int idx = StyledTextSkin.this.lineList.indexOf(this);
- return getSkinnable().getContent().getOffsetAtLine(idx);
- }
-
- /**
- * @return the line length
- */
- public int getLineLength() {
- int idx = StyledTextSkin.this.lineList.indexOf(this);
- String s = getSkinnable().getContent().getLine(idx);
- return s.length();
- }
-
- /**
- * @return the different segments
- */
- @SuppressWarnings("null")
- public List<Segment> getSegments() {
- int idx = StyledTextSkin.this.lineList.indexOf(this);
- List<Segment> segments = new ArrayList<>();
-
- String line = getSkinnable().getContent().getLine(idx);
- if (line != null) {
- int start = getSkinnable().getContent().getOffsetAtLine(idx);
- int length = line.length();
-
- StyleRange[] ranges = getSkinnable().getStyleRanges(start, length, true);
- if (ranges == null) {
- return Collections.emptyList();
- }
-
- if (ranges.length == 0 && line.length() > 0) {
- StyleRange styleRange = new StyleRange((String) null);
- styleRange.start = start;
- styleRange.length = line.length();
-
- Segment seg = new Segment();
- seg.text = removeLineending(line.substring(0, line.length()));
- seg.style = styleRange;
-
- segments.add(seg);
- } else {
- int lastIndex = -1;
-
- if (ranges.length > 0) {
- if (ranges[0].start - start > 0) {
- StyleRange styleRange = new StyleRange((String) null);
- styleRange.start = start;
- styleRange.length = ranges[0].start - start;
-
- Segment seg = new Segment();
- seg.text = removeLineending(line.substring(0, ranges[0].start - start));
- seg.style = styleRange;
-
- segments.add(seg);
- }
- }
-
- for (StyleRange r : ranges) {
- int begin = r.start - start;
- int end = r.start - start + r.length;
-
- if (lastIndex != -1 && lastIndex != begin) {
- StyleRange styleRange = new StyleRange((String) null);
- styleRange.start = start + lastIndex;
- styleRange.length = begin - lastIndex;
-
- Segment seg = new Segment();
- seg.text = removeLineending(line.substring(lastIndex, begin));
- seg.style = styleRange;
-
- segments.add(seg);
- }
-
- Segment seg = new Segment();
- seg.text = removeLineending(line.substring(begin, end));
- seg.style = r;
-
- segments.add(seg);
- lastIndex = end;
- }
-
- if (lastIndex > 0 && lastIndex < line.length()) {
- StyleRange styleRange = new StyleRange((String) null);
- styleRange.start = start + lastIndex;
- styleRange.length = line.length() - lastIndex;
-
- Segment seg = new Segment();
- seg.text = removeLineending(line.substring(lastIndex, line.length()));
- seg.style = styleRange;
-
- segments.add(seg);
- }
- }
- }
-
- return segments;
- }
- }
-
- class Segment {
- public String text;
- public StyleRange style;
-
- @Override
- public String toString() {
- return this.text + " => " + this.style; //$NON-NLS-1$
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + getOuterType().hashCode();
- result = prime * result + ((this.style == null) ? 0 : this.style.hashCode());
- result = prime * result + ((this.text == null) ? 0 : this.text.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- Segment other = (Segment) obj;
- if (!getOuterType().equals(other.getOuterType()))
- return false;
- if (this.style == null) {
- if (other.style != null)
- return false;
- } else if (!this.style.equals(other.style))
- return false;
- if (this.text == null) {
- if (other.text != null)
- return false;
- } else if (!this.text.equals(other.text))
- return false;
- return true;
- }
-
- private StyledTextSkin getOuterType() {
- return StyledTextSkin.this;
- }
- }
static String removeLineending(String s) {
return s.replace("\n", "").replace("\r", ""); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
- class LineInfo extends HBox {
- private Label markerLabel;
- private Label lineText;
- Line line;
-
- public LineInfo() {
- this.markerLabel = new Label();
- this.markerLabel.setPrefWidth(20);
- this.lineText = new Label();
- this.lineText.getStyleClass().add("line-ruler-text"); //$NON-NLS-1$
- this.lineText.setMaxWidth(Double.MAX_VALUE);
- this.lineText.setMaxHeight(Double.MAX_VALUE);
- this.lineText.setAlignment(Pos.CENTER_RIGHT);
- HBox.setHgrow(this.lineText, Priority.ALWAYS);
- getChildren().addAll(this.markerLabel, this.lineText);
- }
-
- public void setDomainElement(Line line) {
- if (line == null) {
- this.line = null;
- setManaged(false);
- StyledTextSkin.this.rootContainer.requestLayout();
- } else {
- if (line != this.line) {
- this.line = line;
- calculateContent();
- }
- }
- }
-
- public void calculateContent() {
- Line line = this.line;
- if (line != null) {
- setManaged(true);
- String newText = this.line.getLineIndex() + 1 + ""; //$NON-NLS-1$
- String oldText = this.lineText.getText();
- if (oldText == null) {
- oldText = ""; //$NON-NLS-1$
- }
- this.lineText.setText(newText);
- this.markerLabel.setGraphic(getSkinnable().getLineRulerGraphicNodeFactory().call(line));
- if (newText.length() != oldText.length()) {
- StyledTextSkin.this.rootContainer.requestLayout();
- }
- StyledTextSkin.this.lineRuler.layout();
- }
- }
- }
-
- class LineRuler extends Pane {
- boolean skipRelayout;
-
- @Override
- protected void layoutChildren() {
- if (this.skipRelayout) {
- return;
- }
- super.layoutChildren();
- Set<Node> children = new HashSet<Node>(getChildren());
- List<LineInfo> layouted = new ArrayList<>();
- double maxWidth = 0;
- for (LineCell c : getCurrentVisibleCells()) {
- if (c.isVisible()) {
- LineInfo lineInfo = StyledTextSkin.this.lineInfoMap.get(c);
- if (lineInfo != null) {
- layouted.add(lineInfo);
- maxWidth = Math.max(maxWidth, lineInfo.getWidth());
- lineInfo.relocate(0, c.getLayoutY());
- lineInfo.resize(lineInfo.getWidth(), c.getHeight());
- lineInfo.setVisible(true);
- children.remove(lineInfo);
- }
- }
- }
-
- for (LineInfo l : layouted) {
- l.resize(maxWidth, l.getHeight());
- }
-
- List<Node> toRemove = new ArrayList<>();
- for (Node n : children) {
- if (n instanceof LineInfo) {
- LineInfo l = (LineInfo) n;
- if (l.line == null) {
- toRemove.add(l);
- }
- }
- n.setVisible(false);
- }
-
- getChildren().removeAll(toRemove);
- }
-
- public void refresh() {
- new ArrayList<>(getChildren()).stream().filter(n -> n instanceof LineInfo).forEach(n -> ((LineInfo) n).calculateContent());
- }
- }
-
- class MyListViewSkin extends ListViewSkin<Line> {
- private MyVirtualFlow flow;
-
- public MyListViewSkin(ListView<Line> listView) {
- super(listView);
- }
-
- public MyVirtualFlow getFlow() {
- return this.flow;
- }
-
- @SuppressWarnings("unchecked")
- @Override
- protected VirtualFlow<ListCell<Line>> createVirtualFlow() {
- this.flow = new MyVirtualFlow();
- return (VirtualFlow<ListCell<Line>>) ((VirtualFlow<?>) this.flow);
- }
-
- }
-
- class MyVirtualFlow extends VirtualFlow<LineCell> {
- public MyVirtualFlow() {
- }
-
- @Override
- protected void positionCell(LineCell cell, double position) {
- super.positionCell(cell, position);
- LineInfo lineInfo = StyledTextSkin.this.lineInfoMap.get(cell);
- if (lineInfo != null) {
- lineInfo.setDomainElement(cell.domainElement);
- lineInfo.setLayoutY(cell.getLayoutY());
- }
- Platform.runLater(new Runnable() {
-
- @Override
- public void run() {
- StyledTextSkin.this.lineRuler.requestLayout();
- }
- });
-
- }
-
- @Override
- public List<LineCell> getCells() {
- return super.getCells();
- }
-
- @Override
- public void rebuildCells() {
- StyledTextSkin.this.lineRuler.skipRelayout = true;
- Platform.runLater(new Runnable() {
-
- @Override
- public void run() {
- StyledTextSkin.this.lineRuler.skipRelayout = false;
- StyledTextSkin.this.lineRuler.requestLayout();
- }
- });
- super.rebuildCells();
- }
+ public int getOffsetAtPosition(double x, double y) {
+ return content.getLineIndex(new Point2D(x, y)).orElse(-1);
}
} \ No newline at end of file
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/styledtextarea.css b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/styledtextarea.css
index fa0ba875d..65c987e6f 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/styledtextarea.css
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/styledtextarea.css
@@ -1,19 +1,30 @@
.styled-text-area .source-segment {
- -fx-font-family: Courier;
- -fx-font-size: 13;
+ -fx-font-family: Monospace;
+ -fx-font-size: 12;
/* @SuppressWarning */
-styled-text-color: black;
-fx-fill: -styled-text-color;
}
+.styled-text-area .line-ruler {
+ -fx-background: white;
+}
+
+.styled-text-area .line-ruler.spacer {
+ -fx-border-width: 0 0.1 0 0;
+ -fx-border-color: gray;
+ -fx-border-style: solid;
+}
+
.styled-text-area .plain-source-segment {
- -fx-font-family: Courier;
- -fx-font-size: 13;
+ -fx-font-family: Monospace;
+ -fx-font-size: 12;
}
.styled-text-area .line-ruler-text {
- -fx-font-family: Courier;
- -fx-font-size: 13;
+ -fx-font-family: Monospace;
+ -fx-font-size: 12;
+ -fx-fill: gray;
}
/* FIXME: Fixes layouting issues with ListView */
@@ -37,6 +48,10 @@
-fx-background-color: transparent;
}
+.styled-text-area .current-line {
+ -fx-background-color: #e8f2fe
+}
+
.styled-text-area .list-cell {
-fx-padding: 0 0 0 5;
}

Back to the top