From 07e5b444c25bcb7d66873c1e14839f9eaf728c6a Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Mon, 9 Dec 2019 21:51:24 +0200 Subject: [PATCH 001/317] Update obtain.py --- queries/strategies/obtain.py | 1 - 1 file changed, 1 deletion(-) diff --git a/queries/strategies/obtain.py b/queries/strategies/obtain.py index d34ced0..e0d5fda 100644 --- a/queries/strategies/obtain.py +++ b/queries/strategies/obtain.py @@ -7,4 +7,3 @@ def obtain_result(result, alternatives): else: return alternatives[0], [x for x in alternatives[1:] if x is not alternatives[0]] -# bug if result is in alternatives \ No newline at end of file From d53e88c27eecf69da42e926b3513360164c8f993 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Tue, 10 Dec 2019 01:24:52 +0200 Subject: [PATCH 002/317] Argument No supports Caller as well --- library/info.py | 4 ++++ queries/argument.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/library/info.py b/library/info.py index 1494591..1055f10 100644 --- a/library/info.py +++ b/library/info.py @@ -237,6 +237,10 @@ def get_argument_from_call(root, index): # print(" I would regret face", temporary) return temporary[index] if temporary and len(temporary)>index else None +def get_caller(root): + return root.func if match_node(root,(ast.Call)) else None + + ################################################################ # arguments from function definitions ################################################################ diff --git a/queries/argument.py b/queries/argument.py index 3c63228..42034f6 100644 --- a/queries/argument.py +++ b/queries/argument.py @@ -1,7 +1,7 @@ import ast from PythonVoiceCodingPlugin.library import nearest_node_from_offset,sorted_by_source_region,get_source_region,node_from_range,make_flat -from PythonVoiceCodingPlugin.library.info import identity,get_argument_from_call, make_information ,correspond_to_index_in_call +from PythonVoiceCodingPlugin.library.info import identity,get_argument_from_call, make_information ,correspond_to_index_in_call,get_caller import PythonVoiceCodingPlugin.library.info as info from PythonVoiceCodingPlugin.library.LCA import LCA from PythonVoiceCodingPlugin.library.level_info import LevelVisitor @@ -38,7 +38,7 @@ def process_line(self,q, root ,atok, origin = None, select_node = None,tiebreak additional_parameters["constrained_space"] = constrained_space - information = make_information(get_argument_from_call,q["argument_index"]-1) + information = make_information(get_argument_from_call,q["argument_index"]-1) if "argument_index" in q else get_caller information_nodes = sorted_by_source_region(atok, find_matching(root, information)) if origin: From 4ba8ece58bff9c7e3b6b06868bd82b0efc18ce71 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Tue, 10 Dec 2019 01:25:17 +0200 Subject: [PATCH 003/317] Experimental`items now support ranges --- queries/insert_item.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/queries/insert_item.py b/queries/insert_item.py index 8365afe..3f3e017 100644 --- a/queries/insert_item.py +++ b/queries/insert_item.py @@ -8,7 +8,20 @@ class InsertItem(InsertionQuery): def handle_single(self,view_information,query_description,extra = {}): state = extra["global_state"] collection = state["collection"] - item = collection[query_description["item_index"]-1] + mode = query_description["format"] + if mode==1: + item = collection[query_description["item_index"]-1] + elif mode==2: + print(mode,query_description) + item_range = (query_description["item_index"]-1, query_description.get("item_index2")) + item = ",".join(collection[item_range[0]:item_range[1]]) + elif mode==3: + item = [] + for i in ["","2","3","4"]: + index = query_description.get("item_index"+i) + if index: + item.append(collection[index-1]) + item = ",".join(item) selection = self._get_selection(view_information,extra) selection = selection if isinstance(selection,list) else [selection] return [(x,item) for x in selection] From cfa229b86b6cae42112cf7326439c754d960566d Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Tue, 10 Dec 2019 21:51:00 +0200 Subject: [PATCH 004/317] Paste back now supports around in punctuation --- queries/insert_item.py | 2 +- queries/paste_back.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/queries/insert_item.py b/queries/insert_item.py index 3f3e017..9672259 100644 --- a/queries/insert_item.py +++ b/queries/insert_item.py @@ -21,7 +21,7 @@ def handle_single(self,view_information,query_description,extra = {}): index = query_description.get("item_index"+i) if index: item.append(collection[index-1]) - item = ",".join(item) + item = ",".join(item) selection = self._get_selection(view_information,extra) selection = selection if isinstance(selection,list) else [selection] return [(x,item) for x in selection] diff --git a/queries/paste_back.py b/queries/paste_back.py index 6bdc070..1bb2fbe 100644 --- a/queries/paste_back.py +++ b/queries/paste_back.py @@ -27,7 +27,8 @@ def handle_single(self,view_information,query_description,extra = {}): alternatives = state["alternatives"] location = alternatives[i-1] if i != 0 else result location = location if isinstance(location,list) else [location] - return [(x,code[l[0]:l[1]]) for x,l in zip(selection, location)] + surrounding = query_description.get("surrounding_punctuation",("","")) + return [(x,surrounding[0]+code[l[0]:l[1]]+surrounding[1]) for x,l in zip(selection, location)] From b4296f84ad209c934bc96621e4a65409b8f49d58 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Tue, 10 Dec 2019 21:51:25 +0200 Subject: [PATCH 005/317] Nowhere near ready but the first step towards adding Outer keyword argument queries --- queries/argument.py | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/queries/argument.py b/queries/argument.py index 42034f6..e0c7feb 100644 --- a/queries/argument.py +++ b/queries/argument.py @@ -257,7 +257,9 @@ def inverse_transformation(node): else: return None - + # these will be written but I just want to check that it works + + result, alternatives = self.process_line( q = query_description, root = statement_node, @@ -266,7 +268,7 @@ def inverse_transformation(node): select_node = origin if selection[0]!=selection[1] else None, tiebreaker = lambda x: tiebreak_on_lca(statement_node,origin,x), transformation = transformation, - inverse_transformation = inverse_transformation + inverse_transformation = inverse_transformation, ) return self._backward_result(result, alternatives,build) @@ -304,9 +306,28 @@ def transformation(node): return None def inverse_transformation(node): - return [node] + return [node] + + + + priority = {} + print(query_description["level"],"the information in the query description") + if query_description["level"]=="outer": + + _,calling_parents = search_upwards_log(origin,targets=ast.stmt,log_targets=(ast.Call)) + print("inside here",calling_parents) + index = query_description["level_index"] + print(len(calling_parents)," that is the length ") + if index Date: Tue, 10 Dec 2019 21:54:10 +0200 Subject: [PATCH 006/317] Update CHANGELOG.md --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea780aa..773207c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [Unreleased] + +### Added + +* upgrades in the paste back query where it can now support surrounded punctuation. + +### Fixed + +### Changed + + ## [0.0.4] - 2019-11-27 From 1194eb2ad2dcceef0648b90dc4ea855fd8521d11 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Tue, 10 Dec 2019 23:51:21 +0200 Subject: [PATCH 007/317] Result And alternatives text In application module --- CHANGELOG.md | 1 + application/application.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 773207c..b7c4b0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Changed +* the application module has been adopted, sought after selection queries we do not only store that arraigns corresponding to the main result in the alternative but we also keep the corresponding text. ## [0.0.4] - 2019-11-27 diff --git a/application/application.py b/application/application.py index 93d5dd3..319034d 100644 --- a/application/application.py +++ b/application/application.py @@ -62,11 +62,14 @@ def respond_to_query(self,interface,query_description): if result: self.state["result"] = result self.state["alternatives"] = [] + self.state["alternatives_text"] = [] + self.state["result_text"] = code[result[0]:result[1]] interface.push_action(SelectionAction(result)) self.history.append(("selection",view_information["change_count"],view_information["selection"],result)) interface.push_action(ClearHighlightAction("alternatives")) if alternatives: self.state["alternatives"] = alternatives + self.state["alternatives_text"] = [code[x[0]:x[1]] for x in alternatives] interface.push_action(DisplayRegionsAction("alternatives",alternatives,"Alternatives:\n")) interface.push_action(HighlightCleverAction(alternatives,"alternatives",result)) From 4daf05dd735890ca9aa35aa3c38701f1d46a6548 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Wed, 11 Dec 2019 00:57:05 +0200 Subject: [PATCH 008/317] Paste back now reads the text directly from the state --- CHANGELOG.md | 1 + queries/paste_back.py | 20 +++++++------------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7c4b0a..8e952d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * the application module has been adopted, sought after selection queries we do not only store that arraigns corresponding to the main result in the alternative but we also keep the corresponding text. +* the paste back query now reads the corresponding text directly from the state instead of getting their location and obtaining from the code as it did in the past ## [0.0.4] - 2019-11-27 diff --git a/queries/paste_back.py b/queries/paste_back.py index 1bb2fbe..606034e 100644 --- a/queries/paste_back.py +++ b/queries/paste_back.py @@ -8,7 +8,6 @@ class PasteBack(InsertionQuery): def handle_single(self,view_information,query_description,extra = {}): state = extra["state"] history = extra["history"] - code = view_information["code"] index = len(history) while history[index-1][0]=="selection" and index>=1 and history[index-1][1] == view_information["change_count"]: index -=1 @@ -16,19 +15,14 @@ def handle_single(self,view_information,query_description,extra = {}): return [] selection = history[index][2] selection = selection if isinstance(selection,list) else [selection] - f = query_description["format"] - if f==1: - i = query_description["paste_back_index"] - elif f==2: - i = query_description["color"] - else: - return [] - result = state["result"] - alternatives = state["alternatives"] - location = alternatives[i-1] if i != 0 else result - location = location if isinstance(location,list) else [location] + i = query_description.get("color",0) + print(" i.e.'s",i) + result_text = state["result_text"] + alternatives_text = state["alternatives_text"] + output = alternatives_text[i-1] if i != 0 else result_text surrounding = query_description.get("surrounding_punctuation",("","")) - return [(x,surrounding[0]+code[l[0]:l[1]]+surrounding[1]) for x,l in zip(selection, location)] + print("output",output) + return [(x,surrounding[0]+output+surrounding[1]) for x in selection] From 5cfce3b11367ca4379d2e1fdf9729b1c3044189e Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Wed, 11 Dec 2019 02:38:09 +0200 Subject: [PATCH 009/317] Auditing delete alternatives module --- CHANGELOG.md | 4 ++++ queries/__init__.py | 2 ++ queries/delete_alternatives.py | 23 +++++++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 queries/delete_alternatives.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e952d3..0929cd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,12 @@ * upgrades in the paste back query where it can now support surrounded punctuation. +* the module delete alternatives has been added. + ### Fixed +* fixed an important bug in the insertion query module. the writing positions were sorted, but on an ascending order which simply broke everything when there are multiple of them. Fix this to sort them in descending order, so the changes that are executed first do not affect the others. + ### Changed * the application module has been adopted, sought after selection queries we do not only store that arraigns corresponding to the main result in the alternative but we also keep the corresponding text. diff --git a/queries/__init__.py b/queries/__init__.py index ccc3dd7..0ca0a42 100644 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -9,6 +9,7 @@ from PythonVoiceCodingPlugin.queries.collect_function_name import CollectFunctionName from PythonVoiceCodingPlugin.queries.collect_imported_value import CollectImportedValue from PythonVoiceCodingPlugin.queries.insert_item import InsertItem +from PythonVoiceCodingPlugin.queries.delete_alternatives import DeleteAlternatives @@ -35,6 +36,7 @@ def get_query(query_description): "collect_imported_value": CollectImportedValue, "collect_function_name": CollectFunctionName, "insert_item": InsertItem, + "delete_alternatives":DeleteAlternatives, } return h[index] diff --git a/queries/delete_alternatives.py b/queries/delete_alternatives.py new file mode 100644 index 0000000..07afa3c --- /dev/null +++ b/queries/delete_alternatives.py @@ -0,0 +1,23 @@ +from PythonVoiceCodingPlugin.queries.abstract import InsertionQuery,no_build_attempt + + +@no_build_attempt +class DeleteAlternatives(InsertionQuery): + select_insertion = True + + def handle_single(self,view_information,query_description,extra = {}): + state = extra["state"] + result = state["result"] + alternatives = state["alternatives"] + candidates = [result]+alternatives if alternatives else [result] + if query_description["format"] == 1: + selection = {candidates[query_description["color"+i]] + for i in ["","2","3","4"] if "color"+i in query_description} + return [(x,"") for x in selection] + + + + + + + From b9552e8ec4c5ecb12e4d1351ea6842411f9c065b Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Wed, 11 Dec 2019 02:38:42 +0200 Subject: [PATCH 010/317] Fix a bug in the Insertion query By reversing sorting order --- queries/abstract/insertion_query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/abstract/insertion_query.py b/queries/abstract/insertion_query.py index d8eb310..9fb7dff 100644 --- a/queries/abstract/insertion_query.py +++ b/queries/abstract/insertion_query.py @@ -49,7 +49,7 @@ def __call__(self,view_information,query_description,extra = {}): if hasattr(self.handle_single,"_original"): return [],[] self.writing_locations_text = self.handle_single(view_information,query_description, extra) - self.writing_locations_text = sorted(self.writing_locations_text, key = lambda x:x[0]) + self.writing_locations_text = sorted(self.writing_locations_text, key = lambda x:x[0],reverse=True) if self.select_insertion: m = ModificationHandler() for location,t in self.writing_locations_text: From 3f0034f4899e67f8af83a7a255ecf42ce338108f Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Wed, 11 Dec 2019 13:11:33 +0200 Subject: [PATCH 011/317] Backend pasting one alternative to another --- queries/paste_back.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/queries/paste_back.py b/queries/paste_back.py index 606034e..6ddc201 100644 --- a/queries/paste_back.py +++ b/queries/paste_back.py @@ -13,15 +13,21 @@ def handle_single(self,view_information,query_description,extra = {}): index -=1 if index==len(history) or history[index][1] != view_information["change_count"]: return [] - selection = history[index][2] - selection = selection if isinstance(selection,list) else [selection] - i = query_description.get("color",0) - print(" i.e.'s",i) + result_text = state["result_text"] alternatives_text = state["alternatives_text"] - output = alternatives_text[i-1] if i != 0 else result_text + candidates = [result_text]+alternatives_text if alternatives_text else [result_text] + if query_description["format"]==1: + selection = history[index][2] + selection = selection if isinstance(selection,list) else [selection] + if query_description["format"]==2: + result = state["result"] + alternatives = state["alternatives"] + candidates_location = [result]+alternatives if alternatives else [result] + selection = {candidates_location[query_description["color"+i]] + for i in ["2","3","4"] if "color"+i in query_description} + output = candidates[query_description.get("color",0)] surrounding = query_description.get("surrounding_punctuation",("","")) - print("output",output) return [(x,surrounding[0]+output+surrounding[1]) for x in selection] From f58cc2bce66ec1456e471cfecd27145deebadcf2 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Wed, 11 Dec 2019 23:41:28 +0200 Subject: [PATCH 012/317] Removes Spam debug print --- application/application.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/application/application.py b/application/application.py index 319034d..bd5576e 100644 --- a/application/application.py +++ b/application/application.py @@ -47,7 +47,6 @@ def respond_to_query(self,interface,query_description): # check if there are exceptions if s.exceptions_raised: interface.push_action(PopUpErrorAction(str(s.exceptions_raised))) - print(s.exceptions_raised) # register build for later use b = s.get_the_latest_build() @@ -61,7 +60,7 @@ def respond_to_query(self,interface,query_description): # self.state["alternatives"] = [] if result: self.state["result"] = result - self.state["alternatives"] = [] + self.state["alternatives"] = self.state["alternatives_text"] = [] self.state["result_text"] = code[result[0]:result[1]] interface.push_action(SelectionAction(result)) From 4710f85ea7a859c943ab3057950909fc936cf02c Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 12 Dec 2019 00:27:32 +0200 Subject: [PATCH 013/317] View information now also exposes they get regions sublime API --- interface/view_info.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/view_info.py b/interface/view_info.py index df767bb..44e931e 100644 --- a/interface/view_info.py +++ b/interface/view_info.py @@ -17,6 +17,8 @@ def __getitem__(self, value): return self.view.rowcol elif value == "text_point": return self.view.text_point + elif value == "get_regions": + return lambda y:[(x.begin(),x.end()) for x in self.view.get_regions(y)] else: return None From 1d491fb7ab783b70841a6bac20a00bea25867d08 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 12 Dec 2019 00:28:00 +0200 Subject: [PATCH 014/317] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0929cd6..3e21769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * upgrades in the paste back query where it can now support surrounded punctuation. * the module delete alternatives has been added. +* View information now also exposes they get regions sublime API ### Fixed From 4841690fbefa76dc5d81b61d6c7563f2024be656 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 12 Dec 2019 02:09:12 +0200 Subject: [PATCH 015/317] The interface module now has a clear_actions methods to flush the already pushed actions. --- CHANGELOG.md | 3 +++ interface/interface.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e21769..1bc6ae0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,11 @@ * upgrades in the paste back query where it can now support surrounded punctuation. * the module delete alternatives has been added. + * View information now also exposes they get regions sublime API +* The interface module now has a clear_actions methods to flush the already pushed actions. + ### Fixed * fixed an important bug in the insertion query module. the writing positions were sorted, but on an ascending order which simply broke everything when there are multiple of them. Fix this to sort them in descending order, so the changes that are executed first do not affect the others. diff --git a/interface/interface.py b/interface/interface.py index ec0bf48..eb74621 100644 --- a/interface/interface.py +++ b/interface/interface.py @@ -29,6 +29,8 @@ def respond_to_query(self,query_description): } for action in self.actions: action.execute(**parameters) + def clear_actions(self): + self.actions = [] From 2303cf65a2624b21cf77d4d24bc808cb3ad2398f Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 12 Dec 2019 02:47:08 +0200 Subject: [PATCH 016/317] Optimistic/double commands He needs Sheila back and attempt for the time being only very simple paste:delete the right --- queries/__init__.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/queries/__init__.py b/queries/__init__.py index 0ca0a42..3120f03 100644 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -41,6 +41,23 @@ def get_query(query_description): return h[index] - +def get_secondary_query(query_description): + if "operation" not in query_description: + return {} + else: + h={ + "paste":{ + "command":"paste_back", + "format":1, + + }, + "delete":{ + "command":"delete_alternatives", + "format":1, + "color":0, + } + + } + return h[query_description["operation"]] From 04a98a475e306ff8d49fd3953b7ca76ea5ba9c5d Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 12 Dec 2019 02:47:25 +0200 Subject: [PATCH 017/317] Same as Previous --- application/application.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/application/application.py b/application/application.py index bd5576e..b533598 100644 --- a/application/application.py +++ b/application/application.py @@ -1,3 +1,5 @@ +from copy import deepcopy + from PythonVoiceCodingPlugin.queries import * from PythonVoiceCodingPlugin.application.build_cache import BuildCache from PythonVoiceCodingPlugin.interface.common.actions import * @@ -23,11 +25,24 @@ def get_application(vid): Application.create_application_for_view(vid) return Application.active_applications[vid] + def update_state(self,view_information,code): + ''' + normally I would like these to be implemented by means of modification handlers + And an event listener transmitting every chains in the code. However it seems + but the provided event listener does not provide me with the location that was changed. + So in order to keep track over the location of important regions when the code changes, + The current solution is to outsource everything to the sublime add_regions/get_regions + functionality + ''' + self.state["result"] = view_information["get_regions"]("result") + self.state["alternatives"] = view_information["get_regions"]("alternatives") + self.state["origin"] = view_information["get_regions"]("origin") + self.state["origin_stack"] = view_information["get_regions"]("origin_stack") - def respond_to_query(self,interface,query_description): + def respond_to_query(self,interface,query_description,secondary=False): extra = {"state":self.state,"global_state":Application.global_state,"history":self.history} view_information = interface.get_view_information() ui_information = interface.get_ui_information() @@ -39,6 +54,9 @@ def respond_to_query(self,interface,query_description): # get the corresponding query and execute it s = get_query(query_description)(code,latest_build) + secondary_query_description = get_secondary_query(query_description) + if secondary_query_description: + backup=[deepcopy(self.state),deepcopy(self.global_state)] try: s(view_information,query_description,extra) except: @@ -46,7 +64,9 @@ def respond_to_query(self,interface,query_description): # check if there are exceptions if s.exceptions_raised: + interface.clear_actions() interface.push_action(PopUpErrorAction(str(s.exceptions_raised))) + return False # register build for later use b = s.get_the_latest_build() @@ -60,7 +80,7 @@ def respond_to_query(self,interface,query_description): # self.state["alternatives"] = [] if result: self.state["result"] = result - self.state["alternatives"] = + self.state["alternatives"] = [] self.state["alternatives_text"] = [] self.state["result_text"] = code[result[0]:result[1]] interface.push_action(SelectionAction(result)) @@ -99,7 +119,13 @@ def respond_to_query(self,interface,query_description): if selections: interface.push_action(SelectionAction(selections)) - + if secondary_query_description: + interface.push_action(ClearHighlightAction("alternatives")) + secondary_success = self.respond_to_query(interface,secondary_query_description,secondary=True) + if not secondary_success: + self.state,self.global_state = backup + return False + return True From 313a32e1bb20fc286d4620f25202bbcc7c00d17e Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 12 Dec 2019 20:33:36 +0200 Subject: [PATCH 018/317] Critical updating the repair module, trying to except blokes Hopping fixed --- library/repair.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/repair.py b/library/repair.py index 4315a72..c19679f 100644 --- a/library/repair.py +++ b/library/repair.py @@ -340,7 +340,7 @@ def work(self): k = 0 self.already_checked = set() for t in self.atok.tokens: - if t.string in ["if","for","while","with","def","elif","else"]: + if t.string in ["if","for","while","with","def","elif","else","try","except"]: if t.string == "elif": k = k + 1 z = handle_empty_compound(self.atok,t,l,b ,self.d) From 550522dee5abc8cbe94d779c6035d77d5fd417d9 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 12 Dec 2019 20:34:27 +0200 Subject: [PATCH 019/317] You can now get parts of names --- library/info.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/library/info.py b/library/info.py index 1055f10..0f79bbf 100644 --- a/library/info.py +++ b/library/info.py @@ -297,6 +297,7 @@ def split_string(s,even_letters = True): return first_attempt second_attempt = [x for x in re.split("[_]",s) if not x.isspace()] if len(second_attempt) > 1: + print(" from second attempt") return second_attempt # https://stackoverflow.com/questions/29916065/how-to-do-camelcase-split-in-python answer from Jossef Harush third_attempt = re.sub('([A-Z][a-z]+)', r' \1', re.sub('([A-Z]+)', r' \1', s)).split() @@ -307,17 +308,21 @@ def split_string(s,even_letters = True): -def get_subparts_of_string(root): +def get_subparts_of_string(root,name_mode = False): output = [] - start_position = root.first_token.startpos + 1 - original = root.s - splitted = split_string(root.s) + start_position = root.first_token.startpos + ( 1 if not name_mode else 0) + original = root.s if not name_mode else root.id + try : + splitted = split_string(root.s if not name_mode else root.id,even_letters = False if name_mode else True) + except : + print(" exceptions were thrown") index = 0 + print("splitted ",splitted) for s in splitted: index = original.find(s,index) fake_token = asttokens.Token(0,s,0,0,0, root.first_token.index,start_position + index,start_position + index + len(s)) - fake_node = ast.Str(s = s) + fake_node = ast.Str(s = s) if not name_mode else ast.Name(id = s,ctx = root.ctx) fake_node.parent = root.parent fake_node.parent_field = root.parent_field fake_node.parent_field_index = root.parent_field_index @@ -326,9 +331,10 @@ def get_subparts_of_string(root): fake_node.fake = True output.append(fake_node) index += len(s) - return output + return output if name_mode or len(output)>1 else [] + + - def get_sub_index(root,index): candidates = [] @@ -358,6 +364,10 @@ def get_sub_index(root,index): candidates = root.dims elif match_node(root,(ast.Str)): candidates = get_subparts_of_string(root) + elif match_node(root,(ast.Name)): + print("whatever ") + candidates = get_subparts_of_string(root,name_mode = True) + print("candidates",candidates) # in the following cases we Certs deeper in the tree if match_node(root,(ast.Subscript)): From 6c0b19e60f9b3d39a20a0bd2b316c8bce2395052 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 12 Dec 2019 20:35:20 +0200 Subject: [PATCH 020/317] Experimental swap --- queries/__init__.py | 3 ++ queries/strategies/__init__.py | 1 + queries/strategies/state_extraction.py | 13 ++++++++ queries/swap_back.py | 42 ++++++++++++++++++++++++++ 4 files changed, 59 insertions(+) create mode 100644 queries/strategies/state_extraction.py create mode 100644 queries/swap_back.py diff --git a/queries/__init__.py b/queries/__init__.py index 3120f03..47cd017 100644 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -10,6 +10,7 @@ from PythonVoiceCodingPlugin.queries.collect_imported_value import CollectImportedValue from PythonVoiceCodingPlugin.queries.insert_item import InsertItem from PythonVoiceCodingPlugin.queries.delete_alternatives import DeleteAlternatives +from PythonVoiceCodingPlugin.queries.swap_back import SwapBack @@ -37,6 +38,7 @@ def get_query(query_description): "collect_function_name": CollectFunctionName, "insert_item": InsertItem, "delete_alternatives":DeleteAlternatives, + "swap_back": SwapBack, } return h[index] @@ -56,6 +58,7 @@ def get_secondary_query(query_description): "format":1, "color":0, } + } return h[query_description["operation"]] diff --git a/queries/strategies/__init__.py b/queries/strategies/__init__.py index 069f38a..1430ae2 100644 --- a/queries/strategies/__init__.py +++ b/queries/strategies/__init__.py @@ -2,4 +2,5 @@ from PythonVoiceCodingPlugin.queries.strategies.adjective_strategy import * from PythonVoiceCodingPlugin.queries.strategies.abstract_vertical import * from PythonVoiceCodingPlugin.queries.strategies.obtain import * +from PythonVoiceCodingPlugin.queries.strategies.state_extraction import * diff --git a/queries/strategies/state_extraction.py b/queries/strategies/state_extraction.py new file mode 100644 index 0000000..311a0ff --- /dev/null +++ b/queries/strategies/state_extraction.py @@ -0,0 +1,13 @@ +def result_alternatives_sequence(state,location=False,text = False): + result_text = state["result_text"] + alternatives_text = state["alternatives_text"] + candidates_text = [result_text]+alternatives_text if alternatives_text else [result_text] + result_location = state["result"] + alternatives_location = state["alternatives"] + candidates_location = [result_location] + alternatives_location if alternatives_location else [result_location] + if location and text: + return list(zip(candidates_location,candidates_text)) + if location: + return candidates_location + if text: + return candidates_text diff --git a/queries/swap_back.py b/queries/swap_back.py new file mode 100644 index 0000000..4a2c652 --- /dev/null +++ b/queries/swap_back.py @@ -0,0 +1,42 @@ +from PythonVoiceCodingPlugin.queries.abstract import InsertionQuery,no_build_attempt +from PythonVoiceCodingPlugin.queries.strategies import result_alternatives_sequence + +def overlap_regions(x,y): + return y[0]<=x[0]=1 and history[index-1][1] == view_information["change_count"]: + index -=1 + if index==len(history) or history[index][1] != view_information["change_count"]: + return [] + + candidates = result_alternatives_sequence(state,location = True,text = True) + if query_description["format"]==1: + selection = history[index][2] + selection = selection if isinstance(selection,list) else [selection] + if query_description["format"]==2: + print(" inside here") + location_text = [candidates[query_description["color"+i]] + for i in ["","2","3","4"] if "color"+i in query_description] + output = [] + for j in range(0,len(location_text)): + x = location_text[j] + y = location_text[j-1] + if overlap_regions(x[0],y[0]): + raise + output.append((x[0],y[1])) + + return output + + + + + + From 076eabec5dd6217cc91d3ae0d475e2076f161968 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 15 Dec 2019 01:06:52 +0200 Subject: [PATCH 021/317] Make information now Accepts Keyword star arguments as well --- library/info.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/library/info.py b/library/info.py index 0f79bbf..f3e3563 100644 --- a/library/info.py +++ b/library/info.py @@ -1,4 +1,5 @@ import ast +import inspect import re from itertools import chain from urllib.parse import urlparse @@ -34,10 +35,13 @@ ################################################################################################ ################################################################################################ -def make_information(c,*arg): - if arg: - return lambda x: c(x,*arg) - return lambda x: c(x) +def make_information(c,*arg,**kwargs): + signature = inspect.signature(c) + if not any(x.kind==x.VAR_KEYWORD for x in signature.parameters.values()): + temporary = {x.name for x in signature.parameters.values()} + kwargs = {k:v for k,v in kwargs.items() if k in temporary} + return lambda x: c(x,*arg,**kwargs) + def identity(information, parameter = None): if parameter: From 43d66193c025066c4724de3efc245627616b436e Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 15 Dec 2019 01:23:27 +0200 Subject: [PATCH 022/317] Beak ROIs now use the make information function as well --- queries/big_roi.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/queries/big_roi.py b/queries/big_roi.py index e938cd3..86b6482 100644 --- a/queries/big_roi.py +++ b/queries/big_roi.py @@ -45,8 +45,9 @@ def preliminary(self,view_information,query_description, extra = {}): ) return build, selection, origin, definition_node - def decode(self,query_description): - standard = lambda x:x + def decode(self,query_description,build): + def standard(x): + return x possibilities = { "return value": ((ast.Return,ast.Yield,ast.YieldFrom),(),get_return_value), "pass":(ast.Pass,(),standard), @@ -71,13 +72,14 @@ def decode(self,query_description): "import statement":((ast.Import,ast.ImportFrom),(),standard), } temporary = possibilities[query_description["big_roi"]] + basic_information = make_information(temporary[2],atok = build[1]) if "big_roi_sub_index" in query_description: if query_description["big_roi_sub_index"] == 0: - return possibilities[query_description["big_roi"]] + return possibilities[query_description["big_roi"]][:2] + (basic_information,) else: index = query_description["big_roi_sub_index"] def modified_information(x, information,index): - data = information(x) + data = basic_information(x) return get_sub_index(data,index) y = lambda x: temporary[2](x) @@ -90,7 +92,7 @@ def case_one(self,view_information,query_description, extra = {}): # ############################################################### build, selection, origin, definition_node = self.preliminary(view_information, query_description,extra) - targets, exclusions, information = self.decode(query_description) + targets, exclusions, information = self.decode(query_description,build) information = getattr(information,"secondary",information) candidates = tiebreak_on_lca(definition_node,origin,find_all_nodes(definition_node, targets, exclusions)) candidates = [information(x) for x in candidates if information(x)] @@ -103,7 +105,7 @@ def case_two(self,view_information,query_description, extra = {}): # ############################################################### build, selection, origin, definition_node = self.preliminary(view_information, query_description,extra) - targets, exclusions, information = self.decode(query_description) + targets, exclusions, information = self.decode(query_description,build) temporary_information = lambda x: information(x) if match_node(x,targets,exclusions) else None additional_parameters = {} root,atok,m,r = build @@ -131,7 +133,7 @@ def case_three(self,view_information,query_description, extra = {}): # [] [] ############################################################### build, selection, origin, definition_node = self.preliminary(view_information, query_description,extra) - targets, exclusions, information = self.decode(query_description) + targets, exclusions, information = self.decode(query_description,build) temporary_information = lambda x: information(x) if match_node(x,targets,exclusions) else None root,atok,m,r = build @@ -168,7 +170,7 @@ def case_four(self,view_information,query_description, extra = {}): # [smart] [] [] [] ############################################################### build, selection, origin, definition_node = self.preliminary(view_information, query_description,extra) - targets, exclusions, information = self.decode(query_description) + targets, exclusions, information = self.decode(query_description,build) temporary_information = lambda x: match_node(x,ast.FunctionDef) root,atok,m,r = build From dcf68a7e069dc9ac11f587b1c616b9839f2c1930 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 15 Dec 2019 01:41:22 +0200 Subject: [PATCH 023/317] Create fake functionality summarize the single place --- library/info.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/library/info.py b/library/info.py index f3e3563..0834bca 100644 --- a/library/info.py +++ b/library/info.py @@ -49,6 +49,20 @@ def identity(information, parameter = None): return lambda x: x if information(x) else None +def create_fake(root,text,start_position,node_type, **kwargs): + + fake_token = asttokens.Token(0,text,0,0,0, + root.first_token.index,start_position,start_position + len(text)) + fake_node = node_type(**kwargs) + fake_node.parent = root.parent + fake_node.parent_field = root.parent_field + fake_node.parent_field_index = root.parent_field_index + fake_node.first_token = fake_token + fake_node.last_token = fake_token + fake_node.fake = True + return fake_node + + ################################################################################################ ################################################################################################ # @@ -258,6 +272,12 @@ def get_argument_from_definition(root,raw = True,index = None): temporary = [(y.arg if isinstance(y,ast.AST) else y) for y in temporary] return temporary[index] if (index is not None) and len(temporary)>index else temporary +def get_definition_name(root,atok): + pass + + + + ################################################################ # sub indexing functions @@ -324,15 +344,10 @@ def get_subparts_of_string(root,name_mode = False): print("splitted ",splitted) for s in splitted: index = original.find(s,index) - fake_token = asttokens.Token(0,s,0,0,0, - root.first_token.index,start_position + index,start_position + index + len(s)) - fake_node = ast.Str(s = s) if not name_mode else ast.Name(id = s,ctx = root.ctx) - fake_node.parent = root.parent - fake_node.parent_field = root.parent_field - fake_node.parent_field_index = root.parent_field_index - fake_node.first_token = fake_token - fake_node.last_token = fake_token - fake_node.fake = True + if name_mode: + fake_node = create_fake(root,s,start_position + index,ast.Name,id = s,ctx = root.ctx) + else: + fake_node = create_fake(root,s,start_position + index,ast.Str,s = s) output.append(fake_node) index += len(s) return output if name_mode or len(output)>1 else [] From 4a41c10302f7030a957a377346f8d04207d36013 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 15 Dec 2019 17:20:54 +0200 Subject: [PATCH 024/317] First attempt with fake nodes to get divination names --- library/info.py | 12 +++++++++++- queries/big_roi.py | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/library/info.py b/library/info.py index 0834bca..2143b56 100644 --- a/library/info.py +++ b/library/info.py @@ -1,6 +1,8 @@ import ast import inspect import re +import tokenize + from itertools import chain from urllib.parse import urlparse @@ -273,7 +275,15 @@ def get_argument_from_definition(root,raw = True,index = None): return temporary[index] if (index is not None) and len(temporary)>index else temporary def get_definition_name(root,atok): - pass + if not match_node(root,ast.FunctionDef): + return None + d = atok.find_token(root.first_token,tokenize.NAME,"def") + x = next_token(atok,d) + if x: + return create_fake(root,x.string,x.startpos,ast.Name,id = x.string,ctx = ast.Store()) + else: + return None + diff --git a/queries/big_roi.py b/queries/big_roi.py index 86b6482..4c431f1 100644 --- a/queries/big_roi.py +++ b/queries/big_roi.py @@ -68,7 +68,7 @@ def standard(x): "expression statement":(ast.Expr,(),standard), "iterable":((ast.For,ast.comprehension),(),get_iterable), "iterator":((ast.For,ast.comprehension),(),get_iterator), - + "definition name":((ast.FunctionDef),(),get_definition_name), "import statement":((ast.Import,ast.ImportFrom),(),standard), } temporary = possibilities[query_description["big_roi"]] From b67bc4f9378d91a135c4d0f94a69fba9f29ec642 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 15 Dec 2019 17:38:27 +0200 Subject: [PATCH 025/317] Same functionality for class names --- library/info.py | 11 +++++++++++ queries/big_roi.py | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/library/info.py b/library/info.py index 2143b56..d8319f7 100644 --- a/library/info.py +++ b/library/info.py @@ -285,6 +285,17 @@ def get_definition_name(root,atok): return None +def get_class_name(root,atok): + print( "inside here to lower" ) + if not match_node(root,ast.ClassDef): + return None + print( "inside here to lowerfor the second time") + d = atok.find_token(root.first_token,tokenize.NAME,"class") + x = next_token(atok,d) + if x: + return create_fake(root,x.string,x.startpos,ast.Name,id = x.string,ctx = ast.Store()) + else: + return None diff --git a/queries/big_roi.py b/queries/big_roi.py index 4c431f1..e9e6368 100644 --- a/queries/big_roi.py +++ b/queries/big_roi.py @@ -40,7 +40,7 @@ def preliminary(self,view_information,query_description, extra = {}): definition_node = search_upwards(origin,ast.FunctionDef) definition_node = ( definition_node - if definition_node and query_description["big_roi"] not in ["import statement"] + if definition_node and query_description["big_roi"] not in ["import statement","class name"] else root ) return build, selection, origin, definition_node @@ -69,6 +69,7 @@ def standard(x): "iterable":((ast.For,ast.comprehension),(),get_iterable), "iterator":((ast.For,ast.comprehension),(),get_iterator), "definition name":((ast.FunctionDef),(),get_definition_name), + "class name":((ast.ClassDef),(),get_class_name), "import statement":((ast.Import,ast.ImportFrom),(),standard), } temporary = possibilities[query_description["big_roi"]] From 1427c0a87397dbb864ffcffb61e9993ac1ac7bcc Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 15 Dec 2019 17:39:04 +0200 Subject: [PATCH 026/317] Example with multiple deletes. --- doc/gif/del1.gif | Bin 0 -> 125117 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/gif/del1.gif diff --git a/doc/gif/del1.gif b/doc/gif/del1.gif new file mode 100644 index 0000000000000000000000000000000000000000..0f1c9ddd43e1cadc6dd9287f0ce52b566bdab518 GIT binary patch literal 125117 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�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 X)yl~daq*hN_3Jj5Y!$7{A*cTf1!{LI literal 0 HcmV?d00001 From 5f6ca5026d8045efc874a9b3c5226fd3d09728c6 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 15 Dec 2019 19:05:06 +0200 Subject: [PATCH 027/317] Partial support for attributes in sub indexing --- library/info.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/library/info.py b/library/info.py index d8319f7..9a77eea 100644 --- a/library/info.py +++ b/library/info.py @@ -286,10 +286,8 @@ def get_definition_name(root,atok): def get_class_name(root,atok): - print( "inside here to lower" ) if not match_node(root,ast.ClassDef): return None - print( "inside here to lowerfor the second time") d = atok.find_token(root.first_token,tokenize.NAME,"class") x = next_token(atok,d) if x: @@ -373,7 +371,20 @@ def get_subparts_of_string(root,name_mode = False): index += len(s) return output if name_mode or len(output)>1 else [] - +def get_subparts_of_attribute(root): + print("entering ") + print("wrote a",ast.dump(root)) + if not match_node(root,ast.Attribute): + return None + l = root.last_token + print("this is not nice ") + fake_node = create_fake(root,l.string,l.startpos,ast.Name,id = l.string,ctx = root.ctx) + if match_node(root.value,ast.Attribute): + print( "inside here" ) + return get_subparts_of_attribute(root.value) + [fake_node] + else: + return [root.value,fake_node] + def get_sub_index(root,index): @@ -405,9 +416,9 @@ def get_sub_index(root,index): elif match_node(root,(ast.Str)): candidates = get_subparts_of_string(root) elif match_node(root,(ast.Name)): - print("whatever ") candidates = get_subparts_of_string(root,name_mode = True) - print("candidates",candidates) + elif match_node(root,ast.Attribute): + candidates = get_subparts_of_attribute(root) # in the following cases we Certs deeper in the tree if match_node(root,(ast.Subscript)): @@ -420,7 +431,7 @@ def get_sub_index(root,index): return get_sub_index(root.body,index) if match_node(root,(ast.Call)): return get_sub_index(root.func,index) - + if index Date: Sun, 15 Dec 2019 19:42:55 +0200 Subject: [PATCH 028/317] Paste Back now works with state extraction --- queries/paste_back.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/queries/paste_back.py b/queries/paste_back.py index 6ddc201..b7c821d 100644 --- a/queries/paste_back.py +++ b/queries/paste_back.py @@ -1,5 +1,5 @@ from PythonVoiceCodingPlugin.queries.abstract import InsertionQuery,no_build_attempt - +from PythonVoiceCodingPlugin.queries.strategies import result_alternatives_sequence @no_build_attempt class PasteBack(InsertionQuery): @@ -13,17 +13,12 @@ def handle_single(self,view_information,query_description,extra = {}): index -=1 if index==len(history) or history[index][1] != view_information["change_count"]: return [] - - result_text = state["result_text"] - alternatives_text = state["alternatives_text"] - candidates = [result_text]+alternatives_text if alternatives_text else [result_text] + candidates = result_alternatives_sequence(state,text = True) + candidates_location = result_alternatives_sequence(state,location = True) if query_description["format"]==1: selection = history[index][2] selection = selection if isinstance(selection,list) else [selection] if query_description["format"]==2: - result = state["result"] - alternatives = state["alternatives"] - candidates_location = [result]+alternatives if alternatives else [result] selection = {candidates_location[query_description["color"+i]] for i in ["2","3","4"] if "color"+i in query_description} output = candidates[query_description.get("color",0)] From 42940e9335fd4d8d423e4909c97a8d335b14aa41 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 15 Dec 2019 19:43:08 +0200 Subject: [PATCH 029/317] Update swap_back.py --- queries/swap_back.py | 1 - 1 file changed, 1 deletion(-) diff --git a/queries/swap_back.py b/queries/swap_back.py index 4a2c652..8c61179 100644 --- a/queries/swap_back.py +++ b/queries/swap_back.py @@ -22,7 +22,6 @@ def handle_single(self,view_information,query_description,extra = {}): selection = history[index][2] selection = selection if isinstance(selection,list) else [selection] if query_description["format"]==2: - print(" inside here") location_text = [candidates[query_description["color"+i]] for i in ["","2","3","4"] if "color"+i in query_description] output = [] From d442ad6753c526e82aa9d64c2692dcf274cee759 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 15 Dec 2019 21:12:02 +0200 Subject: [PATCH 030/317] Backend Has been added for lambda --- library/info.py | 2 +- queries/big_roi.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/library/info.py b/library/info.py index 9a77eea..3781d41 100644 --- a/library/info.py +++ b/library/info.py @@ -267,7 +267,7 @@ def get_caller(root): def get_argument_from_definition(root,raw = True,index = None): if not match_node(root,ast.FunctionDef): return None - x= root.args + x = root.args temporary = x.args + [x.vararg] + x.kwonlyargs + [x.kwarg] temporary = [y for y in temporary if y] if raw: diff --git a/queries/big_roi.py b/queries/big_roi.py index e9e6368..387009b 100644 --- a/queries/big_roi.py +++ b/queries/big_roi.py @@ -71,6 +71,8 @@ def standard(x): "definition name":((ast.FunctionDef),(),get_definition_name), "class name":((ast.ClassDef),(),get_class_name), "import statement":((ast.Import,ast.ImportFrom),(),standard), + "lambda":((ast.Lambda),(),standard), + "lambda body":((ast.Lambda),(),get_body), } temporary = possibilities[query_description["big_roi"]] basic_information = make_information(temporary[2],atok = build[1]) From 466212ebfa3b1518283133ae0f0ea1adae885fa4 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 15 Dec 2019 21:14:29 +0200 Subject: [PATCH 031/317] Delete alternatives Also works with state extraction --- queries/delete_alternatives.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/queries/delete_alternatives.py b/queries/delete_alternatives.py index 07afa3c..2825688 100644 --- a/queries/delete_alternatives.py +++ b/queries/delete_alternatives.py @@ -1,4 +1,5 @@ from PythonVoiceCodingPlugin.queries.abstract import InsertionQuery,no_build_attempt +from PythonVoiceCodingPlugin.queries.strategies import result_alternatives_sequence @no_build_attempt @@ -6,10 +7,7 @@ class DeleteAlternatives(InsertionQuery): select_insertion = True def handle_single(self,view_information,query_description,extra = {}): - state = extra["state"] - result = state["result"] - alternatives = state["alternatives"] - candidates = [result]+alternatives if alternatives else [result] + candidates = result_alternatives_sequence(extra["state"],location= True) if query_description["format"] == 1: selection = {candidates[query_description["color"+i]] for i in ["","2","3","4"] if "color"+i in query_description} From 00794855e26254ba6bce9031aac47f6e6471ad93 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 15 Dec 2019 23:00:08 +0200 Subject: [PATCH 032/317] The caller Can now be Sub indexed as well --- queries/argument.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/queries/argument.py b/queries/argument.py index e0c7feb..d7042f8 100644 --- a/queries/argument.py +++ b/queries/argument.py @@ -1,7 +1,7 @@ import ast from PythonVoiceCodingPlugin.library import nearest_node_from_offset,sorted_by_source_region,get_source_region,node_from_range,make_flat -from PythonVoiceCodingPlugin.library.info import identity,get_argument_from_call, make_information ,correspond_to_index_in_call,get_caller +from PythonVoiceCodingPlugin.library.info import identity,get_argument_from_call, make_information ,correspond_to_index_in_call,get_caller,get_sub_index import PythonVoiceCodingPlugin.library.info as info from PythonVoiceCodingPlugin.library.LCA import LCA from PythonVoiceCodingPlugin.library.level_info import LevelVisitor @@ -25,7 +25,16 @@ class SelectArgument(SelectionQuery): """docstring for SelectArgument""" # def __init__(self): # super(SelectArgument, self).__init__() - + def get_information(self,query_description): + if "argument_index" in query_description: + return make_information(get_argument_from_call,query_description["argument_index"]-1) + else: + if "sub_index" not in query_description: + return get_caller + else: + i = query_description["sub_index"] - 1 + return lambda x:get_sub_index(get_caller(x),i) + def process_line(self,q, root ,atok, origin = None, select_node = None,tiebreaker = lambda x: x, line = None, transformation = None,inverse_transformation = None, priority = {}, @@ -38,7 +47,7 @@ def process_line(self,q, root ,atok, origin = None, select_node = None,tiebreak additional_parameters["constrained_space"] = constrained_space - information = make_information(get_argument_from_call,q["argument_index"]-1) if "argument_index" in q else get_caller + information = self.get_information(q) information_nodes = sorted_by_source_region(atok, find_matching(root, information)) if origin: From e8c3aab99759d93fc81aa261b8616952272ec7ec Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 15 Dec 2019 23:07:50 +0200 Subject: [PATCH 033/317] The pop-up for informing about parsing errors now hides when miles move away --- interface/common/actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/common/actions.py b/interface/common/actions.py index aa9cbfb..c174bbd 100644 --- a/interface/common/actions.py +++ b/interface/common/actions.py @@ -223,7 +223,7 @@ def execute(self,view,settings, sublime,**kwargs): def on_hide(): view.show_popup(final_text,max_width=1024, max_height=10000, flags= sublime.HIDE_ON_MOUSE_MOVE_AWAY) view.show_popup(final_text,max_width=1024, max_height=10000, - flags= sublime.COOPERATE_WITH_AUTO_COMPLETE,on_hide = on_hide) + flags= sublime.HIDE_ON_MOUSE_MOVE_AWAY,on_hide = on_hide) print(view.is_popup_visible()) # style=\"background-color:#000080\" From 44a0b46b6fe40219853ca9bad1afda125b46d187 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 15 Dec 2019 23:10:44 +0200 Subject: [PATCH 034/317] Fixed name in the sublime settings from show_visible to show_invisible --- interface/common/actions.py | 2 +- python_voice_coding_plugin.sublime-settings | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/common/actions.py b/interface/common/actions.py index c174bbd..d730b88 100644 --- a/interface/common/actions.py +++ b/interface/common/actions.py @@ -21,7 +21,7 @@ def execute(self,view,settings,sublime,**kwargs): region = [region] for r in region: view.sel().add(sublime.Region(r[0],r[1])) - if settings.get("show_visible",False): + if settings.get("show_invisible",False): view.show(sublime.Region(region[0][0],region[0][1])) diff --git a/python_voice_coding_plugin.sublime-settings b/python_voice_coding_plugin.sublime-settings index 49aa902..9061a13 100644 --- a/python_voice_coding_plugin.sublime-settings +++ b/python_voice_coding_plugin.sublime-settings @@ -1,4 +1,4 @@ { - "show_visible":true, + "show_invisible":true, "show_error":true, } \ No newline at end of file From a774ad889be1c14f400e8c6e23dbbdacac10ca45 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Mon, 16 Dec 2019 00:08:41 +0200 Subject: [PATCH 035/317] Restructured insert item, item selection strategies are now in a separate module to allow reusability --- queries/insert_item.py | 27 +++++++++------------------ queries/strategies/__init__.py | 1 + queries/strategies/item_selection.py | 22 ++++++++++++++++++++++ 3 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 queries/strategies/item_selection.py diff --git a/queries/insert_item.py b/queries/insert_item.py index 9672259..f641297 100644 --- a/queries/insert_item.py +++ b/queries/insert_item.py @@ -1,30 +1,21 @@ from PythonVoiceCodingPlugin.queries.abstract import InsertionQuery,no_build_attempt - +from PythonVoiceCodingPlugin.queries.strategies import decode_item_selection @no_build_attempt class InsertItem(InsertionQuery): select_insertion = True def handle_single(self,view_information,query_description,extra = {}): - state = extra["global_state"] - collection = state["collection"] - mode = query_description["format"] - if mode==1: - item = collection[query_description["item_index"]-1] - elif mode==2: - print(mode,query_description) - item_range = (query_description["item_index"]-1, query_description.get("item_index2")) - item = ",".join(collection[item_range[0]:item_range[1]]) - elif mode==3: - item = [] - for i in ["","2","3","4"]: - index = query_description.get("item_index"+i) - if index: - item.append(collection[index-1]) - item = ",".join(item) + collection = extra["global_state"]["collection"] + mode = { + 1:"individual", + 3:"individual", + 2:"range", + }[query_description["format"]] + items = ",".join(decode_item_selection(collection,query_description,mode,"item_index")) selection = self._get_selection(view_information,extra) selection = selection if isinstance(selection,list) else [selection] - return [(x,item) for x in selection] + return [(x,items) for x in selection] diff --git a/queries/strategies/__init__.py b/queries/strategies/__init__.py index 1430ae2..62a5b54 100644 --- a/queries/strategies/__init__.py +++ b/queries/strategies/__init__.py @@ -3,4 +3,5 @@ from PythonVoiceCodingPlugin.queries.strategies.abstract_vertical import * from PythonVoiceCodingPlugin.queries.strategies.obtain import * from PythonVoiceCodingPlugin.queries.strategies.state_extraction import * +from PythonVoiceCodingPlugin.queries.strategies.item_selection import * diff --git a/queries/strategies/item_selection.py b/queries/strategies/item_selection.py new file mode 100644 index 0000000..c0d2a31 --- /dev/null +++ b/queries/strategies/item_selection.py @@ -0,0 +1,22 @@ +def translate_indices(query_description,name): + y = [name + x for x in ["","2","3","4"]] + return [query_description[x]-1 for x in y if x in query_description] + + +def decode_item_selection(items,query_description,mode,name): + indices = translate_indices(query_description,name) + print(indices) + if mode == "individual": + print([items[x] for x in indices]) + return [items[x] for x in indices] + elif mode == "range": + if len(indices)!= 1: + return items[indices[0]:indices[1] + 1] + else: + return items[indices[0]:] + + + + + + From f488249318c23d05751505a31e8d37b749d9c944 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Mon, 16 Dec 2019 00:41:57 +0200 Subject: [PATCH 036/317] Parameters upgrade --- queries/collect_parameter.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/queries/collect_parameter.py b/queries/collect_parameter.py index 0036593..dc310da 100644 --- a/queries/collect_parameter.py +++ b/queries/collect_parameter.py @@ -6,7 +6,7 @@ from PythonVoiceCodingPlugin.library.traverse import search_upwards,search_upwards_log, find_matching,match_node, find_all_nodes,search_upwards_for_parent from PythonVoiceCodingPlugin.queries.abstract import CollectionQuery - +from PythonVoiceCodingPlugin.queries.strategies import decode_item_selection @@ -22,7 +22,14 @@ def handle_single(self,view_information,query_description,extra = {}): definition_nodes = [search_upwards(origin,ast.FunctionDef)] if query_description["format"]==2 else find_all_nodes(root,ast.FunctionDef) name_nodes = make_flat([get_argument_from_definition(x) for x in definition_nodes]) names = list(OrderedDict([(x,0) for x in name_nodes]).keys()) - result = names[query_description["collect_index"] - 1] if query_description["format"]==2 else None + if query_description["format"]==1: + result = None + else: + mode = { + 2:"individual", + 3:"range", + }[query_description["format"]] + result = ",".join(decode_item_selection(names,query_description,mode,"item_index")) return result, names From 441ee6f837a9b50ee025ee26fe3dcb5de4438c3e Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Tue, 17 Dec 2019 14:27:44 +0200 Subject: [PATCH 037/317] Important patch Fixing error with if Knowledge and else if conditions --- library/info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/info.py b/library/info.py index 3781d41..e667063 100644 --- a/library/info.py +++ b/library/info.py @@ -131,14 +131,14 @@ def get_condition(root): def get_pure_if_condition(root): return ( root.test - if match_node(root,(ast.If)) and not (match_parent(root,(ast.If)) and root.parent_field == "orelse") + if match_node(root,(ast.If)) and root.first_token.string=="if" else None ) def get_elif_condition(root): return ( root.test - if match_node(root,(ast.If)) and match_parent(root,(ast.If)) and root.parent_field == "orelse" + if match_node(root,(ast.If)) and match_parent(root,(ast.If)) and root.first_token.string=="elif" else None ) From e16e227f15b13e92ce64ff222051051fa3f81800 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Tue, 17 Dec 2019 14:35:33 +0200 Subject: [PATCH 038/317] Botch argument case three outer --- library/info.py | 2 +- queries/argument.py | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/library/info.py b/library/info.py index e667063..a92581d 100644 --- a/library/info.py +++ b/library/info.py @@ -138,7 +138,7 @@ def get_pure_if_condition(root): def get_elif_condition(root): return ( root.test - if match_node(root,(ast.If)) and match_parent(root,(ast.If)) and root.first_token.string=="elif" + if match_node(root,(ast.If)) and root.first_token.string=="elif" else None ) diff --git a/queries/argument.py b/queries/argument.py index d7042f8..708a4d4 100644 --- a/queries/argument.py +++ b/queries/argument.py @@ -267,6 +267,21 @@ def inverse_transformation(node): return None # these will be written but I just want to check that it works + priority = {} + print(query_description["level"],"the information in the query description") + if query_description["level"]=="outer": + + _,calling_parents = search_upwards_log(origin,targets=ast.stmt,log_targets=(ast.Call)) + print("inside here",calling_parents) + index = query_description["level_index"] + print(len(calling_parents)," that is the length ") + if index inside argument + # [] (argument |caller []) ############################################################### selection = self._get_selection(view_information,extra) build = self.general_build if self.general_build else line_partial(selection[0]) From 893b95640c20e5e69f9235477a9c2f96fb4ce983 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Tue, 17 Dec 2019 18:13:09 +0200 Subject: [PATCH 039/317] Update info.py --- library/info.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/library/info.py b/library/info.py index a92581d..4f0b657 100644 --- a/library/info.py +++ b/library/info.py @@ -45,10 +45,12 @@ def make_information(c,*arg,**kwargs): return lambda x: c(x,*arg,**kwargs) -def identity(information, parameter = None): - if parameter: - return lambda x: x if information(x,parameter) else None - return lambda x: x if information(x) else None +def identity(information, *arg,**kwargs): + signature = inspect.signature(information) + if not any(x.kind==x.VAR_KEYWORD for x in signature.parameters.values()): + temporary = {x.name for x in signature.parameters.values()} + kwargs = {k:v for k,v in kwargs.items() if k in temporary} + return lambda x: x if information(x,*arg,**kwargs) else None def create_fake(root,text,start_position,node_type, **kwargs): From d9a049edae7066404f4501ff544b8d871236279f Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Fri, 20 Dec 2019 22:56:18 +0200 Subject: [PATCH 040/317] Add dedicated select Part command ~ --- queries/__init__.py | 2 ++ queries/select_part.py | 46 ++++++++++++++++++++++++++++++++++ queries/strategies/__init__.py | 1 + 3 files changed, 49 insertions(+) create mode 100644 queries/select_part.py diff --git a/queries/__init__.py b/queries/__init__.py index 47cd017..8d2e1f2 100644 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -11,6 +11,7 @@ from PythonVoiceCodingPlugin.queries.insert_item import InsertItem from PythonVoiceCodingPlugin.queries.delete_alternatives import DeleteAlternatives from PythonVoiceCodingPlugin.queries.swap_back import SwapBack +from PythonVoiceCodingPlugin.queries.select_part import SelectPart @@ -39,6 +40,7 @@ def get_query(query_description): "insert_item": InsertItem, "delete_alternatives":DeleteAlternatives, "swap_back": SwapBack, + "select_part": SelectPart, } return h[index] diff --git a/queries/select_part.py b/queries/select_part.py new file mode 100644 index 0000000..b932c91 --- /dev/null +++ b/queries/select_part.py @@ -0,0 +1,46 @@ +import ast + +from PythonVoiceCodingPlugin.library import nearest_node_from_offset,sorted_by_source_region,get_source_region,node_from_range,make_flat +from PythonVoiceCodingPlugin.library.info import * +from PythonVoiceCodingPlugin.library.LCA import LCA +from PythonVoiceCodingPlugin.library.level_info import LevelVisitor +from PythonVoiceCodingPlugin.library.partial import partially_parse, line_partial +from PythonVoiceCodingPlugin.library.traverse import search_upwards,search_upwards_log, find_matching,match_node, find_all_nodes,search_upwards_for_parent + +from PythonVoiceCodingPlugin.queries.abstract import SelectionQuery +from PythonVoiceCodingPlugin.queries.tiebreak import tiebreak_on_lca +from PythonVoiceCodingPlugin.queries.strategies import adjective_strategy,decode_abstract_vertical,translate_adjective,obtain_result + + + + +class SelectPart(SelectionQuery): + """docstring for SelectPart""" + def handle_single(self,view_information,query_description,extra = {}): + print(" inside here selection where he parked ") + selection = self._get_selection(view_information,extra) + build = self.general_build if self.general_build else line_partial(selection[0]) + if not build or not build[0] : + return None,None + root,atok,m,r = build + selection = m.forward(selection) + origin = nearest_node_from_offset(root,atok, selection[0]) if selection[0]==selection[1] else node_from_range(root,atok, selection) + if selection[0]==selection[1]: + return None,None + second_origin = origin + if "nth" in query_description: + print(" hello world ") + print(translate_adjective[query_description["nth"]]) + second_origin = get_sub_index(origin,translate_adjective[query_description["nth"]]-1) + + print(" just before the end",second_origin) + result = get_sub_index(second_origin,query_description["sub_index"]-1) + alternatives = [] + return self._backward_result(result, alternatives,build) + + + + + + + diff --git a/queries/strategies/__init__.py b/queries/strategies/__init__.py index 62a5b54..d68722d 100644 --- a/queries/strategies/__init__.py +++ b/queries/strategies/__init__.py @@ -5,3 +5,4 @@ from PythonVoiceCodingPlugin.queries.strategies.state_extraction import * from PythonVoiceCodingPlugin.queries.strategies.item_selection import * + From a692e1e99d5b4deb298a47555b6f32e6cb7ec511 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Fri, 20 Dec 2019 23:00:25 +0200 Subject: [PATCH 041/317] Remove debugging print() --- library/info.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/library/info.py b/library/info.py index 4f0b657..d2b5c7d 100644 --- a/library/info.py +++ b/library/info.py @@ -85,6 +85,9 @@ def name(root): + + + ################################################################################################ ################################################################################################ # @@ -120,7 +123,7 @@ def get_right(root): return getattr( root ,h[type( root)]) if type( root) in h else None def get_body(root): - return root.attribute if match_node(root,(ast.IfExp, ast.If ,ast.For,ast.While, ast.Try)) else None + return root.body if match_node(root,(ast.IfExp, ast.If ,ast.For,ast.While, ast.Try)) else None @@ -286,6 +289,15 @@ def get_definition_name(root,atok): else: return None +def get_definition_parameter_name(root,atok): + print(ast.dump(root),dir(root)) + if not match_node(root,ast.arg): + return None + x = root.first_token + print([x]) + return create_fake(root,x.string,x.startpos,ast.Name,id = x.string,ctx = ast.Store()) + + def get_class_name(root,atok): if not match_node(root,ast.ClassDef): @@ -332,7 +344,7 @@ def get_subparts_of_binary_operation(root): return left + right -def split_string(s,even_letters = True): +def split_string(s :str ,even_letters = True): s = s.strip() y = urlparse(s) if not (y.scheme=="" and y.netloc==""): @@ -364,6 +376,8 @@ def get_subparts_of_string(root,name_mode = False): index = 0 print("splitted ",splitted) for s in splitted: + if not s: + continue index = original.find(s,index) if name_mode: fake_node = create_fake(root,s,start_position + index,ast.Name,id = s,ctx = root.ctx) @@ -374,15 +388,11 @@ def get_subparts_of_string(root,name_mode = False): return output if name_mode or len(output)>1 else [] def get_subparts_of_attribute(root): - print("entering ") - print("wrote a",ast.dump(root)) if not match_node(root,ast.Attribute): return None l = root.last_token - print("this is not nice ") fake_node = create_fake(root,l.string,l.startpos,ast.Name,id = l.string,ctx = root.ctx) if match_node(root.value,ast.Attribute): - print( "inside here" ) return get_subparts_of_attribute(root.value) + [fake_node] else: return [root.value,fake_node] From d82301be13bc6638c2f4c6b609e4cc8ce330140b Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 00:54:13 +0200 Subject: [PATCH 042/317] Breaking change in sub indexing subscript nodes --- library/info.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/library/info.py b/library/info.py index d2b5c7d..5f44f97 100644 --- a/library/info.py +++ b/library/info.py @@ -417,10 +417,8 @@ def get_sub_index(root,index): candidates = get_subparts_of_binary_operation(root) elif match_node(root,(ast.Compare)) : candidates = [root.left] + root.comparators - elif match_node(root,(ast.Index)): - candidates = [root.value] - if match_node(root.value,(ast.List,ast.Tuple,ast.Set)): - candidates = root.value.elts + elif match_node(root,(ast.Subscript)): + candidates = [root.value,root.slice] elif match_node(root,(ast.Slice)): candidates = [root.lower,root.upper, root.step] elif match_node(root,(ast.ExtSlice)): @@ -433,8 +431,8 @@ def get_sub_index(root,index): candidates = get_subparts_of_attribute(root) # in the following cases we Certs deeper in the tree - if match_node(root,(ast.Subscript)): - return get_sub_index(root.slice,index) + if match_node(root,(ast.Index)): + return get_sub_index(root.value,index) if match_node(root,(ast.Expr)): return get_sub_index(root.value,index) if match_node(root,(ast.UnaryOp)): From b7c79754e00f13f7f1ac982cd33d5deb02c3c926 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 01:50:06 +0200 Subject: [PATCH 043/317] Important bug fix in collect parameters --- queries/collect_parameter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/collect_parameter.py b/queries/collect_parameter.py index dc310da..63e8162 100644 --- a/queries/collect_parameter.py +++ b/queries/collect_parameter.py @@ -19,7 +19,7 @@ def handle_single(self,view_information,query_description,extra = {}): if not build: return None,None root,atok,m,r = build - definition_nodes = [search_upwards(origin,ast.FunctionDef)] if query_description["format"]==2 else find_all_nodes(root,ast.FunctionDef) + definition_nodes = [search_upwards(origin,ast.FunctionDef)] if query_description["format"]>=2 else find_all_nodes(root,ast.FunctionDef) name_nodes = make_flat([get_argument_from_definition(x) for x in definition_nodes]) names = list(OrderedDict([(x,0) for x in name_nodes]).keys()) if query_description["format"]==1: From 33c0208882e9cb6b9dabaeb8bb581ec6c9bd933a Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 01:51:25 +0200 Subject: [PATCH 044/317] Finally blocks Are also Checked if their empty .. --- library/repair.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/repair.py b/library/repair.py index c19679f..bf98620 100644 --- a/library/repair.py +++ b/library/repair.py @@ -340,7 +340,7 @@ def work(self): k = 0 self.already_checked = set() for t in self.atok.tokens: - if t.string in ["if","for","while","with","def","elif","else","try","except"]: + if t.string in ["if","for","while","with","def","elif","else","try","except","finally"]: if t.string == "elif": k = k + 1 z = handle_empty_compound(self.atok,t,l,b ,self.d) From 41befeeb520b3048c6f330221b8d95ee312d2be0 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 01:52:24 +0200 Subject: [PATCH 045/317] Check for empty classes as well --- library/repair.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/repair.py b/library/repair.py index bf98620..2b41382 100644 --- a/library/repair.py +++ b/library/repair.py @@ -340,7 +340,7 @@ def work(self): k = 0 self.already_checked = set() for t in self.atok.tokens: - if t.string in ["if","for","while","with","def","elif","else","try","except","finally"]: + if t.string in ["if","for","while","with","def","elif","else","try","except","finally","class"]: if t.string == "elif": k = k + 1 z = handle_empty_compound(self.atok,t,l,b ,self.d) From b4250838917fb22c30a0619ff577f86debc1b41a Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 12:35:14 +0200 Subject: [PATCH 046/317] Support for multiple Cursors return value These would need modification in the future because there are certain obvious bug but it is good for now --- application/application.py | 20 ++++++++++++++++---- queries/select_part.py | 4 +++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/application/application.py b/application/application.py index b533598..19f19b3 100644 --- a/application/application.py +++ b/application/application.py @@ -59,8 +59,14 @@ def respond_to_query(self,interface,query_description,secondary=False): backup=[deepcopy(self.state),deepcopy(self.global_state)] try: s(view_information,query_description,extra) - except: - pass + except Exception as e: + print("\n\n finally\n\n") + print(e) + print("\n\n finally\n\n") + interface.clear_actions() + interface.push_action(PopUpErrorAction(str(e))) + return False + # check if there are exceptions if s.exceptions_raised: @@ -82,13 +88,19 @@ def respond_to_query(self,interface,query_description,secondary=False): self.state["result"] = result self.state["alternatives"] = [] self.state["alternatives_text"] = [] - self.state["result_text"] = code[result[0]:result[1]] + if not isinstance(result,list): + self.state["result_text"] = code[result[0]:result[1]] + else: + self.state["result_text"] = [code[x[0]:x[1]] for x in result if x] interface.push_action(SelectionAction(result)) self.history.append(("selection",view_information["change_count"],view_information["selection"],result)) interface.push_action(ClearHighlightAction("alternatives")) if alternatives: self.state["alternatives"] = alternatives - self.state["alternatives_text"] = [code[x[0]:x[1]] for x in alternatives] + if not isinstance(alternatives[0],list): + self.state["alternatives_text"] = [code[x[0]:x[1]] for x in alternatives] + else: + self.state["alternatives_text"] = [[code[x[0]:x[1]] for x in y] for y in alternatives if y] interface.push_action(DisplayRegionsAction("alternatives",alternatives,"Alternatives:\n")) interface.push_action(HighlightCleverAction(alternatives,"alternatives",result)) diff --git a/queries/select_part.py b/queries/select_part.py index b932c91..7e3931c 100644 --- a/queries/select_part.py +++ b/queries/select_part.py @@ -15,7 +15,9 @@ class SelectPart(SelectionQuery): - """docstring for SelectPart""" + multiple_in = True + + def handle_single(self,view_information,query_description,extra = {}): print(" inside here selection where he parked ") selection = self._get_selection(view_information,extra) From e5e83f016916c9a7adfbc597959d315f1df6c504 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 12:36:18 +0200 Subject: [PATCH 047/317] Multiple in support for sub indexing --- queries/select_part.py | 1 + 1 file changed, 1 insertion(+) diff --git a/queries/select_part.py b/queries/select_part.py index 7e3931c..a5caabf 100644 --- a/queries/select_part.py +++ b/queries/select_part.py @@ -17,6 +17,7 @@ class SelectPart(SelectionQuery): multiple_in = True + def handle_single(self,view_information,query_description,extra = {}): print(" inside here selection where he parked ") From 23e652d2b8bdbf92c867e3bc4e34a7ff2254fb5c Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 13:03:41 +0200 Subject: [PATCH 048/317] Support for selecting multiple alternatives --- queries/alternatives.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/queries/alternatives.py b/queries/alternatives.py index 895cabe..a9338fa 100644 --- a/queries/alternatives.py +++ b/queries/alternatives.py @@ -1,4 +1,5 @@ from PythonVoiceCodingPlugin.queries.abstract import SelectionQuery, no_build_attempt +from PythonVoiceCodingPlugin.queries.strategies import decode_item_selection @no_build_attempt class SelectAlternative(SelectionQuery): @@ -7,15 +8,14 @@ def handle_single(self,view_information,query_description,extra = {}): state = extra["state"] alternatives = state["alternatives"] if "alternative_index" in query_description: - index = query_description["alternative_index"] + name="alternative_index" elif "color" in query_description: - index = query_description["color"] - else: - return None,None - index=index-1 - if len(alternatives)>index: - return alternatives[index],[] + name = "color" else: return None,None + result = decode_item_selection(alternatives,query_description,"individual",name) + if len(result)==1: + result = result[0] + return result, [] From 977fa286c66b8b4da88d6ad4cde8ee48245ff79e Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 13:16:05 +0200 Subject: [PATCH 049/317] In item selection routine DecrementParameter was added so Subtraction happens only when we wanted to --- queries/alternatives.py | 6 +++--- queries/strategies/item_selection.py | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/queries/alternatives.py b/queries/alternatives.py index a9338fa..89bb7e4 100644 --- a/queries/alternatives.py +++ b/queries/alternatives.py @@ -1,19 +1,19 @@ from PythonVoiceCodingPlugin.queries.abstract import SelectionQuery, no_build_attempt -from PythonVoiceCodingPlugin.queries.strategies import decode_item_selection +from PythonVoiceCodingPlugin.queries.strategies import decode_item_selection,result_alternatives_sequence @no_build_attempt class SelectAlternative(SelectionQuery): """docstring for SelectAlternative""" def handle_single(self,view_information,query_description,extra = {}): state = extra["state"] - alternatives = state["alternatives"] + candidates = result_alternatives_sequence(state,location=True) if "alternative_index" in query_description: name="alternative_index" elif "color" in query_description: name = "color" else: return None,None - result = decode_item_selection(alternatives,query_description,"individual",name) + result = decode_item_selection(candidates,query_description,"individual",name,decrement=False) if len(result)==1: result = result[0] return result, [] diff --git a/queries/strategies/item_selection.py b/queries/strategies/item_selection.py index c0d2a31..bf22dee 100644 --- a/queries/strategies/item_selection.py +++ b/queries/strategies/item_selection.py @@ -1,10 +1,11 @@ -def translate_indices(query_description,name): +def translate_indices(query_description,name,decrement): y = [name + x for x in ["","2","3","4"]] - return [query_description[x]-1 for x in y if x in query_description] + value = 1 if decrement else 0 + return [query_description[x]-value for x in y if x in query_description] -def decode_item_selection(items,query_description,mode,name): - indices = translate_indices(query_description,name) +def decode_item_selection(items,query_description,mode,name,decrement=True): + indices = translate_indices(query_description,name,decrement) print(indices) if mode == "individual": print([items[x] for x in indices]) From 69507031715f66484c5532b2749c47b72bdb54f5 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 14:02:25 +0200 Subject: [PATCH 050/317] Change behavior of inside query --- queries/argument.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/queries/argument.py b/queries/argument.py index 708a4d4..6d787bd 100644 --- a/queries/argument.py +++ b/queries/argument.py @@ -252,7 +252,7 @@ def transformation(node): if not calling_parent: return None field,field_index = lca.get_field_with_respect_to(node,calling_parent) - if correspond_to_index_in_call(calling_parent,query_description["level_index"]-1,field,field_index): + if query_description["level_index"]== 0 or correspond_to_index_in_call(calling_parent,query_description["level_index"]-1,field,field_index): if calling_parent not in temporary: temporary[calling_parent] = [] temporary[calling_parent].append(node) @@ -322,7 +322,7 @@ def transformation(node): calling_parent, field,field_index = level[node] if not calling_parent or calling_parent is level.root: return None - if correspond_to_index_in_call(calling_parent,query_description["level_index"]-1,field,field_index): + if query_description["level_index"]== 0 or correspond_to_index_in_call(calling_parent,query_description["level_index"]-1,field,field_index): adj = translate_adjective[query_description["adjective"]]-1 n = level(node, 3,adj) return node if n is node else None From 366fb7f774569e52c83c101627190b19852e62c2 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 18:20:28 +0200 Subject: [PATCH 051/317] Properly renamed the make information functions --- library/info.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/library/info.py b/library/info.py index 5f44f97..18c135b 100644 --- a/library/info.py +++ b/library/info.py @@ -37,12 +37,12 @@ ################################################################################################ ################################################################################################ -def make_information(c,*arg,**kwargs): - signature = inspect.signature(c) +def make_information(information,*arg,**kwargs): + signature = inspect.signature(information) if not any(x.kind==x.VAR_KEYWORD for x in signature.parameters.values()): temporary = {x.name for x in signature.parameters.values()} kwargs = {k:v for k,v in kwargs.items() if k in temporary} - return lambda x: c(x,*arg,**kwargs) + return lambda root: information(root,*arg,**kwargs) def identity(information, *arg,**kwargs): @@ -50,7 +50,8 @@ def identity(information, *arg,**kwargs): if not any(x.kind==x.VAR_KEYWORD for x in signature.parameters.values()): temporary = {x.name for x in signature.parameters.values()} kwargs = {k:v for k,v in kwargs.items() if k in temporary} - return lambda x: x if information(x,*arg,**kwargs) else None + return lambda root: root if information(root,*arg,**kwargs) else None + def create_fake(root,text,start_position,node_type, **kwargs): @@ -272,6 +273,7 @@ def get_caller(root): def get_argument_from_definition(root,raw = True,index = None): if not match_node(root,ast.FunctionDef): return None + x = root.args temporary = x.args + [x.vararg] + x.kwonlyargs + [x.kwarg] temporary = [y for y in temporary if y] From da0146f634b45dc62f552f86967274cb67fd4dc0 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 19:07:36 +0200 Subject: [PATCH 052/317] Something I do not really like, but needed to get things working in traverse. --- library/traverse.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/traverse.py b/library/traverse.py index daa71b9..1515291 100644 --- a/library/traverse.py +++ b/library/traverse.py @@ -28,7 +28,8 @@ def find_all_nodes(root , targets = (), exclusions = (), visit_all_levels = True targets = targets if targets else ast.AST node_wanted = selector if selector else lambda node: match_node(node,targets,exclusions) reachable = ast.walk if visit_all_levels else ast.iter_child_nodes - return sorted([node for node in reachable(root) if node_wanted(node)] , key=lambda s: (s.first_token.startpos)) + return sorted([node for node in reachable(root) if node_wanted(node) and hasattr(node,"first_token")], + key=lambda s: (s.first_token.startpos)) def find_information(root, information, flatten = False, visit_all_levels = True): reachable = ast.walk if visit_all_levels else ast.iter_child_nodes From 12078a9adbc2fda767b918f8577befa9034741f8 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 19:10:24 +0200 Subject: [PATCH 053/317] Checker for Decorator --- library/info.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/info.py b/library/info.py index 18c135b..f3e01f3 100644 --- a/library/info.py +++ b/library/info.py @@ -84,7 +84,8 @@ def single(root): def name(root): return match_node(root,ast.Name) - +def is_decorator(root): + return root.parent_field=="decorator_list" From 6e3483ec8b711d406c76fec6401910744924e07c Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 19:12:04 +0200 Subject: [PATCH 054/317] Minor cleanup --- library/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/__init__.py b/library/__init__.py index d606867..81a4290 100644 --- a/library/__init__.py +++ b/library/__init__.py @@ -17,8 +17,8 @@ def get_source_region(atok, element): if element is None: return None if isinstance(element,ast.AST): - return atok.get_text_range(element) - else: + return atok.get_text_range(element) + else: regions = [atok.get_text_range(node) for node in element] start = min( regions,key = lambda s: s[0])[0] end = max( regions,key = lambda s: s[1])[1] @@ -47,8 +47,11 @@ def node_from_range(root,atok, r ): inside = lambda x,y: (y[0]<=x[0] Date: Sat, 21 Dec 2019 19:28:21 +0200 Subject: [PATCH 055/317] Nothing --- library/info.py | 5 ++++- queries/alternatives.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/library/info.py b/library/info.py index f3e01f3..c7e5924 100644 --- a/library/info.py +++ b/library/info.py @@ -85,7 +85,10 @@ def name(root): return match_node(root,ast.Name) def is_decorator(root): - return root.parent_field=="decorator_list" + return getattr(root,"parent_field","")=="decorator_list" + +def is_base(root): + return getattr(root,"parent_field","") == "bases" and match_parent(root,ast.keyword) and match_parent(root.parent,ast.ClassDef) diff --git a/queries/alternatives.py b/queries/alternatives.py index 89bb7e4..d10600c 100644 --- a/queries/alternatives.py +++ b/queries/alternatives.py @@ -4,6 +4,7 @@ @no_build_attempt class SelectAlternative(SelectionQuery): """docstring for SelectAlternative""" + def handle_single(self,view_information,query_description,extra = {}): state = extra["state"] candidates = result_alternatives_sequence(state,location=True) From 8f4a554540c468db2e9a18e614758cd2bb68e794 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 19:29:08 +0200 Subject: [PATCH 056/317] Various fixes in order to allow classwide search and level definition --- queries/big_roi.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/queries/big_roi.py b/queries/big_roi.py index 387009b..61c3eee 100644 --- a/queries/big_roi.py +++ b/queries/big_roi.py @@ -40,7 +40,8 @@ def preliminary(self,view_information,query_description, extra = {}): definition_node = search_upwards(origin,ast.FunctionDef) definition_node = ( definition_node - if definition_node and query_description["big_roi"] not in ["import statement","class name"] + if definition_node and query_description["big_roi"] not in ["import statement","class name", + "base class","decorator"] else root ) return build, selection, origin, definition_node @@ -73,7 +74,14 @@ def standard(x): "import statement":((ast.Import,ast.ImportFrom),(),standard), "lambda":((ast.Lambda),(),standard), "lambda body":((ast.Lambda),(),get_body), + "if body":((ast.If, ast.For,ast.comprehension),(),get_body), + "definition parameter": ((ast.arg),(),get_definition_parameter_name), + "decorator":((ast.AST),(),identity(is_decorator)), + "base class":((ast.AST),(),identity(is_base)), + + } + temporary = possibilities[query_description["big_roi"]] basic_information = make_information(temporary[2],atok = build[1]) if "big_roi_sub_index" in query_description: @@ -121,7 +129,7 @@ def case_two(self,view_information,query_description, extra = {}): atok=atok, root = definition_node, adjective_word = query_description["adjective"], - level_nodes = find_all_nodes(definition_node, (ast.If,ast.While,ast.For,ast.Try,ast.With,ast.FunctionDef)), + level_nodes = find_all_nodes(definition_node, (ast.If,ast.While,ast.For,ast.Try,ast.With,ast.FunctionDef,ast.ClassDef)), information_nodes = find_matching(definition_node,temporary_information), **additional_parameters ) From 7df695840a61e01f07c20b272e0c6f95748c6ca6 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 22:30:58 +0200 Subject: [PATCH 057/317] Bug fix in order to get base classes working --- library/info.py | 10 +++++++++- queries/alternatives.py | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/library/info.py b/library/info.py index c7e5924..04a63e1 100644 --- a/library/info.py +++ b/library/info.py @@ -88,7 +88,15 @@ def is_decorator(root): return getattr(root,"parent_field","")=="decorator_list" def is_base(root): - return getattr(root,"parent_field","") == "bases" and match_parent(root,ast.keyword) and match_parent(root.parent,ast.ClassDef) + return ( + ( + getattr(root,"parent_field","") == "bases" and match_parent(root,ast.ClassDef) + ) + or + ( + match_parent(root,ast.keyword) and match_parent(root.parent,ast.ClassDef) + ) + ) diff --git a/queries/alternatives.py b/queries/alternatives.py index d10600c..eb7c463 100644 --- a/queries/alternatives.py +++ b/queries/alternatives.py @@ -1,10 +1,11 @@ from PythonVoiceCodingPlugin.queries.abstract import SelectionQuery, no_build_attempt from PythonVoiceCodingPlugin.queries.strategies import decode_item_selection,result_alternatives_sequence + @no_build_attempt class SelectAlternative(SelectionQuery): """docstring for SelectAlternative""" - + def handle_single(self,view_information,query_description,extra = {}): state = extra["state"] candidates = result_alternatives_sequence(state,location=True) From ea9da7f3633446c806344919da6dc19fd13ef247 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 23:15:46 +0200 Subject: [PATCH 058/317] Update sub indexing function in order to allow to return all the elements if the index is set value None --- library/info.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/info.py b/library/info.py index 04a63e1..093e0c4 100644 --- a/library/info.py +++ b/library/info.py @@ -456,7 +456,9 @@ def get_sub_index(root,index): if match_node(root,(ast.Call)): return get_sub_index(root.func,index) - if index Date: Sat, 21 Dec 2019 23:16:20 +0200 Subject: [PATCH 059/317] Support for every part Commands --- queries/select_part.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/queries/select_part.py b/queries/select_part.py index a5caabf..0e458c6 100644 --- a/queries/select_part.py +++ b/queries/select_part.py @@ -2,14 +2,12 @@ from PythonVoiceCodingPlugin.library import nearest_node_from_offset,sorted_by_source_region,get_source_region,node_from_range,make_flat from PythonVoiceCodingPlugin.library.info import * -from PythonVoiceCodingPlugin.library.LCA import LCA -from PythonVoiceCodingPlugin.library.level_info import LevelVisitor from PythonVoiceCodingPlugin.library.partial import partially_parse, line_partial from PythonVoiceCodingPlugin.library.traverse import search_upwards,search_upwards_log, find_matching,match_node, find_all_nodes,search_upwards_for_parent from PythonVoiceCodingPlugin.queries.abstract import SelectionQuery from PythonVoiceCodingPlugin.queries.tiebreak import tiebreak_on_lca -from PythonVoiceCodingPlugin.queries.strategies import adjective_strategy,decode_abstract_vertical,translate_adjective,obtain_result +from PythonVoiceCodingPlugin.queries.strategies import translate_adjective,obtain_result @@ -37,8 +35,14 @@ def handle_single(self,view_information,query_description,extra = {}): second_origin = get_sub_index(origin,translate_adjective[query_description["nth"]]-1) print(" just before the end",second_origin) - result = get_sub_index(second_origin,query_description["sub_index"]-1) - alternatives = [] + if query_description["format"]!=3: + result = get_sub_index(second_origin,query_description["sub_index"]-1) + alternatives = [] + else: + intermediate = get_sub_index(second_origin,None) + candidates = [get_sub_index(x,query_description["sub_index"]-1) for x in intermediate] + candidates = [x for x in candidates if x] + result,alternatives = obtain_result(None, candidates) return self._backward_result(result, alternatives,build) From 868ef6ff85313cbfa3fbfe25050e06733e2eef54 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 23:16:37 +0200 Subject: [PATCH 060/317] Experimenting with an event listener --- python_voice_coding_plugin.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python_voice_coding_plugin.py b/python_voice_coding_plugin.py index f39bf81..fbba8fd 100644 --- a/python_voice_coding_plugin.py +++ b/python_voice_coding_plugin.py @@ -20,12 +20,13 @@ def plugin_loaded(): -class PythonVoiceCodingPluginCommand(sublime_plugin.TextCommand): +class PythonVoiceCodingPluginCommand(sublime_plugin.TextCommand,sublime_plugin.ViewEventListener): def run(self, edit,arg): self.action_one(edit,arg) def action_one(self, edit,arg): global settings + print(" the counties ",self.view.change_count()) interface = Interface( sublime = sublime, view = self.view, @@ -34,8 +35,10 @@ def action_one(self, edit,arg): settings = settings ) interface.respond_to_query(arg) + print(" the counties ",self.view.change_count()) - + def on_modified(self): + print(" from the event lease and their side of the plug-in",self.view.change_count()) From b787b62fe6163dfc5482253759ae86f6f1cb8446 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 23:37:22 +0200 Subject: [PATCH 061/317] Sub indexing if expressions --- library/info.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/info.py b/library/info.py index 093e0c4..f1c6755 100644 --- a/library/info.py +++ b/library/info.py @@ -443,6 +443,8 @@ def get_sub_index(root,index): candidates = get_subparts_of_string(root,name_mode = True) elif match_node(root,ast.Attribute): candidates = get_subparts_of_attribute(root) + elif match_node(root,ast.IfExp): + candidates = [root.body,root.test,root.orelse] # in the following cases we Certs deeper in the tree if match_node(root,(ast.Index)): From 85dbb4e3a89119d2e4396926e3be718f1ffc1f18 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 21 Dec 2019 23:51:32 +0200 Subject: [PATCH 062/317] Increased sub indexing support --- library/info.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/info.py b/library/info.py index f1c6755..a79e8d7 100644 --- a/library/info.py +++ b/library/info.py @@ -445,6 +445,12 @@ def get_sub_index(root,index): candidates = get_subparts_of_attribute(root) elif match_node(root,ast.IfExp): candidates = [root.body,root.test,root.orelse] + elif match_node(root,ast.withitem): + candidates = [root.context_expr,root.optional_vars] + elif match_node(root,(ast.ListComp,ast.SetComp,ast.GeneratorExp)): + candidates = [root.elt] + root.generators + elif match_node(root,ast.DictComp): + candidates = [[root.key,root.value]] + root.generators # in the following cases we Certs deeper in the tree if match_node(root,(ast.Index)): From 2d94bc59bc7c54c0ee755d3fee1a57e28ade1593 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 22 Dec 2019 00:03:33 +0200 Subject: [PATCH 063/317] Interfase now has Response to event method --- interface/interface.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/interface/interface.py b/interface/interface.py index eb74621..a10d3bf 100644 --- a/interface/interface.py +++ b/interface/interface.py @@ -29,10 +29,23 @@ def respond_to_query(self,query_description): } for action in self.actions: action.execute(**parameters) + + def respond_to_event(self,event_description): + self.actions = [] + application = Application.get_application(self.view.id()) + application.respond_to_query(self,query_description) + parameters = { + "view":self.view,"window":self.window,"edit":self.edit,"sublime":self.sublime,"settings":self.settings, + } + for action in self.actions: + action.execute(**parameters) + + def clear_actions(self): self.actions = [] + \ No newline at end of file From 749bc37d992c6d7a30599e3a6b9ba0d7376337ca Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 22 Dec 2019 00:12:46 +0200 Subject: [PATCH 064/317] Buck and for responding to events fixed --- application/application.py | 11 ++++++++--- interface/interface.py | 2 +- python_voice_coding_plugin.py | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/application/application.py b/application/application.py index 19f19b3..e6bf8ee 100644 --- a/application/application.py +++ b/application/application.py @@ -61,7 +61,9 @@ def respond_to_query(self,interface,query_description,secondary=False): s(view_information,query_description,extra) except Exception as e: print("\n\n finally\n\n") - print(e) + if not s.exceptions_raised : + print(e) + raise e print("\n\n finally\n\n") interface.clear_actions() interface.push_action(PopUpErrorAction(str(e))) @@ -152,8 +154,11 @@ def respond_to_query(self,interface,query_description,secondary=False): - def respond_to_event(interface,event_description): - pass + def respond_to_event(self,interface,event_description): + event = event_description["event"] + if event=="update_change_count": + self.state["change_count"] = event_description["change_count"] + \ No newline at end of file diff --git a/interface/interface.py b/interface/interface.py index a10d3bf..42ad28e 100644 --- a/interface/interface.py +++ b/interface/interface.py @@ -33,7 +33,7 @@ def respond_to_query(self,query_description): def respond_to_event(self,event_description): self.actions = [] application = Application.get_application(self.view.id()) - application.respond_to_query(self,query_description) + application.respond_to_event(self,event_description) parameters = { "view":self.view,"window":self.window,"edit":self.edit,"sublime":self.sublime,"settings":self.settings, } diff --git a/python_voice_coding_plugin.py b/python_voice_coding_plugin.py index fbba8fd..94698e9 100644 --- a/python_voice_coding_plugin.py +++ b/python_voice_coding_plugin.py @@ -35,6 +35,7 @@ def action_one(self, edit,arg): settings = settings ) interface.respond_to_query(arg) + interface.respond_to_event({"event":"update_change_count","change_count":self.view.change_count()}) print(" the counties ",self.view.change_count()) def on_modified(self): From 8b402ba30a11c53ae7b78699fa44680d7c2df738 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 22 Dec 2019 00:26:44 +0200 Subject: [PATCH 065/317] Standardize sub indexing across arguments and big regions of interest --- queries/big_roi.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/queries/big_roi.py b/queries/big_roi.py index 61c3eee..098b6ff 100644 --- a/queries/big_roi.py +++ b/queries/big_roi.py @@ -84,18 +84,17 @@ def standard(x): temporary = possibilities[query_description["big_roi"]] basic_information = make_information(temporary[2],atok = build[1]) - if "big_roi_sub_index" in query_description: - if query_description["big_roi_sub_index"] == 0: - return possibilities[query_description["big_roi"]][:2] + (basic_information,) - else: - index = query_description["big_roi_sub_index"] - def modified_information(x, information,index): - data = basic_information(x) - return get_sub_index(data,index) - - y = lambda x: temporary[2](x) - y.secondary = lambda x: modified_information(x,temporary[2],index-1) - return (temporary[0],temporary[1],y) + if "sub_index" in query_description: + index = query_description["sub_index"] + def modified_information(x, information,index): + data = basic_information(x) + return get_sub_index(data,index) + + y = lambda x: temporary[2](x) + y.secondary = lambda x: modified_information(x,temporary[2],index-1) + return (temporary[0],temporary[1],y) + else: + return possibilities[query_description["big_roi"]][:2] + (basic_information,) def case_one(self,view_information,query_description, extra = {}): From 98e1ac438c14ab0a9b633aa4fedcc3802bd41455 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sun, 22 Dec 2019 19:17:35 +0200 Subject: [PATCH 066/317] First steps towards updating the state in a more better way. --- application/application.py | 32 ++------ application/state_update.py | 151 ++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 27 deletions(-) create mode 100644 application/state_update.py diff --git a/application/application.py b/application/application.py index e6bf8ee..0c7f673 100644 --- a/application/application.py +++ b/application/application.py @@ -2,6 +2,7 @@ from PythonVoiceCodingPlugin.queries import * from PythonVoiceCodingPlugin.application.build_cache import BuildCache +from PythonVoiceCodingPlugin.application.state_update import retrieve_state from PythonVoiceCodingPlugin.interface.common.actions import * @@ -13,7 +14,7 @@ class Application(): def __init__(self,vid): self.history = [] - self.state = {} + self.state = {"result": None,"origin": None,"alternatives": [],"change_count":-1} self.ui_controller = None self.vid = vid @@ -25,20 +26,6 @@ def get_application(vid): Application.create_application_for_view(vid) return Application.active_applications[vid] - def update_state(self,view_information,code): - ''' - normally I would like these to be implemented by means of modification handlers - And an event listener transmitting every chains in the code. However it seems - but the provided event listener does not provide me with the location that was changed. - So in order to keep track over the location of important regions when the code changes, - The current solution is to outsource everything to the sublime add_regions/get_regions - functionality - ''' - self.state["result"] = view_information["get_regions"]("result") - self.state["alternatives"] = view_information["get_regions"]("alternatives") - self.state["origin"] = view_information["get_regions"]("origin") - self.state["origin_stack"] = view_information["get_regions"]("origin_stack") - @@ -51,19 +38,21 @@ def respond_to_query(self,interface,query_description,secondary=False): code = view_information["code"] change_count = view_information["change_count"] latest_build = Application.build_cache.get_build(self.vid,change_count) + retrieve_state(self.state,view_information,code) # get the corresponding query and execute it s = get_query(query_description)(code,latest_build) secondary_query_description = get_secondary_query(query_description) if secondary_query_description: backup=[deepcopy(self.state),deepcopy(self.global_state)] + try: s(view_information,query_description,extra) except Exception as e: print("\n\n finally\n\n") if not s.exceptions_raised : print(e) - raise e + print("\n\n finally\n\n") interface.clear_actions() interface.push_action(PopUpErrorAction(str(e))) @@ -143,17 +132,6 @@ def respond_to_query(self,interface,query_description,secondary=False): - - - - - - - - - - - def respond_to_event(self,interface,event_description): event = event_description["event"] if event=="update_change_count": diff --git a/application/state_update.py b/application/state_update.py new file mode 100644 index 0000000..4c42a41 --- /dev/null +++ b/application/state_update.py @@ -0,0 +1,151 @@ +from copy import deepcopy + +from PythonVoiceCodingPlugin.library import make_flat +from PythonVoiceCodingPlugin.queries import * +from PythonVoiceCodingPlugin.application.build_cache import BuildCache +from PythonVoiceCodingPlugin.interface.common.actions import * + + +####################################################################### +def check_single(*args): + return all(not isinstance(x,list) for x in args) + +def check_item_single(*args): + return all(isinstance(x,list) and all(not isinstance(y,list) or len(y)<=1 for y in x) for x in args) + +def match_length(x,y): + if x is None: + return y is None or y==[] + if y is None: + return False + assert isinstance(x,list) and isinstance(y,list),"expecting lists but got something else" + return [len(z) for z in x]==[len(z) for z in y] + +def function(): + pass + +####################################################################### + + +def get_regions_while_you_still_can(view_information,name): + i = 1 + result=[] + while True: + x = view_information["get_regions"](name + str(i)) + if x: + result.append(x) + else: + break + i+=1 + return result + +def invert_guided(data,guide): + print("inside guide\n",data,guide) + output = [] + for x in guide: + output.append([]) + for i in range(0,len(x)): + output[-1].append(data[i].pop(0)) + if any(len(y)!=0 for y in data): + raise Exception("sublime day.does not months the number of alternatives") + return output + + +def get_location_text(location,code): + if location is None: + return None + if isinstance(location,list): + return [get_location_text(x,code) for x in location] + return code[location[0]:location[1]] + +def convert_single_to_multiple(state): + for k in ["result","origin","alternatives"]: + data = state[k] + if data is None: + data = [] + elif not isinstance(data,list): + data = [[data]] + else: + if any(isinstance(x,list) for x in data): + raise Exception("In single_mode "+k+" cannot be a nested list!") + else: + data = [data] + state[k] = data + +def convert_multiple_to_single(state): + for k in ["result","origin","alternatives"]: + data = state[k] + if k not in ["alternatives"]: + if data == []: + data = None + elif isinstance(data,list) and len(data)==1 and isinstance(data[0],list) and len(data[0])==1: + data = data[0][0] + else: + raise Exception("when converting from multiple mode In single_mode "+k+" cannot be a nested list!") + else: + if isinstance(data,list) and all(isinstance(x,list) and len(x)==1 for x in data): + data = make_flat(data) + else: + raise Exception(" when converting into single mode, each of the items in the alternatives must have length of one") + state[k] = data + + +def clear_state(state): + state["result"] = None + state["origin"] = None + state["alternatives"] = [] + state["change_count"] = -1 + +def retrieve_primitive(state,sublime_data): + output = deepcopy(state) + output["alternatives"] = invert_guided(sublime_data["alternatives"],state["alternatives"]) + for k in ["result","origin"]: + if not match_length(state[k],sublime_data[k]): + raise Exception("state "+k+" does not match the sublime data") + else: + output[k] = sublime_data[k] + return output + + +def retrieve_state(state,view_information,code): + if state["change_count"]>=view_information["change_count"]: + return + if state["change_count"]==-1: + state["change_count"]=view_information["change_count"] + return + # retrieve data from their region tracker + + try : + convert_single_to_multiple(state) + sublime_data = {x:get_regions_while_you_still_can(view_information,x) for x in ["result","origin","alternatives"]} + state = retrieve_primitive(state,sublime_data) + convert_multiple_to_single(state) + except: + clear_state(state) + raise + + + for k in ["result","origin","alternatives"]: + state[k+"_text"] = get_location_text(state[k],code) + + + + + + + + + + def update_state(state,view_information,code): + ''' + normally I would like these to be implemented by means of modification handlers + And an event listener transmitting every chains in the code. However it seems + but the provided event listener does not provide me with the location that was changed. + So in order to keep track over the location of important regions when the code changes, + The current solution is to outsource everything to the sublime add_regions/get_regions + functionality + ''' + state["result"] = view_information["get_regions"]("result") + state["alternatives"] = view_information["get_regions"]("alternatives") + state["origin"] = view_information["get_regions"]("origin") + state["initial_origin"] = view_information["get_regions"]("initial_origin") From 9e23efc17793a491193ec564de3d04f31ce3be94 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Mon, 23 Dec 2019 12:32:06 +0200 Subject: [PATCH 067/317] Bug fixing argument with the default barometer level index for the .Outer keyword --- queries/argument.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/queries/argument.py b/queries/argument.py index 6d787bd..d0090a3 100644 --- a/queries/argument.py +++ b/queries/argument.py @@ -270,7 +270,8 @@ def inverse_transformation(node): priority = {} print(query_description["level"],"the information in the query description") if query_description["level"]=="outer": - + if query_description["level_index"]==0: + query_description["level_index"] = 1 _,calling_parents = search_upwards_log(origin,targets=ast.stmt,log_targets=(ast.Call)) print("inside here",calling_parents) index = query_description["level_index"] @@ -337,7 +338,8 @@ def inverse_transformation(node): priority = {} print(query_description["level"],"the information in the query description") if query_description["level"]=="outer": - + if query_description["level_index"]==0: + query_description["level_index"] = 1 _,calling_parents = search_upwards_log(origin,targets=ast.stmt,log_targets=(ast.Call)) print("inside here",calling_parents) index = query_description["level_index"] From 09c1cbb4ac51e8321cf9d4f3bc0e60ae3cc129ed Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Tue, 24 Dec 2019 15:54:53 +0200 Subject: [PATCH 068/317] Fixed not taking for non-object when drawing the subscript build --- queries/big_roi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/big_roi.py b/queries/big_roi.py index 098b6ff..720042a 100644 --- a/queries/big_roi.py +++ b/queries/big_roi.py @@ -83,7 +83,7 @@ def standard(x): } temporary = possibilities[query_description["big_roi"]] - basic_information = make_information(temporary[2],atok = build[1]) + basic_information = make_information(temporary[2],atok = build[1] if build else None) if "sub_index" in query_description: index = query_description["sub_index"] def modified_information(x, information,index): From d5a382e935efc13aa6fb946cf5ed1f1e3b83bff1 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Tue, 24 Dec 2019 16:22:13 +0200 Subject: [PATCH 069/317] Massive Commit with many bugs For sure --- application/application.py | 85 ++++++++++++++++++----------- application/state_update.py | 72 +++++++++++++++--------- interface/common/actions.py | 53 ++++++++++-------- interface/common/utility.py | 25 +++++++++ queries/__init__.py | 4 ++ queries/abstract/insertion_query.py | 1 + queries/abstract/selection_query.py | 11 +++- queries/paste_back.py | 13 +++-- queries/swap_back.py | 25 ++++++--- 9 files changed, 192 insertions(+), 97 deletions(-) create mode 100644 interface/common/utility.py diff --git a/application/application.py b/application/application.py index 0c7f673..280f1d9 100644 --- a/application/application.py +++ b/application/application.py @@ -2,7 +2,7 @@ from PythonVoiceCodingPlugin.queries import * from PythonVoiceCodingPlugin.application.build_cache import BuildCache -from PythonVoiceCodingPlugin.application.state_update import retrieve_state +from PythonVoiceCodingPlugin.application.state_update import clear_state,retrieve_state,retrieve_text,get_location_text,update_changes from PythonVoiceCodingPlugin.interface.common.actions import * @@ -14,7 +14,7 @@ class Application(): def __init__(self,vid): self.history = [] - self.state = {"result": None,"origin": None,"alternatives": [],"change_count":-1} + self.state = {"result": None,"origin": None,"initial_origin":None,"alternatives": [],"change_count":-1} self.ui_controller = None self.vid = vid @@ -26,7 +26,11 @@ def get_application(vid): Application.create_application_for_view(vid) return Application.active_applications[vid] - + def update_text(self,code): + retrieve_text(self.state,code) + + def update_locations(self,locations_text): + update_changes(self.state,locations_text) def respond_to_query(self,interface,query_description,secondary=False): @@ -38,25 +42,31 @@ def respond_to_query(self,interface,query_description,secondary=False): code = view_information["code"] change_count = view_information["change_count"] latest_build = Application.build_cache.get_build(self.vid,change_count) - retrieve_state(self.state,view_information,code) + print("state\n\n",self.state,"\n\n") + try: + if not secondary: + print("and during inside here ",query_description) + need_update = retrieve_state(self.state,view_information,code) + print("\n needed update: ",need_update,"\n") + print(" after update this state ",self.state) + except: + clear_state(self.state) + if False: + raise # get the corresponding query and execute it s = get_query(query_description)(code,latest_build) secondary_query_description = get_secondary_query(query_description) if secondary_query_description: - backup=[deepcopy(self.state),deepcopy(self.global_state)] + self.backup=[deepcopy(self.state),deepcopy(self.global_state)] try: s(view_information,query_description,extra) except Exception as e: - print("\n\n finally\n\n") - if not s.exceptions_raised : - print(e) - - print("\n\n finally\n\n") - interface.clear_actions() - interface.push_action(PopUpErrorAction(str(e))) - return False + if not s.exceptions_raised: + print("inside exceptions raised",e) + raise + return False # check if there are exceptions @@ -65,35 +75,36 @@ def respond_to_query(self,interface,query_description,secondary=False): interface.push_action(PopUpErrorAction(str(s.exceptions_raised))) return False + # register build for later use b = s.get_the_latest_build() if b: Application.build_cache.register_build(self.vid,change_count,b) + if secondary: + self.state,self.global_state = self.backup + + if isinstance(s,SelectionQuery): result = s.result alternatives = s.alternatives - # self.state["result"] = None - # self.state["alternatives"] = [] + self.state["origin"] = view_information["selection"] + self.state["initial_origin"] = view_information["selection"] + names = ["result","origin", "alternatives","initial_origin"] + for name in names: + interface.push_action(ClearHighlightAction(name)) if result: self.state["result"] = result - self.state["alternatives"] = [] - self.state["alternatives_text"] = [] - if not isinstance(result,list): - self.state["result_text"] = code[result[0]:result[1]] - else: - self.state["result_text"] = [code[x[0]:x[1]] for x in result if x] + self.state["alternatives"] = alternatives + # self.state["alternatives_text"] = get_location_text(alternatives,code) + self.update_text(code) interface.push_action(SelectionAction(result)) self.history.append(("selection",view_information["change_count"],view_information["selection"],result)) - interface.push_action(ClearHighlightAction("alternatives")) - if alternatives: - self.state["alternatives"] = alternatives - if not isinstance(alternatives[0],list): - self.state["alternatives_text"] = [code[x[0]:x[1]] for x in alternatives] - else: - self.state["alternatives_text"] = [[code[x[0]:x[1]] for x in y] for y in alternatives if y] - interface.push_action(DisplayRegionsAction("alternatives",alternatives,"Alternatives:\n")) - interface.push_action(HighlightCleverAction(alternatives,"alternatives",result)) + + + # if alternatives: + # interface.push_action(DisplayRegionsAction("alternatives",alternatives,"Alternatives:\n")) + elif isinstance(s,InsertionQuery): output = s.writing_locations_text @@ -102,6 +113,8 @@ def respond_to_query(self,interface,query_description,secondary=False): for location, text in output: interface.push_action(ReplaceAction(location,text)) self.history.append(("insert")) + self.update_locations(output) + if selections: interface.push_action(SelectionAction(selections)) @@ -113,6 +126,7 @@ def respond_to_query(self,interface,query_description,secondary=False): if result: for location in writing_positions: interface.push_action(ReplaceAction(location,result)) + self.update_locations([(x,result) for x in writing_positions]) if items: print(s.label,"labeling\n") interface.push_action(DisplayNiceAction(s.label,items,True)) @@ -122,11 +136,16 @@ def respond_to_query(self,interface,query_description,secondary=False): if selections: interface.push_action(SelectionAction(selections)) + interface.push_action(HighlightCleverAction(self.state["alternatives"],"alternatives",self.state["result"],colorize = True)) + for name in ["result","origin", "initial_origin"]: + interface.push_action(HighlightCleverAction(self.state[name],name)) + + if secondary_query_description: interface.push_action(ClearHighlightAction("alternatives")) secondary_success = self.respond_to_query(interface,secondary_query_description,secondary=True) if not secondary_success: - self.state,self.global_state = backup + self.state,self.global_state = self.backup return False return True @@ -134,8 +153,12 @@ def respond_to_query(self,interface,query_description,secondary=False): def respond_to_event(self,interface,event_description): event = event_description["event"] + view_information = interface.get_view_information() if event=="update_change_count": + if self.state["change_count"] != event_description["change_count"]: + retrieve_text(self.state,view_information["code"]) self.state["change_count"] = event_description["change_count"] + diff --git a/application/state_update.py b/application/state_update.py index 4c42a41..51815d7 100644 --- a/application/state_update.py +++ b/application/state_update.py @@ -59,7 +59,7 @@ def get_location_text(location,code): return code[location[0]:location[1]] def convert_single_to_multiple(state): - for k in ["result","origin","alternatives"]: + for k in ["result","origin","initial_origin","alternatives"]: data = state[k] if data is None: data = [] @@ -73,17 +73,20 @@ def convert_single_to_multiple(state): state[k] = data def convert_multiple_to_single(state): - for k in ["result","origin","alternatives"]: + print(" inside multiple single ",state) + for k in ["result","origin","initial_origin","alternatives"]: data = state[k] if k not in ["alternatives"]: if data == []: data = None elif isinstance(data,list) and len(data)==1 and isinstance(data[0],list) and len(data[0])==1: + print(" inside this case",k,data) data = data[0][0] + print(" outside this case",k,data) else: raise Exception("when converting from multiple mode In single_mode "+k+" cannot be a nested list!") else: - if isinstance(data,list) and all(isinstance(x,list) and len(x)==1 for x in data): + if isinstance(data,list) and len(data)==1: data = make_flat(data) else: raise Exception(" when converting into single mode, each of the items in the alternatives must have length of one") @@ -93,59 +96,78 @@ def convert_multiple_to_single(state): def clear_state(state): state["result"] = None state["origin"] = None + state["initial_origin"] = None state["alternatives"] = [] state["change_count"] = -1 def retrieve_primitive(state,sublime_data): output = deepcopy(state) output["alternatives"] = invert_guided(sublime_data["alternatives"],state["alternatives"]) - for k in ["result","origin"]: + for k in ["result","origin","initial_origin"]: if not match_length(state[k],sublime_data[k]): raise Exception("state "+k+" does not match the sublime data") else: output[k] = sublime_data[k] - return output + for k in ["result","origin","initial_origin","alternatives"]: + state[k] = output[k] + return state + +def retrieve_text(state,code): + for k in ["result","origin","initial_origin","alternatives"]: + state[k+"_text"] = get_location_text(state[k],code) def retrieve_state(state,view_information,code): + ''' + normally I would like these to be implemented by means of modification handlers + And an event listener transmitting every chains in the code. However it seems + but the provided event listener does not provide me with the location that was changed. + So in order to keep track over the location of important regions when the code changes, + The current solution is to outsource everything to the sublime add_regions/get_regions + functionality + ''' + if state["change_count"]>=view_information["change_count"]: - return + return False if state["change_count"]==-1: state["change_count"]=view_information["change_count"] - return - # retrieve data from their region tracker + return False try : convert_single_to_multiple(state) - sublime_data = {x:get_regions_while_you_still_can(view_information,x) for x in ["result","origin","alternatives"]} + sublime_data = {x:get_regions_while_you_still_can(view_information,x) + for x in ["result","origin","alternatives","initial_origin"]} + print("\nsublime date at ease ",sublime_data,"\n") + state = retrieve_primitive(state,sublime_data) convert_multiple_to_single(state) + print(" after conversion ",state,"\n") except: clear_state(state) raise + retrieve_text(state,code) + return True - for k in ["result","origin","alternatives"]: - state[k+"_text"] = get_location_text(state[k],code) - +def update_changes(state,locations_text): + def forward(x,m): + if x is None: + return x + if isinstance(x,list): + return [forward(y,m) for y in x] + return m.forward(x) + + m = ModificationHandler() + for location,t in locations_text: + m.modify_from(0, location, t) + + for k in ["result","origin","initial_origin","alternatives"]: + state[k] = forward(state[k],m) - def update_state(state,view_information,code): - ''' - normally I would like these to be implemented by means of modification handlers - And an event listener transmitting every chains in the code. However it seems - but the provided event listener does not provide me with the location that was changed. - So in order to keep track over the location of important regions when the code changes, - The current solution is to outsource everything to the sublime add_regions/get_regions - functionality - ''' - state["result"] = view_information["get_regions"]("result") - state["alternatives"] = view_information["get_regions"]("alternatives") - state["origin"] = view_information["get_regions"]("origin") - state["initial_origin"] = view_information["get_regions"]("initial_origin") diff --git a/interface/common/actions.py b/interface/common/actions.py index d730b88..9995cc2 100644 --- a/interface/common/actions.py +++ b/interface/common/actions.py @@ -1,5 +1,7 @@ import html +from PythonVoiceCodingPlugin.interface.common.utility import make_region,make_sequence,all_or_nothing + class InterfaceAction(): """docstring for InterfaceAction""" def __init__(self): @@ -54,6 +56,7 @@ def __init__(self,name): def execute(self,view,**kwargs): index = 0 while True: + print(self.data["name"],index," attempted deletion ") index+=1 r = view.get_regions(self.data["name"]+str(index)) if r: @@ -92,8 +95,8 @@ def execute(self,view,sublime,**kwargs): class HighlightCleverAction(InterfaceAction): """docstring for HighlightAction(InterfaceAction""" - def __init__(self, region,name, result): - self.data = {"region":region, "name":name , "result":result} + def __init__(self, region,name, avoid = [],colorize = False): + self.data = {"region":region, "name":name , "avoid":avoid,"colorize":colorize} def execute(self,view,sublime,**kwargs): # these are the standard color maps for highlighting using the region-ish api @@ -115,33 +118,35 @@ def execute(self,view,sublime,**kwargs): color_order = ["red","blue","green","yellow","orange"] # transform the region variable into at list of sublime regions + single_mode = False region = self.data["region"] if not isinstance(region ,list): - region = [region] - region = [ sublime.Region(x[0],x[1]) for x in region if x] + region = [[region]] if region else [[]] + elif isinstance(region,list): + assert all_or_nothing(region,isinstance,list)," singular regions and sequences of regions are mixed" + if all(not isinstance(x,list) for x in region): + region = [[x] for x in region] + single_mode = True + region = make_region(region) # transform the result into a sublime region - result = self.data["result"] - result = sublime.Region(result[0],result[1]) - - for i,(r,c) in enumerate(zip(region,color_order)): + avoid = self.data["avoid"] + avoid = make_region(avoid) + avoid_sequence = make_sequence(avoid) + overlapping = make_sequence(region) + make_sequence(avoid) + for i,(br,c) in enumerate(zip(region,color_order)): use_reinforced = False - # we use reinforced color if the region is contained within another - for x,y in zip(region,color_order): - if x.contains(r) and x is not r: - use_reinforced = True - - # to avoid foreground background problems if the region contains the selection it will not be reinforced - if r.contains(result): - use_reinforced = False - # however if the region is contained within the selection it must be reinforced - elif result.contains(r): - use_reinforced = True - if r.b-r.a==1: - use_reinforced = True - # we add the region alongside with an index - view.add_regions(self.data["name"]+str(i+1), [r], reinforced_color[c] if use_reinforced else standard_color[c], - "circle") + for r in br: + use_reinforced = any( + (x.contains(r) and x is not r) or (r.contains(x) and x in avoid_sequence) + for x in overlapping + ) or r.b-r.a==1 or use_reinforced + if self.data["colorize"] and i<5: + view.add_regions(self.data["name"]+str(i+1), br, + reinforced_color[c] if use_reinforced and single_mode else standard_color[c],"circle") + else: + view.add_regions(self.data["name"]+str(i+1),br) + diff --git a/interface/common/utility.py b/interface/common/utility.py new file mode 100644 index 0000000..a8add4f --- /dev/null +++ b/interface/common/utility.py @@ -0,0 +1,25 @@ +import sublime +from itertools import chain + +def make_region(r): + if isinstance(r,tuple): + return sublime.Region(r[0],r[1]) + elif isinstance(r,list): + return [make_region(x) for x in r if x] + else: + raise Exception("the maker region function was given an incorrect argument"+str(r)) + +def all_or_nothing(r,check,*arguments): + return all(check(x,*arguments) for x in r) or not any(check(x,*arguments) for x in r) + + +def make_sequence(r): + if not isinstance(r ,list): + return [r] + elif isinstance(r,list): + try : + return list(chain.from_iterable(r)) + except : + print("Gold Inside Here") + return r + diff --git a/queries/__init__.py b/queries/__init__.py index 8d2e1f2..fd0111d 100644 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -59,6 +59,10 @@ def get_secondary_query(query_description): "command":"delete_alternatives", "format":1, "color":0, + }, + "swap":{ + "command":"swap_back", + "format":1, } diff --git a/queries/abstract/insertion_query.py b/queries/abstract/insertion_query.py index 9fb7dff..dbb388f 100644 --- a/queries/abstract/insertion_query.py +++ b/queries/abstract/insertion_query.py @@ -53,6 +53,7 @@ def __call__(self,view_information,query_description,extra = {}): if self.select_insertion: m = ModificationHandler() for location,t in self.writing_locations_text: + print(" inside the loop ", location,t,"\n") m.modify_from(0, location, t) self.optional_selection = [m.forward(x[0]) for x in self.writing_locations_text] return self.writing_locations_text,self.optional_selection diff --git a/queries/abstract/selection_query.py b/queries/abstract/selection_query.py index 7877a3b..d1481e7 100644 --- a/queries/abstract/selection_query.py +++ b/queries/abstract/selection_query.py @@ -35,11 +35,16 @@ def get_alternatives(self): def get_the_latest_build(self): return self.general_build - def _backward_result(self,result,alternatives,build): + def _backward_result(self,result,alternatives,build,individually = False): + print("result",result) + print("alternatives",alternatives) if build and build[0]: m = build[2] - atok = build[1] - result = m.backward(get_source_region(atok, result)) if result else None + atok = build[1] + if individually: + result = [m.backward(get_source_region(atok, x)) if x else None for x in result] + else: + result = m.backward(get_source_region(atok, result)) if result else None #self._get_selection(view_information,extra) alternatives = [m.backward(get_source_region(atok,x)) for x in alternatives] return result, alternatives diff --git a/queries/paste_back.py b/queries/paste_back.py index b7c821d..a58345e 100644 --- a/queries/paste_back.py +++ b/queries/paste_back.py @@ -7,16 +7,19 @@ class PasteBack(InsertionQuery): def handle_single(self,view_information,query_description,extra = {}): state = extra["state"] + print(" inside query",state,"\n") + print("origin\n\n",state["origin"],"\n\n") history = extra["history"] index = len(history) - while history[index-1][0]=="selection" and index>=1 and history[index-1][1] == view_information["change_count"]: - index -=1 - if index==len(history) or history[index][1] != view_information["change_count"]: - return [] + # while history[index-1][0]=="selection" and index>=1 and history[index-1][1] == view_information["change_count"]: + # index -=1 + # if index==len(history) or history[index][1] != view_information["change_count"]: + # return [] candidates = result_alternatives_sequence(state,text = True) candidates_location = result_alternatives_sequence(state,location = True) if query_description["format"]==1: - selection = history[index][2] + # selection = history[index][2] + selection = state["origin"] selection = selection if isinstance(selection,list) else [selection] if query_description["format"]==2: selection = {candidates_location[query_description["color"+i]] diff --git a/queries/swap_back.py b/queries/swap_back.py index 8c61179..ad1a281 100644 --- a/queries/swap_back.py +++ b/queries/swap_back.py @@ -10,17 +10,24 @@ class SwapBack(InsertionQuery): def handle_single(self,view_information,query_description,extra = {}): state = extra["state"] - history = extra["history"] - index = len(history) - while history[index-1][0]=="selection" and index>=1 and history[index-1][1] == view_information["change_count"]: - index -=1 - if index==len(history) or history[index][1] != view_information["change_count"]: - return [] - + # history = extra["history"] + # index = len(history) + # while history[index-1][0]=="selection" and index>=1 and history[index-1][1] == view_information["change_count"]: + # index -=1 + # if index==len(history) or history[index][1] != view_information["change_count"]: + # return [] candidates = result_alternatives_sequence(state,location = True,text = True) if query_description["format"]==1: - selection = history[index][2] - selection = selection if isinstance(selection,list) else [selection] + # selection = history[index][2] + # selection = state["origin"] + # selection = selection if isinstance(selection,list) else [selection] + for location,t in candidates: + if not overlap_regions(location,state["origin"]): + decision = (location,t) + break + else: + raise Exception("Swamp cannot swap regions of overlap!!!") + location_text = [(state["origin"],state["origin_text"]),decision] if query_description["format"]==2: location_text = [candidates[query_description["color"+i]] for i in ["","2","3","4"] if "color"+i in query_description] From 4cee6b97a70af8401ba687c1c0f6968e72984c33 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Tue, 24 Dec 2019 16:42:59 +0200 Subject: [PATCH 070/317] Bug fix swam so that they work properly when used of swap back --- application/application.py | 5 ++++- queries/swap_back.py | 17 +++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/application/application.py b/application/application.py index 280f1d9..790ac4e 100644 --- a/application/application.py +++ b/application/application.py @@ -34,7 +34,10 @@ def update_locations(self,locations_text): def respond_to_query(self,interface,query_description,secondary=False): - extra = {"state":self.state,"global_state":Application.global_state,"history":self.history} + extra = { + "state":self.state,"global_state":Application.global_state, + "history":self.history,"secondary":secondary, + } view_information = interface.get_view_information() ui_information = interface.get_ui_information() diff --git a/queries/swap_back.py b/queries/swap_back.py index ad1a281..4652248 100644 --- a/queries/swap_back.py +++ b/queries/swap_back.py @@ -7,6 +7,7 @@ def overlap_regions(x,y): @no_build_attempt class SwapBack(InsertionQuery): select_insertion = True + multiple_in = True def handle_single(self,view_information,query_description,extra = {}): state = extra["state"] @@ -18,15 +19,15 @@ def handle_single(self,view_information,query_description,extra = {}): # return [] candidates = result_alternatives_sequence(state,location = True,text = True) if query_description["format"]==1: - # selection = history[index][2] - # selection = state["origin"] - # selection = selection if isinstance(selection,list) else [selection] - for location,t in candidates: - if not overlap_regions(location,state["origin"]): - decision = (location,t) - break + if extra["secondary"]: + for location,t in candidates: + if not overlap_regions(location,state["origin"]): + decision = (location,t) + break + else: + raise Exception("Swamp cannot swap regions of overlap!!!") else: - raise Exception("Swamp cannot swap regions of overlap!!!") + decision = candidates[query_description["color"]] location_text = [(state["origin"],state["origin_text"]),decision] if query_description["format"]==2: location_text = [candidates[query_description["color"+i]] From 111c2734131655194cbe319ac84f99dbb7b954ce Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Tue, 24 Dec 2019 17:31:39 +0200 Subject: [PATCH 071/317] Item selection adjusted so was to be able to all oi up --- queries/strategies/item_selection.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/queries/strategies/item_selection.py b/queries/strategies/item_selection.py index bf22dee..6d7ed66 100644 --- a/queries/strategies/item_selection.py +++ b/queries/strategies/item_selection.py @@ -11,10 +11,12 @@ def decode_item_selection(items,query_description,mode,name,decrement=True): print([items[x] for x in indices]) return [items[x] for x in indices] elif mode == "range": - if len(indices)!= 1: + if len(indices) == 2: return items[indices[0]:indices[1] + 1] - else: + elif len(indices) == 1: return items[indices[0]:] + else: + return items From 53207c17d6abf045e3014febb51f3cd99b798844 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Tue, 24 Dec 2019 20:26:28 +0200 Subject: [PATCH 072/317] Go to for fixing the boards and alias nodes --- library/info.py | 82 ++++++++++++++++++++++++++++++++++++++++++++-- queries/big_roi.py | 2 +- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/library/info.py b/library/info.py index a79e8d7..405aa62 100644 --- a/library/info.py +++ b/library/info.py @@ -23,7 +23,7 @@ while trying to minimize exposure to the underlying syntax tree implementation. 3) checkers check if a node satisfies a property(say if its context is store) 4) validators confirm if other root satisfy some property with respect to a given node - +5) fixers (introduced in 0.1.0) responsible for fixing the first token/last token attributes of nodes ''' ################################################################ @@ -323,6 +323,16 @@ def get_class_name(root,atok): else: return None +################################################################ +# import staff +################################################################ + +def get_fixed_import(root,atok): + if not match_node(root,(ast.Import,ast.ImportFrom)): + return None + fix_import(root,atok) + return root + @@ -452,6 +462,8 @@ def get_sub_index(root,index): elif match_node(root,ast.DictComp): candidates = [[root.key,root.value]] + root.generators + + # in the following cases we Certs deeper in the tree if match_node(root,(ast.Index)): return get_sub_index(root.value,index) @@ -463,7 +475,15 @@ def get_sub_index(root,index): return get_sub_index(root.body,index) if match_node(root,(ast.Call)): return get_sub_index(root.func,index) - + if match_node(root,(ast.Import,ast.ImportFrom)): + candidates = root.names + if len(candidates)==1: + temporary = get_sub_index(candidates[0],index) + if temporary: + return temporary + + + if index is None: return candidates elif index Date: Wed, 25 Dec 2019 16:52:44 +0200 Subject: [PATCH 073/317] Bike figs regarding the passing of atok Information down the line Includes one bug fix and refactor --- queries/big_roi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/queries/big_roi.py b/queries/big_roi.py index e72b388..545f816 100644 --- a/queries/big_roi.py +++ b/queries/big_roi.py @@ -87,11 +87,11 @@ def standard(x): if "sub_index" in query_description: index = query_description["sub_index"] def modified_information(x, information,index): - data = basic_information(x) + data = information(x) return get_sub_index(data,index) - y = lambda x: temporary[2](x) - y.secondary = lambda x: modified_information(x,temporary[2],index-1) + y = lambda x: basic_information(x) + y.secondary = lambda x: modified_information(x,basic_information,index-1) return (temporary[0],temporary[1],y) else: return possibilities[query_description["big_roi"]][:2] + (basic_information,) From f9f33230038b556b75f2999de3455d3c9c618e6e Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Wed, 25 Dec 2019 17:23:39 +0200 Subject: [PATCH 074/317] There are still bugs but I commit nonetheless --- library/info.py | 74 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/library/info.py b/library/info.py index 405aa62..266bd8a 100644 --- a/library/info.py +++ b/library/info.py @@ -54,16 +54,17 @@ def identity(information, *arg,**kwargs): -def create_fake(root,text,start_position,node_type, **kwargs): - +def create_fake(root,text,start_position,node_type,real_tokens = None, **kwargs): + if real_tokens and not isinstance(real_tokens): + real_tokens = [real_tokens] fake_token = asttokens.Token(0,text,0,0,0, root.first_token.index,start_position,start_position + len(text)) fake_node = node_type(**kwargs) fake_node.parent = root.parent fake_node.parent_field = root.parent_field fake_node.parent_field_index = root.parent_field_index - fake_node.first_token = fake_token - fake_node.last_token = fake_token + fake_node.first_token = fake_token if not real_tokens else real_tokens[0] + fake_node.last_token = fake_token if not real_tokens else real_tokens[-1] fake_node.fake = True return fake_node @@ -368,7 +369,7 @@ def get_subparts_of_binary_operation(root): return left + right -def split_string(s :str ,even_letters = True): +def split_string(s :str ,even_letters = True,only_first = False): s = s.strip() y = urlparse(s) if not (y.scheme=="" and y.netloc==""): @@ -376,6 +377,8 @@ def split_string(s :str ,even_letters = True): first_attempt = [x for x in re.split("[., :/]",s) if not x.isspace()] if len(first_attempt) > 1: return first_attempt + if only_first: + return [s] second_attempt = [x for x in re.split("[_]",s) if not x.isspace()] if len(second_attempt) > 1: print(" from second attempt") @@ -420,6 +423,18 @@ def get_subparts_of_attribute(root): return get_subparts_of_attribute(root.value) + [fake_node] else: return [root.value,fake_node] + +def get_subparts_of_alias(root): + assert already_fixed(root)," I received an node that needs fixing " + names = get_fix_data(root)["name"] + print("names",names) + left_side =[create_fake(root,"",0,ast.Name,real_tokens=x,id=x.string,ctx=ast.Load()) for x in names] + if roots.asname: + x = root.last_token + right_side = create_fake(root,"",0,ast.Name,real_tokens=x,id=x.string,ctx=ast.Load()) + return [[left_side],right_side] + else: + return left_side @@ -479,6 +494,7 @@ def get_sub_index(root,index): candidates = root.names if len(candidates)==1: temporary = get_sub_index(candidates[0],index) + print(temporary) if temporary: return temporary @@ -574,6 +590,12 @@ def needs_fix(root): def already_fixed(root): return hasattr(root,"_has_been_fixed") +def store_fix_data(root,data): + root._fix_metadata = data + +def get_fix_data(root): + return getattr(root,"_fix_metadata",{}) + ################################################################ # some fixers concerning imports ################################################################ @@ -581,25 +603,39 @@ def already_fixed(root): def fix_import(root,atok): if already_fixed(root): - print("I escaped important fix ") return True + data = {} print("\n\n enduring fixing board statement\n") token = root.first_token - if match_node(root,ast.ImportFrom) and root.module is not None: - for s in split_string(root.module): - token = atok.find_token(token,tokenize.NAME,s) - print("matching name ",root.module," into ",token.string,"\n") + data["module"] = [] + if match_node(root,ast.ImportFrom): + if root.module is not None: + for s in split_string(root.module,only_first = True): + token = atok.find_token(token,tokenize.NAME,s) + data["module"].append(token) for name in root.names: - token = next_token(atok,token) - token = atok.find_token(token,tokenize.NAME,name.name) - print("matching name ",name.name," into ",token.string,"\n") - name.first_token = token - if name.asname: - token = next_token(atok,token) - token = atok.find_token(token,tokenize.NAME,name.asname) - name.last_token = token - mark_fixed(name) + if name.name=="*": + i = atok.find_token(root.first_token,tokenize.NAME,"import") + name.first_token = next_token(atok,i) + name.last_token = next_token(atok,i) + else: + stack = [] + local_data = {} + for s in split_string(name.name,only_first = True): + token = next_token(atok,token) + token = atok.find_token(token,tokenize.NAME,s) + stack.append(token) + local_data["elements"] = stack + name.first_token = token + print("matching name ",name.name," into ",token.string,"\n") + if name.asname: + token = next_token(atok,token) + token = atok.find_token(token,tokenize.NAME,name.asname) + name.last_token = token + mark_fixed(name) + store_fix_data(name,{"name":stack}) mark_fixed(root) + store_fix_data(root,data) return True def fix_alias(root,atok): From 9b1777ba61b791b68863c6d80536b99e650ee40d Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Wed, 25 Dec 2019 17:30:07 +0200 Subject: [PATCH 075/317] Previous bug fixed --- library/info.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/info.py b/library/info.py index 266bd8a..c2b2024 100644 --- a/library/info.py +++ b/library/info.py @@ -55,7 +55,7 @@ def identity(information, *arg,**kwargs): def create_fake(root,text,start_position,node_type,real_tokens = None, **kwargs): - if real_tokens and not isinstance(real_tokens): + if real_tokens and not isinstance(real_tokens,list): real_tokens = [real_tokens] fake_token = asttokens.Token(0,text,0,0,0, root.first_token.index,start_position,start_position + len(text)) @@ -429,7 +429,7 @@ def get_subparts_of_alias(root): names = get_fix_data(root)["name"] print("names",names) left_side =[create_fake(root,"",0,ast.Name,real_tokens=x,id=x.string,ctx=ast.Load()) for x in names] - if roots.asname: + if root.asname: x = root.last_token right_side = create_fake(root,"",0,ast.Name,real_tokens=x,id=x.string,ctx=ast.Load()) return [[left_side],right_side] @@ -476,6 +476,8 @@ def get_sub_index(root,index): candidates = [root.elt] + root.generators elif match_node(root,ast.DictComp): candidates = [[root.key,root.value]] + root.generators + elif match_node(root,ast.alias): + candidates = get_subparts_of_alias(root) From 6395befc535d94241f30d32b409758f93a3aec3a Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 26 Dec 2019 12:22:20 +0200 Subject: [PATCH 076/317] Update info.py --- library/info.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/info.py b/library/info.py index c2b2024..f542fb8 100644 --- a/library/info.py +++ b/library/info.py @@ -335,7 +335,14 @@ def get_fixed_import(root,atok): return root +def get_module(root,atok): + if not match_node(root,(ast.Import,ast.ImportFrom)): + return None + if not already_fixed(root,atok): + fix_import(root,atok) + return root + ################################################################ # sub indexing functions From cddbcb2b0f872d238b78e9a754a76ac2bba0ecd4 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 26 Dec 2019 12:49:22 +0200 Subject: [PATCH 077/317] Import stuff almost done though that are still bugs with adjectives --- library/info.py | 20 ++++++++++++++++---- queries/big_roi.py | 1 + 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/library/info.py b/library/info.py index f542fb8..927be3d 100644 --- a/library/info.py +++ b/library/info.py @@ -57,8 +57,9 @@ def identity(information, *arg,**kwargs): def create_fake(root,text,start_position,node_type,real_tokens = None, **kwargs): if real_tokens and not isinstance(real_tokens,list): real_tokens = [real_tokens] - fake_token = asttokens.Token(0,text,0,0,0, - root.first_token.index,start_position,start_position + len(text)) + if not real_tokens: + fake_token = asttokens.Token(0,text,0,0,0, + root.first_token.index,start_position,start_position + len(text)) fake_node = node_type(**kwargs) fake_node.parent = root.parent fake_node.parent_field = root.parent_field @@ -338,10 +339,21 @@ def get_fixed_import(root,atok): def get_module(root,atok): if not match_node(root,(ast.Import,ast.ImportFrom)): return None - if not already_fixed(root,atok): + if not already_fixed(root): fix_import(root,atok) + data = get_fix_data(root) + m = data["module"] + print("inside get a module date days ",m,"\n") + output = None + for t in m: + + if not output: + output = create_fake(root,"",None,ast.Name,real_tokens=[t,t],id=t.string,ctx=ast.Load()) + else: + output = create_fake(root,"",None,ast.Attribute,real_tokens=[m[0],t],value=output,attr=t.string,ctx=ast.Load()) - return root + print("exiting get a module function ",output,"\n") + return output ################################################################ diff --git a/queries/big_roi.py b/queries/big_roi.py index 545f816..afb1943 100644 --- a/queries/big_roi.py +++ b/queries/big_roi.py @@ -72,6 +72,7 @@ def standard(x): "definition name":((ast.FunctionDef),(),get_definition_name), "class name":((ast.ClassDef),(),get_class_name), "import statement":((ast.Import,ast.ImportFrom),(),get_fixed_import), + "import module":((ast.Import,ast.ImportFrom),(),get_module), "lambda":((ast.Lambda),(),standard), "lambda body":((ast.Lambda),(),get_body), "if body":((ast.If, ast.For,ast.comprehension),(),get_body), From e6167e3fcd0975545aee6d5f59a400ab96631953 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 26 Dec 2019 20:40:09 +0200 Subject: [PATCH 078/317] Definition barometers are steel buggy --- interface/common/actions.py | 2 +- library/info.py | 92 ++++++++++++++++++++++++++++++++++++- queries/big_roi.py | 5 +- 3 files changed, 96 insertions(+), 3 deletions(-) diff --git a/interface/common/actions.py b/interface/common/actions.py index 9995cc2..0242da4 100644 --- a/interface/common/actions.py +++ b/interface/common/actions.py @@ -230,7 +230,7 @@ def on_hide(): view.show_popup(final_text,max_width=1024, max_height=10000, flags= sublime.HIDE_ON_MOUSE_MOVE_AWAY,on_hide = on_hide) print(view.is_popup_visible()) - +# hello world # style=\"background-color:#000080\" diff --git a/library/info.py b/library/info.py index 927be3d..d9c1839 100644 --- a/library/info.py +++ b/library/info.py @@ -306,7 +306,6 @@ def get_definition_name(root,atok): return None def get_definition_parameter_name(root,atok): - print(ast.dump(root),dir(root)) if not match_node(root,ast.arg): return None x = root.first_token @@ -325,6 +324,19 @@ def get_class_name(root,atok): else: return None + +def get_arg(root, atok): + print("inside get argument ",ast.dump(root) if isinstance(root,ast.AST) else "") + if not match_node(root,ast.arg): + if match_node(root,ast.FunctionDef): + fix_definition(root,atok) + return None + if match_parent(root,ast.FunctionDef): + fix_definition(root.parent,atok) + elif match_parent(root.parent,ast.FunctionDef): + fix_definition(root.parent.parent,atok) + return root + ################################################################ # import staff ################################################################ @@ -666,6 +678,84 @@ def fix_alias(root,atok): mark_fixed(name) return True + +def fix_argument(root,atok,token): + print("enduring fix argument ",root,root.parent_field,atok,[token]) + if already_fixed(root): + return token + mark_fixed(root) + root.first_token = token + if root.annotation: + root.last_token = root.annotation.last_token + return root.annotation.last_token + else: + root.last_token = token + return token + + +def fix_definition(root,atok): + print("enduring fix definition ",root,atok) + if already_fixed(root): + return True + + # there is a discrepancy between the 3.3 and 3.4 versions of the abstract syntax tree + # in 3.3 the variable arguments and the variable keyboard arguments are stored in a little bit differently + x = root.args + if x.vararg and not already_fixed(x.vararg): + print(" I am in the process of fixing the viable arguments ",x.vararg,x.varargannotation) + x.vararg = ast.arg(arg=x.vararg,annotation=x.varargannotation) + x.vararg.parent = x + x.vararg.parent_field = "vararg" + x.vararg.parent_field_index = None + mark_fixed(x.vararg) + if x.kwarg and not already_fixed(x.kwarg): + print(" I am in the process of fixing the keyword viable arguments ",x.kwarg,x.kwargannotation) + x.kwarg = ast.arg(arg=x.kwarg,annotation=x.kwargannotation) + x.kwarg.parent = x + x.kwarg.parent_field = "kwarg" + x.kwarg.parent_field_index = None + mark_fixed(x.kwarg) + + # I think the following might be done easier with more iter tools library + token = root.first_token + token = atok.find_token(token,tokenize.NAME,"def") + token = next_token(atok,token ) + print("token ",[token]) + for i,j in zip(x.args,[None]*(len(x.args)-len(x.defaults))+x.defaults): + token = next_token(atok,token) + token = atok.find_token(token,tokenize.NAME,i.arg) + fix_argument(i,atok,token) + if j: + token = j.last_token + print("token ",[token]) + if x.vararg: + i=x.vararg + print("viable argument problem ") + token = next_token(atok,token) + token = atok.find_token(token,tokenize.NAME,i.arg) + fix_argument(i,atok,token) + + for i,j in zip(x.kwonlyargs,x.kw_defaults): + print("you word only problem") + token = next_token(atok,token) + token = atok.find_token(token,tokenize.NAME,i.arg) + fix_argument(i,atok,token) + if j: + token = j.last_token + if x.kwarg: + i=x.kwarg + print("keyword viable arguments problem",[token],"\n\n") + token = next_token(atok,token) + print("before searching for the argument that Tolkien was \n",[token],"\n") + token = atok.find_token(token,tokenize.NAME,i.arg) + print("After that Tolkien was \n",[token],"\n") + fix_argument(i,atok,token) + + + mark_fixed(root) + return True + + def dummy(): pass diff --git a/queries/big_roi.py b/queries/big_roi.py index afb1943..4d6d1a0 100644 --- a/queries/big_roi.py +++ b/queries/big_roi.py @@ -70,13 +70,16 @@ def standard(x): "iterable":((ast.For,ast.comprehension),(),get_iterable), "iterator":((ast.For,ast.comprehension),(),get_iterator), "definition name":((ast.FunctionDef),(),get_definition_name), + "definition parameter": ((ast.FunctionDef, ast.arg),(),get_arg), + # "parameter annotation" : "parameter annotation", + # "default value": "default value", "class name":((ast.ClassDef),(),get_class_name), "import statement":((ast.Import,ast.ImportFrom),(),get_fixed_import), "import module":((ast.Import,ast.ImportFrom),(),get_module), "lambda":((ast.Lambda),(),standard), "lambda body":((ast.Lambda),(),get_body), "if body":((ast.If, ast.For,ast.comprehension),(),get_body), - "definition parameter": ((ast.arg),(),get_definition_parameter_name), + # "definition parameter": ((ast.arg),(),get_definition_parameter_name), "decorator":((ast.AST),(),identity(is_decorator)), "base class":((ast.AST),(),identity(is_base)), From 75ff2b3872b4e7ea469128ab91313447d4376339 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 26 Dec 2019 22:08:26 +0200 Subject: [PATCH 079/317] Bug fix, return statements Were not taken into account --- library/info.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/info.py b/library/info.py index d9c1839..053eb27 100644 --- a/library/info.py +++ b/library/info.py @@ -69,7 +69,8 @@ def create_fake(root,text,start_position,node_type,real_tokens = None, **kwargs) fake_node.fake = True return fake_node - +def empty_fake(root,star_position): + return create_fake(root,"",star_position,ast.Name,id = "",ctx = ast.Load()) ################################################################################################ ################################################################################################ # @@ -169,7 +170,7 @@ def get_comprehension_condition(root): def get_return_value(root): return ( - root.value if match_node(root,(ast.Return, ast.Yield,ast.YieldFrom )) else None + (root.value if root.value else empty_fake(root,root.last_token.endpos+1)) if match_node(root,(ast.Return, ast.Yield,ast.YieldFrom )) else None ) # need to revisit From 1168349e9cfb6691dcc13df481295edab19a855e Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 26 Dec 2019 22:26:12 +0200 Subject: [PATCH 080/317] Bug fixing arguments when statement is a compound and datasets contains other smaller statements on children, causing arguments to "leak" from them --- queries/argument.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/queries/argument.py b/queries/argument.py index d0090a3..2083462 100644 --- a/queries/argument.py +++ b/queries/argument.py @@ -34,7 +34,13 @@ def get_information(self,query_description): else: i = query_description["sub_index"] - 1 return lambda x:get_sub_index(get_caller(x),i) - + + def get_statement(self,origin): + candidate_statement = search_upwards(origin,ast.stmt) + big = (ast.If,ast.While,ast.For,ast.FunctionDef,ast.With,ast.ClassDef,ast.Try) + if match_node(candidate_statement,big): + candidate_statement = search_upwards_for_parent(origin,ast.stmt) + return candidate_statement def process_line(self,q, root ,atok, origin = None, select_node = None,tiebreaker = lambda x: x, line = None, transformation = None,inverse_transformation = None, priority = {}, @@ -153,7 +159,7 @@ def case_one(self,view_information,query_description, extra = {}): root,atok,m,r = build selection = m.forward(selection) origin = nearest_node_from_offset(root,atok, selection[0]) if selection[0]==selection[1] else node_from_range(root,atok, selection) - statement_node = search_upwards(origin,ast.stmt) + statement_node = self.get_statement(origin) result, alternatives = self.process_line( q = query_description, root = statement_node, @@ -194,7 +200,7 @@ def case_two(self,view_information,query_description, extra = {}): selection = m.forward(selection) origin = nearest_node_from_offset(root,atok, selection[0]) if selection[0]==selection[1] else node_from_range(root,atok, selection) - statement_node = search_upwards(origin,ast.stmt) + statement_node = self.get_statement(origin) # @@ -236,7 +242,7 @@ def case_three(self,view_information,query_description, extra = {}): root,atok,m,r = build selection = m.forward(selection) origin = nearest_node_from_offset(root,atok, selection[0]) if selection[0]==selection[1] else node_from_range(root,atok, selection) - statement_node = search_upwards(origin,ast.stmt) + statement_node = self.get_statement(origin) ############################################################### # transformations @@ -309,7 +315,7 @@ def case_four(self,view_information,query_description, extra = {}): root,atok,m,r = build selection = m.forward(selection) origin = nearest_node_from_offset(root,atok, selection[0]) if selection[0]==selection[1] else node_from_range(root,atok, selection) - statement_node = search_upwards(origin,ast.stmt) + statement_node = self.get_statement(origin) ############################################################### # transformationszooming From 038446f1f7d8510d96d7c26b9b2ffe2389f4cbb5 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Fri, 27 Dec 2019 01:01:32 +0200 Subject: [PATCH 081/317] get the message function was missing the return keyboard so it returned nothing --- library/info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/info.py b/library/info.py index 053eb27..c0e1142 100644 --- a/library/info.py +++ b/library/info.py @@ -220,7 +220,7 @@ def get_item_as(root): ) def get_message(root): - root.msg if match_node(root,(ast.Assert)) else None + return root.msg if match_node(root,(ast.Assert)) else None From 1d6a6275d10eddece92f88a1e81a33d8741a4438 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Fri, 27 Dec 2019 01:01:56 +0200 Subject: [PATCH 082/317] Support for selecting ranges of subparts. --- queries/select_part.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/queries/select_part.py b/queries/select_part.py index 0e458c6..32258d1 100644 --- a/queries/select_part.py +++ b/queries/select_part.py @@ -35,9 +35,15 @@ def handle_single(self,view_information,query_description,extra = {}): second_origin = get_sub_index(origin,translate_adjective[query_description["nth"]]-1) print(" just before the end",second_origin) - if query_description["format"]!=3: + if query_description["format"]==1: result = get_sub_index(second_origin,query_description["sub_index"]-1) alternatives = [] + elif query_description["format"]==2: + result = [ + get_sub_index(second_origin,query_description["sub_index"]-1), + get_sub_index(second_origin,query_description.get("sub_index2",0)-1) + ] + alternatives=[] else: intermediate = get_sub_index(second_origin,None) candidates = [get_sub_index(x,query_description["sub_index"]-1) for x in intermediate] From bff0aa868a3af84fd8f952edf7a4426d0fb43592 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Fri, 27 Dec 2019 01:16:57 +0200 Subject: [PATCH 083/317] Support for selecting with ( and sub indexing ) --- library/info.py | 10 ++++++++-- queries/big_roi.py | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/library/info.py b/library/info.py index c0e1142..cec6e52 100644 --- a/library/info.py +++ b/library/info.py @@ -200,6 +200,10 @@ def get_iterator(root): root.target if match_node(root,(ast.For,ast.comprehension)) else None ) +def get_with_items(root): + return ( + root.items if match_node(root,ast.With) else None + ) # need to revisit this def get_body(root): @@ -510,8 +514,10 @@ def get_sub_index(root,index): candidates = [[root.key,root.value]] + root.generators elif match_node(root,ast.alias): candidates = get_subparts_of_alias(root) - - + elif match_node(root,ast.withitem): + candidates = [root.context_expr,root.optional_vars] if root.optional_vars else [root.context_expr] + elif match_node(root,ast.With): + candidates = root.items # in the following cases we Certs deeper in the tree if match_node(root,(ast.Index)): diff --git a/queries/big_roi.py b/queries/big_roi.py index 4d6d1a0..927dba0 100644 --- a/queries/big_roi.py +++ b/queries/big_roi.py @@ -57,6 +57,7 @@ def standard(x): "if condition":(ast.If,(),get_pure_if_condition), "else if condition":(ast.If,(),get_elif_condition), "while condition":(ast.While,(),get_condition), + "with clause":(ast.With,(),get_with_items), "if expression":(ast.IfExp,(),standard), "if expression condition":(ast.IfExp,(),get_condition), "if expression body":(ast.IfExp,(),get_body), From 72d99ae410f186804510813cd341c23fbe50ab89 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Fri, 27 Dec 2019 01:22:05 +0200 Subject: [PATCH 084/317] Support for assertion condition was missing. --- library/info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/info.py b/library/info.py index cec6e52..a3f8155 100644 --- a/library/info.py +++ b/library/info.py @@ -146,7 +146,7 @@ def get_body(root): ############################### def get_condition(root): return ( - root.test if match_node(root,(ast.If,ast.IfExp,ast.While)) else None + root.test if match_node(root,(ast.If,ast.IfExp,ast.While,ast.Assert)) else None ) def get_pure_if_condition(root): From 75c20ea0446add8370547ae9b077fbdb71f88f7d Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 28 Dec 2019 00:52:38 +0200 Subject: [PATCH 085/317] Support for grabbing stuff from exceptions --- library/info.py | 57 +++++++++++++++++++++++++++++++++++++++++++++- queries/big_roi.py | 3 +++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/library/info.py b/library/info.py index a3f8155..71873fd 100644 --- a/library/info.py +++ b/library/info.py @@ -223,9 +223,31 @@ def get_item_as(root): [x.optional_vars for x in root.items] if match_node(root,(ast.With)) else None ) + def get_message(root): return root.msg if match_node(root,(ast.Assert)) else None - + +def get_exception(root,atok): + if not match_node(root,ast.ExceptHandler): + return None + fix_exception_handler(root,atok) + return root.type if match_node(root,ast.ExceptHandler) else None + +def get_exception_name(root,atok): + if not match_node(root,ast.ExceptHandler): + return None + fix_exception_handler(root,atok) + data = get_fix_data(root) + return data.get("node") + +def get_exception_handler(root,atok): + if not match_node(root,ast.ExceptHandler): + return None + fix_exception_handler(root,atok) + output = [x for x in [root.type,get_fix_data(root).get("node")] if x] + print("Output\n\n",output,"\n") + # print(ast.dump()) + return output if output else empty_fake(root,root.first_token.endpos) ################################################################ @@ -518,6 +540,12 @@ def get_sub_index(root,index): candidates = [root.context_expr,root.optional_vars] if root.optional_vars else [root.context_expr] elif match_node(root,ast.With): candidates = root.items + elif match_node(root,(ast.ExceptHandler)): + if root.name is not None and get_fix_data(root).get("node"): + candidates = [root.type,get_fix_data(root).get("node")] + else: + candidates = [root.type] + # in the following cases we Certs deeper in the tree if match_node(root,(ast.Index)): @@ -530,6 +558,9 @@ def get_sub_index(root,index): return get_sub_index(root.body,index) if match_node(root,(ast.Call)): return get_sub_index(root.func,index) + if match_node(root,(ast.ExceptHandler)): + if root.name is None: + return get_sub_index(root.type,index) if match_node(root,(ast.Import,ast.ImportFrom)): candidates = root.names if len(candidates)==1: @@ -762,6 +793,30 @@ def fix_definition(root,atok): mark_fixed(root) return True +def fix_exception_handler(root,atok): + if already_fixed(root): + return True + if not root.type or not root.name: + mark_fixed(root) + return True + token = root.type.last_token + print(" inside fixing before finding the token",[token,root.type.first_token]) + token = atok.find_token(next_token(atok,token),tokenize.NAME, root.name) + f = root.type.first_token + f = atok.find_token(previous_token(atok,f),tokenize.NAME, "except",reverse = True) + + + store_fix_data(root,{"node":create_fake(root,"",None,ast.Name, token,id = token.string,ctx = ast.Load())}) + + root.first_token=root.type.first_token + root.last_token = token + mark_fixed(root) + return True + + + + + def dummy(): pass diff --git a/queries/big_roi.py b/queries/big_roi.py index 927dba0..2814967 100644 --- a/queries/big_roi.py +++ b/queries/big_roi.py @@ -58,6 +58,9 @@ def standard(x): "else if condition":(ast.If,(),get_elif_condition), "while condition":(ast.While,(),get_condition), "with clause":(ast.With,(),get_with_items), + "exception":(ast.ExceptHandler,(),get_exception), + "exception name":(ast.ExceptHandler,(),get_exception_name), + "handler":(ast.ExceptHandler,(),get_exception_handler), "if expression":(ast.IfExp,(),standard), "if expression condition":(ast.IfExp,(),get_condition), "if expression body":(ast.IfExp,(),get_body), From 4899a1c389916252111b89ab29f3ab00e6c6c50d Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 28 Dec 2019 01:00:16 +0200 Subject: [PATCH 086/317] Bug fixing fixing import , was previously setting first token to the last --- library/info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/info.py b/library/info.py index 71873fd..e769183 100644 --- a/library/info.py +++ b/library/info.py @@ -697,7 +697,7 @@ def fix_import(root,atok): token = atok.find_token(token,tokenize.NAME,s) stack.append(token) local_data["elements"] = stack - name.first_token = token + name.first_token = stack[0] print("matching name ",name.name," into ",token.string,"\n") if name.asname: token = next_token(atok,token) From 93a7985a5deb537132c960e0c87cbedd80eea2ce Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 28 Dec 2019 01:22:54 +0200 Subject: [PATCH 087/317] Bug fix, when the module was renamed I was bossing the list of lists instead of a list --- library/info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/info.py b/library/info.py index e769183..4295142 100644 --- a/library/info.py +++ b/library/info.py @@ -485,12 +485,12 @@ def get_subparts_of_attribute(root): def get_subparts_of_alias(root): assert already_fixed(root)," I received an node that needs fixing " names = get_fix_data(root)["name"] - print("names",names) + print("names",names,"\n") left_side =[create_fake(root,"",0,ast.Name,real_tokens=x,id=x.string,ctx=ast.Load()) for x in names] if root.asname: x = root.last_token right_side = create_fake(root,"",0,ast.Name,real_tokens=x,id=x.string,ctx=ast.Load()) - return [[left_side],right_side] + return [left_side,right_side] else: return left_side From d825affebda59eb23f1d7dc0cde35d6c4a372d71 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 28 Dec 2019 02:17:59 +0200 Subject: [PATCH 088/317] Began to expand support for multiple cursors --- application/application.py | 14 +++++++++++++- application/state_update.py | 7 +++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/application/application.py b/application/application.py index 790ac4e..1be32b4 100644 --- a/application/application.py +++ b/application/application.py @@ -14,7 +14,14 @@ class Application(): def __init__(self,vid): self.history = [] - self.state = {"result": None,"origin": None,"initial_origin":None,"alternatives": [],"change_count":-1} + self.state = { + "result": None, + "origin": None, + "initial_origin":None, + "alternatives": [], + "change_count":-1, + "mode":"single", + } self.ui_controller = None self.vid = vid @@ -103,6 +110,11 @@ def respond_to_query(self,interface,query_description,secondary=False): self.update_text(code) interface.push_action(SelectionAction(result)) self.history.append(("selection",view_information["change_count"],view_information["selection"],result)) + if not isinstance(result,list): + self.state["mode"] = "single" + else: + self.state["mode"] = "multiple" + # if alternatives: diff --git a/application/state_update.py b/application/state_update.py index 51815d7..ccf47fc 100644 --- a/application/state_update.py +++ b/application/state_update.py @@ -99,6 +99,7 @@ def clear_state(state): state["initial_origin"] = None state["alternatives"] = [] state["change_count"] = -1 + state["mode"] = "single" def retrieve_primitive(state,sublime_data): output = deepcopy(state) @@ -134,13 +135,15 @@ def retrieve_state(state,view_information,code): return False try : - convert_single_to_multiple(state) + if state["mode"]=="single": + convert_single_to_multiple(state) sublime_data = {x:get_regions_while_you_still_can(view_information,x) for x in ["result","origin","alternatives","initial_origin"]} print("\nsublime date at ease ",sublime_data,"\n") state = retrieve_primitive(state,sublime_data) - convert_multiple_to_single(state) + if state["mode"]=="single": + convert_multiple_to_single(state) print(" after conversion ",state,"\n") except: clear_state(state) From bc63666e9ace6763aa269b0a9f424c6c5de324eb Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 28 Dec 2019 03:31:01 +0200 Subject: [PATCH 089/317] First step --- application/application.py | 11 +++++-- application/state_update.py | 48 +++++++++++++++++++++-------- queries/abstract/selection_query.py | 2 ++ 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/application/application.py b/application/application.py index 1be32b4..0f4f603 100644 --- a/application/application.py +++ b/application/application.py @@ -2,7 +2,7 @@ from PythonVoiceCodingPlugin.queries import * from PythonVoiceCodingPlugin.application.build_cache import BuildCache -from PythonVoiceCodingPlugin.application.state_update import clear_state,retrieve_state,retrieve_text,get_location_text,update_changes +from PythonVoiceCodingPlugin.application.state_update import clear_state,retrieve_state,retrieve_text,get_location_text,update_changes,update_origin from PythonVoiceCodingPlugin.interface.common.actions import * @@ -21,6 +21,7 @@ def __init__(self,vid): "alternatives": [], "change_count":-1, "mode":"single", + "initial_mode":"single", } self.ui_controller = None self.vid = vid @@ -98,8 +99,12 @@ def respond_to_query(self,interface,query_description,secondary=False): if isinstance(s,SelectionQuery): result = s.result alternatives = s.alternatives - self.state["origin"] = view_information["selection"] - self.state["initial_origin"] = view_information["selection"] + selection = view_information["selection"] + mode = isinstance(result,list) or isinstance(selection,list) + update_origin(self.state,"origin",selection,mode) + update_origin(self.state,"initial_origin",selection,mode) + self.state["mode"] = "multiple" if mode else "single" + self.state["initial_mode"] = "multiple" if mode else "single" names = ["result","origin", "alternatives","initial_origin"] for name in names: interface.push_action(ClearHighlightAction(name)) diff --git a/application/state_update.py b/application/state_update.py index ccf47fc..c448d04 100644 --- a/application/state_update.py +++ b/application/state_update.py @@ -58,8 +58,14 @@ def get_location_text(location,code): return [get_location_text(x,code) for x in location] return code[location[0]:location[1]] -def convert_single_to_multiple(state): - for k in ["result","origin","initial_origin","alternatives"]: +def convert_single_to_multiple(state,mode,initial_mode,lenient = False): + names = [] + if mode == "single": + names = names + ["result","origin","alternatives"] + if initial_mode == "single": + names = names + ["initial_origin"] + + for k in names: data = state[k] if data is None: data = [] @@ -67,14 +73,21 @@ def convert_single_to_multiple(state): data = [[data]] else: if any(isinstance(x,list) for x in data): - raise Exception("In single_mode "+k+" cannot be a nested list!") + if lenient: + pass + else: + raise Exception("In single_mode "+k+" cannot be a nested list!") else: data = [data] state[k] = data -def convert_multiple_to_single(state): - print(" inside multiple single ",state) - for k in ["result","origin","initial_origin","alternatives"]: +def convert_multiple_to_single(state,mode,initial_mode,lenient = False): + names = [] + if mode == "single": + names = names + ["result","origin","alternatives"] + if initial_mode == "single": + names = names + ["initial_origin"] + for k in names: data = state[k] if k not in ["alternatives"]: if data == []: @@ -84,7 +97,10 @@ def convert_multiple_to_single(state): data = data[0][0] print(" outside this case",k,data) else: - raise Exception("when converting from multiple mode In single_mode "+k+" cannot be a nested list!") + if lenient: + pass + else: + raise Exception("when converting from multiple mode In single_mode "+k+" cannot be a nested list!") else: if isinstance(data,list) and len(data)==1: data = make_flat(data) @@ -100,6 +116,7 @@ def clear_state(state): state["alternatives"] = [] state["change_count"] = -1 state["mode"] = "single" + state["initial_mode"] = "single" def retrieve_primitive(state,sublime_data): output = deepcopy(state) @@ -127,7 +144,7 @@ def retrieve_state(state,view_information,code): The current solution is to outsource everything to the sublime add_regions/get_regions functionality ''' - + print("\n\n retrieving mode\n",state["mode"],state["initial_mode"],"\n\n") if state["change_count"]>=view_information["change_count"]: return False if state["change_count"]==-1: @@ -135,15 +152,14 @@ def retrieve_state(state,view_information,code): return False try : - if state["mode"]=="single": - convert_single_to_multiple(state) + + convert_single_to_multiple(state,state["mode"],state["initial_mode"]) sublime_data = {x:get_regions_while_you_still_can(view_information,x) for x in ["result","origin","alternatives","initial_origin"]} print("\nsublime date at ease ",sublime_data,"\n") state = retrieve_primitive(state,sublime_data) - if state["mode"]=="single": - convert_multiple_to_single(state) + convert_multiple_to_single(state,state["mode"],state["initial_mode"]) print(" after conversion ",state,"\n") except: clear_state(state) @@ -172,5 +188,13 @@ def forward(x,m): state[k] = forward(state[k],m) +def update_origin(state,name,selection,force_multiple): + assert name in state,"I cannot set something that is not in the state"+name + if isinstance(selection,list): + selection = [[x] for x in selection] + elif force_multiple: + selection = [[selection]] + state[name] = selection + diff --git a/queries/abstract/selection_query.py b/queries/abstract/selection_query.py index d1481e7..a5df68c 100644 --- a/queries/abstract/selection_query.py +++ b/queries/abstract/selection_query.py @@ -66,6 +66,8 @@ def __call__(self,view_information,query_description,extra = {}): temporary_extra = deepcopy(extra) temporary_extra["selection"] = s r,a = self.handle_single(view_information,query_description,temporary_extra) + if not isinstance(r,list): + r =[r] self.result.append(r) self.alternatives.append(a) else: From e4eb7d15d66b7df13e2e3155178200f60632b6f2 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 28 Dec 2019 03:32:24 +0200 Subject: [PATCH 090/317] Make the selection action compatible with the nested list input --- interface/common/actions.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/interface/common/actions.py b/interface/common/actions.py index 0242da4..0d69317 100644 --- a/interface/common/actions.py +++ b/interface/common/actions.py @@ -22,9 +22,17 @@ def execute(self,view,settings,sublime,**kwargs): if not isinstance(region,list): region = [region] for r in region: - view.sel().add(sublime.Region(r[0],r[1])) + if isinstance(r,list): + for x in r: + view.sel().add(sublime.Region(x[0],x[1])) + else: + view.sel().add(sublime.Region(r[0],r[1])) if settings.get("show_invisible",False): - view.show(sublime.Region(region[0][0],region[0][1])) + try : + view.show(sublime.Region(region[0][0],region[0][1])) + except : + view.show(sublime.Region(region[0][0][0],region[0][0][1])) + From d06b27431affbb5790c7aba81023cab8fbb9bc98 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 28 Dec 2019 03:46:04 +0200 Subject: [PATCH 091/317] Knee shall count has been added to the state, --- application/application.py | 7 ++++++- application/state_update.py | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/application/application.py b/application/application.py index 0f4f603..2f05cb7 100644 --- a/application/application.py +++ b/application/application.py @@ -22,6 +22,7 @@ def __init__(self,vid): "change_count":-1, "mode":"single", "initial_mode":"single", + "initial_count":-1, } self.ui_controller = None self.vid = vid @@ -100,11 +101,15 @@ def respond_to_query(self,interface,query_description,secondary=False): result = s.result alternatives = s.alternatives selection = view_information["selection"] + mode = isinstance(result,list) or isinstance(selection,list) update_origin(self.state,"origin",selection,mode) update_origin(self.state,"initial_origin",selection,mode) self.state["mode"] = "multiple" if mode else "single" - self.state["initial_mode"] = "multiple" if mode else "single" + if self.state["initial_count"] Date: Sat, 28 Dec 2019 12:59:30 +0200 Subject: [PATCH 092/317] Very proof of concept support for edit operation Currently it lacks Edie's own dedicated command but these will be changed in the future --- queries/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/queries/__init__.py b/queries/__init__.py index fd0111d..7384ebd 100644 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -53,7 +53,6 @@ def get_secondary_query(query_description): "paste":{ "command":"paste_back", "format":1, - }, "delete":{ "command":"delete_alternatives", @@ -63,9 +62,12 @@ def get_secondary_query(query_description): "swap":{ "command":"swap_back", "format":1, - } - - + }, + "edit":{ + "command":"alternative", + "format":2, + "color":0, + }, } return h[query_description["operation"]] From cc513fb0f5f08c7f5fd0bf474985e6f3ec4543b9 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 28 Dec 2019 13:00:16 +0200 Subject: [PATCH 093/317] Obligation changes to accommodate the need to be able to edit stuff without messing up with initial. origin --- application/application.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/application/application.py b/application/application.py index 2f05cb7..e0bb2d5 100644 --- a/application/application.py +++ b/application/application.py @@ -114,16 +114,17 @@ def respond_to_query(self,interface,query_description,secondary=False): for name in names: interface.push_action(ClearHighlightAction(name)) if result: - self.state["result"] = result - self.state["alternatives"] = alternatives - # self.state["alternatives_text"] = get_location_text(alternatives,code) - self.update_text(code) interface.push_action(SelectionAction(result)) self.history.append(("selection",view_information["change_count"],view_information["selection"],result)) - if not isinstance(result,list): - self.state["mode"] = "single" - else: - self.state["mode"] = "multiple" + if not secondary: + self.state["result"] = result + self.state["alternatives"] = alternatives + # self.state["alternatives_text"] = get_location_text(alternatives,code) + self.update_text(code) + if not isinstance(result,list): + self.state["mode"] = "single" + else: + self.state["mode"] = "multiple" From 9d5099e06286b8a66532d21de448fd6d5524fef9 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 28 Dec 2019 13:29:48 +0200 Subject: [PATCH 094/317] Restore paste back to its previous functionality, By making it work with initial origin --- queries/paste_back.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/paste_back.py b/queries/paste_back.py index a58345e..bf43687 100644 --- a/queries/paste_back.py +++ b/queries/paste_back.py @@ -19,7 +19,7 @@ def handle_single(self,view_information,query_description,extra = {}): candidates_location = result_alternatives_sequence(state,location = True) if query_description["format"]==1: # selection = history[index][2] - selection = state["origin"] + selection = state["initial_origin"] selection = selection if isinstance(selection,list) else [selection] if query_description["format"]==2: selection = {candidates_location[query_description["color"+i]] From 21bec6d9db31c27aa954855bac736eef333ba6ce Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Sat, 28 Dec 2019 13:30:22 +0200 Subject: [PATCH 095/317] Updating Initial origin only when necessary --- application/application.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/application.py b/application/application.py index e0bb2d5..ebd1337 100644 --- a/application/application.py +++ b/application/application.py @@ -104,11 +104,11 @@ def respond_to_query(self,interface,query_description,secondary=False): mode = isinstance(result,list) or isinstance(selection,list) update_origin(self.state,"origin",selection,mode) - update_origin(self.state,"initial_origin",selection,mode) self.state["mode"] = "multiple" if mode else "single" - if self.state["initial_count"] Date: Thu, 2 Jan 2020 14:31:20 +0200 Subject: [PATCH 096/317] The multiple mode must invert alternatives --- application/application.py | 7 +++++-- application/state_update.py | 14 +++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/application/application.py b/application/application.py index ebd1337..bffcb03 100644 --- a/application/application.py +++ b/application/application.py @@ -2,7 +2,7 @@ from PythonVoiceCodingPlugin.queries import * from PythonVoiceCodingPlugin.application.build_cache import BuildCache -from PythonVoiceCodingPlugin.application.state_update import clear_state,retrieve_state,retrieve_text,get_location_text,update_changes,update_origin +from PythonVoiceCodingPlugin.application.state_update import clear_state,retrieve_state,retrieve_text,get_location_text,update_changes,update_origin,horizontal_to_vertical from PythonVoiceCodingPlugin.interface.common.actions import * @@ -162,7 +162,10 @@ def respond_to_query(self,interface,query_description,secondary=False): if selections: interface.push_action(SelectionAction(selections)) - interface.push_action(HighlightCleverAction(self.state["alternatives"],"alternatives",self.state["result"],colorize = True)) + alternatives_output_format = self.state["alternatives"] + if self.state["mode"]=="multiple": + alternatives_output_format = horizontal_to_vertical(self.state["alternatives"]) + interface.push_action(HighlightCleverAction(alternatives_output_format,"alternatives",self.state["result"],colorize = True)) for name in ["result","origin", "initial_origin"]: interface.push_action(HighlightCleverAction(self.state[name],name)) diff --git a/application/state_update.py b/application/state_update.py index 84aaeb7..e428266 100644 --- a/application/state_update.py +++ b/application/state_update.py @@ -51,6 +51,18 @@ def invert_guided(data,guide): return output +def horizontal_to_vertical(data): + if not data: + return [] + l = max([len(x) for x in data]) + print(" just before horizontal to vertical \n") + print([[y[x] for y in data if x Date: Thu, 2 Jan 2020 16:25:32 +0200 Subject: [PATCH 097/317] Initial gamete to make alternatives work with multiple mode, not working fully now --- queries/alternatives.py | 3 +++ queries/strategies/state_extraction.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/queries/alternatives.py b/queries/alternatives.py index eb7c463..eac319f 100644 --- a/queries/alternatives.py +++ b/queries/alternatives.py @@ -6,6 +6,9 @@ class SelectAlternative(SelectionQuery): """docstring for SelectAlternative""" + def handle_multiple(self,view_information,query_description,extra = {}): + return self.handle_single(view_information,query_description,extra) + def handle_single(self,view_information,query_description,extra = {}): state = extra["state"] candidates = result_alternatives_sequence(state,location=True) diff --git a/queries/strategies/state_extraction.py b/queries/strategies/state_extraction.py index 311a0ff..0bbe89b 100644 --- a/queries/strategies/state_extraction.py +++ b/queries/strategies/state_extraction.py @@ -11,3 +11,5 @@ def result_alternatives_sequence(state,location=False,text = False): return candidates_location if text: return candidates_text + + From 3bb8efa227a38dd7ccabd00a276f8f88a1004471 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 2 Jan 2020 16:26:07 +0200 Subject: [PATCH 098/317] Experimental Arguments with multiple cursors --- queries/argument.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/queries/argument.py b/queries/argument.py index 2083462..24785ee 100644 --- a/queries/argument.py +++ b/queries/argument.py @@ -22,9 +22,8 @@ class SelectArgument(SelectionQuery): - """docstring for SelectArgument""" - # def __init__(self): - # super(SelectArgument, self).__init__() + multiple_in = True + def get_information(self,query_description): if "argument_index" in query_description: return make_information(get_argument_from_call,query_description["argument_index"]-1) From 670ada19c1f944fb2b404b700f813e2875378565 Mon Sep 17 00:00:00 2001 From: mpourmpoulis <35875229+mpourmpoulis@users.noreply.github.com> Date: Thu, 2 Jan 2020 16:36:56 +0200 Subject: [PATCH 099/317] Remove extraction over node from selection from the init.py To a separate file --- library/__init__.py | 28 ---------------------------- library/selection_node.py | 28 ++++++++++++++++++++++++++++ queries/argument.py | 1 + queries/big_roi.py | 1 + queries/select_part.py | 1 + 5 files changed, 31 insertions(+), 28 deletions(-) create mode 100644 library/selection_node.py diff --git a/library/__init__.py b/library/__init__.py index 81a4290..45905ef 100644 --- a/library/__init__.py +++ b/library/__init__.py @@ -27,32 +27,6 @@ def get_source_region(atok, element): def sorted_by_source_region(atok, container): return sorted(container,key = lambda x: get_source_region(atok,x)) -def nearest_node_from_offset(root,atok,offset): - converter = atok._line_numbers - inside = lambda x,y: (y[0]<=x Date: Thu, 2 Jan 2020 17:17:34 +0200 Subject: [PATCH 100/317] missing import --- doc/gif/bug1.gif | Bin 0 -> 116292 bytes doc/gif/bug2.gif | Bin 0 -> 112441 bytes library/selection_node.py | 2 ++ 3 files changed, 2 insertions(+) create mode 100644 doc/gif/bug1.gif create mode 100644 doc/gif/bug2.gif diff --git a/doc/gif/bug1.gif b/doc/gif/bug1.gif new file mode 100644 index 0000000000000000000000000000000000000000..fdff8b6ef49be6f517a7f4e980c1dacd53212b92 GIT binary patch literal 116292 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