Skip to content

Commit

Permalink
feat: add support for multiple source highlighting
Browse files Browse the repository at this point in the history
Close #96
  • Loading branch information
javier-godoy committed Jun 26, 2024
1 parent b37ff72 commit f0a4365
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 24 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,18 +166,21 @@ Strictly, the constructor of `SourceCodeViewer` receives a map with arbitrary va
This feature supports highlighting a source code fragment in order to emphasize a section of the code snippet.
The highlighted fragment is automatically scrolled into view.

A fragment is highlighted either by calling `SourceCodeViewer.highlight(name)` or when hovering a component that has been configured with `SourceCodeViewer.highlightOnHover(component,name)`, where `name` is the name of the fragment. `SourceCodeViewer.highlight(null)` turn off the highlighting.
`SourceCodeViewer.highlightOnClick` allows configuring a click listener that turns highlight on.
A fragment is highlighted either by calling `SourceCodeViewer.highlight(filenameAndId)` or when clicking/hovering a component that has been configured with `SourceCodeViewer.highlightOnClick` or `SourceCodeViewer.highlightOnHover`, where `filenameAndId` is the name of the fragment. If the component is in an additional source file, `filenameAndId` can be given as a string in the format `filename#id`. If no `'#'` is present, it is assumed that the identifier corresponds to a block in the first source panel. `SourceCodeViewer.highlight(null)` turns off the highlighting.

In the source code, a fragment is delimited by `// begin-block name` and `// end-block` comments. Nested fragments are not supported.
In the source code, a fragment is delimited by `// begin-block filenameAndId` and `// end-block` comments. Nested fragments are not supported.
The `// begin-block` and `// end-block` comments are removed after post-processing.

```
// begin-block first
Div first = new Div(new Text("First"));
Div first = new Div(new Text("Highlight block in first panel"));
SourceCodeViewer.highlightOnHover(first, "first");
add(first);
// end-block
Div other = new Div(new Text("Highlight additional source"));
SourceCodeViewer.highlightOnHover(other, "AdditionalSource.java#other");
add(other);
```

<!-- FROM https://github.com/FlowingCode/CommonsDemo/pull/62 -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.vaadin.flow.component.tabs.Tabs;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class MultiSourceCodeViewer extends Div {

