Skip to content

Commit a17290d

Browse files
committed
Optimize semantic view editor scrolling event handler
1 parent 7c599bc commit a17290d

File tree

7 files changed

+91
-37
lines changed

7 files changed

+91
-37
lines changed

dist/main.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/main.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/main/atom/views/outline/navTreeUtils.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@ import {isEqual} from "lodash"
22
import {NavigationTree} from "typescript/lib/protocol"
33
import {NavigationTreeViewModel} from "./semanticViewModel"
44

5+
function getElStartLine(elem: HTMLElement): number {
6+
// tslint:disable-next-line: no-string-literal
7+
const v = elem.dataset["startLine"]
8+
return v !== undefined ? parseInt(v, 10) - 1 : 0
9+
}
10+
11+
function getElEndLine(elem: HTMLElement): number {
12+
// tslint:disable-next-line: no-string-literal
13+
const v = elem.dataset["endLine"]
14+
return v !== undefined ? parseInt(v, 10) - 1 : 0
15+
}
16+
517
/**
618
* HELPER find the node that is "furthest down" the
719
* node hiearchy, i.e. which's start-, end-position contains the
@@ -17,15 +29,16 @@ import {NavigationTreeViewModel} from "./semanticViewModel"
1729
export function findNodeAt(
1830
startLine: number,
1931
endLine: number,
20-
node: NavigationTreeViewModel,
21-
): NavigationTreeViewModel | undefined {
22-
if (!node.childItems) {
32+
node: HTMLLIElement,
33+
): HTMLLIElement | undefined {
34+
const children = node.querySelectorAll<HTMLLIElement>(":scope > ol > li.node")
35+
if (children.length === 0) {
2336
return undefined
2437
}
2538

26-
for (const elem of node.childItems) {
27-
const start: number = getNodeStartLine(elem)
28-
const end: number = getNodeEndLine(elem)
39+
for (const elem of Array.from(children)) {
40+
const start: number = getElStartLine(elem)
41+
const end: number = getElEndLine(elem)
2942
if (isFinite(start) && isFinite(end)) {
3043
if (startLine >= start && endLine <= end) {
3144
const selected = findNodeAt(startLine, endLine, elem)
@@ -46,8 +59,8 @@ export function findNodeAt(
4659
}
4760
}
4861

49-
const nstart: number = getNodeStartLine(node)
50-
const nend: number = getNodeEndLine(node)
62+
const nstart: number = getElStartLine(node)
63+
const nend: number = getElEndLine(node)
5164
if (isFinite(nstart) && isFinite(nend) && startLine >= nstart && endLine <= nend) {
5265
return node
5366
}
@@ -182,10 +195,10 @@ export function prepareNavTree(navTree: NavigationTreeViewModel | null): void {
182195
* the cursor (line) position in the editor
183196
* @return {Boolean} true, if the node's HTML representation should be selected
184197
*/
185-
export function isSelected(node: NavigationTreeViewModel, pos: number): boolean {
186-
if (getNodeStartLine(node) <= pos && getNodeEndLine(node) >= pos) {
187-
const start: number = getNodeStartLine(node)
188-
const end: number = getNodeEndLine(node)
198+
export function isSelected(node: HTMLLIElement, pos: number): boolean {
199+
const start: number = getElStartLine(node)
200+
const end: number = getElEndLine(node)
201+
if (start <= pos && end >= pos) {
189202
if (findNodeAt(start, end, node)) {
190203
// -> there is a node "further down" that should get selected
191204
return false

lib/main/atom/views/outline/navigationNodeComponent.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as etch from "etch"
22
import {handlePromise} from "../../../../utils"
3-
import {isSameNode, isToggleEntry} from "./navTreeUtils"
3+
import {isToggleEntry} from "./navTreeUtils"
44
import {NavigationTreeViewModel, SelectableNode, ToNodeScrollableEditor} from "./semanticViewModel"
55

66
export interface Props extends JSX.Props {
@@ -25,18 +25,18 @@ export class NavigationNodeComponent implements JSX.ElementClass {
2525
public render(): JSX.Element {
2626
const node = this.props.navTree
2727
const {ctrl} = this.props
28-
const selectedNode = ctrl.getSelectedNode()
29-
const selected = selectedNode && isSameNode(node, selectedNode)
3028
const classes =
31-
(node.childItems ? "nested-" : "") +
32-
"item" +
33-
(node.collapsed ? " collapsed" : " expanded") +
34-
(selected ? " selected" : "")
29+
(node.childItems ? "nested-" : "") + "item" + (node.collapsed ? " collapsed" : " expanded")
3530
const styleClasses = this.getStyles()
3631

3732
return (
38-
<li className={"node entry exanded list-" + classes}>
39-
<div className="header list-item" on={{click: (event) => this.entryClicked(event, node)}}>
33+
<li
34+
className={"node entry exanded list-" + classes}
35+
dataset={{
36+
startLine: this.props.navTree.spans[0]?.start?.line,
37+
endLine: this.props.navTree.spans[0]?.end?.line,
38+
}}>
39+
<div className={`header list-item`} on={{click: (event) => this.entryClicked(event, node)}}>
4040
<span className={styleClasses}>{node.text}</span>
4141
</div>
4242
<ol className="entries list-tree">

lib/main/atom/views/outline/navigationTreeComponent.tsx

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import * as etch from "etch"
33
import {isEqual} from "lodash"
44
import {NavigationTree} from "typescript/lib/protocol"
55
import {GetClientFunction} from "../../../../client"
6-
import {handlePromise} from "../../../../utils"
76
import * as atomUtils from "../../utils"
87
import {NavigationNodeComponent} from "./navigationNodeComponent"
98
import {
@@ -25,7 +24,6 @@ export class NavigationTreeComponent
2524
private editor?: TextEditor
2625
private editorScrolling?: Disposable
2726
private editorChanging?: Disposable
28-
private selectedNode?: NavigationTreeViewModel
2927
private getClient?: GetClientFunction
3028
private subscriptions = new CompositeDisposable()
3129

@@ -48,7 +46,6 @@ export class NavigationTreeComponent
4846
if (this.editorChanging) this.editorChanging.dispose()
4947
this.editorScrolling = undefined
5048
this.editorChanging = undefined
51-
this.selectedNode = undefined
5249
this.subscriptions.dispose()
5350
await etch.destroy(this)
5451
}
@@ -59,7 +56,25 @@ export class NavigationTreeComponent
5956
}
6057

6158
public getSelectedNode() {
62-
return this.selectedNode
59+
return this.element.querySelector<HTMLElement>(".header.selected") ?? undefined
60+
}
61+
62+
public clearSelection() {
63+
const elems = this.element.querySelectorAll<HTMLElement>(".header.selected")
64+
for (let i = 0; i < elems.length; i += 1) {
65+
elems.item(i).classList.remove("selected")
66+
}
67+
}
68+
69+
public markSelection(node?: HTMLLIElement) {
70+
this.clearSelection()
71+
if (!node) return
72+
const h = node.querySelector<HTMLElement>(".header")
73+
if (h) h.classList.add("selected")
74+
}
75+
76+
public firstNode() {
77+
return this.element.querySelector<HTMLLIElement>("li.node") ?? undefined
6378
}
6479

6580
public render() {
@@ -106,14 +121,13 @@ export class NavigationTreeComponent
106121
restoreCollapsed(navTree, this.props.navTree)
107122
this.props.navTree = navTree
108123

109-
let selectedNode: NavigationTreeViewModel | undefined
110-
if (navTree) {
124+
const node = this.firstNode()
125+
if (node) {
111126
const cursorLine = this.getCursorLine()
112127
if (cursorLine !== undefined) {
113-
selectedNode = findNodeAt(cursorLine, cursorLine, navTree)
128+
this.markSelection(findNodeAt(cursorLine, cursorLine, node))
114129
}
115130
}
116-
this.selectedNode = selectedNode
117131
}
118132

119133
private loadNavTree = async () => {
@@ -139,15 +153,18 @@ export class NavigationTreeComponent
139153
* current cursor position
140154
*/
141155
private selectAtCursorLine = ({newBufferPosition}: CursorPositionChangedEvent) => {
142-
if (!this.props.navTree) {
156+
const firstNodeElem = this.firstNode()
157+
if (!firstNodeElem) {
143158
return
144159
}
145160
const cursorLine = newBufferPosition.row
146161

147-
const selectedChild = findNodeAt(cursorLine, cursorLine, this.props.navTree)
148-
if (selectedChild !== this.selectedNode) {
149-
this.selectedNode = selectedChild
150-
handlePromise(etch.update(this))
162+
const selectedChild =
163+
findNodeAt(cursorLine, cursorLine, firstNodeElem)?.querySelector(".header") ?? undefined
164+
const currentSelection = this.getSelectedNode()
165+
if (selectedChild !== currentSelection) {
166+
if (currentSelection) currentSelection.classList.remove("selected")
167+
if (selectedChild) selectedChild.classList.add("selected")
151168
}
152169
}
153170

lib/main/atom/views/outline/semanticViewModel.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ export interface ToNodeScrollableEditor {
2525
}
2626

2727
export interface SelectableNode {
28-
getSelectedNode(): NavigationTreeViewModel | undefined
28+
getSelectedNode(): HTMLElement | undefined
2929
}

styles/semantic-view.less

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,30 @@
3636
.atomts-semantic-view {
3737
overflow-y: auto;
3838

39+
div.header {
40+
position: initial;
41+
}
42+
43+
div.header.selected {
44+
color: @text-color-selected;
45+
& > span {
46+
z-index: 1;
47+
}
48+
&::before {
49+
z-index: 1;
50+
}
51+
&::after {
52+
position: absolute;
53+
left: 0;
54+
right: 0;
55+
padding-top: 2em;
56+
margin-top: -2em;
57+
background-color: @background-color-selected;
58+
display: block;
59+
content: "";
60+
}
61+
}
62+
3963
// @syntax-color-variable
4064
// @syntax-color-constant
4165
// @syntax-color-property

0 commit comments

Comments
 (0)