1
- import { range , zip } from "lodash" ;
2
1
import { ensureSingleTarget } from "../../util/targetUtils" ;
3
2
4
3
import { open } from "fs/promises" ;
5
4
import { join } from "path" ;
6
- import { commands , window , workspace } from "vscode" ;
7
- import { performEditsAndUpdateSelections } from "../../core/updateSelections/updateSelections " ;
5
+ import { commands , Range , window , workspace } from "vscode" ;
6
+ import { Offsets } from "../../processTargets/modifiers/surroundingPair/types " ;
8
7
import { Target } from "../../typings/target.types" ;
9
8
import { Graph } from "../../typings/Types" ;
10
- import { performDocumentEdits } from "../../util/performDocumentEdits" ;
11
9
import { Action , ActionReturnValue } from "../actions.types" ;
12
- import Substituter from "./Substituter" ;
13
10
import { constructSnippetBody } from "./constructSnippetBody" ;
11
+ import { editText } from "./editText" ;
12
+ import Substituter from "./Substituter" ;
14
13
15
14
interface Variable {
15
+ /**
16
+ * The start an end offsets of the variable relative to the text of the
17
+ * snippet that contains it
18
+ */
19
+ offsets : Offsets ;
20
+
16
21
/**
17
22
* The default name for the given variable that will appear as the placeholder
18
23
* text in the meta snippet
@@ -42,12 +47,13 @@ interface Variable {
42
47
*
43
48
* 1. Ask user for snippet name if not provided as arg
44
49
* 2. Find all cursor selections inside target
45
- * 3. Replace cursor selections in document with random ids that won't be
50
+ * 3. Extract text of target
51
+ * 4. Replace cursor selections in text with random ids that won't be
46
52
* affected by json serialization. After serialization we'll replace these
47
- * id's by snippet placeholders. Note that this modifies the document, so we
48
- * need to reset later.
49
- * 4 . Construct a snippet body as a list of strings
50
- * 5. Construct a snippet as a javascript object
53
+ * id's by snippet placeholders.
54
+ * 4. Construct the user snippet body as a list of strings
55
+ * 5 . Construct a javascript object that will be json-ified to become the meta
56
+ * snippet
51
57
* 6. Serialize the javascript object to json
52
58
* 7. Perform replacements on the random id's appearing in this json to get the
53
59
* text we desire. This modified json output is the meta snippet.
@@ -88,72 +94,100 @@ export default class GenerateSnippet implements Action {
88
94
/** The next placeholder index to use for the meta snippet */
89
95
let nextPlaceholderIndex = 1 ;
90
96
91
- /**
92
- * The original selections and the editor that will become variables in the
93
- * user snippet
94
- */
95
- const originalVariableSelections = editor . selections . filter ( ( selection ) =>
96
- target . contentRange . contains ( selection )
97
- ) ;
98
-
99
- /**
100
- * The original text of the selections where the variables were in the
101
- * document to use to restore the document contents when we're done.
102
- */
103
- const originalVariableTexts = originalVariableSelections . map ( ( selection ) =>
104
- editor . document . getText ( selection )
105
- ) ;
97
+ const baseOffset = editor . document . offsetAt ( target . contentRange . start ) ;
106
98
107
99
/**
108
100
* The variables that will appear in the user snippet. Note that
109
101
* `placeholderIndex` here is the placeholder index in the meta snippet not
110
102
* the user snippet.
111
103
*/
112
- const variables : Variable [ ] = range ( originalVariableSelections . length ) . map (
113
- ( index ) => ( {
104
+ const variables : Variable [ ] = editor . selections
105
+ . filter ( ( selection ) => target . contentRange . contains ( selection ) )
106
+ . map ( ( selection , index ) => ( {
107
+ offsets : {
108
+ start : editor . document . offsetAt ( selection . start ) - baseOffset ,
109
+ end : editor . document . offsetAt ( selection . end ) - baseOffset ,
110
+ } ,
114
111
defaultName : `variable${ index + 1 } ` ,
115
112
placeholderIndex : nextPlaceholderIndex ++ ,
116
- } )
117
- ) ;
113
+ } ) ) ;
118
114
119
115
/**
120
116
* Constructs random ids that can be put into the text that won't be
121
117
* modified by json serialization.
122
118
*/
123
119
const substituter = new Substituter ( ) ;
124
120
125
- const [ variableSelections , [ targetSelection ] ] =
126
- await performEditsAndUpdateSelections (
127
- this . graph . rangeUpdater ,
128
- editor ,
129
- originalVariableSelections . map ( ( selection , index ) => ( {
130
- editor,
131
- range : selection ,
132
- text : substituter . addSubstitution (
133
- [
134
- "\\$${" ,
135
- variables [ index ] . placeholderIndex ,
136
- ":" ,
137
- variables [ index ] . defaultName ,
138
- "}" ,
139
- ] . join ( "" )
140
- ) ,
141
- } ) ) ,
142
- [ originalVariableSelections , [ target . contentSelection ] ]
143
- ) ;
144
-
145
- const snippetLines = constructSnippetBody ( editor , targetSelection ) ;
121
+ const linePrefix = editor . document . getText (
122
+ new Range (
123
+ target . contentRange . start . with ( undefined , 0 ) ,
124
+ target . contentRange . start
125
+ )
126
+ ) ;
146
127
147
- await performDocumentEdits (
148
- this . graph . rangeUpdater ,
149
- editor ,
150
- zip ( variableSelections , originalVariableTexts ) . map ( ( [ range , text ] ) => ( {
151
- editor,
152
- range : range ! ,
153
- text : text ! ,
128
+ /** The text of the snippet, with placeholders inserted for variables */
129
+ const snippetBodyText = editText (
130
+ editor . document . getText ( target . contentRange ) ,
131
+ variables . map ( ( { offsets, defaultName, placeholderIndex } ) => ( {
132
+ offsets,
133
+ text : substituter . addSubstitution (
134
+ [
135
+ // This `\$` will end up being a `$` in the final document. It
136
+ // indicates the start of a variable in the user snippet. We need
137
+ // the `\` so that the meta-snippet doesn't see it as one of its
138
+ // placeholders.
139
+ // Note that the reason we use the substituter here is primarily so
140
+ // that the `\` here doesn't get escaped upon conversion to json.
141
+ "\\$" ,
142
+
143
+ // The remaining text here is a placeholder in the meta-snippet
144
+ // that the user can use to name their snippet variable that will
145
+ // be in the user snippet.
146
+ "${" ,
147
+ placeholderIndex ,
148
+ ":" ,
149
+ defaultName ,
150
+ "}" ,
151
+ ] . join ( "" )
152
+ ) ,
154
153
} ) )
155
154
) ;
156
155
156
+ const snippetLines = constructSnippetBody ( snippetBodyText , linePrefix ) ;
157
+
158
+ /**
159
+ * Constructs a key-value entry for use in the variable description section
160
+ * of the user snippet definition. It contains tabstops for use in the
161
+ * meta-snippet.
162
+ * @param variable The variable
163
+ * @returns A [key, value] pair for use in the meta-snippet
164
+ */
165
+ const constructVariableDescriptionEntry = ( {
166
+ placeholderIndex,
167
+ } : Variable ) : [ string , string ] => {
168
+ // The key will have the same placeholder index as the other location
169
+ // where this variable appears.
170
+ const key = "$" + placeholderIndex ;
171
+
172
+ // The value will end up being an empty object with a tabstop in the
173
+ // middle so that the user can add information about the variable, such
174
+ // as wrapperScopeType. Ie the output will look like `{|}` (with the `|`
175
+ // representing a tabstop in the meta-snippet)
176
+ //
177
+ // NB: We use the subsituter here, with `isQuoted=true` because in order
178
+ // to make this work for the meta-snippet, we want to end up with
179
+ // something like `{$3}`, which is not valid json. So we instead arrange
180
+ // to end up with json like `"hgidfsivhs"`, and then replace the whole
181
+ // string (including quotes) with `{$3}` after json-ification
182
+ const value = substituter . addSubstitution (
183
+ "{$" + nextPlaceholderIndex ++ + "}" ,
184
+ true
185
+ ) ;
186
+
187
+ return [ key , value ] ;
188
+ } ;
189
+
190
+ /** An object that will be json-ified to become the meta-snippet */
157
191
const snippet = {
158
192
[ snippetName ] : {
159
193
definitions : [
@@ -166,23 +200,22 @@ export default class GenerateSnippet implements Action {
166
200
] ,
167
201
description : `$${ nextPlaceholderIndex ++ } ` ,
168
202
variables :
169
- originalVariableSelections . length === 0
203
+ variables . length === 0
170
204
? undefined
171
205
: Object . fromEntries (
172
- range ( originalVariableSelections . length ) . map ( ( index ) => [
173
- `$${ variables [ index ] . placeholderIndex } ` ,
174
- substituter . addSubstitution (
175
- `{$${ nextPlaceholderIndex ++ } }` ,
176
- true
177
- ) ,
178
- ] )
206
+ variables . map ( constructVariableDescriptionEntry )
179
207
) ,
180
208
} ,
181
209
} ;
210
+
211
+ /**
212
+ * This is the text of the meta-snippet in Textmate format that we will
213
+ * insert into the new document where the user will fill out their snippet
214
+ * definition
215
+ */
182
216
const snippetText = substituter . makeSubstitutions (
183
217
JSON . stringify ( snippet , null , 2 )
184
218
) ;
185
- console . debug ( snippetText ) ;
186
219
187
220
const userSnippetsDir = workspace
188
221
. getConfiguration ( "cursorless.experimental" )
0 commit comments