From 0577c9202c3e7d474ebe27f04bddd9ce237eb75f Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 7 Aug 2023 12:30:50 -0700 Subject: [PATCH 01/82] receive file content from ts, parse, collect in py --- pythonFiles/normalizeSelection.py | 29 ++++++++++++++++++- .../codeExecution/codeExecutionManager.ts | 3 +- src/client/terminals/codeExecution/helper.ts | 5 ++-- src/client/terminals/types.ts | 2 +- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 0363702717ab..e725817add23 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -3,11 +3,18 @@ import ast import json +import os +import pathlib import re import sys import textwrap +script_dir = pathlib.Path('User/anthonykim/Desktop/vscode-python/pythonFiles/lib/python') +sys.path.append(os.fspath(script_dir)) +import debugpy +debugpy.connect(5678) +debugpy.breakpoint() def split_lines(source): """ Split selection lines in a version-agnostic way. @@ -125,6 +132,24 @@ def normalize_lines(selection): return source +top_level_nodes = [] # collection of top level nodes + +class file_node_visitor(ast.NodeVisitor): + def visit_nodes(self, node): + top_level_nodes.append(node) + self.generic_visit(node) + +def traverse_file(wholeFileContent): + # use ast module to parse content of the file + parsed_file_content = ast.parse(wholeFileContent) + file_node_visitor().visit(parsed_file_content) + + for node in ast.iter_child_nodes(parsed_file_content): + top_level_nodes.append(node) + line_start = node.lineno + line_end = node.end_lineno + code_of_node = ast.get_source + # ast.get_source_segment(wholeFileContent, node) This is way to get original code of the selected node if __name__ == "__main__": # Content is being sent from the extension as a JSON object. @@ -134,7 +159,9 @@ def normalize_lines(selection): contents = json.loads(raw.decode("utf-8")) normalized = normalize_lines(contents["code"]) - + normalized_whole_file = normalize_lines(contents["wholeFileContent"]) + traverse_file(contents["wholeFileContent"]) # traverse file + file_node_visitor().visit(ast.parse(contents["wholeFileContent"])) # Send the normalized code back to the extension in a JSON object. data = json.dumps({"normalized": normalized}) diff --git a/src/client/terminals/codeExecution/codeExecutionManager.ts b/src/client/terminals/codeExecution/codeExecutionManager.ts index 9f1ba6e90d90..5e1f66b89d2d 100644 --- a/src/client/terminals/codeExecution/codeExecutionManager.ts +++ b/src/client/terminals/codeExecution/codeExecutionManager.ts @@ -147,7 +147,8 @@ export class CodeExecutionManager implements ICodeExecutionManager { } const codeExecutionHelper = this.serviceContainer.get(ICodeExecutionHelper); const codeToExecute = await codeExecutionHelper.getSelectedTextToExecute(activeEditor!); - const normalizedCode = await codeExecutionHelper.normalizeLines(codeToExecute!); + const wholeFileContent = activeEditor!.document.getText(); + const normalizedCode = await codeExecutionHelper.normalizeLines(codeToExecute!, wholeFileContent); if (!normalizedCode || normalizedCode.trim().length === 0) { return; } diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 0d5694b4a28d..68d5aa63e8af 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -33,7 +33,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { this.interpreterService = serviceContainer.get(IInterpreterService); } - public async normalizeLines(code: string, resource?: Uri): Promise { + public async normalizeLines(code: string, wholeFileContent: string, resource?: Uri): Promise { try { if (code.trim().length === 0) { return ''; @@ -66,7 +66,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { // The normalization script expects a serialized JSON object, with the selection under the "code" key. // We're using a JSON object so that we don't have to worry about encoding, or escaping non-ASCII characters. - const input = JSON.stringify({ code }); + const input = JSON.stringify({ code, wholeFileContent }); observable.proc?.stdin?.write(input); observable.proc?.stdin?.end(); @@ -110,6 +110,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { const { selection } = textEditor; let code: string; + const wholeFileContent = textEditor.document.getText(); // This is a way to get the whole text content from the user if (selection.isEmpty) { code = textEditor.document.lineAt(selection.start.line).text; } else if (selection.isSingleLine) { diff --git a/src/client/terminals/types.ts b/src/client/terminals/types.ts index 47ac16d9e08b..585fea913166 100644 --- a/src/client/terminals/types.ts +++ b/src/client/terminals/types.ts @@ -15,7 +15,7 @@ export interface ICodeExecutionService { export const ICodeExecutionHelper = Symbol('ICodeExecutionHelper'); export interface ICodeExecutionHelper { - normalizeLines(code: string): Promise; + normalizeLines(code: string, wholeFileContent: string): Promise; getFileToExecute(): Promise; saveFileIfDirty(file: Uri): Promise; getSelectedTextToExecute(textEditor: TextEditor): Promise; From 73a38c64a6ebaee0fd279bfe30a04460c199a624 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 9 Aug 2023 17:56:49 -0700 Subject: [PATCH 02/82] collect top level and comment out potential cursor --- pythonFiles/normalizeSelection.py | 34 ++++++++++++++++---- src/client/terminals/codeExecution/helper.ts | 11 +++++-- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index e725817add23..104c6041fe70 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -133,13 +133,15 @@ def normalize_lines(selection): return source top_level_nodes = [] # collection of top level nodes - +top_level_to_min_difference = {} # dictionary of top level nodes to difference in relative to given code block to run +min_key = None class file_node_visitor(ast.NodeVisitor): def visit_nodes(self, node): top_level_nodes.append(node) self.generic_visit(node) -def traverse_file(wholeFileContent): +# Function that traverses the file and calculate the minimum viable top level block +def traverse_file(wholeFileContent, start_line, end_line): # use ast module to parse content of the file parsed_file_content = ast.parse(wholeFileContent) file_node_visitor().visit(parsed_file_content) @@ -148,9 +150,23 @@ def traverse_file(wholeFileContent): top_level_nodes.append(node) line_start = node.lineno line_end = node.end_lineno - code_of_node = ast.get_source + code_of_node = ast.get_source_segment(wholeFileContent, node) # ast.get_source_segment(wholeFileContent, node) This is way to get original code of the selected node + # With the given start_line and end_line number from VSCode, + # Calculate the absolute difference between each of the top level block and given code (via line number) + for top_node in top_level_nodes: + top_level_block_start_line = top_node.lineno + top_level_block_end_line = top_node.end_lineno + abs_difference = abs(start_line - top_level_block_start_line) + abs(end_line - top_level_block_end_line) + top_level_to_min_difference[top_node] = abs_difference + + # get the minimum viable block node reference + min_key = min(top_level_to_min_difference, key=top_level_to_min_difference.get) + min_viable_code = ast.get_source_segment(wholeFileContent, min_key) # Minimum viable code + normalized_min_viable_code = normalize_lines(min_viable_code) # Normalized minimum viable code + return normalized_min_viable_code # return minimial viable code + if __name__ == "__main__": # Content is being sent from the extension as a JSON object. # Decode the data from the raw bytes. @@ -160,11 +176,17 @@ def traverse_file(wholeFileContent): normalized = normalize_lines(contents["code"]) normalized_whole_file = normalize_lines(contents["wholeFileContent"]) - traverse_file(contents["wholeFileContent"]) # traverse file + + # we also get the activeEditor selection start line and end line from the typescript vscode side + # remember to add 1 to each of the received since vscode starts line counting from 0 + vscode_start_line = contents["startLine"] + vscode_end_line = contents["endLine"] + + temp = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line) # traverse file file_node_visitor().visit(ast.parse(contents["wholeFileContent"])) # Send the normalized code back to the extension in a JSON object. - data = json.dumps({"normalized": normalized}) - + # data = json.dumps({"normalized": normalized}) # This is how it used to be + data = json.dumps({"normalized": temp}) stdout = sys.stdout if sys.version_info < (3,) else sys.stdout.buffer stdout.write(data.encode("utf-8")) stdout.close() diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 68d5aa63e8af..67946cf3ed3f 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -3,7 +3,7 @@ import '../../common/extensions'; import { inject, injectable } from 'inversify'; -import { l10n, Position, Range, TextEditor, Uri } from 'vscode'; +import { l10n, Position, Range, TextEditor, Uri, commands } from 'vscode'; import { IApplicationShell, IDocumentManager, IWorkspaceService } from '../../common/application/types'; import { PYTHON_LANGUAGE } from '../../common/constants'; @@ -42,6 +42,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { // So just remove cr from the input. code = code.replace(new RegExp('\\r', 'g'), ''); + const activeEditor = this.documentManager.activeTextEditor; const interpreter = await this.interpreterService.getActiveInterpreter(resource); const processService = await this.processServiceFactory.create(resource); @@ -66,7 +67,12 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { // The normalization script expects a serialized JSON object, with the selection under the "code" key. // We're using a JSON object so that we don't have to worry about encoding, or escaping non-ASCII characters. - const input = JSON.stringify({ code, wholeFileContent }); + const input = JSON.stringify({ + code, + wholeFileContent, + startLine: activeEditor!.selection.start.line, + endLine: activeEditor!.selection.end.line, + }); observable.proc?.stdin?.write(input); observable.proc?.stdin?.end(); @@ -118,6 +124,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { } else { code = getMultiLineSelectionText(textEditor); } + // commands.executeCommand('cursorMove', { to: 'down' }); // trial: move to the next line when you run? return code; } From 02a15e6d95dfc8c9a5b40c023a8ecea75f6f5ec1 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 14 Aug 2023 00:41:45 -0700 Subject: [PATCH 03/82] handle both highlight and non highlight --- pythonFiles/normalizeSelection.py | 58 ++++++++++++++------ src/client/terminals/codeExecution/helper.ts | 1 + 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 104c6041fe70..f744faa113f9 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -9,12 +9,12 @@ import sys import textwrap -script_dir = pathlib.Path('User/anthonykim/Desktop/vscode-python/pythonFiles/lib/python') -sys.path.append(os.fspath(script_dir)) -import debugpy +# script_dir = pathlib.Path('User/anthonykim/Desktop/vscode-python/pythonFiles/lib/python') +# sys.path.append(os.fspath(script_dir)) +# import debugpy -debugpy.connect(5678) -debugpy.breakpoint() +# debugpy.connect(5678) +# debugpy.breakpoint() def split_lines(source): """ Split selection lines in a version-agnostic way. @@ -125,6 +125,7 @@ def normalize_lines(selection): # Insert a newline between each top-level statement, and append a newline to the selection. source = "\n".join(statements) + "\n" + # source = "\n".join(statements) except Exception: # If there's a problem when parsing statements, # append a blank line to end the block and send it as-is. @@ -135,16 +136,15 @@ def normalize_lines(selection): top_level_nodes = [] # collection of top level nodes top_level_to_min_difference = {} # dictionary of top level nodes to difference in relative to given code block to run min_key = None -class file_node_visitor(ast.NodeVisitor): - def visit_nodes(self, node): - top_level_nodes.append(node) - self.generic_visit(node) + +should_run_top_blocks = [] + # Function that traverses the file and calculate the minimum viable top level block -def traverse_file(wholeFileContent, start_line, end_line): +def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # use ast module to parse content of the file parsed_file_content = ast.parse(wholeFileContent) - file_node_visitor().visit(parsed_file_content) + temp_code = "" for node in ast.iter_child_nodes(parsed_file_content): top_level_nodes.append(node) @@ -160,12 +160,32 @@ def traverse_file(wholeFileContent, start_line, end_line): top_level_block_end_line = top_node.end_lineno abs_difference = abs(start_line - top_level_block_start_line) + abs(end_line - top_level_block_end_line) top_level_to_min_difference[top_node] = abs_difference + # Also see if given start and end line is within the top level block 8/13/2023 -------------------------------------------- + # if top_level_block_start_line >= start_line or top_level_block_end_line >= end_line: + # should_run_top_blocks.append(top_node) + # temp_code += str(ast.get_source_segment(wholeFileContent, top_node)) + # temp_code += "\n" ---------------------------------------------------- + # We need to handle the case of 1. just hanging cursor vs. actual highlighting/selection. + if was_highlighted: # There was actual highlighting of some text + if top_level_block_start_line >= start_line and top_level_block_end_line <= end_line: + should_run_top_blocks.append(top_node) + temp_code += str(ast.get_source_segment(wholeFileContent, top_node)) + temp_code += "\n" + else: # not highlighted case. Meaning just a cursor hanging + if start_line >= top_level_block_start_line and end_line <= top_level_block_end_line: + should_run_top_blocks.append(top_node) + temp_code += str(ast.get_source_segment(wholeFileContent, top_node)) + temp_code += "\n" + # get the minimum viable block node reference min_key = min(top_level_to_min_difference, key=top_level_to_min_difference.get) min_viable_code = ast.get_source_segment(wholeFileContent, min_key) # Minimum viable code normalized_min_viable_code = normalize_lines(min_viable_code) # Normalized minimum viable code - return normalized_min_viable_code # return minimial viable code + + temp_result = normalize_lines(temp_code) + # return normalized_min_viable_code # return minimial viable code + return temp_result if __name__ == "__main__": # Content is being sent from the extension as a JSON object. @@ -176,14 +196,18 @@ def traverse_file(wholeFileContent, start_line, end_line): normalized = normalize_lines(contents["code"]) normalized_whole_file = normalize_lines(contents["wholeFileContent"]) - + # Need to get information on whether there was a selection via Highlight. + # empty_Highlight = True + empty_Highlight = False + if contents["emptyHighlight"] == True: + empty_Highlight = True # we also get the activeEditor selection start line and end line from the typescript vscode side # remember to add 1 to each of the received since vscode starts line counting from 0 - vscode_start_line = contents["startLine"] - vscode_end_line = contents["endLine"] + vscode_start_line = contents["startLine"] + 1 + vscode_end_line = contents["endLine"] + 1 + + temp = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) # traverse file - temp = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line) # traverse file - file_node_visitor().visit(ast.parse(contents["wholeFileContent"])) # Send the normalized code back to the extension in a JSON object. # data = json.dumps({"normalized": normalized}) # This is how it used to be data = json.dumps({"normalized": temp}) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 67946cf3ed3f..afe4c59d8628 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -72,6 +72,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { wholeFileContent, startLine: activeEditor!.selection.start.line, endLine: activeEditor!.selection.end.line, + emptyHighlight: activeEditor!.selection.isEmpty, }); observable.proc?.stdin?.write(input); observable.proc?.stdin?.end(); From 3d91423cb738b796d0199f8c54518d0e2a364323 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 15 Aug 2023 22:28:04 -0700 Subject: [PATCH 04/82] remove trailing newline and add test cases --- pythonFiles/tests/test_normalize_selection.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pythonFiles/tests/test_normalize_selection.py b/pythonFiles/tests/test_normalize_selection.py index 138c5ad2f522..53afa9c30473 100644 --- a/pythonFiles/tests/test_normalize_selection.py +++ b/pythonFiles/tests/test_normalize_selection.py @@ -1,8 +1,12 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +import os +import sys import textwrap +# __file__ = "/Users/anthonykim/Desktop/vscode-python/pythonFiles/normalizeSelection.py" +# sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__)))) import normalizeSelection From f594c858cf696e765c88cd753adfe42ca6e37674 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 15 Aug 2023 22:41:48 -0700 Subject: [PATCH 05/82] delete trailing new line and start testing --- pythonFiles/normalizeSelection.py | 56 +++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index f744faa113f9..99f8a799b217 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -13,6 +13,7 @@ # sys.path.append(os.fspath(script_dir)) # import debugpy + # debugpy.connect(5678) # debugpy.breakpoint() def split_lines(source): @@ -125,6 +126,8 @@ def normalize_lines(selection): # Insert a newline between each top-level statement, and append a newline to the selection. source = "\n".join(statements) + "\n" + if selection[-2] == "}": + source = source[:-1] # source = "\n".join(statements) except Exception: # If there's a problem when parsing statements, @@ -133,8 +136,11 @@ def normalize_lines(selection): return source -top_level_nodes = [] # collection of top level nodes -top_level_to_min_difference = {} # dictionary of top level nodes to difference in relative to given code block to run + +top_level_nodes = [] # collection of top level nodes +top_level_to_min_difference = ( + {} +) # dictionary of top level nodes to difference in relative to given code block to run min_key = None should_run_top_blocks = [] @@ -158,7 +164,10 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): for top_node in top_level_nodes: top_level_block_start_line = top_node.lineno top_level_block_end_line = top_node.end_lineno - abs_difference = abs(start_line - top_level_block_start_line) + abs(end_line - top_level_block_end_line) + # top_level_block_end_line = top_node.end_lineno if hasattr(top_node, "end_lineno") else 0 + abs_difference = abs(start_line - top_level_block_start_line) + abs( + end_line - top_level_block_end_line + ) top_level_to_min_difference[top_node] = abs_difference # Also see if given start and end line is within the top level block 8/13/2023 -------------------------------------------- # if top_level_block_start_line >= start_line or top_level_block_end_line >= end_line: @@ -166,27 +175,41 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # temp_code += str(ast.get_source_segment(wholeFileContent, top_node)) # temp_code += "\n" ---------------------------------------------------- # We need to handle the case of 1. just hanging cursor vs. actual highlighting/selection. - if was_highlighted: # There was actual highlighting of some text - if top_level_block_start_line >= start_line and top_level_block_end_line <= end_line: + if ( + was_highlighted + ): # There was actual highlighting of some text # Smart Selection disbled for part of the broken send. + if ( + top_level_block_start_line >= start_line + and top_level_block_end_line <= end_line + ): should_run_top_blocks.append(top_node) - temp_code += str(ast.get_source_segment(wholeFileContent, top_node)) + temp_str = ast.get_source_segment(wholeFileContent, top_node) + temp_code += str(temp_str) temp_code += "\n" - else: # not highlighted case. Meaning just a cursor hanging - if start_line >= top_level_block_start_line and end_line <= top_level_block_end_line: + else: # not highlighted case. Meaning just a cursor hanging + if ( + start_line >= top_level_block_start_line + and end_line <= top_level_block_end_line + ): should_run_top_blocks.append(top_node) - temp_code += str(ast.get_source_segment(wholeFileContent, top_node)) + temp_str = ast.get_source_segment(wholeFileContent, top_node) + temp_code += str(temp_str) temp_code += "\n" - # get the minimum viable block node reference min_key = min(top_level_to_min_difference, key=top_level_to_min_difference.get) - min_viable_code = ast.get_source_segment(wholeFileContent, min_key) # Minimum viable code - normalized_min_viable_code = normalize_lines(min_viable_code) # Normalized minimum viable code + min_viable_code = ast.get_source_segment( + wholeFileContent, min_key + ) # Minimum viable code + normalized_min_viable_code = normalize_lines( + min_viable_code + ) # Normalized minimum viable code temp_result = normalize_lines(temp_code) # return normalized_min_viable_code # return minimial viable code return temp_result + if __name__ == "__main__": # Content is being sent from the extension as a JSON object. # Decode the data from the raw bytes. @@ -199,14 +222,19 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # Need to get information on whether there was a selection via Highlight. # empty_Highlight = True empty_Highlight = False - if contents["emptyHighlight"] == True: + if contents["emptyHighlight"] is True: empty_Highlight = True # we also get the activeEditor selection start line and end line from the typescript vscode side # remember to add 1 to each of the received since vscode starts line counting from 0 vscode_start_line = contents["startLine"] + 1 vscode_end_line = contents["endLine"] + 1 - temp = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) # traverse file + temp = traverse_file( + contents["wholeFileContent"], + vscode_start_line, + vscode_end_line, + not empty_Highlight, + ) # traverse file # Send the normalized code back to the extension in a JSON object. # data = json.dumps({"normalized": normalized}) # This is how it used to be From 0cacc3526f8dc9405661c849cd3a6b5a0830c7a2 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 15 Aug 2023 22:41:51 -0700 Subject: [PATCH 06/82] delete trailing new line and add new tests --- pythonFiles/normalizeSelection.py | 55 ++++---------- pythonFiles/tests/test_smart_selection.py | 92 +++++++++++++++++++++++ 2 files changed, 108 insertions(+), 39 deletions(-) create mode 100644 pythonFiles/tests/test_smart_selection.py diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 99f8a799b217..9a50814040f2 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -13,7 +13,6 @@ # sys.path.append(os.fspath(script_dir)) # import debugpy - # debugpy.connect(5678) # debugpy.breakpoint() def split_lines(source): @@ -126,7 +125,7 @@ def normalize_lines(selection): # Insert a newline between each top-level statement, and append a newline to the selection. source = "\n".join(statements) + "\n" - if selection[-2] == "}": + if (selection[-2] == '}'): source = source[:-1] # source = "\n".join(statements) except Exception: @@ -136,11 +135,8 @@ def normalize_lines(selection): return source - -top_level_nodes = [] # collection of top level nodes -top_level_to_min_difference = ( - {} -) # dictionary of top level nodes to difference in relative to given code block to run +top_level_nodes = [] # collection of top level nodes +top_level_to_min_difference = {} # dictionary of top level nodes to difference in relative to given code block to run min_key = None should_run_top_blocks = [] @@ -154,9 +150,9 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): for node in ast.iter_child_nodes(parsed_file_content): top_level_nodes.append(node) - line_start = node.lineno - line_end = node.end_lineno - code_of_node = ast.get_source_segment(wholeFileContent, node) + # line_start = node.lineno + # line_end = node.end_lineno + # code_of_node = ast.get_source_segment(wholeFileContent, node) # ast.get_source_segment(wholeFileContent, node) This is way to get original code of the selected node # With the given start_line and end_line number from VSCode, @@ -165,9 +161,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): top_level_block_start_line = top_node.lineno top_level_block_end_line = top_node.end_lineno # top_level_block_end_line = top_node.end_lineno if hasattr(top_node, "end_lineno") else 0 - abs_difference = abs(start_line - top_level_block_start_line) + abs( - end_line - top_level_block_end_line - ) + abs_difference = abs(start_line - top_level_block_start_line) + abs(end_line - top_level_block_end_line) top_level_to_min_difference[top_node] = abs_difference # Also see if given start and end line is within the top level block 8/13/2023 -------------------------------------------- # if top_level_block_start_line >= start_line or top_level_block_end_line >= end_line: @@ -175,41 +169,29 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # temp_code += str(ast.get_source_segment(wholeFileContent, top_node)) # temp_code += "\n" ---------------------------------------------------- # We need to handle the case of 1. just hanging cursor vs. actual highlighting/selection. - if ( - was_highlighted - ): # There was actual highlighting of some text # Smart Selection disbled for part of the broken send. - if ( - top_level_block_start_line >= start_line - and top_level_block_end_line <= end_line - ): + if was_highlighted: # There was actual highlighting of some text # Smart Selection disbled for part of the broken send. + if top_level_block_start_line >= start_line and top_level_block_end_line <= end_line: should_run_top_blocks.append(top_node) temp_str = ast.get_source_segment(wholeFileContent, top_node) temp_code += str(temp_str) temp_code += "\n" - else: # not highlighted case. Meaning just a cursor hanging - if ( - start_line >= top_level_block_start_line - and end_line <= top_level_block_end_line - ): + else: # not highlighted case. Meaning just a cursor hanging + if start_line >= top_level_block_start_line and end_line <= top_level_block_end_line: should_run_top_blocks.append(top_node) temp_str = ast.get_source_segment(wholeFileContent, top_node) temp_code += str(temp_str) temp_code += "\n" + # get the minimum viable block node reference min_key = min(top_level_to_min_difference, key=top_level_to_min_difference.get) - min_viable_code = ast.get_source_segment( - wholeFileContent, min_key - ) # Minimum viable code - normalized_min_viable_code = normalize_lines( - min_viable_code - ) # Normalized minimum viable code + min_viable_code = ast.get_source_segment(wholeFileContent, min_key) # Minimum viable code + normalized_min_viable_code = normalize_lines(min_viable_code) # Normalized minimum viable code temp_result = normalize_lines(temp_code) # return normalized_min_viable_code # return minimial viable code return temp_result - if __name__ == "__main__": # Content is being sent from the extension as a JSON object. # Decode the data from the raw bytes. @@ -222,19 +204,14 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # Need to get information on whether there was a selection via Highlight. # empty_Highlight = True empty_Highlight = False - if contents["emptyHighlight"] is True: + if contents['emptyHighlight'] is True: empty_Highlight = True # we also get the activeEditor selection start line and end line from the typescript vscode side # remember to add 1 to each of the received since vscode starts line counting from 0 vscode_start_line = contents["startLine"] + 1 vscode_end_line = contents["endLine"] + 1 - temp = traverse_file( - contents["wholeFileContent"], - vscode_start_line, - vscode_end_line, - not empty_Highlight, - ) # traverse file + temp = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) # traverse file # Send the normalized code back to the extension in a JSON object. # data = json.dumps({"normalized": normalized}) # This is how it used to be diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py new file mode 100644 index 000000000000..4a67310a0802 --- /dev/null +++ b/pythonFiles/tests/test_smart_selection.py @@ -0,0 +1,92 @@ +import ast +import os +import sys +import textwrap + +import normalizeSelection + + +def test_part_dictionary(): + src = 'import textwrap\nimport ast\n\nprint("Audi")\nprint("Genesis")\n\n\nprint("Audi");print("BMW");print("Mercedes")\n\nmy_dict = {\n "key1": "value1",\n "key2": "value2"\n}\n\n\nsrc = """\nmy_dict = {\n"key1": "value1",\n"key2": "value2"\n}\n"""\n\ntop_level_nodes = []\n\nparsed_file_content = ast.parse(src)\nprint(ast.dump(parsed_file_content))\n\nparsed_dict_content2 = ast.parse(str(my_dict))\nprint(ast.dump(parsed_dict_content2))\n\n\nfor node in ast.iter_child_nodes(parsed_file_content):\n top_level_nodes.append(node)\n line_start = node.lineno\n line_end = node.end_lineno\n code_of_node = ast.get_source_segment(wholeFileContent, node)\n ast.get_source_segment(wholeFileContent, node) # This is way to get original code of the selected node\n\n################################################################################\n# New test case(s):\n# what should happen when shift enter at line 5? \n# follow ast says ----- TODO \n\n# execute individually line 5 bc two statements ---- TODO \n#################################################################################' + + expected = 'my_dict = {\n "key1": "value1",\n "key2": "value2"\n}\n' + # parsed_file_content = ast.parse(src) + # top_level_nodes = [] + # for node in ast.iter_child_nodes(parsed_file_content): + # print(node.__dir__()) + result = normalizeSelection.traverse_file(src, 10, 11, False) + assert result == expected + + +def test_smart_shift_enter_multiple_statements(): + src = textwrap.dedent( + """\ + import textwrap + import ast + + print("Porsche") + print("Genesis") + + + print("Audi");print("BMW");print("Mercedes") + + print("dont print me") + + """ + ) + # Expected to printing statement line by line + expected = textwrap.dedent( + """\ + print("Audi") + print("BMW") + print("Mercedes") + print("Audi") + print("BMW") + print("Mercedes") + """ + ) + result = normalizeSelection.traverse_file(src, 8, 8, False) + assert result == expected + + +def test_two_layer_dictionary(): + src = textwrap.dedent( + """\ + print("dont print me") + + two_layered_dictionary = { + 'inner_dict_one': { + 'Audi': 'Germany', + 'BMW': 'Germnay', + 'Genesis': 'Korea', + }, + 'inner_dict_two': { + 'Mercedes': 'Germany', + 'Porsche': 'Germany', + 'Lamborghini': 'Italy', + 'Ferrari': 'Italy', + 'Maserati': 'Italy' + } + } + """ + ) + expected = textwrap.dedent( + """\ + two_layered_dictionary = { + 'inner_dict_one': { + 'Audi': 'Germany', + 'BMW': 'Germnay', + 'Genesis': 'Korea', + }, + 'inner_dict_two': { + 'Mercedes': 'Germany', + 'Porsche': 'Germany', + 'Lamborghini': 'Italy', + 'Ferrari': 'Italy', + 'Maserati': 'Italy' + } + } + """ + ) + result = normalizeSelection.traverse_file(src, 6, 7, False) + assert result == expected From fbe3d5c3451b041a5023c943c8db6e15f0346c4d Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 16 Aug 2023 11:12:55 -0700 Subject: [PATCH 07/82] fix tests and add printf string test --- pythonFiles/normalizeSelection.py | 6 ++-- pythonFiles/tests/test_smart_selection.py | 41 +++++++++++++++++++++-- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 9a50814040f2..00609fbedf38 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -184,9 +184,9 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # get the minimum viable block node reference - min_key = min(top_level_to_min_difference, key=top_level_to_min_difference.get) - min_viable_code = ast.get_source_segment(wholeFileContent, min_key) # Minimum viable code - normalized_min_viable_code = normalize_lines(min_viable_code) # Normalized minimum viable code + # min_key = min(top_level_to_min_difference, key=top_level_to_min_difference.get) + # min_viable_code = ast.get_source_segment(wholeFileContent, min_key) # Minimum viable code + # normalized_min_viable_code = normalize_lines(min_viable_code) # Normalized minimum viable code temp_result = normalize_lines(temp_code) # return normalized_min_viable_code # return minimial viable code diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index 4a67310a0802..29b79e0c7423 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -1,4 +1,5 @@ import ast +import importlib import os import sys import textwrap @@ -7,6 +8,7 @@ def test_part_dictionary(): + importlib.reload(normalizeSelection) src = 'import textwrap\nimport ast\n\nprint("Audi")\nprint("Genesis")\n\n\nprint("Audi");print("BMW");print("Mercedes")\n\nmy_dict = {\n "key1": "value1",\n "key2": "value2"\n}\n\n\nsrc = """\nmy_dict = {\n"key1": "value1",\n"key2": "value2"\n}\n"""\n\ntop_level_nodes = []\n\nparsed_file_content = ast.parse(src)\nprint(ast.dump(parsed_file_content))\n\nparsed_dict_content2 = ast.parse(str(my_dict))\nprint(ast.dump(parsed_dict_content2))\n\n\nfor node in ast.iter_child_nodes(parsed_file_content):\n top_level_nodes.append(node)\n line_start = node.lineno\n line_end = node.end_lineno\n code_of_node = ast.get_source_segment(wholeFileContent, node)\n ast.get_source_segment(wholeFileContent, node) # This is way to get original code of the selected node\n\n################################################################################\n# New test case(s):\n# what should happen when shift enter at line 5? \n# follow ast says ----- TODO \n\n# execute individually line 5 bc two statements ---- TODO \n#################################################################################' expected = 'my_dict = {\n "key1": "value1",\n "key2": "value2"\n}\n' @@ -19,6 +21,7 @@ def test_part_dictionary(): def test_smart_shift_enter_multiple_statements(): + importlib.reload(normalizeSelection) src = textwrap.dedent( """\ import textwrap @@ -40,16 +43,15 @@ def test_smart_shift_enter_multiple_statements(): print("Audi") print("BMW") print("Mercedes") - print("Audi") - print("BMW") - print("Mercedes") """ ) result = normalizeSelection.traverse_file(src, 8, 8, False) + # print(result) assert result == expected def test_two_layer_dictionary(): + importlib.reload(normalizeSelection) src = textwrap.dedent( """\ print("dont print me") @@ -89,4 +91,37 @@ def test_two_layer_dictionary(): """ ) result = normalizeSelection.traverse_file(src, 6, 7, False) + + assert result == expected + +def test_fstring(): + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + name = "Ahri" + age = 10 + print(f'My name is {name}') + """ + ) + + expected = textwrap.dedent( + """\ + name = "Ahri" + age = 10 + print(f'My name is {name}') + """ + ) + result = normalizeSelection.traverse_file(src, 1, 4, True) + assert result == expected + +def test_simple_print(): + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + print("Audi") + + """ + ) + print(normalizeSelection.traverse_file(src, 1, 1, False)) + From 3401e70c3348300cb11e1edaf3c66f5fbae437b1 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 16 Aug 2023 21:17:09 -0700 Subject: [PATCH 08/82] add simple list comprehension test --- pythonFiles/tests/test_smart_selection.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index 29b79e0c7423..3c5683c9e5f6 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -115,13 +115,26 @@ def test_fstring(): assert result == expected -def test_simple_print(): +def test_list_comp(): importlib.reload(normalizeSelection) - src = textwrap.dedent( + hi = textwrap.dedent( """\ - print("Audi") + names = ['Ahri', 'Bobby', 'Charlie'] + breed = ['Pomeranian', 'Welsh Corgi', 'Siberian Husky'] + dogs = [(name, breed) for name, breed in zip(names, breed)] + print(dogs) + """ + ) + expected = textwrap.dedent( + """\ + names = ['Ahri', 'Bobby', 'Charlie'] + breed = ['Pomeranian', 'Welsh Corgi', 'Siberian Husky'] + dogs = [(name, breed) for name, breed in zip(names, breed)] + print(dogs) """ ) - print(normalizeSelection.traverse_file(src, 1, 1, False)) + result = normalizeSelection.traverse_file(hi, 1, 4, True) + + assert result == expected From ca166332c93a113b4a4d5d0e1d0447db7c5cd6c9 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 16 Aug 2023 23:56:50 -0700 Subject: [PATCH 09/82] cherry pick exact statements and pickup inside def --- pythonFiles/normalizeSelection.py | 65 ++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 00609fbedf38..5cafe543900b 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -141,19 +141,39 @@ def normalize_lines(selection): should_run_top_blocks = [] +def check_exact_exist(top_level_nodes, start_line, end_line): + exact_node = None + for node in top_level_nodes: + if node.lineno == start_line and node.end_lineno == end_line: + exact_node = node + break + + return exact_node # Function that traverses the file and calculate the minimum viable top level block def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # use ast module to parse content of the file parsed_file_content = ast.parse(wholeFileContent) - temp_code = "" + smart_code = "" for node in ast.iter_child_nodes(parsed_file_content): top_level_nodes.append(node) + if hasattr(node, 'body'): + for child_nodes in node.body: + top_level_nodes.append(child_nodes) + # line_start = node.lineno # line_end = node.end_lineno # code_of_node = ast.get_source_segment(wholeFileContent, node) # ast.get_source_segment(wholeFileContent, node) This is way to get original code of the selected node + # top_level_nodes = sorted(top_level_nodes, key=lambda node: node.lineno) # Sort top level blocks in ascending for binary search + # has_exact_match = binary_search_node_by_line_numbers(top_level_nodes, start_line, end_line) + exact_node = check_exact_exist(top_level_nodes, start_line, end_line) + # Just return the exact top level line, if present. + if exact_node is not None: + smart_code += str(ast.get_source_segment(wholeFileContent, exact_node)) + smart_code += "\n" + return smart_code # With the given start_line and end_line number from VSCode, # Calculate the absolute difference between each of the top level block and given code (via line number) @@ -172,25 +192,25 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): if was_highlighted: # There was actual highlighting of some text # Smart Selection disbled for part of the broken send. if top_level_block_start_line >= start_line and top_level_block_end_line <= end_line: should_run_top_blocks.append(top_node) - temp_str = ast.get_source_segment(wholeFileContent, top_node) - temp_code += str(temp_str) - temp_code += "\n" + + smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) + smart_code += "\n" + elif start_line == top_level_block_start_line and end_line == top_level_block_end_line: + should_run_top_blocks.append(top_node) + + smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) + smart_code += "\n" + break # Break out of the loop since we found the exact match. else: # not highlighted case. Meaning just a cursor hanging if start_line >= top_level_block_start_line and end_line <= top_level_block_end_line: should_run_top_blocks.append(top_node) - temp_str = ast.get_source_segment(wholeFileContent, top_node) - temp_code += str(temp_str) - temp_code += "\n" + smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) + smart_code += "\n" - # get the minimum viable block node reference - # min_key = min(top_level_to_min_difference, key=top_level_to_min_difference.get) - # min_viable_code = ast.get_source_segment(wholeFileContent, min_key) # Minimum viable code - # normalized_min_viable_code = normalize_lines(min_viable_code) # Normalized minimum viable code + normalized_smart_result = normalize_lines(smart_code) - temp_result = normalize_lines(temp_code) - # return normalized_min_viable_code # return minimial viable code - return temp_result + return normalized_smart_result if __name__ == "__main__": # Content is being sent from the extension as a JSON object. @@ -198,9 +218,8 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): stdin = sys.stdin if sys.version_info < (3,) else sys.stdin.buffer raw = stdin.read() contents = json.loads(raw.decode("utf-8")) - - normalized = normalize_lines(contents["code"]) - normalized_whole_file = normalize_lines(contents["wholeFileContent"]) + # normalized = normalize_lines(contents["code"]) + # normalized_whole_file = normalize_lines(contents["wholeFileContent"]) # Need to get information on whether there was a selection via Highlight. # empty_Highlight = True empty_Highlight = False @@ -211,11 +230,19 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): vscode_start_line = contents["startLine"] + 1 vscode_end_line = contents["endLine"] + 1 - temp = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) # traverse file + # temp = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) # traverse file # Send the normalized code back to the extension in a JSON object. + data = None + # Depending on whether there was a explicit highlight, send smart selection or regular normalization. + if contents['emptyHighlight'] is True: + normalized = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) + else: + normalized = normalize_lines(contents["code"]) + + data = json.dumps({"normalized": normalized}) # data = json.dumps({"normalized": normalized}) # This is how it used to be - data = json.dumps({"normalized": temp}) + # data = json.dumps({"normalized": temp}) # 8/16/23 save stdout = sys.stdout if sys.version_info < (3,) else sys.stdout.buffer stdout.write(data.encode("utf-8")) stdout.close() From 7b2769e200429c38e6287391854aa03c768a079c Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 17 Aug 2023 00:08:33 -0700 Subject: [PATCH 10/82] fix multiple statements in same line --- pythonFiles/normalizeSelection.py | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 5cafe543900b..bedfd5d2b322 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -142,13 +142,13 @@ def normalize_lines(selection): should_run_top_blocks = [] def check_exact_exist(top_level_nodes, start_line, end_line): - exact_node = None + exact_nodes = [] for node in top_level_nodes: if node.lineno == start_line and node.end_lineno == end_line: - exact_node = node - break + exact_nodes.append(node) - return exact_node + + return exact_nodes # Function that traverses the file and calculate the minimum viable top level block def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): @@ -162,17 +162,13 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): for child_nodes in node.body: top_level_nodes.append(child_nodes) - # line_start = node.lineno - # line_end = node.end_lineno - # code_of_node = ast.get_source_segment(wholeFileContent, node) - # ast.get_source_segment(wholeFileContent, node) This is way to get original code of the selected node - # top_level_nodes = sorted(top_level_nodes, key=lambda node: node.lineno) # Sort top level blocks in ascending for binary search - # has_exact_match = binary_search_node_by_line_numbers(top_level_nodes, start_line, end_line) - exact_node = check_exact_exist(top_level_nodes, start_line, end_line) + exact_nodes = check_exact_exist(top_level_nodes, start_line, end_line) + # Just return the exact top level line, if present. - if exact_node is not None: - smart_code += str(ast.get_source_segment(wholeFileContent, exact_node)) - smart_code += "\n" + if len(exact_nodes) > 0: + for same_line_node in exact_nodes: + smart_code += str(ast.get_source_segment(wholeFileContent, same_line_node)) + smart_code += "\n" return smart_code # With the given start_line and end_line number from VSCode, @@ -183,11 +179,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # top_level_block_end_line = top_node.end_lineno if hasattr(top_node, "end_lineno") else 0 abs_difference = abs(start_line - top_level_block_start_line) + abs(end_line - top_level_block_end_line) top_level_to_min_difference[top_node] = abs_difference - # Also see if given start and end line is within the top level block 8/13/2023 -------------------------------------------- - # if top_level_block_start_line >= start_line or top_level_block_end_line >= end_line: - # should_run_top_blocks.append(top_node) - # temp_code += str(ast.get_source_segment(wholeFileContent, top_node)) - # temp_code += "\n" ---------------------------------------------------- + # We need to handle the case of 1. just hanging cursor vs. actual highlighting/selection. if was_highlighted: # There was actual highlighting of some text # Smart Selection disbled for part of the broken send. if top_level_block_start_line >= start_line and top_level_block_end_line <= end_line: From 6b20b3c48aea58eacbda2a7a82dfaa9c8b79c132 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 18 Aug 2023 00:12:32 -0700 Subject: [PATCH 11/82] dynamic cursor movement --- pythonFiles/normalizeSelection.py | 29 +++++++++++++++++--- src/client/terminals/codeExecution/helper.ts | 8 ++++-- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index bedfd5d2b322..48e94f32514c 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -138,7 +138,7 @@ def normalize_lines(selection): top_level_nodes = [] # collection of top level nodes top_level_to_min_difference = {} # dictionary of top level nodes to difference in relative to given code block to run min_key = None - +global_next_lineno = None should_run_top_blocks = [] def check_exact_exist(top_level_nodes, start_line, end_line): @@ -167,13 +167,15 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # Just return the exact top level line, if present. if len(exact_nodes) > 0: for same_line_node in exact_nodes: + should_run_top_blocks.append(same_line_node) smart_code += str(ast.get_source_segment(wholeFileContent, same_line_node)) smart_code += "\n" + global_next_lineno = get_next_block_lineno() return smart_code # With the given start_line and end_line number from VSCode, # Calculate the absolute difference between each of the top level block and given code (via line number) - for top_node in top_level_nodes: + for top_node in ast.iter_child_nodes(parsed_file_content): top_level_block_start_line = top_node.lineno top_level_block_end_line = top_node.end_lineno # top_level_block_end_line = top_node.end_lineno if hasattr(top_node, "end_lineno") else 0 @@ -183,11 +185,13 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # We need to handle the case of 1. just hanging cursor vs. actual highlighting/selection. if was_highlighted: # There was actual highlighting of some text # Smart Selection disbled for part of the broken send. if top_level_block_start_line >= start_line and top_level_block_end_line <= end_line: + # global should_run_top_blocks should_run_top_blocks.append(top_node) smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) smart_code += "\n" elif start_line == top_level_block_start_line and end_line == top_level_block_end_line: + # global should_run_top_blocks should_run_top_blocks.append(top_node) smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) @@ -195,15 +199,31 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): break # Break out of the loop since we found the exact match. else: # not highlighted case. Meaning just a cursor hanging if start_line >= top_level_block_start_line and end_line <= top_level_block_end_line: + # global should_run_top_blocks should_run_top_blocks.append(top_node) smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) smart_code += "\n" normalized_smart_result = normalize_lines(smart_code) + global_next_lineno = get_next_block_lineno() return normalized_smart_result +# Look at the last top block added, find lineno for the next upcoming block, +# This will allow us to move cursor in vscode. +def get_next_block_lineno(): + last_ran_lineno = int(should_run_top_blocks[-1].end_lineno) + temp_next_lineno = int(should_run_top_blocks[-1].end_lineno) + + # next_lineno = should_run_top_blocks[-1].end_lineno + + for reverse_node in top_level_nodes: + if reverse_node.lineno > last_ran_lineno: + temp_next_lineno = reverse_node.lineno + break + return temp_next_lineno - 1 + if __name__ == "__main__": # Content is being sent from the extension as a JSON object. # Decode the data from the raw bytes. @@ -231,8 +251,9 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): normalized = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) else: normalized = normalize_lines(contents["code"]) - - data = json.dumps({"normalized": normalized}) + # next_block_lineno + which_line_next = get_next_block_lineno() + data = json.dumps({"normalized": normalized, "nextBlockLineno": which_line_next}) # data = json.dumps({"normalized": normalized}) # This is how it used to be # data = json.dumps({"normalized": temp}) # 8/16/23 save stdout = sys.stdout if sys.version_info < (3,) else sys.stdout.buffer diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index afe4c59d8628..c15d9eccf352 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -80,7 +80,11 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { // We expect a serialized JSON object back, with the normalized code under the "normalized" key. const result = await normalizeOutput.promise; const object = JSON.parse(result); - + // this.smartMoveCursor(object.nextBlockIndex); + // commands.executeCommand('cursorMove', { to: 'down'}); + // calculate and return offset + const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line; + commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); return parse(object.normalized); } catch (ex) { traceError(ex, 'Python: Failed to normalize code for execution in terminal'); @@ -117,7 +121,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { const { selection } = textEditor; let code: string; - const wholeFileContent = textEditor.document.getText(); // This is a way to get the whole text content from the user + // const wholeFileContent = textEditor.document.getText(); // This is a way to get the whole text content from the user if (selection.isEmpty) { code = textEditor.document.lineAt(selection.start.line).text; } else if (selection.isSingleLine) { From 74eb85ac6e0bdbc501766d31f5cdf8bab8489ca7 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Sun, 20 Aug 2023 01:45:16 -0700 Subject: [PATCH 12/82] fix broken prev normalization --- pythonFiles/normalizeSelection.py | 4 +++- src/client/terminals/codeExecution/helper.ts | 7 +++++-- src/test/terminals/codeExecution/helper.test.ts | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 48e94f32514c..b2cca1609aa3 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -246,13 +246,15 @@ def get_next_block_lineno(): # Send the normalized code back to the extension in a JSON object. data = None + which_line_next = 0 # Depending on whether there was a explicit highlight, send smart selection or regular normalization. if contents['emptyHighlight'] is True: normalized = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) + which_line_next = get_next_block_lineno() # Only figure out next block line number for smart shift+enter else: normalized = normalize_lines(contents["code"]) # next_block_lineno - which_line_next = get_next_block_lineno() + # which_line_next = get_next_block_lineno() data = json.dumps({"normalized": normalized, "nextBlockLineno": which_line_next}) # data = json.dumps({"normalized": normalized}) # This is how it used to be # data = json.dumps({"normalized": temp}) # 8/16/23 save diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index c15d9eccf352..5ea8b6ff7811 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -83,8 +83,11 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { // this.smartMoveCursor(object.nextBlockIndex); // commands.executeCommand('cursorMove', { to: 'down'}); // calculate and return offset - const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line; - commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); + // Smart cursor move only for smart shift+enter + if (activeEditor!.selection.isEmpty) { + const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line; + commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); + } return parse(object.normalized); } catch (ex) { traceError(ex, 'Python: Failed to normalize code for execution in terminal'); diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index ac47037a2344..5de4e276f38d 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -112,7 +112,7 @@ suite('Terminal - Code Execution Helper', () => { return ({} as unknown) as ObservableExecutionResult; }); - await helper.normalizeLines('print("hello")'); + await helper.normalizeLines('print("hello")', 'print("hello")'); expect(execArgs).to.contain('normalizeSelection.py'); }); @@ -124,7 +124,7 @@ suite('Terminal - Code Execution Helper', () => { .returns((file, args, options) => actualProcessService.execObservable.apply(actualProcessService, [file, args, options]), ); - const normalizedCode = await helper.normalizeLines(source); + const normalizedCode = await helper.normalizeLines(source, source); const normalizedExpected = expectedSource.replace(/\r\n/g, '\n'); expect(normalizedCode).to.be.equal(normalizedExpected); } From 8cfe2d5dfafa0932233adbd6d430207d9bb506b5 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 21 Aug 2023 16:02:42 -0700 Subject: [PATCH 13/82] start setting up experiment for smart send --- package.json | 12 ++++++---- package.nls.json | 1 + src/client/common/experiments/groups.ts | 4 ++++ src/client/terminals/codeExecution/helper.ts | 23 +++++++++++++++----- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 7e044a95f7af..16337e6eb669 100644 --- a/package.json +++ b/package.json @@ -544,14 +544,16 @@ "pythonSurveyNotification", "pythonPromptNewToolsExt", "pythonTerminalEnvVarActivation", - "pythonTestAdapter" + "pythonTestAdapter", + "pythonREPLSmartSend" ], "enumDescriptions": [ "%python.experiments.All.description%", "%python.experiments.pythonSurveyNotification.description%", "%python.experiments.pythonPromptNewToolsExt.description%", "%python.experiments.pythonTerminalEnvVarActivation.description%", - "%python.experiments.pythonTestAdapter.description%" + "%python.experiments.pythonTestAdapter.description%", + "%python.experiments.pythonREPLSmartSend.description%" ] }, "scope": "machine", @@ -567,14 +569,16 @@ "pythonSurveyNotification", "pythonPromptNewToolsExt", "pythonTerminalEnvVarActivation", - "pythonTestAdapter" + "pythonTestAdapter", + "pythonREPLSmartSend" ], "enumDescriptions": [ "%python.experiments.All.description%", "%python.experiments.pythonSurveyNotification.description%", "%python.experiments.pythonPromptNewToolsExt.description%", "%python.experiments.pythonTerminalEnvVarActivation.description%", - "%python.experiments.pythonTestAdapter.description%" + "%python.experiments.pythonTestAdapter.description%", + "%python.experiments.pythonREPLSmartSend.description%" ] }, "scope": "machine", diff --git a/package.nls.json b/package.nls.json index b8e82b150e76..e74858f3ecf0 100644 --- a/package.nls.json +++ b/package.nls.json @@ -44,6 +44,7 @@ "python.experiments.pythonPromptNewToolsExt.description": "Denotes the Python Prompt New Tools Extension experiment.", "python.experiments.pythonTerminalEnvVarActivation.description": "Enables use of environment variables to activate terminals instead of sending activation commands.", "python.experiments.pythonTestAdapter.description": "Denotes the Python Test Adapter experiment.", + "python.experiments.pythonREPLSmartSend.description": "Denotes the Python REPL Smart Send experiment.", "python.formatting.autopep8Args.description": "Arguments passed in. Each argument is a separate item in the array.", "python.formatting.autopep8Args.markdownDeprecationMessage": "This setting will soon be deprecated. Please use the [Autopep8 extension](https://marketplace.visualstudio.com/items?itemName=ms-python.autopep8).
Learn more [here](https://aka.ms/AAlgvkb).", "python.formatting.autopep8Args.deprecationMessage": "This setting will soon be deprecated. Please use the Autopep8 extension. Learn more here: https://aka.ms/AAlgvkb.", diff --git a/src/client/common/experiments/groups.ts b/src/client/common/experiments/groups.ts index 1ee06469095c..b7a598e0a08a 100644 --- a/src/client/common/experiments/groups.ts +++ b/src/client/common/experiments/groups.ts @@ -18,3 +18,7 @@ export enum ShowFormatterExtensionPrompt { export enum EnableTestAdapterRewrite { experiment = 'pythonTestAdapter', } +// Experiment to enable smart shift+enter, advance cursor. +export enum EnableREPLSmartSend { + experiment = 'pythonREPLSmartSend', +} diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 5ea8b6ff7811..1e21c5bdd249 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -14,7 +14,8 @@ import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; import { ICodeExecutionHelper } from '../types'; import { traceError } from '../../logging'; -import { Resource } from '../../common/types'; +import { IExperimentService, Resource } from '../../common/types'; +import { EnableREPLSmartSend } from '../../common/experiments/groups'; @injectable() export class CodeExecutionHelper implements ICodeExecutionHelper { @@ -26,11 +27,14 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { private readonly interpreterService: IInterpreterService; + // private readonly configSettings: IConfigurationService; + constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) { this.documentManager = serviceContainer.get(IDocumentManager); this.applicationShell = serviceContainer.get(IApplicationShell); this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); this.interpreterService = serviceContainer.get(IInterpreterService); + // this.configSettings = serviceContainer.get(IConfigurationService); } public async normalizeLines(code: string, wholeFileContent: string, resource?: Uri): Promise { @@ -80,14 +84,16 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { // We expect a serialized JSON object back, with the normalized code under the "normalized" key. const result = await normalizeOutput.promise; const object = JSON.parse(result); - // this.smartMoveCursor(object.nextBlockIndex); - // commands.executeCommand('cursorMove', { to: 'down'}); - // calculate and return offset - // Smart cursor move only for smart shift+enter - if (activeEditor!.selection.isEmpty) { + + // Let user experience smart shift+enter, advance cursor if in experiment + if (pythonSmartSendEnabled(this.serviceContainer)) { + // Advance cursor move only for smart shift+enter + if (activeEditor!.selection.isEmpty) { const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line; commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); + } } + return parse(object.normalized); } catch (ex) { traceError(ex, 'Python: Failed to normalize code for execution in terminal'); @@ -251,3 +257,8 @@ function getMultiLineSelectionText(textEditor: TextEditor): string { // ↑<---------------- To here return selectionText; } + +function pythonSmartSendEnabled(serviceContainer: IServiceContainer): boolean { + const experiment = serviceContainer.get(IExperimentService); + return experiment.inExperimentSync(EnableREPLSmartSend.experiment); +} From 6dd24406e043a2e62b8430e8aba89ef8ce785024 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 22 Aug 2023 10:30:50 -0700 Subject: [PATCH 14/82] make dynamic cursor movement to move to EOL --- src/client/terminals/codeExecution/helper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 1e21c5bdd249..e257ec1dc842 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -91,6 +91,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { if (activeEditor!.selection.isEmpty) { const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line; commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); + commands.executeCommand('cursorEnd'); } } From 262ac48a419100308f2d9133156595048013367b Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 22 Aug 2023 10:41:04 -0700 Subject: [PATCH 15/82] fix broken helper.test.ts --- src/client/terminals/codeExecution/helper.ts | 2 +- src/client/terminals/types.ts | 2 +- src/test/terminals/codeExecution/helper.test.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index e257ec1dc842..34c386486303 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -37,7 +37,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { // this.configSettings = serviceContainer.get(IConfigurationService); } - public async normalizeLines(code: string, wholeFileContent: string, resource?: Uri): Promise { + public async normalizeLines(code: string, wholeFileContent?: string, resource?: Uri): Promise { try { if (code.trim().length === 0) { return ''; diff --git a/src/client/terminals/types.ts b/src/client/terminals/types.ts index 585fea913166..48e39d4e1c81 100644 --- a/src/client/terminals/types.ts +++ b/src/client/terminals/types.ts @@ -15,7 +15,7 @@ export interface ICodeExecutionService { export const ICodeExecutionHelper = Symbol('ICodeExecutionHelper'); export interface ICodeExecutionHelper { - normalizeLines(code: string, wholeFileContent: string): Promise; + normalizeLines(code: string, wholeFileContent?: string): Promise; getFileToExecute(): Promise; saveFileIfDirty(file: Uri): Promise; getSelectedTextToExecute(textEditor: TextEditor): Promise; diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index 5de4e276f38d..ac47037a2344 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -112,7 +112,7 @@ suite('Terminal - Code Execution Helper', () => { return ({} as unknown) as ObservableExecutionResult; }); - await helper.normalizeLines('print("hello")', 'print("hello")'); + await helper.normalizeLines('print("hello")'); expect(execArgs).to.contain('normalizeSelection.py'); }); @@ -124,7 +124,7 @@ suite('Terminal - Code Execution Helper', () => { .returns((file, args, options) => actualProcessService.execObservable.apply(actualProcessService, [file, args, options]), ); - const normalizedCode = await helper.normalizeLines(source, source); + const normalizedCode = await helper.normalizeLines(source); const normalizedExpected = expectedSource.replace(/\r\n/g, '\n'); expect(normalizedCode).to.be.equal(normalizedExpected); } From f63fcc42d0763601b996e95ab9cab9280a3c9a8d Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 22 Aug 2023 11:03:58 -0700 Subject: [PATCH 16/82] fix linting complaints --- pythonFiles/normalizeSelection.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index b2cca1609aa3..b52ad429ca72 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -159,8 +159,25 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): for node in ast.iter_child_nodes(parsed_file_content): top_level_nodes.append(node) if hasattr(node, 'body'): - for child_nodes in node.body: - top_level_nodes.append(child_nodes) + # Not adding below check will have linting and python type complain + if (isinstance(node, ast.Module) or + isinstance(node, ast.Interactive) or + isinstance(node, ast.Expression) or + isinstance(node, ast.FunctionDef) or + isinstance(node, ast.AsyncFunctionDef) or + isinstance(node, ast.ClassDef) or + isinstance(node, ast.For) or + isinstance(node, ast.AsyncFor) or + isinstance(node, ast.While) or + isinstance(node, ast.If) or + isinstance(node, ast.With) or + isinstance(node, ast.AsyncWith) or + isinstance(node, ast.Try) or + isinstance(node, ast.Lambda) or + isinstance(node, ast.IfExp) or + isinstance(node, ast.ExceptHandler)): + for child_nodes in node.body: + top_level_nodes.append(child_nodes) exact_nodes = check_exact_exist(top_level_nodes, start_line, end_line) From 0caad7f4cd6f96ecc23ef6025a40c26214a37f05 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 22 Aug 2023 12:34:53 -0700 Subject: [PATCH 17/82] run prettier on help.ts --- src/client/terminals/codeExecution/helper.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 34c386486303..02bab4048ab5 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -89,9 +89,9 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { if (pythonSmartSendEnabled(this.serviceContainer)) { // Advance cursor move only for smart shift+enter if (activeEditor!.selection.isEmpty) { - const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line; - commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); - commands.executeCommand('cursorEnd'); + const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line; + commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); + commands.executeCommand('cursorEnd'); } } From 54dc0c70c4ce28f37015168984a2e9b0a40d27d8 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 22 Aug 2023 17:01:29 -0700 Subject: [PATCH 18/82] figure out which line is causing prev test to fail --- pythonFiles/normalizeSelection.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index b52ad429ca72..f84d880d8cb7 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -125,6 +125,7 @@ def normalize_lines(selection): # Insert a newline between each top-level statement, and append a newline to the selection. source = "\n".join(statements) + "\n" + # source = "\n".join(statements) if (selection[-2] == '}'): source = source[:-1] # source = "\n".join(statements) @@ -266,8 +267,9 @@ def get_next_block_lineno(): which_line_next = 0 # Depending on whether there was a explicit highlight, send smart selection or regular normalization. if contents['emptyHighlight'] is True: - normalized = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) - which_line_next = get_next_block_lineno() # Only figure out next block line number for smart shift+enter + # normalized = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) + # which_line_next = get_next_block_lineno() # Only figure out next block line number for smart shift+ + normalized = normalize_lines(contents["code"]) else: normalized = normalize_lines(contents["code"]) # next_block_lineno From a65d32998bb089c2f8aea356f45c35e7a5400101 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 22 Aug 2023 18:05:38 -0700 Subject: [PATCH 19/82] investigate previous normalization --- pythonFiles/normalizeSelection.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index f84d880d8cb7..5f49a0157e53 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -266,12 +266,12 @@ def get_next_block_lineno(): data = None which_line_next = 0 # Depending on whether there was a explicit highlight, send smart selection or regular normalization. - if contents['emptyHighlight'] is True: - # normalized = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) - # which_line_next = get_next_block_lineno() # Only figure out next block line number for smart shift+ - normalized = normalize_lines(contents["code"]) - else: - normalized = normalize_lines(contents["code"]) + # if contents['emptyHighlight'] is True: + # normalized = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) + # which_line_next = get_next_block_lineno() # Only figure out next block line number for smart shift+ + # # normalized = normalize_lines(contents["code"]) + # else: + normalized = normalize_lines(contents["code"]) # next_block_lineno # which_line_next = get_next_block_lineno() data = json.dumps({"normalized": normalized, "nextBlockLineno": which_line_next}) From 461851fc43f196fe49b9e88745a215287866ccd1 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 28 Aug 2023 21:56:47 -0700 Subject: [PATCH 20/82] uncomment emptyHighlight --- pythonFiles/normalizeSelection.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 5f49a0157e53..9cbf0fd1511a 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -266,12 +266,12 @@ def get_next_block_lineno(): data = None which_line_next = 0 # Depending on whether there was a explicit highlight, send smart selection or regular normalization. - # if contents['emptyHighlight'] is True: - # normalized = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) - # which_line_next = get_next_block_lineno() # Only figure out next block line number for smart shift+ - # # normalized = normalize_lines(contents["code"]) - # else: - normalized = normalize_lines(contents["code"]) + if contents['emptyHighlight'] is True: + normalized = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) + which_line_next = get_next_block_lineno() # Only figure out next block line number for smart shift+ + # normalized = normalize_lines(contents["code"]) + else: + normalized = normalize_lines(contents["code"]) # next_block_lineno # which_line_next = get_next_block_lineno() data = json.dumps({"normalized": normalized, "nextBlockLineno": which_line_next}) From 8f23da7ae2b158252d32e9cefce6dbb75c14f39e Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 21 Aug 2023 16:02:42 -0700 Subject: [PATCH 21/82] start setting up experiment for smart send --- src/client/terminals/codeExecution/helper.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 02bab4048ab5..8e093082a510 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -14,8 +14,8 @@ import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; import { ICodeExecutionHelper } from '../types'; import { traceError } from '../../logging'; -import { IExperimentService, Resource } from '../../common/types'; -import { EnableREPLSmartSend } from '../../common/experiments/groups'; +import { IExperimentService, IExperimentService, Resource } from '../../common/types'; +import { EnableREPLSmartSend, EnableREPLSmartSend } from '../../common/experiments/groups'; @injectable() export class CodeExecutionHelper implements ICodeExecutionHelper { @@ -29,6 +29,8 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { // private readonly configSettings: IConfigurationService; + // private readonly configSettings: IConfigurationService; + constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) { this.documentManager = serviceContainer.get(IDocumentManager); this.applicationShell = serviceContainer.get(IApplicationShell); @@ -263,3 +265,8 @@ function pythonSmartSendEnabled(serviceContainer: IServiceContainer): boolean { const experiment = serviceContainer.get(IExperimentService); return experiment.inExperimentSync(EnableREPLSmartSend.experiment); } + +function pythonSmartSendEnabled(serviceContainer: IServiceContainer): boolean { + const experiment = serviceContainer.get(IExperimentService); + return experiment.inExperimentSync(EnableREPLSmartSend.experiment); +} From c57fc7d3787c859e167147f8ffe4df3553438cc5 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 12 Sep 2023 12:01:50 -0700 Subject: [PATCH 22/82] remove duplicate import in helper.ts --- src/client/terminals/codeExecution/helper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 8e093082a510..9affeb45154f 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -14,8 +14,8 @@ import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; import { ICodeExecutionHelper } from '../types'; import { traceError } from '../../logging'; -import { IExperimentService, IExperimentService, Resource } from '../../common/types'; -import { EnableREPLSmartSend, EnableREPLSmartSend } from '../../common/experiments/groups'; +import { IExperimentService, Resource } from '../../common/types'; +import { EnableREPLSmartSend } from '../../common/experiments/groups'; @injectable() export class CodeExecutionHelper implements ICodeExecutionHelper { From 5eca7ff51ada91d1506681c4127524ead06662d8 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 12 Sep 2023 12:04:29 -0700 Subject: [PATCH 23/82] remove duplicate function --- src/client/terminals/codeExecution/helper.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 9affeb45154f..28decc3dc9bb 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -265,8 +265,3 @@ function pythonSmartSendEnabled(serviceContainer: IServiceContainer): boolean { const experiment = serviceContainer.get(IExperimentService); return experiment.inExperimentSync(EnableREPLSmartSend.experiment); } - -function pythonSmartSendEnabled(serviceContainer: IServiceContainer): boolean { - const experiment = serviceContainer.get(IExperimentService); - return experiment.inExperimentSync(EnableREPLSmartSend.experiment); -} From 92cf2fdc14bac0ceb50c946283ce9358f1c0e340 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 12 Sep 2023 16:27:19 -0700 Subject: [PATCH 24/82] format normalize and test_smart_selection --- pythonFiles/normalizeSelection.py | 97 +++++++++++++++-------- pythonFiles/tests/test_smart_selection.py | 2 + 2 files changed, 65 insertions(+), 34 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 9cbf0fd1511a..85726a4f1157 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -13,6 +13,7 @@ # sys.path.append(os.fspath(script_dir)) # import debugpy + # debugpy.connect(5678) # debugpy.breakpoint() def split_lines(source): @@ -126,7 +127,7 @@ def normalize_lines(selection): # Insert a newline between each top-level statement, and append a newline to the selection. source = "\n".join(statements) + "\n" # source = "\n".join(statements) - if (selection[-2] == '}'): + if selection[-2] == "}": source = source[:-1] # source = "\n".join(statements) except Exception: @@ -136,21 +137,25 @@ def normalize_lines(selection): return source -top_level_nodes = [] # collection of top level nodes -top_level_to_min_difference = {} # dictionary of top level nodes to difference in relative to given code block to run + +top_level_nodes = [] # collection of top level nodes +top_level_to_min_difference = ( + {} +) # dictionary of top level nodes to difference in relative to given code block to run min_key = None global_next_lineno = None should_run_top_blocks = [] + def check_exact_exist(top_level_nodes, start_line, end_line): exact_nodes = [] for node in top_level_nodes: if node.lineno == start_line and node.end_lineno == end_line: exact_nodes.append(node) - return exact_nodes + # Function that traverses the file and calculate the minimum viable top level block def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # use ast module to parse content of the file @@ -159,26 +164,28 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): for node in ast.iter_child_nodes(parsed_file_content): top_level_nodes.append(node) - if hasattr(node, 'body'): + if hasattr(node, "body"): # Not adding below check will have linting and python type complain - if (isinstance(node, ast.Module) or - isinstance(node, ast.Interactive) or - isinstance(node, ast.Expression) or - isinstance(node, ast.FunctionDef) or - isinstance(node, ast.AsyncFunctionDef) or - isinstance(node, ast.ClassDef) or - isinstance(node, ast.For) or - isinstance(node, ast.AsyncFor) or - isinstance(node, ast.While) or - isinstance(node, ast.If) or - isinstance(node, ast.With) or - isinstance(node, ast.AsyncWith) or - isinstance(node, ast.Try) or - isinstance(node, ast.Lambda) or - isinstance(node, ast.IfExp) or - isinstance(node, ast.ExceptHandler)): - for child_nodes in node.body: - top_level_nodes.append(child_nodes) + if ( + isinstance(node, ast.Module) + or isinstance(node, ast.Interactive) + or isinstance(node, ast.Expression) + or isinstance(node, ast.FunctionDef) + or isinstance(node, ast.AsyncFunctionDef) + or isinstance(node, ast.ClassDef) + or isinstance(node, ast.For) + or isinstance(node, ast.AsyncFor) + or isinstance(node, ast.While) + or isinstance(node, ast.If) + or isinstance(node, ast.With) + or isinstance(node, ast.AsyncWith) + or isinstance(node, ast.Try) + or isinstance(node, ast.Lambda) + or isinstance(node, ast.IfExp) + or isinstance(node, ast.ExceptHandler) + ): + for child_nodes in node.body: + top_level_nodes.append(child_nodes) exact_nodes = check_exact_exist(top_level_nodes, start_line, end_line) @@ -197,26 +204,39 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): top_level_block_start_line = top_node.lineno top_level_block_end_line = top_node.end_lineno # top_level_block_end_line = top_node.end_lineno if hasattr(top_node, "end_lineno") else 0 - abs_difference = abs(start_line - top_level_block_start_line) + abs(end_line - top_level_block_end_line) + abs_difference = abs(start_line - top_level_block_start_line) + abs( + end_line - top_level_block_end_line + ) top_level_to_min_difference[top_node] = abs_difference # We need to handle the case of 1. just hanging cursor vs. actual highlighting/selection. - if was_highlighted: # There was actual highlighting of some text # Smart Selection disbled for part of the broken send. - if top_level_block_start_line >= start_line and top_level_block_end_line <= end_line: + if ( + was_highlighted + ): # There was actual highlighting of some text # Smart Selection disbled for part of the broken send. + if ( + top_level_block_start_line >= start_line + and top_level_block_end_line <= end_line + ): # global should_run_top_blocks should_run_top_blocks.append(top_node) smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) smart_code += "\n" - elif start_line == top_level_block_start_line and end_line == top_level_block_end_line: + elif ( + start_line == top_level_block_start_line + and end_line == top_level_block_end_line + ): # global should_run_top_blocks should_run_top_blocks.append(top_node) smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) smart_code += "\n" - break # Break out of the loop since we found the exact match. - else: # not highlighted case. Meaning just a cursor hanging - if start_line >= top_level_block_start_line and end_line <= top_level_block_end_line: + break # Break out of the loop since we found the exact match. + else: # not highlighted case. Meaning just a cursor hanging + if ( + start_line >= top_level_block_start_line + and end_line <= top_level_block_end_line + ): # global should_run_top_blocks should_run_top_blocks.append(top_node) @@ -228,6 +248,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): return normalized_smart_result + # Look at the last top block added, find lineno for the next upcoming block, # This will allow us to move cursor in vscode. def get_next_block_lineno(): @@ -242,6 +263,7 @@ def get_next_block_lineno(): break return temp_next_lineno - 1 + if __name__ == "__main__": # Content is being sent from the extension as a JSON object. # Decode the data from the raw bytes. @@ -253,7 +275,7 @@ def get_next_block_lineno(): # Need to get information on whether there was a selection via Highlight. # empty_Highlight = True empty_Highlight = False - if contents['emptyHighlight'] is True: + if contents["emptyHighlight"] is True: empty_Highlight = True # we also get the activeEditor selection start line and end line from the typescript vscode side # remember to add 1 to each of the received since vscode starts line counting from 0 @@ -266,9 +288,16 @@ def get_next_block_lineno(): data = None which_line_next = 0 # Depending on whether there was a explicit highlight, send smart selection or regular normalization. - if contents['emptyHighlight'] is True: - normalized = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) - which_line_next = get_next_block_lineno() # Only figure out next block line number for smart shift+ + if contents["emptyHighlight"] is True: + normalized = traverse_file( + contents["wholeFileContent"], + vscode_start_line, + vscode_end_line, + not empty_Highlight, + ) + which_line_next = ( + get_next_block_lineno() + ) # Only figure out next block line number for smart shift+ # normalized = normalize_lines(contents["code"]) else: normalized = normalize_lines(contents["code"]) diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index 3c5683c9e5f6..3865dfec2693 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -94,6 +94,7 @@ def test_two_layer_dictionary(): assert result == expected + def test_fstring(): importlib.reload(normalizeSelection) src = textwrap.dedent( @@ -115,6 +116,7 @@ def test_fstring(): assert result == expected + def test_list_comp(): importlib.reload(normalizeSelection) hi = textwrap.dedent( From 143ed5a8117347e19495edd39c00ceb42b2f47bb Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 13 Sep 2023 00:08:09 -0700 Subject: [PATCH 25/82] format python files for ruff compliance --- pythonFiles/tests/test_normalize_selection.py | 3 +-- pythonFiles/tests/test_smart_selection.py | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/pythonFiles/tests/test_normalize_selection.py b/pythonFiles/tests/test_normalize_selection.py index 53afa9c30473..d2d45d1ce6af 100644 --- a/pythonFiles/tests/test_normalize_selection.py +++ b/pythonFiles/tests/test_normalize_selection.py @@ -1,8 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -import os -import sys + import textwrap # __file__ = "/Users/anthonykim/Desktop/vscode-python/pythonFiles/normalizeSelection.py" diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index 3865dfec2693..6bee3008f493 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -1,7 +1,4 @@ -import ast import importlib -import os -import sys import textwrap import normalizeSelection @@ -9,7 +6,7 @@ def test_part_dictionary(): importlib.reload(normalizeSelection) - src = 'import textwrap\nimport ast\n\nprint("Audi")\nprint("Genesis")\n\n\nprint("Audi");print("BMW");print("Mercedes")\n\nmy_dict = {\n "key1": "value1",\n "key2": "value2"\n}\n\n\nsrc = """\nmy_dict = {\n"key1": "value1",\n"key2": "value2"\n}\n"""\n\ntop_level_nodes = []\n\nparsed_file_content = ast.parse(src)\nprint(ast.dump(parsed_file_content))\n\nparsed_dict_content2 = ast.parse(str(my_dict))\nprint(ast.dump(parsed_dict_content2))\n\n\nfor node in ast.iter_child_nodes(parsed_file_content):\n top_level_nodes.append(node)\n line_start = node.lineno\n line_end = node.end_lineno\n code_of_node = ast.get_source_segment(wholeFileContent, node)\n ast.get_source_segment(wholeFileContent, node) # This is way to get original code of the selected node\n\n################################################################################\n# New test case(s):\n# what should happen when shift enter at line 5? \n# follow ast says ----- TODO \n\n# execute individually line 5 bc two statements ---- TODO \n#################################################################################' + src = 'import textwrap\nimport ast\n\nprint("Audi")\nprint("Genesis")\n\n\nprint("Audi");print("BMW");print("Mercedes")\n\nmy_dict = {\n "key1": "value1",\n "key2": "value2"\n}\n\n\nsrc = """\nmy_dict = {\n"key1": "value1",\n"key2": "value2"\n}\n"""\n\ntop_level_nodes = []\n\nparsed_file_content = ast.parse(src)\nprint(ast.dump(parsed_file_content))\n\nparsed_dict_content2 = ast.parse(str(my_dict))\nprint(ast.dump(parsed_dict_content2))\n\n\nfor node in ast.iter_child_nodes(parsed_file_content):\n top_level_nodes.append(node)\n line_start = node.lineno\n line_end = node.end_lineno\n code_of_node = ast.get_source_segment(wholeFileContent, node)\n ast.get_source_segment(wholeFileContent, node) # This is way to get original code of the selected node\n\n################################################################################\n# New test case(s):\n# what should happen when shift enter at line 5? \n# follow ast says ----- TODO \n\n# execute individually line 5 bc two statements ---- TODO \n#################################################################################' # noqa: E501 expected = 'my_dict = {\n "key1": "value1",\n "key2": "value2"\n}\n' # parsed_file_content = ast.parse(src) From 874cb067fad62bc79debc985da24d5aeb0af8bc9 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 13 Sep 2023 23:54:31 -0700 Subject: [PATCH 26/82] add file scope to EventName.EXECUTION_CODE --- src/client/telemetry/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index f4947cd73f05..c0661d3fa3e6 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -821,11 +821,11 @@ export interface IEventNamePropertyMapping { */ [EventName.EXECUTION_CODE]: { /** - * Whether the user executed a file in the terminal or just the selected text. + * Whether the user executed a file in the terminal or just the selected text or line by shift+enter. * * @type {('file' | 'selection')} */ - scope: 'file' | 'selection'; + scope: 'file' | 'selection' | 'line'; /** * How was the code executed (through the command or by clicking the `Run File` icon). * From 75a21354475a4690e83f0d9e522769cb05a120f0 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 14 Sep 2023 00:20:33 -0700 Subject: [PATCH 27/82] add imports for new telemetry scope --- src/client/terminals/codeExecution/helper.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 28decc3dc9bb..23647d243c49 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -16,6 +16,8 @@ import { ICodeExecutionHelper } from '../types'; import { traceError } from '../../logging'; import { IExperimentService, Resource } from '../../common/types'; import { EnableREPLSmartSend } from '../../common/experiments/groups'; +import { sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; @injectable() export class CodeExecutionHelper implements ICodeExecutionHelper { @@ -70,7 +72,10 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { normalizeOutput.resolve(normalized); }, }); - + // If there is no explicit selection, we are exeucting 'line' or 'block'. + if (activeEditor!.selection.isEmpty) { + sendTelemetryEvent(EventName.EXECUTION_CODE, undefined, { scope: 'line' }); + } // The normalization script expects a serialized JSON object, with the selection under the "code" key. // We're using a JSON object so that we don't have to worry about encoding, or escaping non-ASCII characters. const input = JSON.stringify({ From 33b3f5a9606a5c34505e1ceef0e311887eed59ce Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 14 Sep 2023 11:25:09 -0700 Subject: [PATCH 28/82] fix broken unittest --- src/client/terminals/codeExecution/codeExecutionManager.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/terminals/codeExecution/codeExecutionManager.ts b/src/client/terminals/codeExecution/codeExecutionManager.ts index 5e1f66b89d2d..534059e89a7c 100644 --- a/src/client/terminals/codeExecution/codeExecutionManager.ts +++ b/src/client/terminals/codeExecution/codeExecutionManager.ts @@ -147,7 +147,10 @@ export class CodeExecutionManager implements ICodeExecutionManager { } const codeExecutionHelper = this.serviceContainer.get(ICodeExecutionHelper); const codeToExecute = await codeExecutionHelper.getSelectedTextToExecute(activeEditor!); - const wholeFileContent = activeEditor!.document.getText(); + let wholeFileContent = ''; + if (activeEditor && activeEditor.document) { + wholeFileContent = activeEditor.document.getText(); + } const normalizedCode = await codeExecutionHelper.normalizeLines(codeToExecute!, wholeFileContent); if (!normalizedCode || normalizedCode.trim().length === 0) { return; From fe0603a3086258f7bf86ceb6490e2f26b76506f5 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 14 Sep 2023 11:52:22 -0700 Subject: [PATCH 29/82] check if node.body is iterable --- pythonFiles/normalizeSelection.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 85726a4f1157..1ebc01119f74 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -8,6 +8,7 @@ import re import sys import textwrap +from typing import Iterable # script_dir = pathlib.Path('User/anthonykim/Desktop/vscode-python/pythonFiles/lib/python') # sys.path.append(os.fspath(script_dir)) @@ -184,8 +185,9 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): or isinstance(node, ast.IfExp) or isinstance(node, ast.ExceptHandler) ): - for child_nodes in node.body: - top_level_nodes.append(child_nodes) + if isinstance(node.body, Iterable): + for child_nodes in node.body: + top_level_nodes.append(child_nodes) exact_nodes = check_exact_exist(top_level_nodes, start_line, end_line) From bcf67cb8a0dda8a7d7941d1ef22152b7c1455b74 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 14 Sep 2023 16:24:27 -0700 Subject: [PATCH 30/82] switch behavior if smart send experiment enabled --- pythonFiles/normalizeSelection.py | 3 ++- src/client/terminals/codeExecution/helper.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 1ebc01119f74..a6e72db1bb29 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -290,7 +290,8 @@ def get_next_block_lineno(): data = None which_line_next = 0 # Depending on whether there was a explicit highlight, send smart selection or regular normalization. - if contents["emptyHighlight"] is True: + # Experiment also has to be enable to use smart selection. + if contents["emptyHighlight"] is True and contents["smartSendExperimentEnabled"] is True: normalized = traverse_file( contents["wholeFileContent"], vscode_start_line, diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 23647d243c49..bbd59ef12a17 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -84,6 +84,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { startLine: activeEditor!.selection.start.line, endLine: activeEditor!.selection.end.line, emptyHighlight: activeEditor!.selection.isEmpty, + smartSendExperimentEnabled: pythonSmartSendEnabled(this.serviceContainer), }); observable.proc?.stdin?.write(input); observable.proc?.stdin?.end(); From 871e91ba4a9a7e3e0549abb877b72702a2d7ff45 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 14 Sep 2023 16:37:14 -0700 Subject: [PATCH 31/82] format normalizeSelection --- pythonFiles/normalizeSelection.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index a6e72db1bb29..24362ed29116 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -291,7 +291,10 @@ def get_next_block_lineno(): which_line_next = 0 # Depending on whether there was a explicit highlight, send smart selection or regular normalization. # Experiment also has to be enable to use smart selection. - if contents["emptyHighlight"] is True and contents["smartSendExperimentEnabled"] is True: + if ( + contents["emptyHighlight"] is True + and contents["smartSendExperimentEnabled"] is True + ): normalized = traverse_file( contents["wholeFileContent"], vscode_start_line, From 360069f586a3996b7e9a99d9f5518ced3768a965 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 15 Sep 2023 18:18:33 -0700 Subject: [PATCH 32/82] wrapper for undefined values --- pythonFiles/normalizeSelection.py | 6 ++---- src/client/terminals/codeExecution/helper.ts | 21 +++++++++++++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 24362ed29116..4f3ec142942b 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -166,7 +166,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): for node in ast.iter_child_nodes(parsed_file_content): top_level_nodes.append(node) if hasattr(node, "body"): - # Not adding below check will have linting and python type complain + # Check if node has attribute body, then we have to go add child blocks. if ( isinstance(node, ast.Module) or isinstance(node, ast.Interactive) @@ -205,7 +205,6 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): for top_node in ast.iter_child_nodes(parsed_file_content): top_level_block_start_line = top_node.lineno top_level_block_end_line = top_node.end_lineno - # top_level_block_end_line = top_node.end_lineno if hasattr(top_node, "end_lineno") else 0 abs_difference = abs(start_line - top_level_block_start_line) + abs( end_line - top_level_block_end_line ) @@ -219,7 +218,6 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): top_level_block_start_line >= start_line and top_level_block_end_line <= end_line ): - # global should_run_top_blocks should_run_top_blocks.append(top_node) smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) @@ -246,7 +244,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): smart_code += "\n" normalized_smart_result = normalize_lines(smart_code) - global_next_lineno = get_next_block_lineno() + # global_next_lineno = get_next_block_lineno() return normalized_smart_result diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index bbd59ef12a17..82a244607c4c 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -73,18 +73,24 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { }, }); // If there is no explicit selection, we are exeucting 'line' or 'block'. - if (activeEditor!.selection.isEmpty) { + if (activeEditor && activeEditor.selection && activeEditor!.selection.isEmpty) { sendTelemetryEvent(EventName.EXECUTION_CODE, undefined, { scope: 'line' }); } // The normalization script expects a serialized JSON object, with the selection under the "code" key. // We're using a JSON object so that we don't have to worry about encoding, or escaping non-ASCII characters. + const startLineVal = activeEditor && activeEditor.selection ? activeEditor.selection.start.line : 0; + const endLineVal = activeEditor && activeEditor.selection ? activeEditor.selection.end.line : 0; + const emptyHighlightVal = activeEditor && activeEditor.selection ? activeEditor.selection.isEmpty : true; + const smartSendExperimentEnabledVal = pythonSmartSendEnabled(this.serviceContainer) + ? pythonSmartSendEnabled(this.serviceContainer) + : false; const input = JSON.stringify({ code, wholeFileContent, - startLine: activeEditor!.selection.start.line, - endLine: activeEditor!.selection.end.line, - emptyHighlight: activeEditor!.selection.isEmpty, - smartSendExperimentEnabled: pythonSmartSendEnabled(this.serviceContainer), + startLine: startLineVal, + endLine: endLineVal, + emptyHighlight: emptyHighlightVal, + smartSendExperimentEnabled: smartSendExperimentEnabledVal, }); observable.proc?.stdin?.write(input); observable.proc?.stdin?.end(); @@ -96,7 +102,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { // Let user experience smart shift+enter, advance cursor if in experiment if (pythonSmartSendEnabled(this.serviceContainer)) { // Advance cursor move only for smart shift+enter - if (activeEditor!.selection.isEmpty) { + if (activeEditor && activeEditor.selection && activeEditor!.selection.isEmpty) { const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line; commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); commands.executeCommand('cursorEnd'); @@ -269,5 +275,6 @@ function getMultiLineSelectionText(textEditor: TextEditor): string { function pythonSmartSendEnabled(serviceContainer: IServiceContainer): boolean { const experiment = serviceContainer.get(IExperimentService); - return experiment.inExperimentSync(EnableREPLSmartSend.experiment); + + return experiment ? experiment.inExperimentSync(EnableREPLSmartSend.experiment) : false; } From ecb9331b1b1de8f223458b26d6e2d001e2e712c3 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 18 Sep 2023 03:21:16 -0700 Subject: [PATCH 33/82] fix linting error --- pythonFiles/normalizeSelection.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 4f3ec142942b..2cebe729eafc 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -3,8 +3,6 @@ import ast import json -import os -import pathlib import re import sys import textwrap @@ -197,7 +195,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): should_run_top_blocks.append(same_line_node) smart_code += str(ast.get_source_segment(wholeFileContent, same_line_node)) smart_code += "\n" - global_next_lineno = get_next_block_lineno() + # global_next_lineno = get_next_block_lineno() return smart_code # With the given start_line and end_line number from VSCode, From 9f65f8460943aee949e95474f1905ee8b48b5b7a Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 18 Sep 2023 14:11:54 -0700 Subject: [PATCH 34/82] clean up normalizeSelection.py --- pythonFiles/normalizeSelection.py | 58 ++++++------------------------- 1 file changed, 10 insertions(+), 48 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 2cebe729eafc..da9e34b0924d 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -8,13 +8,7 @@ import textwrap from typing import Iterable -# script_dir = pathlib.Path('User/anthonykim/Desktop/vscode-python/pythonFiles/lib/python') -# sys.path.append(os.fspath(script_dir)) -# import debugpy - -# debugpy.connect(5678) -# debugpy.breakpoint() def split_lines(source): """ Split selection lines in a version-agnostic way. @@ -125,10 +119,8 @@ def normalize_lines(selection): # Insert a newline between each top-level statement, and append a newline to the selection. source = "\n".join(statements) + "\n" - # source = "\n".join(statements) if selection[-2] == "}": source = source[:-1] - # source = "\n".join(statements) except Exception: # If there's a problem when parsing statements, # append a blank line to end the block and send it as-is. @@ -155,9 +147,9 @@ def check_exact_exist(top_level_nodes, start_line, end_line): return exact_nodes -# Function that traverses the file and calculate the minimum viable top level block +# Function that traverses the file and calculate the minimum viable top level block. def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): - # use ast module to parse content of the file + # Use ast module to parse content of the file. parsed_file_content = ast.parse(wholeFileContent) smart_code = "" @@ -195,7 +187,6 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): should_run_top_blocks.append(same_line_node) smart_code += str(ast.get_source_segment(wholeFileContent, same_line_node)) smart_code += "\n" - # global_next_lineno = get_next_block_lineno() return smart_code # With the given start_line and end_line number from VSCode, @@ -207,54 +198,37 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): end_line - top_level_block_end_line ) top_level_to_min_difference[top_node] = abs_difference - - # We need to handle the case of 1. just hanging cursor vs. actual highlighting/selection. + # Handle case for same line run. if ( - was_highlighted - ): # There was actual highlighting of some text # Smart Selection disbled for part of the broken send. - if ( - top_level_block_start_line >= start_line - and top_level_block_end_line <= end_line - ): - should_run_top_blocks.append(top_node) - - smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) - smart_code += "\n" - elif ( start_line == top_level_block_start_line and end_line == top_level_block_end_line ): - # global should_run_top_blocks should_run_top_blocks.append(top_node) smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) smart_code += "\n" break # Break out of the loop since we found the exact match. - else: # not highlighted case. Meaning just a cursor hanging + else: # Case to apply smart selection for multiple line. if ( start_line >= top_level_block_start_line and end_line <= top_level_block_end_line ): - # global should_run_top_blocks should_run_top_blocks.append(top_node) smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) smart_code += "\n" normalized_smart_result = normalize_lines(smart_code) - # global_next_lineno = get_next_block_lineno() return normalized_smart_result # Look at the last top block added, find lineno for the next upcoming block, -# This will allow us to move cursor in vscode. +# This will allow us to move cursor in VS Code. def get_next_block_lineno(): last_ran_lineno = int(should_run_top_blocks[-1].end_lineno) temp_next_lineno = int(should_run_top_blocks[-1].end_lineno) - # next_lineno = should_run_top_blocks[-1].end_lineno - for reverse_node in top_level_nodes: if reverse_node.lineno > last_ran_lineno: temp_next_lineno = reverse_node.lineno @@ -268,29 +242,21 @@ def get_next_block_lineno(): stdin = sys.stdin if sys.version_info < (3,) else sys.stdin.buffer raw = stdin.read() contents = json.loads(raw.decode("utf-8")) - # normalized = normalize_lines(contents["code"]) - # normalized_whole_file = normalize_lines(contents["wholeFileContent"]) # Need to get information on whether there was a selection via Highlight. - # empty_Highlight = True empty_Highlight = False if contents["emptyHighlight"] is True: empty_Highlight = True - # we also get the activeEditor selection start line and end line from the typescript vscode side - # remember to add 1 to each of the received since vscode starts line counting from 0 + # We also get the activeEditor selection start line and end line from the typescript VS Code side. + # Remember to add 1 to each of the received since vscode starts line counting from 0 . vscode_start_line = contents["startLine"] + 1 vscode_end_line = contents["endLine"] + 1 - # temp = traverse_file(contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight) # traverse file - # Send the normalized code back to the extension in a JSON object. data = None which_line_next = 0 # Depending on whether there was a explicit highlight, send smart selection or regular normalization. # Experiment also has to be enable to use smart selection. - if ( - contents["emptyHighlight"] is True - and contents["smartSendExperimentEnabled"] is True - ): + if empty_Highlight is True and contents["smartSendExperimentEnabled"] is True: normalized = traverse_file( contents["wholeFileContent"], vscode_start_line, @@ -299,15 +265,11 @@ def get_next_block_lineno(): ) which_line_next = ( get_next_block_lineno() - ) # Only figure out next block line number for smart shift+ - # normalized = normalize_lines(contents["code"]) + ) # Only figure out next block line number for smart shift+enter. else: normalized = normalize_lines(contents["code"]) - # next_block_lineno - # which_line_next = get_next_block_lineno() + data = json.dumps({"normalized": normalized, "nextBlockLineno": which_line_next}) - # data = json.dumps({"normalized": normalized}) # This is how it used to be - # data = json.dumps({"normalized": temp}) # 8/16/23 save stdout = sys.stdout if sys.version_info < (3,) else sys.stdout.buffer stdout.write(data.encode("utf-8")) stdout.close() From 930c991b88aa160eb5165af93564865c98154fc9 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 18 Sep 2023 15:04:42 -0700 Subject: [PATCH 35/82] add more test to smart and normalize selection --- pythonFiles/tests/test_normalize_selection.py | 46 +++++++++++++++++ pythonFiles/tests/test_smart_selection.py | 49 ++++++++++--------- 2 files changed, 73 insertions(+), 22 deletions(-) diff --git a/pythonFiles/tests/test_normalize_selection.py b/pythonFiles/tests/test_normalize_selection.py index d2d45d1ce6af..f14fb35fe0f9 100644 --- a/pythonFiles/tests/test_normalize_selection.py +++ b/pythonFiles/tests/test_normalize_selection.py @@ -2,6 +2,7 @@ # Licensed under the MIT License. +import importlib import textwrap # __file__ = "/Users/anthonykim/Desktop/vscode-python/pythonFiles/normalizeSelection.py" @@ -218,3 +219,48 @@ def show_something(): ) result = normalizeSelection.normalize_lines(src) assert result == expected + + def test_fstring(self): + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + name = "Ahri" + age = 10 + print(f'My name is {name}') + """ + ) + + expected = textwrap.dedent( + """\ + name = "Ahri" + age = 10 + print(f'My name is {name}') + """ + ) + result = normalizeSelection.normalize_lines(src) + + assert result == expected + + def test_list_comp(self): + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + names = ['Ahri', 'Bobby', 'Charlie'] + breed = ['Pomeranian', 'Welsh Corgi', 'Siberian Husky'] + dogs = [(name, breed) for name, breed in zip(names, breed)] + print(dogs) + """ + ) + + expected = textwrap.dedent( + """\ + names = ['Ahri', 'Bobby', 'Charlie'] + breed = ['Pomeranian', 'Welsh Corgi', 'Siberian Husky'] + dogs = [(name, breed) for name, breed in zip(names, breed)] + print(dogs) + """ + ) + + result = normalizeSelection.normalize_lines(src) + + assert result == expected diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index 6bee3008f493..95783f08976c 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -92,48 +92,53 @@ def test_two_layer_dictionary(): assert result == expected -def test_fstring(): +def test_run_whole_func(): importlib.reload(normalizeSelection) src = textwrap.dedent( """\ - name = "Ahri" - age = 10 - print(f'My name is {name}') + def my_dogs(): + print("Corgi") + print("Husky") + print("Corgi2") + print("Husky2") + print("no dogs") """ ) - + # Expected to printing statement line by line expected = textwrap.dedent( """\ - name = "Ahri" - age = 10 - print(f'My name is {name}') + def my_dogs(): + print("Corgi") + print("Husky") + print("Corgi2") + print("Husky2") + print("no dogs") + """ ) - result = normalizeSelection.traverse_file(src, 1, 4, True) + result = normalizeSelection.traverse_file(src, 1, 1, False) assert result == expected - -def test_list_comp(): +def test_small_forloop(): importlib.reload(normalizeSelection) - hi = textwrap.dedent( + src = textwrap.dedent( """\ - names = ['Ahri', 'Bobby', 'Charlie'] - breed = ['Pomeranian', 'Welsh Corgi', 'Siberian Husky'] - dogs = [(name, breed) for name, breed in zip(names, breed)] - print(dogs) + for i in range(1, 6): + print(i) + print("Please also send this print statement") """ ) - expected = textwrap.dedent( """\ - names = ['Ahri', 'Bobby', 'Charlie'] - breed = ['Pomeranian', 'Welsh Corgi', 'Siberian Husky'] - dogs = [(name, breed) for name, breed in zip(names, breed)] - print(dogs) + for i in range(1, 6): + print(i) + print("Please also send this print statement") + """ ) - result = normalizeSelection.traverse_file(hi, 1, 4, True) + # Cover the whole for loop block with multiple inner statements + result = normalizeSelection.traverse_file(src,1,1,False) assert result == expected From b0614791618eae67b0889a90a8f84c6008d67412 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 18 Sep 2023 15:09:56 -0700 Subject: [PATCH 36/82] make black formatter happy --- pythonFiles/tests/test_smart_selection.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index 95783f08976c..a302c14603ac 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -120,6 +120,7 @@ def my_dogs(): assert result == expected + def test_small_forloop(): importlib.reload(normalizeSelection) src = textwrap.dedent( @@ -139,6 +140,6 @@ def test_small_forloop(): ) # Cover the whole for loop block with multiple inner statements - result = normalizeSelection.traverse_file(src,1,1,False) + result = normalizeSelection.traverse_file(src, 1, 1, False) assert result == expected From 20637f797c36ee1b976d73abe150474d249e1d33 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 18 Sep 2023 21:29:29 -0700 Subject: [PATCH 37/82] remove redundant comments --- pythonFiles/normalizeSelection.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index da9e34b0924d..859e16c9ac70 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -129,10 +129,7 @@ def normalize_lines(selection): return source -top_level_nodes = [] # collection of top level nodes -top_level_to_min_difference = ( - {} -) # dictionary of top level nodes to difference in relative to given code block to run +top_level_nodes = [] min_key = None global_next_lineno = None should_run_top_blocks = [] @@ -194,11 +191,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): for top_node in ast.iter_child_nodes(parsed_file_content): top_level_block_start_line = top_node.lineno top_level_block_end_line = top_node.end_lineno - abs_difference = abs(start_line - top_level_block_start_line) + abs( - end_line - top_level_block_end_line - ) - top_level_to_min_difference[top_node] = abs_difference - # Handle case for same line run. + if ( start_line == top_level_block_start_line and end_line == top_level_block_end_line From 13ebe557f646c55bdd0aadb43461b66523b67438 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 18 Sep 2023 21:38:09 -0700 Subject: [PATCH 38/82] bring back configSettings --- src/client/terminals/codeExecution/helper.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 82a244607c4c..1e9c7747250d 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -14,7 +14,7 @@ import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; import { ICodeExecutionHelper } from '../types'; import { traceError } from '../../logging'; -import { IExperimentService, Resource } from '../../common/types'; +import { IConfigurationService, IExperimentService, Resource } from '../../common/types'; import { EnableREPLSmartSend } from '../../common/experiments/groups'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; @@ -29,16 +29,14 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { private readonly interpreterService: IInterpreterService; - // private readonly configSettings: IConfigurationService; - - // private readonly configSettings: IConfigurationService; + private readonly configSettings: IConfigurationService; constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) { this.documentManager = serviceContainer.get(IDocumentManager); this.applicationShell = serviceContainer.get(IApplicationShell); this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); this.interpreterService = serviceContainer.get(IInterpreterService); - // this.configSettings = serviceContainer.get(IConfigurationService); + this.configSettings = serviceContainer.get(IConfigurationService); } public async normalizeLines(code: string, wholeFileContent?: string, resource?: Uri): Promise { From 43631cbf21486c3d005b6e94dae8b0bc3fb05797 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 18 Sep 2023 21:39:14 -0700 Subject: [PATCH 39/82] clean up helper.ts --- src/client/terminals/codeExecution/helper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 1e9c7747250d..862e149cc02b 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -143,7 +143,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { const { selection } = textEditor; let code: string; - // const wholeFileContent = textEditor.document.getText(); // This is a way to get the whole text content from the user + if (selection.isEmpty) { code = textEditor.document.lineAt(selection.start.line).text; } else if (selection.isSingleLine) { @@ -151,7 +151,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { } else { code = getMultiLineSelectionText(textEditor); } - // commands.executeCommand('cursorMove', { to: 'down' }); // trial: move to the next line when you run? + return code; } From aacd6ab3bbe4663cbed6a5459a15af81421e6342 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 18 Sep 2023 21:44:24 -0700 Subject: [PATCH 40/82] disable error for unused configSettings --- src/client/terminals/codeExecution/helper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 862e149cc02b..a365b8a86b45 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -29,6 +29,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { private readonly interpreterService: IInterpreterService; + /* tslint:disable:no-unused-variable */ private readonly configSettings: IConfigurationService; constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) { From 544ac88f940a20eaa73192e93102c4c302b869a1 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 18 Sep 2023 23:56:18 -0700 Subject: [PATCH 41/82] add doc string, more comments, eslint for config --- pythonFiles/normalizeSelection.py | 36 ++++++++++++++------ src/client/terminals/codeExecution/helper.ts | 3 +- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 859e16c9ac70..c85385c4e002 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -144,7 +144,11 @@ def check_exact_exist(top_level_nodes, start_line, end_line): return exact_nodes -# Function that traverses the file and calculate the minimum viable top level block. +""" +Traverse through a user's given file content and find, collect all appropriate lines +that should be sent to the REPL in case of smart selection. +Then call the normalize_lines function to normalize our smartly selected code block. +""" def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # Use ast module to parse content of the file. parsed_file_content = ast.parse(wholeFileContent) @@ -186,8 +190,9 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): smart_code += "\n" return smart_code - # With the given start_line and end_line number from VSCode, - # Calculate the absolute difference between each of the top level block and given code (via line number) + # Iterate through all of the nodes from the parsed file content, + # and add the appropriate source code line(s) to be sent to the REPL, dependent on + # user is trying to send and execute single line/statement or multiple with smart selection. for top_node in ast.iter_child_nodes(parsed_file_content): top_level_block_start_line = top_node.lineno top_level_block_end_line = top_node.end_lineno @@ -201,15 +206,24 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) smart_code += "\n" break # Break out of the loop since we found the exact match. - else: # Case to apply smart selection for multiple line. - if ( - start_line >= top_level_block_start_line - and end_line <= top_level_block_end_line - ): - should_run_top_blocks.append(top_node) + elif ( + start_line >= top_level_block_start_line + and end_line <= top_level_block_end_line + ): + # Case to apply smart selection for multiple line. + # This is the case for when we have to add multiple lines that should be included in the smart send. + # For example: + # 'my_dictionary': { + # 'Audi': 'Germany', + # 'BMW': 'Germany', + # 'Genesis': 'Korea', + # } + # with the mouse cursor at 'BMW': 'Germany', should send all of the lines that pertains to my_dictionary. - smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) - smart_code += "\n" + should_run_top_blocks.append(top_node) + + smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) + smart_code += "\n" normalized_smart_result = normalize_lines(smart_code) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index a365b8a86b45..acd7567c97a6 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -29,7 +29,8 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { private readonly interpreterService: IInterpreterService; - /* tslint:disable:no-unused-variable */ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error TS6133: 'configSettings' is declared but its value is never read. private readonly configSettings: IConfigurationService; constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) { From b0da736e86142620f9f8cd4153d0055a21bcfe11 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 00:22:06 -0700 Subject: [PATCH 42/82] cleaner way to check ast types and node.body --- pythonFiles/normalizeSelection.py | 35 +++++++++++-------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index c85385c4e002..1bb27ed3be99 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -154,31 +154,20 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): parsed_file_content = ast.parse(wholeFileContent) smart_code = "" + # Iterate through the top level nodes in user's file document, + # and add to our top_level_nodes array. + # then for each of the top level, if it is valid ast_types with + # node.body, we will add all the child nodes that pertains to that + # top block code to the array as well. for node in ast.iter_child_nodes(parsed_file_content): top_level_nodes.append(node) - if hasattr(node, "body"): - # Check if node has attribute body, then we have to go add child blocks. - if ( - isinstance(node, ast.Module) - or isinstance(node, ast.Interactive) - or isinstance(node, ast.Expression) - or isinstance(node, ast.FunctionDef) - or isinstance(node, ast.AsyncFunctionDef) - or isinstance(node, ast.ClassDef) - or isinstance(node, ast.For) - or isinstance(node, ast.AsyncFor) - or isinstance(node, ast.While) - or isinstance(node, ast.If) - or isinstance(node, ast.With) - or isinstance(node, ast.AsyncWith) - or isinstance(node, ast.Try) - or isinstance(node, ast.Lambda) - or isinstance(node, ast.IfExp) - or isinstance(node, ast.ExceptHandler) - ): - if isinstance(node.body, Iterable): - for child_nodes in node.body: - top_level_nodes.append(child_nodes) + + ast_types_with_nodebody = (ast.Module, ast.Interactive, ast.Expression, ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, + ast.For, ast.AsyncFor, ast.While, ast.If, ast.With, ast.AsyncWith, + ast.Try, ast.Lambda, ast.IfExp, ast.ExceptHandler) + if isinstance(node, ast_types_with_nodebody) and isinstance(node.body, Iterable): + for child_nodes in node.body: + top_level_nodes.append(child_nodes) exact_nodes = check_exact_exist(top_level_nodes, start_line, end_line) From 4ae8177e9c3eff6f308dd190afe39f6a8c4ddee9 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 00:27:17 -0700 Subject: [PATCH 43/82] format ast_types_with_nodebody --- pythonFiles/normalizeSelection.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 1bb27ed3be99..9cfe9ddf9131 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -149,6 +149,8 @@ def check_exact_exist(top_level_nodes, start_line, end_line): that should be sent to the REPL in case of smart selection. Then call the normalize_lines function to normalize our smartly selected code block. """ + + def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # Use ast module to parse content of the file. parsed_file_content = ast.parse(wholeFileContent) @@ -162,10 +164,27 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): for node in ast.iter_child_nodes(parsed_file_content): top_level_nodes.append(node) - ast_types_with_nodebody = (ast.Module, ast.Interactive, ast.Expression, ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, - ast.For, ast.AsyncFor, ast.While, ast.If, ast.With, ast.AsyncWith, - ast.Try, ast.Lambda, ast.IfExp, ast.ExceptHandler) - if isinstance(node, ast_types_with_nodebody) and isinstance(node.body, Iterable): + ast_types_with_nodebody = ( + ast.Module, + ast.Interactive, + ast.Expression, + ast.FunctionDef, + ast.AsyncFunctionDef, + ast.ClassDef, + ast.For, + ast.AsyncFor, + ast.While, + ast.If, + ast.With, + ast.AsyncWith, + ast.Try, + ast.Lambda, + ast.IfExp, + ast.ExceptHandler, + ) + if isinstance(node, ast_types_with_nodebody) and isinstance( + node.body, Iterable + ): for child_nodes in node.body: top_level_nodes.append(child_nodes) From ee40d5f76c7da726fd4ab5e33ff337c44ce3a723 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 01:05:39 -0700 Subject: [PATCH 44/82] utilize f string as recommended --- pythonFiles/normalizeSelection.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 9cfe9ddf9131..0e5ab7fd09bc 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -194,8 +194,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): if len(exact_nodes) > 0: for same_line_node in exact_nodes: should_run_top_blocks.append(same_line_node) - smart_code += str(ast.get_source_segment(wholeFileContent, same_line_node)) - smart_code += "\n" + smart_code += f"{ast.get_source_segment(wholeFileContent, same_line_node)}\n" return smart_code # Iterate through all of the nodes from the parsed file content, @@ -211,8 +210,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): ): should_run_top_blocks.append(top_node) - smart_code += str(ast.get_source_segment(wholeFileContent, top_node)) - smart_code += "\n" + smart_code += f"{ast.get_source_segment(wholeFileContent, top_node)}\n" break # Break out of the loop since we found the exact match. elif ( start_line >= top_level_block_start_line From d71352c41302b2552e8a21a0c4573f17e0242c23 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 10:14:51 -0700 Subject: [PATCH 45/82] simplify empty_highlight assignment and format --- pythonFiles/normalizeSelection.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 0e5ab7fd09bc..276d9bec7d61 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -194,7 +194,9 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): if len(exact_nodes) > 0: for same_line_node in exact_nodes: should_run_top_blocks.append(same_line_node) - smart_code += f"{ast.get_source_segment(wholeFileContent, same_line_node)}\n" + smart_code += ( + f"{ast.get_source_segment(wholeFileContent, same_line_node)}\n" + ) return smart_code # Iterate through all of the nodes from the parsed file content, @@ -256,9 +258,8 @@ def get_next_block_lineno(): raw = stdin.read() contents = json.loads(raw.decode("utf-8")) # Need to get information on whether there was a selection via Highlight. - empty_Highlight = False - if contents["emptyHighlight"] is True: - empty_Highlight = True + empty_Highlight = contents.get("emptyHighlight", False) + # We also get the activeEditor selection start line and end line from the typescript VS Code side. # Remember to add 1 to each of the received since vscode starts line counting from 0 . vscode_start_line = contents["startLine"] + 1 From d7997ed445260e87aa4465da14f82ba08a418b56 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 10:27:24 -0700 Subject: [PATCH 46/82] fix test_normalize_selection --- pythonFiles/tests/test_normalize_selection.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pythonFiles/tests/test_normalize_selection.py b/pythonFiles/tests/test_normalize_selection.py index f14fb35fe0f9..5f4d6d7d4a1f 100644 --- a/pythonFiles/tests/test_normalize_selection.py +++ b/pythonFiles/tests/test_normalize_selection.py @@ -226,6 +226,7 @@ def test_fstring(self): """\ name = "Ahri" age = 10 + print(f'My name is {name}') """ ) @@ -248,7 +249,9 @@ def test_list_comp(self): names = ['Ahri', 'Bobby', 'Charlie'] breed = ['Pomeranian', 'Welsh Corgi', 'Siberian Husky'] dogs = [(name, breed) for name, breed in zip(names, breed)] + print(dogs) + my_family_dog = 'Corgi' """ ) @@ -258,6 +261,7 @@ def test_list_comp(self): breed = ['Pomeranian', 'Welsh Corgi', 'Siberian Husky'] dogs = [(name, breed) for name, breed in zip(names, breed)] print(dogs) + my_family_dog = 'Corgi' """ ) From 2fda1ce9f240f1db2e57bf3cbf4d9d0b4e16cf07 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 12:00:19 -0700 Subject: [PATCH 47/82] more docstring --- pythonFiles/normalizeSelection.py | 15 ++-- pythonFiles/tests/test_smart_selection.py | 85 ++++++++++++++++++++++- 2 files changed, 91 insertions(+), 9 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 276d9bec7d61..435cd3339cf0 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -144,14 +144,12 @@ def check_exact_exist(top_level_nodes, start_line, end_line): return exact_nodes -""" -Traverse through a user's given file content and find, collect all appropriate lines -that should be sent to the REPL in case of smart selection. -Then call the normalize_lines function to normalize our smartly selected code block. -""" - - def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): + """ + Traverse through a user's given file content and find, collect all appropriate lines + that should be sent to the REPL in case of smart selection. + Then call the normalize_lines function to normalize our smartly selected code block. + """ # Use ast module to parse content of the file. parsed_file_content = ast.parse(wholeFileContent) smart_code = "" @@ -238,8 +236,9 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): return normalized_smart_result +# Intent on code;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; # Look at the last top block added, find lineno for the next upcoming block, -# This will allow us to move cursor in VS Code. +# This will allow us to move cursor in VS Code. #Send the list, receive it.----------------------------- def get_next_block_lineno(): last_ran_lineno = int(should_run_top_blocks[-1].end_lineno) temp_next_lineno = int(should_run_top_blocks[-1].end_lineno) diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index a302c14603ac..3803c887b07f 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -104,7 +104,7 @@ def my_dogs(): print("no dogs") """ ) - # Expected to printing statement line by line + expected = textwrap.dedent( """\ def my_dogs(): @@ -140,6 +140,89 @@ def test_small_forloop(): ) # Cover the whole for loop block with multiple inner statements + # Make sure to contain all of the print statements included. result = normalizeSelection.traverse_file(src, 1, 1, False) assert result == expected + + +def inner_for_loop_component(): + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + for i in range(1, 6): + print(i) + print("Please also send this print statement") + """ + ) + result = normalizeSelection.traverse_file(src, 2, 2, False) + expected = textwrap.dedent( + """\ + print(i) + """ + ) + # Pressing shift+enter inside a for loop, + # specifically on a viable expression + # by itself, such as print(i) + # should only return that exact expression + assert result == expected + + +def test_dict_comprehension(): + """ + Having the mouse cursor on the first line, + and pressing shift+enter should return the + whole dictionary, respecting user's code style. + """ + + importlib.reload + src = textwrap.dedent( + """\ + my_dict_comp = {temp_mover: + temp_mover for temp_mover in range(1, 7)} + """ + ) + + expected = textwrap.dedent( + """\ + my_dict_comp = {temp_mover: + temp_mover for temp_mover in range(1, 7)} + """ + ) + + result = normalizeSelection.traverse_file(src, 1, 1, False) + + assert result == expected + + +def test_send_whole_generator(): + """ + Pressing shift+enter on the first line, which is the '(' + should be returning the whole generator expression instead of just the '(' + """ + + importlib.reload + src = textwrap.dedent( + """\ + ( + my_first_var + for my_first_var in range(1, 10) + if my_first_var % 2 == 0 + ) + """ + ) + + expected = textwrap.dedent( + """\ + ( + my_first_var + for my_first_var in range(1, 10) + if my_first_var % 2 == 0 + ) + + """ + ) + + result = normalizeSelection.traverse_file(src, 1, 1, False) + + assert expected == result From 7db00f4027710fb2f4488f26fc7af2eafaa9ad5c Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 12:06:51 -0700 Subject: [PATCH 48/82] simplify activeEditor conditional --- pythonFiles/tests/test_smart_selection.py | 2 +- src/client/terminals/codeExecution/helper.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index 3803c887b07f..d0186a297f8d 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -172,7 +172,7 @@ def test_dict_comprehension(): """ Having the mouse cursor on the first line, and pressing shift+enter should return the - whole dictionary, respecting user's code style. + whole dictionary comp, respecting user's code style. """ importlib.reload diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index acd7567c97a6..692b3ff72756 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -73,7 +73,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { }, }); // If there is no explicit selection, we are exeucting 'line' or 'block'. - if (activeEditor && activeEditor.selection && activeEditor!.selection.isEmpty) { + if (activeEditor?.selection?.isEmpty) { sendTelemetryEvent(EventName.EXECUTION_CODE, undefined, { scope: 'line' }); } // The normalization script expects a serialized JSON object, with the selection under the "code" key. From a9bd9a1a995ef13790023588d07b3894076e9a6e Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 12:28:17 -0700 Subject: [PATCH 49/82] better ts syntax for start,endline and highlight --- src/client/terminals/codeExecution/helper.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 692b3ff72756..0f281b96935a 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -78,9 +78,9 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { } // The normalization script expects a serialized JSON object, with the selection under the "code" key. // We're using a JSON object so that we don't have to worry about encoding, or escaping non-ASCII characters. - const startLineVal = activeEditor && activeEditor.selection ? activeEditor.selection.start.line : 0; - const endLineVal = activeEditor && activeEditor.selection ? activeEditor.selection.end.line : 0; - const emptyHighlightVal = activeEditor && activeEditor.selection ? activeEditor.selection.isEmpty : true; + const startLineVal = activeEditor?.selection?.start.line ?? 0; + const endLineVal = activeEditor?.selection?.end.line ?? 0; + const emptyHighlightVal = activeEditor?.selection?.isEmpty ?? true; const smartSendExperimentEnabledVal = pythonSmartSendEnabled(this.serviceContainer) ? pythonSmartSendEnabled(this.serviceContainer) : false; From 039c1ded3a959f53e53324702e51a56e6ababc3d Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 12:34:22 -0700 Subject: [PATCH 50/82] cleaner contents.get --- pythonFiles/normalizeSelection.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 435cd3339cf0..674264529e5b 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -188,6 +188,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): exact_nodes = check_exact_exist(top_level_nodes, start_line, end_line) + # Just return the exact top level line, if present. if len(exact_nodes) > 0: for same_line_node in exact_nodes: @@ -269,7 +270,7 @@ def get_next_block_lineno(): which_line_next = 0 # Depending on whether there was a explicit highlight, send smart selection or regular normalization. # Experiment also has to be enable to use smart selection. - if empty_Highlight is True and contents["smartSendExperimentEnabled"] is True: + if empty_Highlight and contents.get("smartSendExperimentEnabled"): normalized = traverse_file( contents["wholeFileContent"], vscode_start_line, From 96a5f30652a76a202b117d7dc98f2c32c7b08723 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 13:52:35 -0700 Subject: [PATCH 51/82] more tests added in test_smart_selection --- pythonFiles/tests/test_smart_selection.py | 45 ++++++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index d0186a297f8d..17ab05294dfe 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -34,7 +34,9 @@ def test_smart_shift_enter_multiple_statements(): """ ) - # Expected to printing statement line by line + # Expected to printing statement line by line, + # for when multiple print statements are ran + # from the same line. expected = textwrap.dedent( """\ print("Audi") @@ -43,7 +45,6 @@ def test_smart_shift_enter_multiple_statements(): """ ) result = normalizeSelection.traverse_file(src, 8, 8, False) - # print(result) assert result == expected @@ -147,6 +148,12 @@ def test_small_forloop(): def inner_for_loop_component(): + """ + Pressing shift+enter inside a for loop, + specifically on a viable expression + by itself, such as print(i) + should only return that exact expression + """ importlib.reload(normalizeSelection) src = textwrap.dedent( """\ @@ -161,10 +168,7 @@ def inner_for_loop_component(): print(i) """ ) - # Pressing shift+enter inside a for loop, - # specifically on a viable expression - # by itself, such as print(i) - # should only return that exact expression + assert result == expected @@ -226,3 +230,32 @@ def test_send_whole_generator(): result = normalizeSelection.traverse_file(src, 1, 1, False) assert expected == result + + +def test_multiline_lambda(): + """ + Shift+enter on part of the lambda expression + should return the whole lambda expression, + regardless of whether all the component of + lambda expression is on the same or not. + """ + + importlib.reload + src = textwrap.dedent( + """\ + my_lambda = lambda x: ( + x + 1 + ) + """ + ) + expected = textwrap.dedent( + """\ + my_lambda = lambda x: ( + x + 1 + ) + + """ + ) + + result = normalizeSelection.traverse_file(src, 1, 1, False) + assert expected == result From 942857837d30e7894f654d23a2987b3a220aa756 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 13:58:37 -0700 Subject: [PATCH 52/82] fix test_part_dictionary test --- pythonFiles/tests/test_smart_selection.py | 29 ++++++++++++++++------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index 17ab05294dfe..64f73f23a708 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -6,14 +6,27 @@ def test_part_dictionary(): importlib.reload(normalizeSelection) - src = 'import textwrap\nimport ast\n\nprint("Audi")\nprint("Genesis")\n\n\nprint("Audi");print("BMW");print("Mercedes")\n\nmy_dict = {\n "key1": "value1",\n "key2": "value2"\n}\n\n\nsrc = """\nmy_dict = {\n"key1": "value1",\n"key2": "value2"\n}\n"""\n\ntop_level_nodes = []\n\nparsed_file_content = ast.parse(src)\nprint(ast.dump(parsed_file_content))\n\nparsed_dict_content2 = ast.parse(str(my_dict))\nprint(ast.dump(parsed_dict_content2))\n\n\nfor node in ast.iter_child_nodes(parsed_file_content):\n top_level_nodes.append(node)\n line_start = node.lineno\n line_end = node.end_lineno\n code_of_node = ast.get_source_segment(wholeFileContent, node)\n ast.get_source_segment(wholeFileContent, node) # This is way to get original code of the selected node\n\n################################################################################\n# New test case(s):\n# what should happen when shift enter at line 5? \n# follow ast says ----- TODO \n\n# execute individually line 5 bc two statements ---- TODO \n#################################################################################' # noqa: E501 - - expected = 'my_dict = {\n "key1": "value1",\n "key2": "value2"\n}\n' - # parsed_file_content = ast.parse(src) - # top_level_nodes = [] - # for node in ast.iter_child_nodes(parsed_file_content): - # print(node.__dir__()) - result = normalizeSelection.traverse_file(src, 10, 11, False) + src = textwrap.dedent( + """\ + not_dictionary = 'hi' + my_dict = { + "key1": "value1", + "key2": "value2" + } + print('only send the dictionary') + """ + ) + + expected = textwrap.dedent( + """\ + my_dict = { + "key1": "value1", + "key2": "value2" + } + """ + ) + + result = normalizeSelection.traverse_file(src, 3, 3, False) assert result == expected From afe9a725e08aeb8433cf99e1ae293180683644aa Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 14:03:56 -0700 Subject: [PATCH 53/82] format, more comments --- pythonFiles/normalizeSelection.py | 1 - pythonFiles/tests/test_smart_selection.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 674264529e5b..f119101b285f 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -188,7 +188,6 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): exact_nodes = check_exact_exist(top_level_nodes, start_line, end_line) - # Just return the exact top level line, if present. if len(exact_nodes) > 0: for same_line_node in exact_nodes: diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index 64f73f23a708..7a9b00448fe0 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -110,6 +110,7 @@ def test_run_whole_func(): importlib.reload(normalizeSelection) src = textwrap.dedent( """\ + print("Decide which dog you will choose") def my_dogs(): print("Corgi") print("Husky") @@ -130,7 +131,7 @@ def my_dogs(): """ ) - result = normalizeSelection.traverse_file(src, 1, 1, False) + result = normalizeSelection.traverse_file(src, 2, 2, False) assert result == expected From 6768f85b9e8ac504f0d1f7462b92469fd87e3266 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 14:32:46 -0700 Subject: [PATCH 54/82] new function moveToNextBlock --- src/client/terminals/codeExecution/helper.ts | 29 ++++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 0f281b96935a..c4728cbecba5 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -99,15 +99,8 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { const result = await normalizeOutput.promise; const object = JSON.parse(result); - // Let user experience smart shift+enter, advance cursor if in experiment - if (pythonSmartSendEnabled(this.serviceContainer)) { - // Advance cursor move only for smart shift+enter - if (activeEditor && activeEditor.selection && activeEditor!.selection.isEmpty) { - const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line; - commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); - commands.executeCommand('cursorEnd'); - } - } + const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line; + this.moveToNextBlock(lineOffset, activeEditor); return parse(object.normalized); } catch (ex) { @@ -116,6 +109,24 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { } } + public async moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): Promise { + /** + * Depending on whether or not user is in experiment for smart send, + * dynamically move the cursor to the next block of code. + * The cursor movement is not moved by one everytime, + * since with the smart selection, the next executable code block + * can be multiple lines away. + * Intended to provide smooth shift+enter user experience + * bringing user's cursor to the next executable block of code when used with smart selection. + */ + if (pythonSmartSendEnabled(this.serviceContainer)) { + if (activeEditor?.selection?.isEmpty) { + commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); + commands.executeCommand('cursorEnd'); + } + } + } + public async getFileToExecute(): Promise { const activeEditor = this.documentManager.activeTextEditor; if (!activeEditor) { From 194e413ca0258ba255838748b601c8d96df2ab9f Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 14:57:07 -0700 Subject: [PATCH 55/82] comment before the function for moveToNextBlock --- src/client/terminals/codeExecution/helper.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index c4728cbecba5..597f779d53cb 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -109,16 +109,16 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { } } + /** + * Depending on whether or not user is in experiment for smart send, + * dynamically move the cursor to the next block of code. + * The cursor movement is not moved by one everytime, + * since with the smart selection, the next executable code block + * can be multiple lines away. + * Intended to provide smooth shift+enter user experience + * bringing user's cursor to the next executable block of code when used with smart selection. + */ public async moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): Promise { - /** - * Depending on whether or not user is in experiment for smart send, - * dynamically move the cursor to the next block of code. - * The cursor movement is not moved by one everytime, - * since with the smart selection, the next executable code block - * can be multiple lines away. - * Intended to provide smooth shift+enter user experience - * bringing user's cursor to the next executable block of code when used with smart selection. - */ if (pythonSmartSendEnabled(this.serviceContainer)) { if (activeEditor?.selection?.isEmpty) { commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); From 4c6a0ba54964d5d5b234400fdfc2fdb1c8be82b1 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 15:29:23 -0700 Subject: [PATCH 56/82] prevent access .getText from undefined --- src/client/terminals/codeExecution/helper.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 597f779d53cb..adfada578cb6 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -99,8 +99,10 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { const result = await normalizeOutput.promise; const object = JSON.parse(result); - const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line; - this.moveToNextBlock(lineOffset, activeEditor); + if (activeEditor?.selection) { + const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line; + this.moveToNextBlock(lineOffset, activeEditor); + } return parse(object.normalized); } catch (ex) { From 3b11ed80615a022afce4358d06a8a66494ad558f Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 23:15:41 -0700 Subject: [PATCH 57/82] remove should_run_top_blocks from global variable --- pythonFiles/normalizeSelection.py | 24 +++++++++++------------- src/client/terminals/types.ts | 1 + 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index f119101b285f..4b3dc9a13ade 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -132,7 +132,6 @@ def normalize_lines(selection): top_level_nodes = [] min_key = None global_next_lineno = None -should_run_top_blocks = [] def check_exact_exist(top_level_nodes, start_line, end_line): @@ -153,7 +152,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # Use ast module to parse content of the file. parsed_file_content = ast.parse(wholeFileContent) smart_code = "" - + should_run_top_blocks = [] # Iterate through the top level nodes in user's file document, # and add to our top_level_nodes array. # then for each of the top level, if it is valid ast_types with @@ -195,7 +194,8 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): smart_code += ( f"{ast.get_source_segment(wholeFileContent, same_line_node)}\n" ) - return smart_code + which_line_next = get_next_block_lineno(should_run_top_blocks) + return {"normalized_smart_result": smart_code, "which_line_next": which_line_next} # Iterate through all of the nodes from the parsed file content, # and add the appropriate source code line(s) to be sent to the REPL, dependent on @@ -232,16 +232,16 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): smart_code += "\n" normalized_smart_result = normalize_lines(smart_code) - - return normalized_smart_result + which_line_next = get_next_block_lineno(should_run_top_blocks) + return {"normalized_smart_result": normalized_smart_result, "which_line_next": which_line_next} # Intent on code;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; # Look at the last top block added, find lineno for the next upcoming block, # This will allow us to move cursor in VS Code. #Send the list, receive it.----------------------------- -def get_next_block_lineno(): - last_ran_lineno = int(should_run_top_blocks[-1].end_lineno) - temp_next_lineno = int(should_run_top_blocks[-1].end_lineno) +def get_next_block_lineno(which_line_next): + last_ran_lineno = int(which_line_next[-1].end_lineno) + temp_next_lineno = int(which_line_next[-1].end_lineno) for reverse_node in top_level_nodes: if reverse_node.lineno > last_ran_lineno: @@ -270,19 +270,17 @@ def get_next_block_lineno(): # Depending on whether there was a explicit highlight, send smart selection or regular normalization. # Experiment also has to be enable to use smart selection. if empty_Highlight and contents.get("smartSendExperimentEnabled"): - normalized = traverse_file( + result = traverse_file( contents["wholeFileContent"], vscode_start_line, vscode_end_line, not empty_Highlight, ) - which_line_next = ( - get_next_block_lineno() - ) # Only figure out next block line number for smart shift+enter. + normalized = result["normalized_smart_result"] else: normalized = normalize_lines(contents["code"]) - data = json.dumps({"normalized": normalized, "nextBlockLineno": which_line_next}) + data = json.dumps({"normalized": normalized, "nextBlockLineno": result["which_line_next"]}) stdout = sys.stdout if sys.version_info < (3,) else sys.stdout.buffer stdout.write(data.encode("utf-8")) stdout.close() diff --git a/src/client/terminals/types.ts b/src/client/terminals/types.ts index 48e39d4e1c81..88e33365ea5f 100644 --- a/src/client/terminals/types.ts +++ b/src/client/terminals/types.ts @@ -19,6 +19,7 @@ export interface ICodeExecutionHelper { getFileToExecute(): Promise; saveFileIfDirty(file: Uri): Promise; getSelectedTextToExecute(textEditor: TextEditor): Promise; + moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): void; } export const ICodeExecutionManager = Symbol('ICodeExecutionManager'); From 094a5de2be92b7c81ce55ada38735bc9c0edd744 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 23:25:08 -0700 Subject: [PATCH 58/82] delete global_next_lineno --- pythonFiles/normalizeSelection.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 4b3dc9a13ade..68d4cd229393 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -131,7 +131,6 @@ def normalize_lines(selection): top_level_nodes = [] min_key = None -global_next_lineno = None def check_exact_exist(top_level_nodes, start_line, end_line): @@ -195,7 +194,10 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): f"{ast.get_source_segment(wholeFileContent, same_line_node)}\n" ) which_line_next = get_next_block_lineno(should_run_top_blocks) - return {"normalized_smart_result": smart_code, "which_line_next": which_line_next} + return { + "normalized_smart_result": smart_code, + "which_line_next": which_line_next, + } # Iterate through all of the nodes from the parsed file content, # and add the appropriate source code line(s) to be sent to the REPL, dependent on @@ -233,12 +235,14 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): normalized_smart_result = normalize_lines(smart_code) which_line_next = get_next_block_lineno(should_run_top_blocks) - return {"normalized_smart_result": normalized_smart_result, "which_line_next": which_line_next} + return { + "normalized_smart_result": normalized_smart_result, + "which_line_next": which_line_next, + } -# Intent on code;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; # Look at the last top block added, find lineno for the next upcoming block, -# This will allow us to move cursor in VS Code. #Send the list, receive it.----------------------------- +# This will allow us to move cursor in VS Code. def get_next_block_lineno(which_line_next): last_ran_lineno = int(which_line_next[-1].end_lineno) temp_next_lineno = int(which_line_next[-1].end_lineno) @@ -280,7 +284,9 @@ def get_next_block_lineno(which_line_next): else: normalized = normalize_lines(contents["code"]) - data = json.dumps({"normalized": normalized, "nextBlockLineno": result["which_line_next"]}) + data = json.dumps( + {"normalized": normalized, "nextBlockLineno": result["which_line_next"]} + ) stdout = sys.stdout if sys.version_info < (3,) else sys.stdout.buffer stdout.write(data.encode("utf-8")) stdout.close() From c8b0e8c43eb2665407224b442ce68ea68a095207 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 23:40:33 -0700 Subject: [PATCH 59/82] comply to new return value for traverse_file --- pythonFiles/tests/test_smart_selection.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index 7a9b00448fe0..424dc52cdfa5 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -27,7 +27,7 @@ def test_part_dictionary(): ) result = normalizeSelection.traverse_file(src, 3, 3, False) - assert result == expected + assert result["normalized_smart_result"] == expected def test_smart_shift_enter_multiple_statements(): @@ -58,7 +58,7 @@ def test_smart_shift_enter_multiple_statements(): """ ) result = normalizeSelection.traverse_file(src, 8, 8, False) - assert result == expected + assert result["normalized_smart_result"] == expected def test_two_layer_dictionary(): @@ -103,7 +103,7 @@ def test_two_layer_dictionary(): ) result = normalizeSelection.traverse_file(src, 6, 7, False) - assert result == expected + assert result["normalized_smart_result"] == expected def test_run_whole_func(): @@ -133,7 +133,7 @@ def my_dogs(): ) result = normalizeSelection.traverse_file(src, 2, 2, False) - assert result == expected + assert result["normalized_smart_result"] == expected def test_small_forloop(): @@ -158,7 +158,7 @@ def test_small_forloop(): # Make sure to contain all of the print statements included. result = normalizeSelection.traverse_file(src, 1, 1, False) - assert result == expected + assert result["normalized_smart_result"] == expected def inner_for_loop_component(): @@ -183,7 +183,7 @@ def inner_for_loop_component(): """ ) - assert result == expected + assert result["normalized_smart_result"] == expected def test_dict_comprehension(): @@ -210,7 +210,7 @@ def test_dict_comprehension(): result = normalizeSelection.traverse_file(src, 1, 1, False) - assert result == expected + assert result["normalized_smart_result"] == expected def test_send_whole_generator(): @@ -243,7 +243,7 @@ def test_send_whole_generator(): result = normalizeSelection.traverse_file(src, 1, 1, False) - assert expected == result + assert result["normalized_smart_result"] == expected def test_multiline_lambda(): @@ -272,4 +272,4 @@ def test_multiline_lambda(): ) result = normalizeSelection.traverse_file(src, 1, 1, False) - assert expected == result + assert result["normalized_smart_result"] == expected From 8d640aac1fa0f2d414393ad5bd1a742ad6d0dba2 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 23:53:02 -0700 Subject: [PATCH 60/82] fix linting error unbound variable --- pythonFiles/normalizeSelection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 68d4cd229393..9065cf0e6a1d 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -281,6 +281,7 @@ def get_next_block_lineno(which_line_next): not empty_Highlight, ) normalized = result["normalized_smart_result"] + which_line_next = result["which_line_next"] else: normalized = normalize_lines(contents["code"]) From dc9a6de40a61db711cec95e98127d2ddb7a69dec Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 19 Sep 2023 23:59:18 -0700 Subject: [PATCH 61/82] fix linting error attempt2 --- pythonFiles/normalizeSelection.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 9065cf0e6a1d..3b404f04ca0e 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -282,12 +282,15 @@ def get_next_block_lineno(which_line_next): ) normalized = result["normalized_smart_result"] which_line_next = result["which_line_next"] + data = json.dumps( + {"normalized": normalized, "nextBlockLineno": result["which_line_next"]} + ) else: normalized = normalize_lines(contents["code"]) - - data = json.dumps( - {"normalized": normalized, "nextBlockLineno": result["which_line_next"]} + data = json.dumps( + {"normalized": normalized} ) + stdout = sys.stdout if sys.version_info < (3,) else sys.stdout.buffer stdout.write(data.encode("utf-8")) stdout.close() From bd4abf29f2d60c32c40e9de8b814fc2755564ba2 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 20 Sep 2023 00:04:16 -0700 Subject: [PATCH 62/82] set default which_line_next value --- pythonFiles/normalizeSelection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 3b404f04ca0e..144585cf6916 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -188,6 +188,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # Just return the exact top level line, if present. if len(exact_nodes) > 0: + which_line_next = 0 for same_line_node in exact_nodes: should_run_top_blocks.append(same_line_node) smart_code += ( From 9987441d1fe01cf3c0ac20ece5f1fa1fb121d4c1 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 20 Sep 2023 00:08:14 -0700 Subject: [PATCH 63/82] format with black --- pythonFiles/normalizeSelection.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 144585cf6916..11b79612e8bf 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -284,13 +284,11 @@ def get_next_block_lineno(which_line_next): normalized = result["normalized_smart_result"] which_line_next = result["which_line_next"] data = json.dumps( - {"normalized": normalized, "nextBlockLineno": result["which_line_next"]} - ) + {"normalized": normalized, "nextBlockLineno": result["which_line_next"]} + ) else: normalized = normalize_lines(contents["code"]) - data = json.dumps( - {"normalized": normalized} - ) + data = json.dumps({"normalized": normalized}) stdout = sys.stdout if sys.version_info < (3,) else sys.stdout.buffer stdout.write(data.encode("utf-8")) From 23a1c169e02844ab3780b81d36f73a5d9f6c5d3b Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 20 Sep 2023 10:44:58 -0700 Subject: [PATCH 64/82] add test_dynamic_cursor --- pythonFiles/normalizeSelection.py | 2 +- pythonFiles/tests/test_dynamic_cursor.py | 126 +++++++++++++++++++ src/client/terminals/codeExecution/helper.ts | 4 +- 3 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 pythonFiles/tests/test_dynamic_cursor.py diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 11b79612e8bf..a35bd9c86fbe 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -252,7 +252,7 @@ def get_next_block_lineno(which_line_next): if reverse_node.lineno > last_ran_lineno: temp_next_lineno = reverse_node.lineno break - return temp_next_lineno - 1 + return temp_next_lineno if __name__ == "__main__": diff --git a/pythonFiles/tests/test_dynamic_cursor.py b/pythonFiles/tests/test_dynamic_cursor.py new file mode 100644 index 000000000000..e6b5d61748c3 --- /dev/null +++ b/pythonFiles/tests/test_dynamic_cursor.py @@ -0,0 +1,126 @@ +import importlib +import textwrap + +import normalizeSelection + + +def test_dictionary_mouse_mover(): + """ + Having the mouse cursor on second line, + 'my_dict = {' + and pressing shift+enter should bring the + mouse cursor to line 6, on and to be able to run + 'print('only send the dictionary')' + """ + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + not_dictionary = 'hi' + my_dict = { + "key1": "value1", + "key2": "value2" + } + print('only send the dictionary') + """ + ) + + result = normalizeSelection.traverse_file(src, 2, 2, False) + + assert result["which_line_next"] == 6 + + +def test_beginning_func(): + """ + Pressing shift+enter on the very first line, + of function definition, such as 'my_func():' + It should properly skip the comment and assert the + next executable line to be executed is line 5 at + 'my_dict = {' + """ + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + def my_func(): + print("line 2") + print("line 3") + # Skip line 4 because it is a comment + my_dict = { + "key1": "value1", + "key2": "value2" + } + """ + ) + + result = normalizeSelection.traverse_file(src, 1, 1, False) + + assert result["which_line_next"] == 5 + + +def test_cursor_forloop(): + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + lucid_dream = ["Corgi", "Husky", "Pomsky"] + for dogs in lucid_dream: # initial starting position + print(dogs) + print("I wish I had a dog!") + + print("This should be the next block that should be ran") + """ + ) + + result = normalizeSelection.traverse_file(src, 2, 2, False) + + assert result["which_line_next"] == 6 + + +def test_inside_forloop(): + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + for food in lucid_dream: + print("We are starting") # initial starting position + print("Next cursor should be here!") + + """ + ) + + result = normalizeSelection.traverse_file(src, 2, 2, False) + + assert result["which_line_next"] == 3 + + +def test_skip_sameline_statements(): + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + print("Audi");print("BMW");print("Mercedes") + print("Next line to be run is here!") + """ + ) + result = normalizeSelection.traverse_file(src, 1, 1, False) + + assert result["which_line_next"] == 2 + + +def test_skip_multi_comp_lambda(): + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + ( + my_first_var + for my_first_var in range(1, 10) + if my_first_var % 2 == 0 + ) + + my_lambda = lambda x: ( + x + 1 + ) + """ + ) + + result = normalizeSelection.traverse_file(src, 1, 1, False) + # Shift enter from the very first ( should make + # next executable statement as the lambda expression + assert result["which_line_next"] == 7 + diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index adfada578cb6..d3cd435a541b 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -100,8 +100,8 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { const object = JSON.parse(result); if (activeEditor?.selection) { - const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line; - this.moveToNextBlock(lineOffset, activeEditor); + const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line - 1; + await this.moveToNextBlock(lineOffset, activeEditor); } return parse(object.normalized); From 97f9e2b50376a9e463d60ea3ca2d94b2d24dccf2 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 20 Sep 2023 10:55:22 -0700 Subject: [PATCH 65/82] more tests --- pythonFiles/tests/test_dynamic_cursor.py | 38 +++++++++++++++++++++++ pythonFiles/tests/test_smart_selection.py | 35 +++++++++++++++++++-- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/pythonFiles/tests/test_dynamic_cursor.py b/pythonFiles/tests/test_dynamic_cursor.py index e6b5d61748c3..02bb539d6f91 100644 --- a/pythonFiles/tests/test_dynamic_cursor.py +++ b/pythonFiles/tests/test_dynamic_cursor.py @@ -124,3 +124,41 @@ def test_skip_multi_comp_lambda(): # next executable statement as the lambda expression assert result["which_line_next"] == 7 +def test_move_whole_class(): + """ + Shift+enter on a class definition + should move the cursor after running whole class. + """ + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + class Stub(object): + def __init__(self): + self.calls = [] + + def add_call(self, name, args=None, kwargs=None): + self.calls.append((name, args, kwargs)) + print("We should be here after running whole class") + """) + result = normalizeSelection.traverse_file(src, 1, 1, False) + + assert result["which_line_next"] == 7 + +def test_def_to_def(): + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + def my_dogs(): + print("Corgi") + print("Husky") + print("Corgi2") + print("Husky2") + print("no dogs") + + # Skip here + def next_func(): + print("Not here but above") + """) + result = normalizeSelection.traverse_file(src, 1, 1, False) + + assert result["which_line_next"] == 9 diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index 424dc52cdfa5..3273c580bb60 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -219,7 +219,7 @@ def test_send_whole_generator(): should be returning the whole generator expression instead of just the '(' """ - importlib.reload + importlib.reload(normalizeSelection) src = textwrap.dedent( """\ ( @@ -254,7 +254,7 @@ def test_multiline_lambda(): lambda expression is on the same or not. """ - importlib.reload + importlib.reload(normalizeSelection) src = textwrap.dedent( """\ my_lambda = lambda x: ( @@ -273,3 +273,34 @@ def test_multiline_lambda(): result = normalizeSelection.traverse_file(src, 1, 1, False) assert result["normalized_smart_result"] == expected + +def test_send_whole_class(): + """ + Shift+enter on a class definition + should send the whole class definition + """ + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + class Stub(object): + def __init__(self): + self.calls = [] + + def add_call(self, name, args=None, kwargs=None): + self.calls.append((name, args, kwargs)) + print("We should be here after running whole class") + """) + result = normalizeSelection.traverse_file(src, 1, 1, False) + expected = textwrap.dedent( + """\ + class Stub(object): + def __init__(self): + self.calls = [] + def add_call(self, name, args=None, kwargs=None): + self.calls.append((name, args, kwargs)) + + """ + ) + assert result["normalized_smart_result"] == expected + + From 6ff726d326204e3b46ced8b7e35926502ff80942 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 20 Sep 2023 11:10:59 -0700 Subject: [PATCH 66/82] more tests smart_selection, dynamic_cursor --- pythonFiles/tests/test_dynamic_cursor.py | 15 ++++++++ pythonFiles/tests/test_smart_selection.py | 47 +++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/pythonFiles/tests/test_dynamic_cursor.py b/pythonFiles/tests/test_dynamic_cursor.py index 02bb539d6f91..ebf3c366e14c 100644 --- a/pythonFiles/tests/test_dynamic_cursor.py +++ b/pythonFiles/tests/test_dynamic_cursor.py @@ -162,3 +162,18 @@ def next_func(): result = normalizeSelection.traverse_file(src, 1, 1, False) assert result["which_line_next"] == 9 + +def test_try_catch_move(): + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + try: + 1+1 + except: + print("error") + + print("Should be here afterwards") + """) + + result = normalizeSelection.traverse_file(src, 1, 1, False) + assert result["which_line_next"] == 6 diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index 3273c580bb60..623ac12bee5a 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -303,4 +303,51 @@ def add_call(self, name, args=None, kwargs=None): ) assert result["normalized_smart_result"] == expected +def test_send_whole_if_statement(): + """ + Shift+enter on an if statement + should send the whole if statement + including statements inside and else. + """ + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + if True: + print('send this') + else: + print('also send this') + + print('cursor here afterwards') + """) + expected = textwrap.dedent( + """\ + if True: + print('send this') + else: + print('also send this') + + """) + result = normalizeSelection.traverse_file(src, 1, 1, False) + assert result["normalized_smart_result"] == expected + +def test_send_try(): + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + try: + 1+1 + except: + print("error") + + print("Not running this") + """) + expected = textwrap.dedent( + """\ + try: + 1+1 + except: + print("error") + """) + result = normalizeSelection.traverse_file(src, 1, 1, False) + assert result["normalized_smart_result"] == expected From 482c4abea4e10054025ddbede34e0639fd70d719 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 20 Sep 2023 11:28:17 -0700 Subject: [PATCH 67/82] more test cases --- pythonFiles/normalizeSelection.py | 12 +++--- pythonFiles/tests/test_dynamic_cursor.py | 30 +++++++++++++-- pythonFiles/tests/test_smart_selection.py | 45 ++++++++++++++++++++--- 3 files changed, 73 insertions(+), 14 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index a35bd9c86fbe..a2b26950d313 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -148,7 +148,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): that should be sent to the REPL in case of smart selection. Then call the normalize_lines function to normalize our smartly selected code block. """ - # Use ast module to parse content of the file. + # We need to collect ast nodes to iterate through. parsed_file_content = ast.parse(wholeFileContent) smart_code = "" should_run_top_blocks = [] @@ -200,8 +200,8 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): "which_line_next": which_line_next, } - # Iterate through all of the nodes from the parsed file content, - # and add the appropriate source code line(s) to be sent to the REPL, dependent on + # For each of the nodes in the parsed file content, + # add the appropriate source code line(s) to be sent to the REPL, dependent on # user is trying to send and execute single line/statement or multiple with smart selection. for top_node in ast.iter_child_nodes(parsed_file_content): top_level_block_start_line = top_node.lineno @@ -214,7 +214,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): should_run_top_blocks.append(top_node) smart_code += f"{ast.get_source_segment(wholeFileContent, top_node)}\n" - break # Break out of the loop since we found the exact match. + break # If we found exact match, don't waste computation in parsing extra nodes. elif ( start_line >= top_level_block_start_line and end_line <= top_level_block_end_line @@ -243,7 +243,7 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # Look at the last top block added, find lineno for the next upcoming block, -# This will allow us to move cursor in VS Code. +# This will be used in calculating lineOffset to move cursor in VS Code. def get_next_block_lineno(which_line_next): last_ran_lineno = int(which_line_next[-1].end_lineno) temp_next_lineno = int(which_line_next[-1].end_lineno) @@ -261,7 +261,7 @@ def get_next_block_lineno(which_line_next): stdin = sys.stdin if sys.version_info < (3,) else sys.stdin.buffer raw = stdin.read() contents = json.loads(raw.decode("utf-8")) - # Need to get information on whether there was a selection via Highlight. + # Empty highlight means user has not explicitly selected specific text. empty_Highlight = contents.get("emptyHighlight", False) # We also get the activeEditor selection start line and end line from the typescript VS Code side. diff --git a/pythonFiles/tests/test_dynamic_cursor.py b/pythonFiles/tests/test_dynamic_cursor.py index ebf3c366e14c..7aea59427aa6 100644 --- a/pythonFiles/tests/test_dynamic_cursor.py +++ b/pythonFiles/tests/test_dynamic_cursor.py @@ -124,6 +124,7 @@ def test_skip_multi_comp_lambda(): # next executable statement as the lambda expression assert result["which_line_next"] == 7 + def test_move_whole_class(): """ Shift+enter on a class definition @@ -139,11 +140,13 @@ def __init__(self): def add_call(self, name, args=None, kwargs=None): self.calls.append((name, args, kwargs)) print("We should be here after running whole class") - """) + """ + ) result = normalizeSelection.traverse_file(src, 1, 1, False) assert result["which_line_next"] == 7 + def test_def_to_def(): importlib.reload(normalizeSelection) src = textwrap.dedent( @@ -158,11 +161,13 @@ def my_dogs(): # Skip here def next_func(): print("Not here but above") - """) + """ + ) result = normalizeSelection.traverse_file(src, 1, 1, False) assert result["which_line_next"] == 9 + def test_try_catch_move(): importlib.reload(normalizeSelection) src = textwrap.dedent( @@ -173,7 +178,26 @@ def test_try_catch_move(): print("error") print("Should be here afterwards") - """) + """ + ) result = normalizeSelection.traverse_file(src, 1, 1, False) assert result["which_line_next"] == 6 + + +def test_skip_nested(): + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + for i in range(1, 6): + for j in range(1, 6): + for x in range(1, 5): + for y in range(1, 5): + for z in range(1,10): + print(i, j, x, y, z) + + print("Cursor should be here after running line 1") + """ + ) + result = normalizeSelection.traverse_file(src, 1, 1, False) + assert result["which_line_next"] == 8 diff --git a/pythonFiles/tests/test_smart_selection.py b/pythonFiles/tests/test_smart_selection.py index 623ac12bee5a..b86e6f9dc82e 100644 --- a/pythonFiles/tests/test_smart_selection.py +++ b/pythonFiles/tests/test_smart_selection.py @@ -30,6 +30,33 @@ def test_part_dictionary(): assert result["normalized_smart_result"] == expected +def test_nested_loop(): + importlib.reload(normalizeSelection) + src = textwrap.dedent( + """\ + for i in range(1, 6): + for j in range(1, 6): + for x in range(1, 5): + for y in range(1, 5): + for z in range(1,10): + print(i, j, x, y, z) + """ + ) + expected = textwrap.dedent( + """\ + for i in range(1, 6): + for j in range(1, 6): + for x in range(1, 5): + for y in range(1, 5): + for z in range(1,10): + print(i, j, x, y, z) + + """ + ) + result = normalizeSelection.traverse_file(src, 1, 1, False) + assert result["normalized_smart_result"] == expected + + def test_smart_shift_enter_multiple_statements(): importlib.reload(normalizeSelection) src = textwrap.dedent( @@ -274,6 +301,7 @@ def test_multiline_lambda(): result = normalizeSelection.traverse_file(src, 1, 1, False) assert result["normalized_smart_result"] == expected + def test_send_whole_class(): """ Shift+enter on a class definition @@ -289,7 +317,8 @@ def __init__(self): def add_call(self, name, args=None, kwargs=None): self.calls.append((name, args, kwargs)) print("We should be here after running whole class") - """) + """ + ) result = normalizeSelection.traverse_file(src, 1, 1, False) expected = textwrap.dedent( """\ @@ -303,6 +332,7 @@ def add_call(self, name, args=None, kwargs=None): ) assert result["normalized_smart_result"] == expected + def test_send_whole_if_statement(): """ Shift+enter on an if statement @@ -318,7 +348,8 @@ def test_send_whole_if_statement(): print('also send this') print('cursor here afterwards') - """) + """ + ) expected = textwrap.dedent( """\ if True: @@ -326,10 +357,12 @@ def test_send_whole_if_statement(): else: print('also send this') - """) + """ + ) result = normalizeSelection.traverse_file(src, 1, 1, False) assert result["normalized_smart_result"] == expected + def test_send_try(): importlib.reload(normalizeSelection) src = textwrap.dedent( @@ -340,7 +373,8 @@ def test_send_try(): print("error") print("Not running this") - """) + """ + ) expected = textwrap.dedent( """\ try: @@ -348,6 +382,7 @@ def test_send_try(): except: print("error") - """) + """ + ) result = normalizeSelection.traverse_file(src, 1, 1, False) assert result["normalized_smart_result"] == expected From 5c03e54d6ca62427e131a877d635a7edbf890570 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 21 Sep 2023 14:43:10 -0700 Subject: [PATCH 68/82] moveToNextBlock as private --- pythonFiles/normalizeSelection.py | 17 ++++++++++------- src/client/terminals/codeExecution/helper.ts | 8 +++++--- src/client/terminals/types.ts | 1 - 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index a2b26950d313..ed85f85a1606 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -144,19 +144,22 @@ def check_exact_exist(top_level_nodes, start_line, end_line): def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): """ - Traverse through a user's given file content and find, collect all appropriate lines + Intended to traverse through a user's given file content and find, collect all appropriate lines that should be sent to the REPL in case of smart selection. + This could be exact statement such as just a single line print statement, + or a multiline dictionary, or differently styled multi-line list comprehension, etc. Then call the normalize_lines function to normalize our smartly selected code block. """ - # We need to collect ast nodes to iterate through. + parsed_file_content = ast.parse(wholeFileContent) smart_code = "" should_run_top_blocks = [] - # Iterate through the top level nodes in user's file document, - # and add to our top_level_nodes array. - # then for each of the top level, if it is valid ast_types with - # node.body, we will add all the child nodes that pertains to that - # top block code to the array as well. + + # Purpose of this loop is to fetch and collect all the + # AST top level nodes, and its node.body as child nodes. + # Individual nodes will contain information like + # the start line, end line and get source segment information + # that will be used to smartly select, and send normalized code. for node in ast.iter_child_nodes(parsed_file_content): top_level_nodes.append(node) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index d3cd435a541b..6d160e8813a1 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -120,13 +120,15 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { * Intended to provide smooth shift+enter user experience * bringing user's cursor to the next executable block of code when used with smart selection. */ - public async moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): Promise { + // eslint-disable-next-line class-methods-use-this + private async moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): Promise { if (pythonSmartSendEnabled(this.serviceContainer)) { if (activeEditor?.selection?.isEmpty) { - commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); - commands.executeCommand('cursorEnd'); + await commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); + await commands.executeCommand('cursorEnd'); } } + return Promise.resolve(); } public async getFileToExecute(): Promise { diff --git a/src/client/terminals/types.ts b/src/client/terminals/types.ts index 88e33365ea5f..48e39d4e1c81 100644 --- a/src/client/terminals/types.ts +++ b/src/client/terminals/types.ts @@ -19,7 +19,6 @@ export interface ICodeExecutionHelper { getFileToExecute(): Promise; saveFileIfDirty(file: Uri): Promise; getSelectedTextToExecute(textEditor: TextEditor): Promise; - moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): void; } export const ICodeExecutionManager = Symbol('ICodeExecutionManager'); From 4d21121f35cfdfa432dc0025d01a53b610fc6ad2 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Sun, 24 Sep 2023 22:17:19 -0700 Subject: [PATCH 69/82] typescript testing for cursor move --- src/client/common/application/commands.ts | 8 ++ src/client/terminals/codeExecution/helper.ts | 28 ++++- src/client/terminals/types.ts | 1 + .../terminals/codeExecution/smartSend.test.ts | 106 ++++++++++++++++++ 4 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 src/test/terminals/codeExecution/smartSend.test.ts diff --git a/src/client/common/application/commands.ts b/src/client/common/application/commands.ts index 2a4404440101..ca6f18762fb9 100644 --- a/src/client/common/application/commands.ts +++ b/src/client/common/application/commands.ts @@ -103,4 +103,12 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu [Commands.Tests_Configure]: [undefined, undefined | CommandSource, undefined | Uri]; [Commands.LaunchTensorBoard]: [TensorBoardEntrypoint, TensorBoardEntrypointTrigger]; ['workbench.view.testing.focus']: []; + ['cursorMove']: [ + { + to: string; + by: string; + value: number; + }, + ]; + ['cursorEnd']: []; } diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 6d160e8813a1..f9947ed128cd 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -5,7 +5,12 @@ import '../../common/extensions'; import { inject, injectable } from 'inversify'; import { l10n, Position, Range, TextEditor, Uri, commands } from 'vscode'; -import { IApplicationShell, IDocumentManager, IWorkspaceService } from '../../common/application/types'; +import { + IApplicationShell, + ICommandManager, + IDocumentManager, + IWorkspaceService, +} from '../../common/application/types'; import { PYTHON_LANGUAGE } from '../../common/constants'; import * as internalScripts from '../../common/process/internal/scripts'; import { IProcessServiceFactory } from '../../common/process/types'; @@ -29,6 +34,8 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { private readonly interpreterService: IInterpreterService; + private readonly commandManager: ICommandManager; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error TS6133: 'configSettings' is declared but its value is never read. private readonly configSettings: IConfigurationService; @@ -39,6 +46,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); this.interpreterService = serviceContainer.get(IInterpreterService); this.configSettings = serviceContainer.get(IConfigurationService); + this.commandManager = serviceContainer.get(ICommandManager); } public async normalizeLines(code: string, wholeFileContent?: string, resource?: Uri): Promise { @@ -121,11 +129,23 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { * bringing user's cursor to the next executable block of code when used with smart selection. */ // eslint-disable-next-line class-methods-use-this - private async moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): Promise { + public async moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): Promise { if (pythonSmartSendEnabled(this.serviceContainer)) { if (activeEditor?.selection?.isEmpty) { - await commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); - await commands.executeCommand('cursorEnd'); + // await commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); // dont import from vscode directly + // await commands.executeCommand('cursorEnd'); + // const dictionarySecond = { + // to: 'down', + // by: 'line', + // value: Number(lineOffset), + // }; + + await this.commandManager.executeCommand('cursorMove', { + to: 'down', + by: 'line', + value: Number(lineOffset), + }); + await this.commandManager.executeCommand('cursorEnd'); } } return Promise.resolve(); diff --git a/src/client/terminals/types.ts b/src/client/terminals/types.ts index 48e39d4e1c81..2345bc969479 100644 --- a/src/client/terminals/types.ts +++ b/src/client/terminals/types.ts @@ -19,6 +19,7 @@ export interface ICodeExecutionHelper { getFileToExecute(): Promise; saveFileIfDirty(file: Uri): Promise; getSelectedTextToExecute(textEditor: TextEditor): Promise; + moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): Promise; } export const ICodeExecutionManager = Symbol('ICodeExecutionManager'); diff --git a/src/test/terminals/codeExecution/smartSend.test.ts b/src/test/terminals/codeExecution/smartSend.test.ts new file mode 100644 index 000000000000..11a4833552da --- /dev/null +++ b/src/test/terminals/codeExecution/smartSend.test.ts @@ -0,0 +1,106 @@ +import * as TypeMoq from 'typemoq'; +import { TextEditor, Selection } from 'vscode'; +import { IApplicationShell, ICommandManager, IDocumentManager } from '../../../client/common/application/types'; +import { IProcessServiceFactory } from '../../../client/common/process/types'; +import { IInterpreterService } from '../../../client/interpreter/contracts'; +import { IConfigurationService, IExperimentService } from '../../../client/common/types'; +import { CodeExecutionHelper } from '../../../client/terminals/codeExecution/helper'; +import { IServiceContainer } from '../../../client/ioc/types'; +import { ICodeExecutionHelper } from '../../../client/terminals/types'; +import { EnableREPLSmartSend } from '../../../client/common/experiments/groups'; + +suite('REPL - Smart Send', () => { + let documentManager: TypeMoq.IMock; + let applicationShell: TypeMoq.IMock; + + let interpreterService: TypeMoq.IMock; + let commandManager: TypeMoq.IMock; + + let processServiceFactory: TypeMoq.IMock; + let configurationService: TypeMoq.IMock; + + let serviceContainer: TypeMoq.IMock; + let codeExecutionHelper: ICodeExecutionHelper; + let experimentService: TypeMoq.IMock; + + // suite set up only run once for each suite. Very start + // set up --- before each test + // tests -- actual tests + // tear down -- run after each test + // suite tear down only run once at the very end. + + // all object that is common to every test. What each test needs + setup(() => { + // Create mock + documentManager = TypeMoq.Mock.ofType(); + applicationShell = TypeMoq.Mock.ofType(); + processServiceFactory = TypeMoq.Mock.ofType(); + interpreterService = TypeMoq.Mock.ofType(); + commandManager = TypeMoq.Mock.ofType(); + configurationService = TypeMoq.Mock.ofType(); + serviceContainer = TypeMoq.Mock.ofType(); + experimentService = TypeMoq.Mock.ofType(); + + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IDocumentManager))) + .returns(() => documentManager.object); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IApplicationShell))) + .returns(() => applicationShell.object); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IProcessServiceFactory))) + .returns(() => processServiceFactory.object); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IInterpreterService))) + .returns(() => interpreterService.object); + serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(ICommandManager))).returns(() => commandManager.object); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IConfigurationService))) + .returns(() => configurationService.object); + serviceContainer + .setup((s) => s.get(TypeMoq.It.isValue(IExperimentService))) + .returns(() => experimentService.object); + codeExecutionHelper = new CodeExecutionHelper(serviceContainer.object); + }); + + test('Test executeCommand with cursorMove is called', async () => { + const activeEditor = TypeMoq.Mock.ofType(); + const selection = TypeMoq.Mock.ofType(); + selection.setup((s) => s.isEmpty).returns(() => true); + activeEditor.setup((e) => e.selection).returns(() => selection.object); + // experimentService + // .setup((exp) => exp.inExperiment(EnableREPLSmartSend.experiment)) + // .returns(() => Promise.resolve(true)) + // .verifiable(TypeMoq.Times.once()); + experimentService + .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) + .returns(() => true); + // mock experiment service. + // mock what happens when commandManager.executeCommand + // just use Typemoq + // verify arugments that executeCommand is being passed + // verify that executeCommand is called once for each argument + + commandManager + .setup((c) => + c.executeCommand('cursorMove', { + to: 'down', + by: 'line', + value: Number(3), + }), + ) + .verifiable(TypeMoq.Times.once()); + commandManager + .setup((c) => c.executeCommand('cursorEnd')) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + // .returns(() => Promise.resolve()) + try { + await codeExecutionHelper.moveToNextBlock(3, activeEditor.object); + + commandManager.verifyAll(); + } catch (error) { + console.log(error); + } + }); +}); From 94146cf753f4360c5ea30b662a9d8601736dae80 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 26 Sep 2023 02:13:36 -0700 Subject: [PATCH 70/82] smart selection typescript --- .../terminalExec/sample_smart_selection.py | 21 +++++ .../terminals/codeExecution/smartSend.test.ts | 89 ++++++++++++++++++- 2 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 src/test/pythonFiles/terminalExec/sample_smart_selection.py diff --git a/src/test/pythonFiles/terminalExec/sample_smart_selection.py b/src/test/pythonFiles/terminalExec/sample_smart_selection.py new file mode 100644 index 000000000000..3933f06b5d65 --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample_smart_selection.py @@ -0,0 +1,21 @@ +my_dict = { + "key1": "value1", + "key2": "value2" +} +#Sample + +print("Audi");print("BMW");print("Mercedes") + +# print("dont print me") + +def my_dogs(): + print("Corgi") + print("Husky") + print("Corgi2") + print("Husky2") + print("no dogs") + +# Skip me to prove that you did a good job +def next_func(): + print("You") + diff --git a/src/test/terminals/codeExecution/smartSend.test.ts b/src/test/terminals/codeExecution/smartSend.test.ts index 11a4833552da..2d7f8f5978fb 100644 --- a/src/test/terminals/codeExecution/smartSend.test.ts +++ b/src/test/terminals/codeExecution/smartSend.test.ts @@ -1,13 +1,23 @@ import * as TypeMoq from 'typemoq'; -import { TextEditor, Selection } from 'vscode'; +import * as path from 'path'; +import { TextEditor, Selection, window, Uri, Position, TextDocument } from 'vscode'; +import * as fs from 'fs-extra'; +import { SemVer } from 'semver'; import { IApplicationShell, ICommandManager, IDocumentManager } from '../../../client/common/application/types'; -import { IProcessServiceFactory } from '../../../client/common/process/types'; +import { IProcessService, IProcessServiceFactory } from '../../../client/common/process/types'; import { IInterpreterService } from '../../../client/interpreter/contracts'; import { IConfigurationService, IExperimentService } from '../../../client/common/types'; import { CodeExecutionHelper } from '../../../client/terminals/codeExecution/helper'; import { IServiceContainer } from '../../../client/ioc/types'; import { ICodeExecutionHelper } from '../../../client/terminals/types'; import { EnableREPLSmartSend } from '../../../client/common/experiments/groups'; +import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; +import { EnvironmentType, PythonEnvironment } from '../../../client/pythonEnvironments/info'; +import { PYTHON_PATH } from '../../common'; +import { Architecture } from '../../../client/common/utils/platform'; +import { ProcessService } from '../../../client/common/process/proc'; + +const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'terminalExec'); suite('REPL - Smart Send', () => { let documentManager: TypeMoq.IMock; @@ -23,6 +33,19 @@ suite('REPL - Smart Send', () => { let codeExecutionHelper: ICodeExecutionHelper; let experimentService: TypeMoq.IMock; + let processService: TypeMoq.IMock; + + let document: TypeMoq.IMock; + const workingPython: PythonEnvironment = { + path: PYTHON_PATH, + version: new SemVer('3.6.6-final'), + sysVersion: '1.0.0.0', + sysPrefix: 'Python', + displayName: 'Python', + envType: EnvironmentType.Unknown, + architecture: Architecture.x64, + }; + // suite set up only run once for each suite. Very start // set up --- before each test // tests -- actual tests @@ -40,13 +63,19 @@ suite('REPL - Smart Send', () => { configurationService = TypeMoq.Mock.ofType(); serviceContainer = TypeMoq.Mock.ofType(); experimentService = TypeMoq.Mock.ofType(); + processService = TypeMoq.Mock.ofType(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + processService.setup((x: any) => x.then).returns(() => undefined); serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IDocumentManager))) .returns(() => documentManager.object); serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IApplicationShell))) .returns(() => applicationShell.object); + processServiceFactory + .setup((p) => p.create(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(processService.object)); serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IProcessServiceFactory))) .returns(() => processServiceFactory.object); @@ -60,7 +89,26 @@ suite('REPL - Smart Send', () => { serviceContainer .setup((s) => s.get(TypeMoq.It.isValue(IExperimentService))) .returns(() => experimentService.object); + // interpreterService + // .setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny())) + // .returns(() => Promise.resolve(({ path: 'ps' } as unknown) as PythonEnvironment)); + interpreterService + .setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(workingPython)); + // processServiceFactory.setup((p) => p.create()).returns(() => Promise.resolve(processService.object)); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + // processService.setup((p) => (p as any).then).returns(() => undefined); + + /// ///////////////// + // processServiceFactory + // .setup((p) => p.create(TypeMoq.It.isAny())) + // .returns(() => Promise.resolve(processService.object)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + // processService.setup((p) => (p as any).then).returns(() => undefined); + codeExecutionHelper = new CodeExecutionHelper(serviceContainer.object); + document = TypeMoq.Mock.ofType(); }); test('Test executeCommand with cursorMove is called', async () => { @@ -80,7 +128,6 @@ suite('REPL - Smart Send', () => { // just use Typemoq // verify arugments that executeCommand is being passed // verify that executeCommand is called once for each argument - commandManager .setup((c) => c.executeCommand('cursorMove', { @@ -103,4 +150,40 @@ suite('REPL - Smart Send', () => { console.log(error); } }); + + test('Smart selection before normalization', async () => { + experimentService + .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) + .returns(() => true); + + // editor.setup((e) => e.selection).returns(() => new Selection(0, 0, 0, 0)); + // const textEditor = await window.showTextDocument(Uri.file(path.join(TEST_FILES_PATH, `sample_raw.py`))); + + // const activeEditor = TypeMoq.Mock.ofType(); + // const selection = TypeMoq.Mock.ofType(); + // selection.setup((s) => s.isEmpty).returns(() => true); + + const activeEditor = TypeMoq.Mock.ofType(); + const firstIndexPosition = new Position(0, 0); + const selection = TypeMoq.Mock.ofType(); + // activeEditor.setup((e) => e.selection).returns(() => selection.object); + const wholeFileContent = await fs.readFile(path.join(TEST_FILES_PATH, `sample_smart_selection.py`), 'utf8'); + + selection.setup((s) => s.anchor).returns(() => firstIndexPosition); + selection.setup((s) => s.active).returns(() => firstIndexPosition); + selection.setup((s) => s.isEmpty).returns(() => true); + activeEditor.setup((e) => e.selection).returns(() => selection.object); + + documentManager.setup((d) => d.activeTextEditor).returns(() => activeEditor.object); + document.setup((d) => d.getText(TypeMoq.It.isAny())).returns(() => wholeFileContent); + const actualProcessService = new ProcessService(); + processService + .setup((p) => p.execObservable(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns((file, args, options) => + actualProcessService.execObservable.apply(actualProcessService, [file, args, options]), + ); + // Imitiate we are sending from the very first line. + const normalizedCode = await codeExecutionHelper.normalizeLines('my_dict = {', wholeFileContent); + console.log('hi'); + }); }); From ccaaef5b7902ad0849ccf4ddc0853c5b93acfc6c Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 2 Oct 2023 00:23:42 -0700 Subject: [PATCH 71/82] more typescript tests --- src/client/terminals/codeExecution/helper.ts | 2 +- .../terminals/codeExecution/smartSend.test.ts | 135 +++++++++++------- 2 files changed, 83 insertions(+), 54 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index f9947ed128cd..e7e49a831c86 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -3,7 +3,7 @@ import '../../common/extensions'; import { inject, injectable } from 'inversify'; -import { l10n, Position, Range, TextEditor, Uri, commands } from 'vscode'; +import { l10n, Position, Range, TextEditor, Uri } from 'vscode'; import { IApplicationShell, diff --git a/src/test/terminals/codeExecution/smartSend.test.ts b/src/test/terminals/codeExecution/smartSend.test.ts index 2d7f8f5978fb..e5d663e312bf 100644 --- a/src/test/terminals/codeExecution/smartSend.test.ts +++ b/src/test/terminals/codeExecution/smartSend.test.ts @@ -1,8 +1,9 @@ import * as TypeMoq from 'typemoq'; import * as path from 'path'; -import { TextEditor, Selection, window, Uri, Position, TextDocument } from 'vscode'; +import { TextEditor, Selection, Position, TextDocument } from 'vscode'; import * as fs from 'fs-extra'; import { SemVer } from 'semver'; +import { assert, expect } from 'chai'; import { IApplicationShell, ICommandManager, IDocumentManager } from '../../../client/common/application/types'; import { IProcessService, IProcessServiceFactory } from '../../../client/common/process/types'; import { IInterpreterService } from '../../../client/interpreter/contracts'; @@ -54,7 +55,6 @@ suite('REPL - Smart Send', () => { // all object that is common to every test. What each test needs setup(() => { - // Create mock documentManager = TypeMoq.Mock.ofType(); applicationShell = TypeMoq.Mock.ofType(); processServiceFactory = TypeMoq.Mock.ofType(); @@ -89,23 +89,9 @@ suite('REPL - Smart Send', () => { serviceContainer .setup((s) => s.get(TypeMoq.It.isValue(IExperimentService))) .returns(() => experimentService.object); - // interpreterService - // .setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny())) - // .returns(() => Promise.resolve(({ path: 'ps' } as unknown) as PythonEnvironment)); interpreterService .setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny())) .returns(() => Promise.resolve(workingPython)); - // processServiceFactory.setup((p) => p.create()).returns(() => Promise.resolve(processService.object)); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - // processService.setup((p) => (p as any).then).returns(() => undefined); - - /// ///////////////// - // processServiceFactory - // .setup((p) => p.create(TypeMoq.It.isAny())) - // .returns(() => Promise.resolve(processService.object)); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - // processService.setup((p) => (p as any).then).returns(() => undefined); codeExecutionHelper = new CodeExecutionHelper(serviceContainer.object); document = TypeMoq.Mock.ofType(); @@ -116,57 +102,75 @@ suite('REPL - Smart Send', () => { const selection = TypeMoq.Mock.ofType(); selection.setup((s) => s.isEmpty).returns(() => true); activeEditor.setup((e) => e.selection).returns(() => selection.object); - // experimentService - // .setup((exp) => exp.inExperiment(EnableREPLSmartSend.experiment)) - // .returns(() => Promise.resolve(true)) - // .verifiable(TypeMoq.Times.once()); + experimentService .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) .returns(() => true); - // mock experiment service. - // mock what happens when commandManager.executeCommand - // just use Typemoq - // verify arugments that executeCommand is being passed - // verify that executeCommand is called once for each argument + + // Callback function here: + // When executeCommand iscalled, instead of calling actual executeCommand, + // Call this function and make sure arg2 is equal to the object we want. + // This is a better approach to verify complex argument. commandManager - .setup((c) => - c.executeCommand('cursorMove', { + .setup((c) => c.executeCommand('cursorMove', TypeMoq.It.isAny())) + .callback((_, arg2) => { + assert.deepEqual(arg2, { to: 'down', by: 'line', - value: Number(3), - }), - ) + value: 3, + }); + return Promise.resolve(); + }) .verifiable(TypeMoq.Times.once()); commandManager .setup((c) => c.executeCommand('cursorEnd')) .returns(() => Promise.resolve()) .verifiable(TypeMoq.Times.once()); - // .returns(() => Promise.resolve()) - try { - await codeExecutionHelper.moveToNextBlock(3, activeEditor.object); - - commandManager.verifyAll(); - } catch (error) { - console.log(error); - } + + await codeExecutionHelper.moveToNextBlock(3, activeEditor.object); + + commandManager.verifyAll(); + }); + + test('Cursor is not moved when not in SmartSend experiment', async () => { + const activeEditor = TypeMoq.Mock.ofType(); + const selection = TypeMoq.Mock.ofType(); + selection.setup((s) => s.isEmpty).returns(() => true); + activeEditor.setup((e) => e.selection).returns(() => selection.object); + + experimentService.setup((exp) => exp.inExperimentSync(TypeMoq.It.isAny())).returns(() => false); + commandManager + .setup((c) => c.executeCommand('cursorMove', TypeMoq.It.isAny())) + .callback((_, arg2) => { + assert.deepEqual(arg2, { + to: 'down', + by: 'line', + value: 3, + }); + return Promise.resolve(); + }) + .verifiable(TypeMoq.Times.never()); + + commandManager + .setup((c) => c.executeCommand('cursorEnd')) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.never()); + + await codeExecutionHelper.moveToNextBlock(3, activeEditor.object); + commandManager.verifyAll(); }); - test('Smart selection before normalization', async () => { + // make test to make sure when not at experiment, never call move cursor test- done + // When there is selection, never call cursor test- work in progress + + test('Smart send should perform smart selection and move cursor', async () => { experimentService .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) .returns(() => true); - // editor.setup((e) => e.selection).returns(() => new Selection(0, 0, 0, 0)); - // const textEditor = await window.showTextDocument(Uri.file(path.join(TEST_FILES_PATH, `sample_raw.py`))); - - // const activeEditor = TypeMoq.Mock.ofType(); - // const selection = TypeMoq.Mock.ofType(); - // selection.setup((s) => s.isEmpty).returns(() => true); - const activeEditor = TypeMoq.Mock.ofType(); const firstIndexPosition = new Position(0, 0); const selection = TypeMoq.Mock.ofType(); - // activeEditor.setup((e) => e.selection).returns(() => selection.object); const wholeFileContent = await fs.readFile(path.join(TEST_FILES_PATH, `sample_smart_selection.py`), 'utf8'); selection.setup((s) => s.anchor).returns(() => firstIndexPosition); @@ -177,13 +181,38 @@ suite('REPL - Smart Send', () => { documentManager.setup((d) => d.activeTextEditor).returns(() => activeEditor.object); document.setup((d) => d.getText(TypeMoq.It.isAny())).returns(() => wholeFileContent); const actualProcessService = new ProcessService(); + + const { execObservable } = actualProcessService; + processService .setup((p) => p.execObservable(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns((file, args, options) => - actualProcessService.execObservable.apply(actualProcessService, [file, args, options]), - ); - // Imitiate we are sending from the very first line. - const normalizedCode = await codeExecutionHelper.normalizeLines('my_dict = {', wholeFileContent); - console.log('hi'); + .returns((file, args, options) => execObservable.apply(actualProcessService, [file, args, options])); + + const actualSmartOutput = await codeExecutionHelper.normalizeLines('my_dict = {', wholeFileContent); + + // my_dict = { <----- smart shift+enter here + // "key1": "value1", + // "key2": "value2" + // } <---- cursor should be here afterwards, hence offset 3 + commandManager + .setup((c) => c.executeCommand('cursorMove', TypeMoq.It.isAny())) + .callback((_, arg2) => { + assert.deepEqual(arg2, { + to: 'down', + by: 'line', + value: 3, + }); + return Promise.resolve(); + }) + .verifiable(TypeMoq.Times.once()); + + commandManager + .setup((c) => c.executeCommand('cursorEnd')) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + + const expectedSmartOutput = 'my_dict = {\n "key1": "value1",\n "key2": "value2"\n}\n'; + expect(actualSmartOutput).to.be.equal(expectedSmartOutput); + commandManager.verifyAll(); }); }); From 848b80d2e70c26943accae8f7f72d66aa43e606f Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 2 Oct 2023 11:25:26 -0700 Subject: [PATCH 72/82] save more tests --- .../terminals/codeExecution/smartSend.test.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/test/terminals/codeExecution/smartSend.test.ts b/src/test/terminals/codeExecution/smartSend.test.ts index e5d663e312bf..51adc0abc9af 100644 --- a/src/test/terminals/codeExecution/smartSend.test.ts +++ b/src/test/terminals/codeExecution/smartSend.test.ts @@ -163,6 +163,53 @@ suite('REPL - Smart Send', () => { // make test to make sure when not at experiment, never call move cursor test- done // When there is selection, never call cursor test- work in progress + test('Cursor is not moved when explicit selection is present', async () => { + experimentService + .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) + .returns(() => true); + + const activeEditor = TypeMoq.Mock.ofType(); + const firstIndexPosition = new Position(0, 0); + const selection = TypeMoq.Mock.ofType(); + const wholeFileContent = await fs.readFile(path.join(TEST_FILES_PATH, `sample_smart_selection.py`), 'utf8'); + + selection.setup((s) => s.anchor).returns(() => firstIndexPosition); + selection.setup((s) => s.active).returns(() => firstIndexPosition); + selection.setup((s) => s.isEmpty).returns(() => false); + activeEditor.setup((e) => e.selection).returns(() => selection.object); + + documentManager.setup((d) => d.activeTextEditor).returns(() => activeEditor.object); + document.setup((d) => d.getText(TypeMoq.It.isAny())).returns(() => wholeFileContent); + const actualProcessService = new ProcessService(); + + const { execObservable } = actualProcessService; + + processService + .setup((p) => p.execObservable(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns((file, args, options) => execObservable.apply(actualProcessService, [file, args, options])); + + await codeExecutionHelper.normalizeLines('my_dict = {', wholeFileContent); + + commandManager + .setup((c) => c.executeCommand('cursorMove', TypeMoq.It.isAny())) + .callback((_, arg2) => { + assert.deepEqual(arg2, { + to: 'down', + by: 'line', + value: 3, + }); + return Promise.resolve(); + }) + .verifiable(TypeMoq.Times.never()); + + commandManager + .setup((c) => c.executeCommand('cursorEnd')) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.never()); + + commandManager.verifyAll(); + }); + test('Smart send should perform smart selection and move cursor', async () => { experimentService .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) @@ -215,4 +262,6 @@ suite('REPL - Smart Send', () => { expect(actualSmartOutput).to.be.equal(expectedSmartOutput); commandManager.verifyAll(); }); + + // Do not perform smart selection when there is explicit selection }); From 3643651e508748424c52626b1a85c79fe3669e9b Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 2 Oct 2023 23:49:15 -0700 Subject: [PATCH 73/82] add more tests --- .../terminals/codeExecution/smartSend.test.ts | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/test/terminals/codeExecution/smartSend.test.ts b/src/test/terminals/codeExecution/smartSend.test.ts index 51adc0abc9af..db59a2b602d3 100644 --- a/src/test/terminals/codeExecution/smartSend.test.ts +++ b/src/test/terminals/codeExecution/smartSend.test.ts @@ -160,9 +160,6 @@ suite('REPL - Smart Send', () => { commandManager.verifyAll(); }); - // make test to make sure when not at experiment, never call move cursor test- done - // When there is selection, never call cursor test- work in progress - test('Cursor is not moved when explicit selection is present', async () => { experimentService .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) @@ -264,4 +261,32 @@ suite('REPL - Smart Send', () => { }); // Do not perform smart selection when there is explicit selection + test('Smart send should not perform smart selection when there is explicit selection', async () => { + experimentService + .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) + .returns(() => true); + const activeEditor = TypeMoq.Mock.ofType(); + const firstIndexPosition = new Position(0, 0); + const selection = TypeMoq.Mock.ofType(); + const wholeFileContent = await fs.readFile(path.join(TEST_FILES_PATH, `sample_smart_selection.py`), 'utf8'); + + selection.setup((s) => s.anchor).returns(() => firstIndexPosition); + selection.setup((s) => s.active).returns(() => firstIndexPosition); + selection.setup((s) => s.isEmpty).returns(() => false); + activeEditor.setup((e) => e.selection).returns(() => selection.object); + + documentManager.setup((d) => d.activeTextEditor).returns(() => activeEditor.object); + document.setup((d) => d.getText(TypeMoq.It.isAny())).returns(() => wholeFileContent); + const actualProcessService = new ProcessService(); + + const { execObservable } = actualProcessService; + + processService + .setup((p) => p.execObservable(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns((file, args, options) => execObservable.apply(actualProcessService, [file, args, options])); + + const actualNonSmartResult = await codeExecutionHelper.normalizeLines('my_dict = {', wholeFileContent); + const expectedNonSmartResult = 'my_dict = {\n\n'; // Standard for previous normalization logic + expect(actualNonSmartResult).to.be.equal(expectedNonSmartResult); + }); }); From 15ad56b51e14d8daee37b7c691dc52eee2537864 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Sun, 8 Oct 2023 11:41:40 -0700 Subject: [PATCH 74/82] add smoke test for smart send --- src/test/smoke/smartSend.smoke.test.ts | 91 +++++++++++++++++++ .../smokeTests/create_delete_file.py | 5 + 2 files changed, 96 insertions(+) create mode 100644 src/test/smoke/smartSend.smoke.test.ts create mode 100644 src/testMultiRootWkspc/smokeTests/create_delete_file.py diff --git a/src/test/smoke/smartSend.smoke.test.ts b/src/test/smoke/smartSend.smoke.test.ts new file mode 100644 index 000000000000..89e82ad1766e --- /dev/null +++ b/src/test/smoke/smartSend.smoke.test.ts @@ -0,0 +1,91 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { assert } from 'chai'; +import { IS_SMOKE_TEST, EXTENSION_ROOT_DIR_FOR_TESTS } from '../constants'; +import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { openFile, waitForCondition } from '../common'; + +// const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'terminalExec'); + +suite('Smoke Test: Run Smart Selection and Advance Cursor', () => { + // "keybindings": [ + // { + // "command": "python.execSelectionInTerminal", + // "key": "shift+enter", + // "when": "editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused" + // }, + suiteSetup(async function () { + if (!IS_SMOKE_TEST) { + return this.skip(); + } + await initialize(); + return undefined; + }); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); + + test('Smart Send', async () => { + const file = path.join( + EXTENSION_ROOT_DIR_FOR_TESTS, + 'src', + 'testMultiRootWkspc', + 'smokeTests', + 'create_delete_file.py', + ); + const outputFile = path.join( + EXTENSION_ROOT_DIR_FOR_TESTS, + 'src', + 'testMultiRootWkspc', + 'smokeTests', + 'smart_send_smoke.txt', + ); + + await fs.remove(outputFile); + + const textDocument = await openFile(file); + + if (vscode.window.activeTextEditor) { + const myPos = new vscode.Position(0, 0); + vscode.window.activeTextEditor!.selections = [new vscode.Selection(myPos, myPos)]; + } + await vscode.commands + .executeCommand('python.execSelectionInTerminal', textDocument.uri) + .then(undefined, (err) => { + assert.fail(`Something went wrong running the Python file in the terminal: ${err}`); + }); + + const checkIfFileHasBeenCreated = () => fs.pathExists(outputFile); + await waitForCondition(checkIfFileHasBeenCreated, 30_000, `"${outputFile}" file not created`); + + // Shift+enter two more times so we can track cursor movement with deletion of file + await vscode.commands + .executeCommand('python.execSelectionInTerminal', textDocument.uri) + .then(undefined, (err) => { + assert.fail(`Something went wrong running the Python file in the terminal: ${err}`); + }); + await vscode.commands + .executeCommand('python.execSelectionInTerminal', textDocument.uri) + .then(undefined, (err) => { + assert.fail(`Something went wrong running the Python file in the terminal: ${err}`); + }); + + async function waitThirtySeconds() { + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 30000); + }); + } + + await waitThirtySeconds(); + + const deletedFile = !(await fs.pathExists(outputFile)); + if (deletedFile) { + assert.ok(true, `"${outputFile}" file has been deleted`); + } else { + assert.fail(`"${outputFile}" file still exists`); + } + }); +}); diff --git a/src/testMultiRootWkspc/smokeTests/create_delete_file.py b/src/testMultiRootWkspc/smokeTests/create_delete_file.py new file mode 100644 index 000000000000..399bc4863c15 --- /dev/null +++ b/src/testMultiRootWkspc/smokeTests/create_delete_file.py @@ -0,0 +1,5 @@ +with open('smart_send_smoke.txt', 'w') as f: + f.write('This is for smart send smoke test') +import os + +os.remove('smart_send_smoke.txt') From 905a31dbe68e8a2f1e921b629b9220ce210c1186 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 9 Oct 2023 01:14:04 -0700 Subject: [PATCH 75/82] delete unnecessary comment --- pythonFiles/normalizeSelection.py | 3 +-- src/client/terminals/codeExecution/helper.ts | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index ed85f85a1606..dde8b40009aa 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -275,8 +275,7 @@ def get_next_block_lineno(which_line_next): # Send the normalized code back to the extension in a JSON object. data = None which_line_next = 0 - # Depending on whether there was a explicit highlight, send smart selection or regular normalization. - # Experiment also has to be enable to use smart selection. + if empty_Highlight and contents.get("smartSendExperimentEnabled"): result = traverse_file( contents["wholeFileContent"], diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index e7e49a831c86..01a97975c205 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -132,14 +132,6 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { public async moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): Promise { if (pythonSmartSendEnabled(this.serviceContainer)) { if (activeEditor?.selection?.isEmpty) { - // await commands.executeCommand('cursorMove', { to: 'down', by: 'line', value: Number(lineOffset) }); // dont import from vscode directly - // await commands.executeCommand('cursorEnd'); - // const dictionarySecond = { - // to: 'down', - // by: 'line', - // value: Number(lineOffset), - // }; - await this.commandManager.executeCommand('cursorMove', { to: 'down', by: 'line', From 36f5d5b94cc8e50d18e59f592cff1466d1bcb886 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 9 Oct 2023 10:15:47 -0700 Subject: [PATCH 76/82] move moveCursor to private --- src/client/terminals/codeExecution/helper.ts | 2 +- src/client/terminals/types.ts | 1 - src/test/smoke/smartSend.smoke.test.ts | 13 ++++ .../terminals/codeExecution/smartSend.test.ts | 63 ------------------- 4 files changed, 14 insertions(+), 65 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 01a97975c205..0a2941cbbb34 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -129,7 +129,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { * bringing user's cursor to the next executable block of code when used with smart selection. */ // eslint-disable-next-line class-methods-use-this - public async moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): Promise { + private async moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): Promise { if (pythonSmartSendEnabled(this.serviceContainer)) { if (activeEditor?.selection?.isEmpty) { await this.commandManager.executeCommand('cursorMove', { diff --git a/src/client/terminals/types.ts b/src/client/terminals/types.ts index 2345bc969479..48e39d4e1c81 100644 --- a/src/client/terminals/types.ts +++ b/src/client/terminals/types.ts @@ -19,7 +19,6 @@ export interface ICodeExecutionHelper { getFileToExecute(): Promise; saveFileIfDirty(file: Uri): Promise; getSelectedTextToExecute(textEditor: TextEditor): Promise; - moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): Promise; } export const ICodeExecutionManager = Symbol('ICodeExecutionManager'); diff --git a/src/test/smoke/smartSend.smoke.test.ts b/src/test/smoke/smartSend.smoke.test.ts index 89e82ad1766e..39e65309afa6 100644 --- a/src/test/smoke/smartSend.smoke.test.ts +++ b/src/test/smoke/smartSend.smoke.test.ts @@ -1,14 +1,18 @@ import * as vscode from 'vscode'; +import * as TypeMoq from 'typemoq'; import * as path from 'path'; import * as fs from 'fs-extra'; import { assert } from 'chai'; import { IS_SMOKE_TEST, EXTENSION_ROOT_DIR_FOR_TESTS } from '../constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { openFile, waitForCondition } from '../common'; +import { IExperimentService } from '../../client/common/types'; +import { EnableREPLSmartSend } from '../../client/common/experiments/groups'; // const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'terminalExec'); suite('Smoke Test: Run Smart Selection and Advance Cursor', () => { + let experimentService: TypeMoq.IMock; // "keybindings": [ // { // "command": "python.execSelectionInTerminal", @@ -16,17 +20,26 @@ suite('Smoke Test: Run Smart Selection and Advance Cursor', () => { // "when": "editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused" // }, suiteSetup(async function () { + experimentService = TypeMoq.Mock.ofType(); + experimentService + .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) + .returns(() => true); if (!IS_SMOKE_TEST) { return this.skip(); } await initialize(); return undefined; }); + setup(initializeTest); suiteTeardown(closeActiveWindows); teardown(closeActiveWindows); test('Smart Send', async () => { + experimentService = TypeMoq.Mock.ofType(); + experimentService + .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) + .returns(() => true); const file = path.join( EXTENSION_ROOT_DIR_FOR_TESTS, 'src', diff --git a/src/test/terminals/codeExecution/smartSend.test.ts b/src/test/terminals/codeExecution/smartSend.test.ts index db59a2b602d3..8d70ab6e01e0 100644 --- a/src/test/terminals/codeExecution/smartSend.test.ts +++ b/src/test/terminals/codeExecution/smartSend.test.ts @@ -97,69 +97,6 @@ suite('REPL - Smart Send', () => { document = TypeMoq.Mock.ofType(); }); - test('Test executeCommand with cursorMove is called', async () => { - const activeEditor = TypeMoq.Mock.ofType(); - const selection = TypeMoq.Mock.ofType(); - selection.setup((s) => s.isEmpty).returns(() => true); - activeEditor.setup((e) => e.selection).returns(() => selection.object); - - experimentService - .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) - .returns(() => true); - - // Callback function here: - // When executeCommand iscalled, instead of calling actual executeCommand, - // Call this function and make sure arg2 is equal to the object we want. - // This is a better approach to verify complex argument. - commandManager - .setup((c) => c.executeCommand('cursorMove', TypeMoq.It.isAny())) - .callback((_, arg2) => { - assert.deepEqual(arg2, { - to: 'down', - by: 'line', - value: 3, - }); - return Promise.resolve(); - }) - .verifiable(TypeMoq.Times.once()); - commandManager - .setup((c) => c.executeCommand('cursorEnd')) - .returns(() => Promise.resolve()) - .verifiable(TypeMoq.Times.once()); - - await codeExecutionHelper.moveToNextBlock(3, activeEditor.object); - - commandManager.verifyAll(); - }); - - test('Cursor is not moved when not in SmartSend experiment', async () => { - const activeEditor = TypeMoq.Mock.ofType(); - const selection = TypeMoq.Mock.ofType(); - selection.setup((s) => s.isEmpty).returns(() => true); - activeEditor.setup((e) => e.selection).returns(() => selection.object); - - experimentService.setup((exp) => exp.inExperimentSync(TypeMoq.It.isAny())).returns(() => false); - commandManager - .setup((c) => c.executeCommand('cursorMove', TypeMoq.It.isAny())) - .callback((_, arg2) => { - assert.deepEqual(arg2, { - to: 'down', - by: 'line', - value: 3, - }); - return Promise.resolve(); - }) - .verifiable(TypeMoq.Times.never()); - - commandManager - .setup((c) => c.executeCommand('cursorEnd')) - .returns(() => Promise.resolve()) - .verifiable(TypeMoq.Times.never()); - - await codeExecutionHelper.moveToNextBlock(3, activeEditor.object); - commandManager.verifyAll(); - }); - test('Cursor is not moved when explicit selection is present', async () => { experimentService .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) From b70b0f1ac12e537ef952f8dfc75d3478e4185864 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 9 Oct 2023 11:26:18 -0700 Subject: [PATCH 77/82] comment out smoke test --- src/test/smoke/smartSend.smoke.test.ts | 233 +++++++++++++++---------- 1 file changed, 141 insertions(+), 92 deletions(-) diff --git a/src/test/smoke/smartSend.smoke.test.ts b/src/test/smoke/smartSend.smoke.test.ts index 39e65309afa6..3c44ce9ff6e8 100644 --- a/src/test/smoke/smartSend.smoke.test.ts +++ b/src/test/smoke/smartSend.smoke.test.ts @@ -1,104 +1,153 @@ -import * as vscode from 'vscode'; -import * as TypeMoq from 'typemoq'; -import * as path from 'path'; -import * as fs from 'fs-extra'; -import { assert } from 'chai'; -import { IS_SMOKE_TEST, EXTENSION_ROOT_DIR_FOR_TESTS } from '../constants'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; -import { openFile, waitForCondition } from '../common'; -import { IExperimentService } from '../../client/common/types'; -import { EnableREPLSmartSend } from '../../client/common/experiments/groups'; +// import * as vscode from 'vscode'; +// import * as TypeMoq from 'typemoq'; +// import * as path from 'path'; +// import * as fs from 'fs-extra'; +// import { assert } from 'chai'; +// import { mock, when } from 'ts-mockito'; +// import * as tasClient from 'vscode-tas-client'; +// import sinon from 'sinon'; +// import { IS_SMOKE_TEST, EXTENSION_ROOT_DIR_FOR_TESTS, PVSC_EXTENSION_ID_FOR_TESTS } from '../constants'; +// import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +// import { openFile, waitForCondition } from '../common'; +// import { IExperimentService } from '../../client/common/types'; +// import { EnableREPLSmartSend } from '../../client/common/experiments/groups'; +// import { IServiceContainer } from '../../client/ioc/types'; +// import { IApplicationEnvironment, IWorkspaceService } from '../../client/common/application/types'; +// import { WorkspaceService } from '../../client/common/application/workspace'; +// import { Channel } from '../../client/common/constants'; -// const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'terminalExec'); +// // const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'terminalExec'); -suite('Smoke Test: Run Smart Selection and Advance Cursor', () => { - let experimentService: TypeMoq.IMock; - // "keybindings": [ - // { - // "command": "python.execSelectionInTerminal", - // "key": "shift+enter", - // "when": "editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused" - // }, - suiteSetup(async function () { - experimentService = TypeMoq.Mock.ofType(); - experimentService - .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) - .returns(() => true); - if (!IS_SMOKE_TEST) { - return this.skip(); - } - await initialize(); - return undefined; - }); +// suite('Smoke Test: Run Smart Selection and Advance Cursor', () => { +// // const extensionVersion = '1.2.3'; +// let workspaceService: IWorkspaceService; +// let experimentService: TypeMoq.IMock; +// let serviceContainer: TypeMoq.IMock; +// let appEnvironment: IApplicationEnvironment; +// // "keybindings": [ +// // { +// // "command": "python.execSelectionInTerminal", +// // "key": "shift+enter", +// // "when": "editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused" +// // }, +// function configureApplicationEnvironment(channel: Channel, version: string, contributes?: Record) { +// when(appEnvironment.extensionChannel).thenReturn(channel); +// when(appEnvironment.extensionName).thenReturn(PVSC_EXTENSION_ID_FOR_TESTS); +// when(appEnvironment.packageJson).thenReturn({ version, contributes }); +// } +// function configureSettings(enabled: boolean, optInto: string[], optOutFrom: string[]) { +// when(workspaceService.getConfiguration('python')).thenReturn({ +// get: (key: string) => { +// if (key === 'experiments.enabled') { +// return enabled; +// } +// if (key === 'experiments.optInto') { +// return optInto; +// } +// if (key === 'experiments.optOutFrom') { +// return optOutFrom; +// } +// return undefined; +// }, +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// } as any); +// } +// suiteSetup(async function () { +// workspaceService = mock(WorkspaceService); +// configureSettings(true, ['pythonREPLSmartSend', 'EnableREPLSmartSend'], []); +// // configureApplicationEnvironment('stable', extensionVersion); - setup(initializeTest); - suiteTeardown(closeActiveWindows); - teardown(closeActiveWindows); +// serviceContainer = TypeMoq.Mock.ofType(); +// serviceContainer +// .setup((s) => s.get(TypeMoq.It.isValue(IExperimentService))) +// .returns(() => experimentService.object); +// experimentService = TypeMoq.Mock.ofType(); +// experimentService +// .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) +// .returns(() => true); +// if (!IS_SMOKE_TEST) { +// return this.skip(); +// } +// await initialize(); +// return undefined; +// }); - test('Smart Send', async () => { - experimentService = TypeMoq.Mock.ofType(); - experimentService - .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) - .returns(() => true); - const file = path.join( - EXTENSION_ROOT_DIR_FOR_TESTS, - 'src', - 'testMultiRootWkspc', - 'smokeTests', - 'create_delete_file.py', - ); - const outputFile = path.join( - EXTENSION_ROOT_DIR_FOR_TESTS, - 'src', - 'testMultiRootWkspc', - 'smokeTests', - 'smart_send_smoke.txt', - ); +// // setup(initializeTest); +// // suiteTeardown(closeActiveWindows); +// // teardown(closeActiveWindows); - await fs.remove(outputFile); +// test('Smart Send', async () => { +// sinon.stub(tasClient, 'getExperimentationService'); +// configureSettings(true, ['pythonREPLSmartSend', 'EnableREPLSmartSend'], []); +// serviceContainer = TypeMoq.Mock.ofType(); +// serviceContainer +// .setup((s) => s.get(TypeMoq.It.isValue(IExperimentService))) +// .returns(() => experimentService.object); +// experimentService = TypeMoq.Mock.ofType(); - const textDocument = await openFile(file); +// experimentService +// .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) +// .returns(() => true); - if (vscode.window.activeTextEditor) { - const myPos = new vscode.Position(0, 0); - vscode.window.activeTextEditor!.selections = [new vscode.Selection(myPos, myPos)]; - } - await vscode.commands - .executeCommand('python.execSelectionInTerminal', textDocument.uri) - .then(undefined, (err) => { - assert.fail(`Something went wrong running the Python file in the terminal: ${err}`); - }); +// const file = path.join( +// EXTENSION_ROOT_DIR_FOR_TESTS, +// 'src', +// 'testMultiRootWkspc', +// 'smokeTests', +// 'create_delete_file.py', +// ); +// const outputFile = path.join( +// EXTENSION_ROOT_DIR_FOR_TESTS, +// 'src', +// 'testMultiRootWkspc', +// 'smokeTests', +// 'smart_send_smoke.txt', +// ); - const checkIfFileHasBeenCreated = () => fs.pathExists(outputFile); - await waitForCondition(checkIfFileHasBeenCreated, 30_000, `"${outputFile}" file not created`); +// await fs.remove(outputFile); - // Shift+enter two more times so we can track cursor movement with deletion of file - await vscode.commands - .executeCommand('python.execSelectionInTerminal', textDocument.uri) - .then(undefined, (err) => { - assert.fail(`Something went wrong running the Python file in the terminal: ${err}`); - }); - await vscode.commands - .executeCommand('python.execSelectionInTerminal', textDocument.uri) - .then(undefined, (err) => { - assert.fail(`Something went wrong running the Python file in the terminal: ${err}`); - }); +// const textDocument = await openFile(file); - async function waitThirtySeconds() { - return new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, 30000); - }); - } +// if (vscode.window.activeTextEditor) { +// const myPos = new vscode.Position(0, 0); +// vscode.window.activeTextEditor!.selections = [new vscode.Selection(myPos, myPos)]; +// } +// await vscode.commands +// .executeCommand('python.execSelectionInTerminal', textDocument.uri) +// .then(undefined, (err) => { +// assert.fail(`Something went wrong running the Python file in the terminal: ${err}`); +// }); - await waitThirtySeconds(); +// const checkIfFileHasBeenCreated = () => fs.pathExists(outputFile); +// await waitForCondition(checkIfFileHasBeenCreated, 30_000, `"${outputFile}" file not created`); - const deletedFile = !(await fs.pathExists(outputFile)); - if (deletedFile) { - assert.ok(true, `"${outputFile}" file has been deleted`); - } else { - assert.fail(`"${outputFile}" file still exists`); - } - }); -}); +// // Shift+enter two more times so we can track cursor movement with deletion of file +// await vscode.commands +// .executeCommand('python.execSelectionInTerminal', textDocument.uri) +// .then(undefined, (err) => { +// assert.fail(`Something went wrong running the Python file in the terminal: ${err}`); +// }); +// await vscode.commands +// .executeCommand('python.execSelectionInTerminal', textDocument.uri) +// .then(undefined, (err) => { +// assert.fail(`Something went wrong running the Python file in the terminal: ${err}`); +// }); + +// async function waitThirtySeconds() { +// return new Promise((resolve) => { +// setTimeout(() => { +// resolve(); +// }, 30000); +// }); +// } + +// await waitThirtySeconds(); + +// const deletedFile = !(await fs.pathExists(outputFile)); +// if (deletedFile) { +// assert.ok(true, `"${outputFile}" file has been deleted`); +// } else { +// assert.fail(`"${outputFile}" file still exists`); +// } +// }); +// }); From 4f4c86ad0f092c43a83bcc62ac13f0144cf61f7e Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 9 Oct 2023 11:36:53 -0700 Subject: [PATCH 78/82] clean up variable names --- pythonFiles/normalizeSelection.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index dde8b40009aa..85c054b6ced7 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -207,20 +207,20 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # add the appropriate source code line(s) to be sent to the REPL, dependent on # user is trying to send and execute single line/statement or multiple with smart selection. for top_node in ast.iter_child_nodes(parsed_file_content): - top_level_block_start_line = top_node.lineno - top_level_block_end_line = top_node.end_lineno + # top_level_block_start_line = top_node.lineno + # top_level_block_end_line = top_node.end_lineno if ( - start_line == top_level_block_start_line - and end_line == top_level_block_end_line + start_line == top_node.lineno + and end_line == top_node.end_lineno ): should_run_top_blocks.append(top_node) smart_code += f"{ast.get_source_segment(wholeFileContent, top_node)}\n" break # If we found exact match, don't waste computation in parsing extra nodes. elif ( - start_line >= top_level_block_start_line - and end_line <= top_level_block_end_line + start_line >= top_node.lineno + and end_line <= top_node.end_lineno ): # Case to apply smart selection for multiple line. # This is the case for when we have to add multiple lines that should be included in the smart send. From 9450f760a2039fb4fe85569ca7d5b673edc29a62 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 9 Oct 2023 11:40:58 -0700 Subject: [PATCH 79/82] format and rename variables --- pythonFiles/normalizeSelection.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 85c054b6ced7..57edf68e0a6c 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -210,18 +210,12 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # top_level_block_start_line = top_node.lineno # top_level_block_end_line = top_node.end_lineno - if ( - start_line == top_node.lineno - and end_line == top_node.end_lineno - ): + if start_line == top_node.lineno and end_line == top_node.end_lineno: should_run_top_blocks.append(top_node) smart_code += f"{ast.get_source_segment(wholeFileContent, top_node)}\n" break # If we found exact match, don't waste computation in parsing extra nodes. - elif ( - start_line >= top_node.lineno - and end_line <= top_node.end_lineno - ): + elif start_line >= top_node.lineno and end_line <= top_node.end_lineno: # Case to apply smart selection for multiple line. # This is the case for when we have to add multiple lines that should be included in the smart send. # For example: @@ -249,13 +243,13 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # This will be used in calculating lineOffset to move cursor in VS Code. def get_next_block_lineno(which_line_next): last_ran_lineno = int(which_line_next[-1].end_lineno) - temp_next_lineno = int(which_line_next[-1].end_lineno) + next_lineno = int(which_line_next[-1].end_lineno) for reverse_node in top_level_nodes: if reverse_node.lineno > last_ran_lineno: - temp_next_lineno = reverse_node.lineno + next_lineno = reverse_node.lineno break - return temp_next_lineno + return next_lineno if __name__ == "__main__": From d416fecb0af4cd7c08bc3ba58cfcb40f32f1cf87 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 9 Oct 2023 12:29:08 -0700 Subject: [PATCH 80/82] leave out smoke test for now --- src/test/smoke/smartSend.smoke.test.ts | 153 ------------------------- 1 file changed, 153 deletions(-) diff --git a/src/test/smoke/smartSend.smoke.test.ts b/src/test/smoke/smartSend.smoke.test.ts index 3c44ce9ff6e8..e69de29bb2d1 100644 --- a/src/test/smoke/smartSend.smoke.test.ts +++ b/src/test/smoke/smartSend.smoke.test.ts @@ -1,153 +0,0 @@ -// import * as vscode from 'vscode'; -// import * as TypeMoq from 'typemoq'; -// import * as path from 'path'; -// import * as fs from 'fs-extra'; -// import { assert } from 'chai'; -// import { mock, when } from 'ts-mockito'; -// import * as tasClient from 'vscode-tas-client'; -// import sinon from 'sinon'; -// import { IS_SMOKE_TEST, EXTENSION_ROOT_DIR_FOR_TESTS, PVSC_EXTENSION_ID_FOR_TESTS } from '../constants'; -// import { closeActiveWindows, initialize, initializeTest } from '../initialize'; -// import { openFile, waitForCondition } from '../common'; -// import { IExperimentService } from '../../client/common/types'; -// import { EnableREPLSmartSend } from '../../client/common/experiments/groups'; -// import { IServiceContainer } from '../../client/ioc/types'; -// import { IApplicationEnvironment, IWorkspaceService } from '../../client/common/application/types'; -// import { WorkspaceService } from '../../client/common/application/workspace'; -// import { Channel } from '../../client/common/constants'; - -// // const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'terminalExec'); - -// suite('Smoke Test: Run Smart Selection and Advance Cursor', () => { -// // const extensionVersion = '1.2.3'; -// let workspaceService: IWorkspaceService; -// let experimentService: TypeMoq.IMock; -// let serviceContainer: TypeMoq.IMock; -// let appEnvironment: IApplicationEnvironment; -// // "keybindings": [ -// // { -// // "command": "python.execSelectionInTerminal", -// // "key": "shift+enter", -// // "when": "editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused" -// // }, -// function configureApplicationEnvironment(channel: Channel, version: string, contributes?: Record) { -// when(appEnvironment.extensionChannel).thenReturn(channel); -// when(appEnvironment.extensionName).thenReturn(PVSC_EXTENSION_ID_FOR_TESTS); -// when(appEnvironment.packageJson).thenReturn({ version, contributes }); -// } -// function configureSettings(enabled: boolean, optInto: string[], optOutFrom: string[]) { -// when(workspaceService.getConfiguration('python')).thenReturn({ -// get: (key: string) => { -// if (key === 'experiments.enabled') { -// return enabled; -// } -// if (key === 'experiments.optInto') { -// return optInto; -// } -// if (key === 'experiments.optOutFrom') { -// return optOutFrom; -// } -// return undefined; -// }, -// // eslint-disable-next-line @typescript-eslint/no-explicit-any -// } as any); -// } -// suiteSetup(async function () { -// workspaceService = mock(WorkspaceService); -// configureSettings(true, ['pythonREPLSmartSend', 'EnableREPLSmartSend'], []); -// // configureApplicationEnvironment('stable', extensionVersion); - -// serviceContainer = TypeMoq.Mock.ofType(); -// serviceContainer -// .setup((s) => s.get(TypeMoq.It.isValue(IExperimentService))) -// .returns(() => experimentService.object); -// experimentService = TypeMoq.Mock.ofType(); -// experimentService -// .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) -// .returns(() => true); -// if (!IS_SMOKE_TEST) { -// return this.skip(); -// } -// await initialize(); -// return undefined; -// }); - -// // setup(initializeTest); -// // suiteTeardown(closeActiveWindows); -// // teardown(closeActiveWindows); - -// test('Smart Send', async () => { -// sinon.stub(tasClient, 'getExperimentationService'); -// configureSettings(true, ['pythonREPLSmartSend', 'EnableREPLSmartSend'], []); -// serviceContainer = TypeMoq.Mock.ofType(); -// serviceContainer -// .setup((s) => s.get(TypeMoq.It.isValue(IExperimentService))) -// .returns(() => experimentService.object); -// experimentService = TypeMoq.Mock.ofType(); - -// experimentService -// .setup((exp) => exp.inExperimentSync(TypeMoq.It.isValue(EnableREPLSmartSend.experiment))) -// .returns(() => true); - -// const file = path.join( -// EXTENSION_ROOT_DIR_FOR_TESTS, -// 'src', -// 'testMultiRootWkspc', -// 'smokeTests', -// 'create_delete_file.py', -// ); -// const outputFile = path.join( -// EXTENSION_ROOT_DIR_FOR_TESTS, -// 'src', -// 'testMultiRootWkspc', -// 'smokeTests', -// 'smart_send_smoke.txt', -// ); - -// await fs.remove(outputFile); - -// const textDocument = await openFile(file); - -// if (vscode.window.activeTextEditor) { -// const myPos = new vscode.Position(0, 0); -// vscode.window.activeTextEditor!.selections = [new vscode.Selection(myPos, myPos)]; -// } -// await vscode.commands -// .executeCommand('python.execSelectionInTerminal', textDocument.uri) -// .then(undefined, (err) => { -// assert.fail(`Something went wrong running the Python file in the terminal: ${err}`); -// }); - -// const checkIfFileHasBeenCreated = () => fs.pathExists(outputFile); -// await waitForCondition(checkIfFileHasBeenCreated, 30_000, `"${outputFile}" file not created`); - -// // Shift+enter two more times so we can track cursor movement with deletion of file -// await vscode.commands -// .executeCommand('python.execSelectionInTerminal', textDocument.uri) -// .then(undefined, (err) => { -// assert.fail(`Something went wrong running the Python file in the terminal: ${err}`); -// }); -// await vscode.commands -// .executeCommand('python.execSelectionInTerminal', textDocument.uri) -// .then(undefined, (err) => { -// assert.fail(`Something went wrong running the Python file in the terminal: ${err}`); -// }); - -// async function waitThirtySeconds() { -// return new Promise((resolve) => { -// setTimeout(() => { -// resolve(); -// }, 30000); -// }); -// } - -// await waitThirtySeconds(); - -// const deletedFile = !(await fs.pathExists(outputFile)); -// if (deletedFile) { -// assert.ok(true, `"${outputFile}" file has been deleted`); -// } else { -// assert.fail(`"${outputFile}" file still exists`); -// } -// }); -// }); From 004dab2275ccfdd369f320179f0c563667ca0820 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 9 Oct 2023 13:17:28 -0700 Subject: [PATCH 81/82] format normalizeSelection --- pythonFiles/normalizeSelection.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pythonFiles/normalizeSelection.py b/pythonFiles/normalizeSelection.py index 57edf68e0a6c..0ac47ab5dc3b 100644 --- a/pythonFiles/normalizeSelection.py +++ b/pythonFiles/normalizeSelection.py @@ -207,9 +207,6 @@ def traverse_file(wholeFileContent, start_line, end_line, was_highlighted): # add the appropriate source code line(s) to be sent to the REPL, dependent on # user is trying to send and execute single line/statement or multiple with smart selection. for top_node in ast.iter_child_nodes(parsed_file_content): - # top_level_block_start_line = top_node.lineno - # top_level_block_end_line = top_node.end_lineno - if start_line == top_node.lineno and end_line == top_node.end_lineno: should_run_top_blocks.append(top_node) From 99ee2cecfc8a45b7ee51d4d6786da3bd561ef238 Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Tue, 10 Oct 2023 01:18:00 -0700 Subject: [PATCH 82/82] Update src/client/terminals/codeExecution/helper.ts Co-authored-by: Karthik Nadig --- src/client/terminals/codeExecution/helper.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 0a2941cbbb34..c560de9c17b7 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -89,9 +89,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { const startLineVal = activeEditor?.selection?.start.line ?? 0; const endLineVal = activeEditor?.selection?.end.line ?? 0; const emptyHighlightVal = activeEditor?.selection?.isEmpty ?? true; - const smartSendExperimentEnabledVal = pythonSmartSendEnabled(this.serviceContainer) - ? pythonSmartSendEnabled(this.serviceContainer) - : false; + const smartSendExperimentEnabledVal = pythonSmartSendEnabled(this.serviceContainer); const input = JSON.stringify({ code, wholeFileContent,