Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/emmet/src/selectItemHTML.ts
4772 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as vscode from 'vscode';
7
import { getDeepestFlatNode, findNextWord, findPrevWord, getHtmlFlatNode, offsetRangeToSelection } from './util';
8
import { HtmlNode } from 'EmmetFlatNode';
9
10
export function nextItemHTML(document: vscode.TextDocument, selectionStart: vscode.Position, selectionEnd: vscode.Position, rootNode: HtmlNode): vscode.Selection | undefined {
11
const selectionEndOffset = document.offsetAt(selectionEnd);
12
let currentNode = getHtmlFlatNode(document.getText(), rootNode, selectionEndOffset, false);
13
let nextNode: HtmlNode | undefined = undefined;
14
15
if (!currentNode) {
16
return;
17
}
18
19
if (currentNode.type !== 'comment') {
20
// If cursor is in the tag name, select tag
21
if (currentNode.open &&
22
selectionEndOffset <= currentNode.open.start + currentNode.name.length) {
23
return getSelectionFromNode(document, currentNode);
24
}
25
26
// If cursor is in the open tag, look for attributes
27
if (currentNode.open &&
28
selectionEndOffset < currentNode.open.end) {
29
const selectionStartOffset = document.offsetAt(selectionStart);
30
const attrSelection = getNextAttribute(document, selectionStartOffset, selectionEndOffset, currentNode);
31
if (attrSelection) {
32
return attrSelection;
33
}
34
}
35
36
// Get the first child of current node which is right after the cursor and is not a comment
37
nextNode = currentNode.firstChild;
38
while (nextNode && (selectionEndOffset >= nextNode.end || nextNode.type === 'comment')) {
39
nextNode = nextNode.nextSibling;
40
}
41
}
42
43
// Get next sibling of current node which is not a comment. If none is found try the same on the parent
44
while (!nextNode && currentNode) {
45
if (currentNode.nextSibling) {
46
if (currentNode.nextSibling.type !== 'comment') {
47
nextNode = currentNode.nextSibling;
48
} else {
49
currentNode = currentNode.nextSibling;
50
}
51
} else {
52
currentNode = currentNode.parent;
53
}
54
}
55
56
return nextNode && getSelectionFromNode(document, nextNode);
57
}
58
59
export function prevItemHTML(document: vscode.TextDocument, selectionStart: vscode.Position, selectionEnd: vscode.Position, rootNode: HtmlNode): vscode.Selection | undefined {
60
const selectionStartOffset = document.offsetAt(selectionStart);
61
let currentNode = getHtmlFlatNode(document.getText(), rootNode, selectionStartOffset, false);
62
let prevNode: HtmlNode | undefined = undefined;
63
64
if (!currentNode) {
65
return;
66
}
67
68
const selectionEndOffset = document.offsetAt(selectionEnd);
69
if (currentNode.open &&
70
currentNode.type !== 'comment' &&
71
selectionStartOffset - 1 > currentNode.open.start) {
72
if (selectionStartOffset < currentNode.open.end || !currentNode.firstChild || selectionEndOffset <= currentNode.firstChild.start) {
73
prevNode = currentNode;
74
} else {
75
// Select the child that appears just before the cursor and is not a comment
76
prevNode = currentNode.firstChild;
77
let oldOption: HtmlNode | undefined = undefined;
78
while (prevNode.nextSibling && selectionStartOffset >= prevNode.nextSibling.end) {
79
if (prevNode && prevNode.type !== 'comment') {
80
oldOption = prevNode;
81
}
82
prevNode = prevNode.nextSibling;
83
}
84
85
prevNode = <HtmlNode>getDeepestFlatNode((prevNode && prevNode.type !== 'comment') ? prevNode : oldOption);
86
}
87
}
88
89
// Select previous sibling which is not a comment. If none found, then select parent
90
while (!prevNode && currentNode) {
91
if (currentNode.previousSibling) {
92
if (currentNode.previousSibling.type !== 'comment') {
93
prevNode = <HtmlNode>getDeepestFlatNode(currentNode.previousSibling);
94
} else {
95
currentNode = currentNode.previousSibling;
96
}
97
} else {
98
prevNode = currentNode.parent;
99
}
100
101
}
102
103
if (!prevNode) {
104
return undefined;
105
}
106
107
const attrSelection = getPrevAttribute(document, selectionStartOffset, selectionEndOffset, prevNode);
108
return attrSelection ? attrSelection : getSelectionFromNode(document, prevNode);
109
}
110
111
function getSelectionFromNode(document: vscode.TextDocument, node: HtmlNode): vscode.Selection | undefined {
112
if (node && node.open) {
113
const selectionStart = node.open.start + 1;
114
const selectionEnd = selectionStart + node.name.length;
115
return offsetRangeToSelection(document, selectionStart, selectionEnd);
116
}
117
return undefined;
118
}
119
120
function getNextAttribute(document: vscode.TextDocument, selectionStart: number, selectionEnd: number, node: HtmlNode): vscode.Selection | undefined {
121
if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {
122
return;
123
}
124
125
for (const attr of node.attributes) {
126
if (selectionEnd < attr.start) {
127
// select full attr
128
return offsetRangeToSelection(document, attr.start, attr.end);
129
}
130
131
if (!attr.value || attr.value.start === attr.value.end) {
132
// No attr value to select
133
continue;
134
}
135
136
if ((selectionStart === attr.start && selectionEnd === attr.end) ||
137
selectionEnd < attr.value.start) {
138
// cursor is in attr name, so select full attr value
139
return offsetRangeToSelection(document, attr.value.start, attr.value.end);
140
}
141
142
// Fetch the next word in the attr value
143
if (!attr.value.toString().includes(' ')) {
144
// attr value does not have space, so no next word to find
145
continue;
146
}
147
148
let pos: number | undefined = undefined;
149
if (selectionStart === attr.value.start && selectionEnd === attr.value.end) {
150
pos = -1;
151
}
152
if (pos === undefined && selectionEnd < attr.end) {
153
const selectionEndCharacter = document.positionAt(selectionEnd).character;
154
const attrValueStartCharacter = document.positionAt(attr.value.start).character;
155
pos = selectionEndCharacter - attrValueStartCharacter - 1;
156
}
157
158
if (pos !== undefined) {
159
const [newSelectionStartOffset, newSelectionEndOffset] = findNextWord(attr.value.toString(), pos);
160
if (newSelectionStartOffset === undefined || newSelectionEndOffset === undefined) {
161
return;
162
}
163
if (newSelectionStartOffset >= 0 && newSelectionEndOffset >= 0) {
164
const newSelectionStart = attr.value.start + newSelectionStartOffset;
165
const newSelectionEnd = attr.value.start + newSelectionEndOffset;
166
return offsetRangeToSelection(document, newSelectionStart, newSelectionEnd);
167
}
168
}
169
170
}
171
172
return;
173
}
174
175
function getPrevAttribute(document: vscode.TextDocument, selectionStart: number, selectionEnd: number, node: HtmlNode): vscode.Selection | undefined {
176
if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {
177
return;
178
}
179
180
for (let i = node.attributes.length - 1; i >= 0; i--) {
181
const attr = node.attributes[i];
182
if (selectionStart <= attr.start) {
183
continue;
184
}
185
186
if (!attr.value || attr.value.start === attr.value.end || selectionStart < attr.value.start) {
187
// select full attr
188
return offsetRangeToSelection(document, attr.start, attr.end);
189
}
190
191
if (selectionStart === attr.value.start) {
192
if (selectionEnd >= attr.value.end) {
193
// select full attr
194
return offsetRangeToSelection(document, attr.start, attr.end);
195
}
196
// select attr value
197
return offsetRangeToSelection(document, attr.value.start, attr.value.end);
198
}
199
200
// Fetch the prev word in the attr value
201
const selectionStartCharacter = document.positionAt(selectionStart).character;
202
const attrValueStartCharacter = document.positionAt(attr.value.start).character;
203
const pos = selectionStart > attr.value.end ? attr.value.toString().length :
204
selectionStartCharacter - attrValueStartCharacter;
205
const [newSelectionStartOffset, newSelectionEndOffset] = findPrevWord(attr.value.toString(), pos);
206
if (newSelectionStartOffset === undefined || newSelectionEndOffset === undefined) {
207
return;
208
}
209
if (newSelectionStartOffset >= 0 && newSelectionEndOffset >= 0) {
210
const newSelectionStart = attr.value.start + newSelectionStartOffset;
211
const newSelectionEnd = attr.value.start + newSelectionEndOffset;
212
return offsetRangeToSelection(document, newSelectionStart, newSelectionEnd);
213
}
214
}
215
216
return;
217
}
218
219