Expand All @@ -15,13 +16,21 @@ public class MultiSourceCodeViewer extends Div {

private SourceCodeViewer codeViewer;
private Tab selectedTab;
private Tabs tabs;

public MultiSourceCodeViewer(List<SourceCodeTab> sourceCodeTabs, Map<String, String> properties) {
if (sourceCodeTabs.size() > 1) {
Tabs tabs = new Tabs(createTabs(sourceCodeTabs));
tabs = new Tabs(createTabs(sourceCodeTabs));
tabs.addSelectedChangeListener(ev -> onTabSelected(ev.getSelectedTab()));
add(tabs);
selectedTab = tabs.getSelectedTab();

getElement().addEventListener("fragment-request", ev -> {
String filename = ev.getEventData().get("event.detail.filename").asString();
findTabWithFilename(filename).ifPresent(tab -> {
tabs.setSelectedTab(tab);
});
}).addEventData("event.detail.filename");
} else {
selectedTab = createTab(sourceCodeTabs.get(0));
}
Expand Down Expand Up @@ -89,7 +98,7 @@ private String getExtension(String filename) {
}

private void onTabSelected(Tab tab) {
this.selectedTab = tab;
selectedTab = tab;

String url = (String) ComponentUtil.getData(tab, DATA_URL);
String language = (String) ComponentUtil.getData(tab, DATA_LANGUAGE);
Expand All @@ -104,4 +113,15 @@ public SourcePosition getSourcePosition() {
return (SourcePosition) ComponentUtil.getData(selectedTab, DATA_POSITION);
}

private Optional<Tab> findTabWithFilename(String filename) {
if (tabs != null) {
return tabs.getChildren().filter(Tab.class::isInstance).map(Tab.class::cast).filter(tab -> {
String url = (String) ComponentUtil.getData(tab, DATA_URL);
return filename == null || getFilename(url).equals(filename);
}).findFirst();
} else {
return Optional.empty();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,54 @@ private void setProperties(Map<String, String> properties) {
}
}

public static void highlightOnHover(Component c, String id) {
/**
* Highlights the block identified by {@code filenameAndId} when the component is hovered over.
* <p>
* If the component is in an additional source, {@code filenameAndId} can be given as a string in
* the format {@code filename#id}. If no {@code '#'} is present, it is assumed that the identifier
* corresponds to a block in the first source panel.
*
* @param c The component that triggers the highlight action when hovered over.
* @param filenameAndId The identifier string that combines filename and id separated by
* {@code '#'}.
*/
public static void highlightOnHover(Component c, String filenameAndId) {
c.addAttachListener(ev -> {
c.getElement().executeJs("Vaadin.Flow.fcCodeViewerConnector.highlightOnHover(this,$0)", id);
c.getElement().executeJs("Vaadin.Flow.fcCodeViewerConnector.highlightOnHover(this,$0)", filenameAndId);
});
}

public static <T extends HasElement & ClickNotifier<?>> void highlightOnClick(T c, String id) {
/**
* Highlight block {@code id} when the component is clicked.
* <p>
* If the component is in an additional source, {@code filenameAndId} can be given as a string in
* the format {@code filename#id}. If no {@code '#'} is present, it is assumed that the identifier
* corresponds to a block in the first source panel.
*
* @param c The component that triggers the highlight action when clicked.
* @param filenameAndId The identifier string that combines filename and id separated by
* {@code '#'}.
*/
public static <T extends HasElement & ClickNotifier<?>> void highlightOnClick(T c,
String filenameAndId) {
c.addClickListener(ev -> {
c.getElement().executeJs("Vaadin.Flow.fcCodeViewerConnector.highlight($0)", id);
c.getElement().executeJs("Vaadin.Flow.fcCodeViewerConnector.highlight($0)", filenameAndId);
});
}

public static void highlight(String id) {
UI.getCurrent().getElement().executeJs("Vaadin.Flow.fcCodeViewerConnector.highlight($0)", id);
/**
* Highlights the block identified by {@code filenameAndId}.
* <p>
* If the component is in an additional source, {@code filenameAndId} can be given as a string in
* the format {@code filename#id}. If no {@code '#'} is present, it is assumed that the identifier
* corresponds to a block in the first source panel.
*
* @param filenameAndId The identifier string that combines filename and id separated by
* {@code '#'}.
*/

public static void highlight(String filenameAndId) {
UI.getCurrent().getElement().executeJs("Vaadin.Flow.fcCodeViewerConnector.highlight($0)", filenameAndId);
}

}
30 changes: 24 additions & 6 deletions src/main/resources/META-INF/resources/frontend/code-viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* #%L
* Commons Demo
* %%
* Copyright (C) 2020 - 2023 Flowing Code
* Copyright (C) 2020 - 2024 Flowing Code
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -51,6 +51,8 @@ export class CodeViewer extends LitElement {

private __license : Element[] = [];

private __highlightedBlock : string | null = null;

env: any = {};

createRenderRoot() {
Expand Down Expand Up @@ -234,6 +236,7 @@ pre[class*="language-"] {
(window as any).Prism.highlightAllUnder(self);
self.__license.reverse().forEach(e=>self.querySelector('pre code')?.prepend(e));
self.process(code);
self._highlight(self.__highlightedBlock, false);
}};
xhr.open('GET', sourceUrl, true);
xhr.send();
Expand Down Expand Up @@ -435,20 +438,35 @@ pre[class*="language-"] {
}

/** @deprecated Use highlight(id: string|null) instead */
highligth(id:string|null) {
this.highlight(id);
highligth(filenameAndId:string|null) {
this.highlight(filenameAndId);
}

//highlight a marked block
highlight(id:string|null) {
highlight(filenameAndId:string|null) {
this._highlight(filenameAndId, true);
}

//highlight a marked block. If request is true, dispatch a fragment request if the block is not found.
private _highlight(filenameAndId:string|null, request:boolean) {
this.__highlightedBlock=filenameAndId;
const div = this.querySelector('.highlight') as HTMLElement;

div.style.removeProperty('top');
div.style.removeProperty('height');
if (id!==null) {
if (filenameAndId!==null) {

var ss = filenameAndId!.split('#',2);
var id = ss!.pop();
var filename = ss!.pop();

var begin = this.querySelector('.begin-'+id) as HTMLElement;
var end = this.querySelector('.end-'+id) as HTMLElement;
if (begin && end && begin.offsetTop<=end.offsetTop) {
if (!begin || !end) {
if (request) {
this.dispatchEvent(new CustomEvent('fragment-request', {bubbles: true, detail: {filename}}));
}
} else if (begin.offsetTop<=end.offsetTop) {
var top = begin.offsetTop;
var height = end.offsetTop+end.offsetHeight-top;
div.style.top= `calc( ${top}px + 0.75em)`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.flowingcode.vaadin.addons.demo;

public class AdditionalSources {
// begin-block fragment
// this class has more sources
// end-block fragment
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,34 @@
*/
package com.flowingcode.vaadin.addons.demo;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.dependency.StyleSheet;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

@Route(value = "demo/multisource", layout = Demo.class)
@PageTitle("Demo with multiple sources")
// show-source @DemoSource
// show-source @DemoSource(clazz = AdditionalSources.class)
// show-source @DemoSource("/src/test/resources/META-INF/resources/frontend/multi-source-demo.css")
// show-source @DemoSource(clazz = Demo.class)
@DemoSource
@DemoSource(clazz = AdditionalSources.class)
@DemoSource("/src/test/resources/META-INF/resources/frontend/multi-source-demo.css")
@DemoSource(clazz = Demo.class)
@StyleSheet("./multi-source-demo.css")
public class MultiSourceDemo extends Div {
public MultiSourceDemo() {
Span span = new Span("This is the main source");
span.addClassName("custom-style");
add(span);

// begin-block main
Div div = new Div("This is the main source");
div.addClassName("custom-style");
SourceCodeViewer.highlightOnHover(div, "main");
add(div);
// end-block

Button button1 = new Button("Highlight code in AdditionalSources");
SourceCodeViewer.highlightOnClick(button1, "AdditionalSources.java#fragment");
add(button1);
}

}

0 comments on commit f0a4365

Please sign in to comment.