Skip to content

Commit 526fb22

Browse files
Handle block comments with text on the same line as <# #> (#2848)
* Handle block comments that have content on the same line as <##> * add more tests * handle \r\n better * support <# foo #> * include line ending in metadata
1 parent bb5dbb8 commit 526fb22

File tree

3 files changed

+321
-51
lines changed

3 files changed

+321
-51
lines changed

src/features/PowerShellNotebooks.ts

Lines changed: 143 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,24 @@ export class PowerShellNotebooksFeature extends LanguageClientConsumer {
6666
}
6767
}
6868

69+
interface IPowerShellNotebookCellMetadata {
70+
commentType: CommentType;
71+
openBlockCommentOnOwnLine?: boolean;
72+
closeBlockCommentOnOwnLine?: boolean;
73+
}
74+
75+
function CreateCell(cellKind: vscode.CellKind, source: string[], metadata: IPowerShellNotebookCellMetadata): vscode.NotebookCellData {
76+
return {
77+
cellKind,
78+
language: cellKind === vscode.CellKind.Markdown ? "markdown" : "powershell",
79+
outputs: [],
80+
source: source.join("\n"),
81+
metadata: {
82+
custom: metadata,
83+
},
84+
};
85+
}
86+
6987
class PowerShellNotebookContentProvider implements vscode.NotebookContentProvider {
7088
private _onDidChangeNotebook = new vscode.EventEmitter<vscode.NotebookDocumentEditEvent>();
7189
public onDidChangeNotebook: vscode.Event<vscode.NotebookDocumentEditEvent> = this._onDidChangeNotebook.event;
@@ -79,40 +97,72 @@ class PowerShellNotebookContentProvider implements vscode.NotebookContentProvide
7997
this.logger.writeDiagnostic(`Opening Notebook: ${uri.toString()}`);
8098

8199
const data = (await vscode.workspace.fs.readFile(actualUri)).toString();
82-
const lines = data.split(/\r\n|\r|\n/g);
100+
101+
let lines: string[];
102+
// store the line ending in the metadata of the document
103+
// so that we honor the line ending of the original file
104+
// on save.
105+
let lineEnding: string;
106+
if (data.indexOf('\r\n') !== -1) {
107+
lines = data.split(/\r\n/g);
108+
lineEnding = '\r\n';
109+
} else {
110+
lines = data.split(/\n/g);
111+
lineEnding = '\n';
112+
}
83113

84114
const notebookData: vscode.NotebookData = {
85115
languages: ["powershell"],
86116
cells: [],
87-
metadata: {}
117+
metadata: {
118+
custom: {
119+
lineEnding,
120+
}
121+
}
88122
};
89123

90124
let currentCellSource: string[] = [];
91125
let cellKind: vscode.CellKind | undefined;
92126
let insideBlockComment: boolean = false;
93127

128+
// This dictates whether the BlockComment cell was read in with content on the same
129+
// line as the opening <#. This is so we can preserve the format of the backing file on save.
130+
let openBlockCommentOnOwnLine: boolean = false;
131+
94132
// Iterate through all lines in a document (aka ps1 file) and group the lines
95133
// into cells (markdown or code) that will be rendered in Notebook mode.
96134
// tslint:disable-next-line: prefer-for-of
97135
for (let i = 0; i < lines.length; i++) {
98136
// Handle block comments
99137
if (insideBlockComment) {
100-
if (lines[i] === "#>") {
138+
if (lines[i].endsWith("#>")) {
139+
// Get the content of the current line without #>
140+
const currentLine = lines[i]
141+
.substring(0, lines[i].length - 2)
142+
.trimRight();
143+
144+
// This dictates whether the BlockComment cell was read in with content on the same
145+
// line as the closing #>. This is so we can preserve the format of the backing file
146+
// on save.
147+
let closeBlockCommentOnOwnLine: boolean = true;
148+
if (currentLine) {
149+
closeBlockCommentOnOwnLine = false;
150+
currentCellSource.push(currentLine);
151+
}
152+
101153
// We've reached the end of a block comment,
102154
// push a markdown cell.
103155
insideBlockComment = false;
104156

105-
notebookData.cells.push({
106-
cellKind: vscode.CellKind.Markdown,
107-
language: "markdown",
108-
outputs: [],
109-
source: currentCellSource.join("\n"),
110-
metadata: {
111-
custom: {
112-
commentType: CommentType.BlockComment
113-
}
157+
notebookData.cells.push(CreateCell(
158+
vscode.CellKind.Markdown,
159+
currentCellSource,
160+
{
161+
commentType: CommentType.BlockComment,
162+
openBlockCommentOnOwnLine,
163+
closeBlockCommentOnOwnLine
114164
}
115-
});
165+
));
116166

117167
currentCellSource = [];
118168
cellKind = null;
@@ -122,29 +172,65 @@ class PowerShellNotebookContentProvider implements vscode.NotebookContentProvide
122172
// If we're still in a block comment, push the line and continue.
123173
currentCellSource.push(lines[i]);
124174
continue;
125-
} else if (lines[i] === "<#") {
175+
} else if (lines[i].startsWith("<#")) {
126176
// If we found the start of a block comment,
127177
// insert what we saw leading up to this.
128178
// If cellKind is null/undefined, that means we
129179
// are starting the file with a BlockComment.
130180
if (cellKind) {
131-
notebookData.cells.push({
181+
notebookData.cells.push(CreateCell(
132182
cellKind,
133-
language: cellKind === vscode.CellKind.Markdown ? "markdown" : "powershell",
134-
outputs: [],
135-
source: currentCellSource.join("\n"),
136-
metadata: {
137-
custom: {
138-
commentType: cellKind === vscode.CellKind.Markdown ? CommentType.LineComment : CommentType.Disabled,
139-
}
183+
currentCellSource,
184+
{
185+
commentType: cellKind === vscode.CellKind.Markdown ? CommentType.LineComment : CommentType.Disabled,
140186
}
141-
});
187+
));
142188
}
143189

144-
// reset state because we're starting a new Markdown cell.
145-
currentCellSource = [];
190+
// We're starting a new Markdown cell.
146191
cellKind = vscode.CellKind.Markdown;
147192
insideBlockComment = true;
193+
194+
// Get the content of the current line without `<#`
195+
const currentLine = lines[i]
196+
.substring(2, lines[i].length)
197+
.trimLeft();
198+
199+
// If we have additional text on the line with the `<#`
200+
// We need to keep track of what comes after it.
201+
if (currentLine) {
202+
// If both the `<#` and the `#>` are on the same line
203+
// we want to push a markdown cell.
204+
if (currentLine.endsWith("#>")) {
205+
// Get the content of the current line without `#>`
206+
const newCurrentLine = currentLine
207+
.substring(0, currentLine.length - 2)
208+
.trimRight();
209+
210+
notebookData.cells.push(CreateCell(
211+
vscode.CellKind.Markdown,
212+
[ newCurrentLine ],
213+
{
214+
commentType: CommentType.BlockComment,
215+
openBlockCommentOnOwnLine: false,
216+
closeBlockCommentOnOwnLine: false,
217+
}
218+
));
219+
220+
// Reset
221+
currentCellSource = [];
222+
cellKind = null;
223+
insideBlockComment = false;
224+
continue;
225+
}
226+
227+
openBlockCommentOnOwnLine = false;
228+
currentCellSource = [ currentLine ];
229+
} else {
230+
openBlockCommentOnOwnLine = true;
231+
currentCellSource = [];
232+
}
233+
148234
continue;
149235
}
150236

@@ -158,17 +244,13 @@ class PowerShellNotebookContentProvider implements vscode.NotebookContentProvide
158244
} else {
159245
// If cellKind has a value, then we can add the cell we've just computed.
160246
if (cellKind) {
161-
notebookData.cells.push({
162-
cellKind: cellKind!,
163-
language: cellKind === vscode.CellKind.Markdown ? "markdown" : "powershell",
164-
outputs: [],
165-
source: currentCellSource.join("\n"),
166-
metadata: {
167-
custom: {
168-
commentType: cellKind === vscode.CellKind.Markdown ? CommentType.LineComment : CommentType.Disabled,
169-
}
247+
notebookData.cells.push(CreateCell(
248+
cellKind,
249+
currentCellSource,
250+
{
251+
commentType: cellKind === vscode.CellKind.Markdown ? CommentType.LineComment : CommentType.Disabled,
170252
}
171-
});
253+
));
172254
}
173255

174256
// set initial new cell state
@@ -182,17 +264,13 @@ class PowerShellNotebookContentProvider implements vscode.NotebookContentProvide
182264
// when there is only the _start_ of a block comment but not an _end_.)
183265
// add the appropriate cell.
184266
if (currentCellSource.length) {
185-
notebookData.cells.push({
186-
cellKind: cellKind!,
187-
language: cellKind === vscode.CellKind.Markdown ? "markdown" : "powershell",
188-
outputs: [],
189-
source: currentCellSource.join("\n"),
190-
metadata: {
191-
custom: {
192-
commentType: cellKind === vscode.CellKind.Markdown ? CommentType.LineComment : CommentType.Disabled,
193-
}
267+
notebookData.cells.push(CreateCell(
268+
cellKind!,
269+
currentCellSource,
270+
{
271+
commentType: cellKind === vscode.CellKind.Markdown ? CommentType.LineComment : CommentType.Disabled,
194272
}
195-
});
273+
));
196274
}
197275

198276
return notebookData;
@@ -228,23 +306,37 @@ class PowerShellNotebookContentProvider implements vscode.NotebookContentProvide
228306
const retArr: string[] = [];
229307
for (const cell of document.cells) {
230308
if (cell.cellKind === vscode.CellKind.Code) {
231-
retArr.push(...cell.document.getText().split(/\r|\n|\r\n/));
309+
retArr.push(...cell.document.getText().split(/\r\n|\n/));
232310
} else {
233311
// First honor the comment type of the cell if it already has one.
234312
// If not, use the user setting.
235313
const commentKind = cell.metadata.custom?.commentType || Settings.load().notebooks.saveMarkdownCellsAs;
236314

237315
if (commentKind === CommentType.BlockComment) {
238-
retArr.push("<#");
239-
retArr.push(...cell.document.getText().split(/\r|\n|\r\n/));
240-
retArr.push("#>");
316+
const openBlockCommentOnOwnLine: boolean = cell.metadata.custom?.openBlockCommentOnOwnLine;
317+
const closeBlockCommentOnOwnLine: boolean = cell.metadata.custom?.closeBlockCommentOnOwnLine;
318+
const text = cell.document.getText().split(/\r\n|\n/);
319+
if (openBlockCommentOnOwnLine) {
320+
retArr.push("<#");
321+
} else {
322+
text[0] = `<# ${text[0]}`;
323+
}
324+
325+
if (!closeBlockCommentOnOwnLine) {
326+
text[text.length - 1] += " #>";
327+
retArr.push(...text);
328+
} else {
329+
retArr.push(...text);
330+
retArr.push("#>");
331+
}
241332
} else {
242-
retArr.push(...cell.document.getText().split(/\r|\n|\r\n/).map((line) => `# ${line}`));
333+
retArr.push(...cell.document.getText().split(/\r\n|\n/).map((line) => `# ${line}`));
243334
}
244335
}
245336
}
246337

247-
await vscode.workspace.fs.writeFile(targetResource, new TextEncoder().encode(retArr.join("\n")));
338+
const eol = document.metadata.custom.lineEnding;
339+
await vscode.workspace.fs.writeFile(targetResource, new TextEncoder().encode(retArr.join(eol)));
248340
}
249341
}
250342

0 commit comments

Comments
 (0)