diff --git a/fkie_node_manager/src/fkie_node_manager/editor/graph_view.py b/fkie_node_manager/src/fkie_node_manager/editor/graph_view.py index 0999aa56..fc7ad9ac 100644 --- a/fkie_node_manager/src/fkie_node_manager/editor/graph_view.py +++ b/fkie_node_manager/src/fkie_node_manager/editor/graph_view.py @@ -76,7 +76,8 @@ class GraphViewWidget(QDockWidget): DATA_ARGS = Qt.UserRole + 7 DATA_DEF_ARGS_NOT_SET = Qt.UserRole + 8 DATA_ARG_NAME = Qt.UserRole + 9 - ITEM_TYPE = Qt.UserRole + 10 + DATA_FILE_EXISTS = Qt.UserRole + 10 + ITEM_TYPE = Qt.UserRole + 11 ITEM_TYPE_INC_FILE = 1 ITEM_TYPE_INC_GROUP_ARG = 2 ITEM_TYPE_INC_ARG = 3 @@ -216,6 +217,21 @@ def get_include_args(self, arglist, inc_string, from_file): result[arg].append(incargs[arg]) return result + def get_included_files(self, from_file, inc_string=''): + ''' + Returns all included files. If inc_string is not empty only files with raw string containing in inc_string are returned. + :rtype: Tuple(path, raw_data, line, exists) + ''' + result = [] + file_items = self.graphTreeView.model().match(self.graphTreeView.model().index(0, 0), self.DATA_INC_FILE, from_file, 10, Qt.MatchRecursive) + for index in file_items: + item = self.graphTreeView.model().itemFromIndex(index) + for row_idx in range(item.rowCount()): + inc_item = item.child(row_idx) + if not inc_string or inc_item.data(self.DATA_RAW) in inc_string: + result.append((inc_item.data(self.DATA_INC_FILE), inc_item.data(self.DATA_RAW), inc_item.data(self.DATA_LINE), inc_item.data(self.DATA_FILE_EXISTS))) + return result + def _refill_tree(self, tree, create_tree=True): deep = 0 file_dsrc = self._root_path @@ -304,6 +320,7 @@ def _append_items(self, item, deep, items=[]): inc_item.setData(inc_file.raw_inc_path, self.DATA_RAW) inc_item.setData(inc_file.args, self.DATA_ARGS) inc_item.setData(inc_file.unset_default_args, self.DATA_DEF_ARGS_NOT_SET) + inc_item.setData(inc_file.exists, self.DATA_FILE_EXISTS) # add included arguments if inc_file.unset_default_args or inc_file.args: arg_item = QStandardItem('arguments') @@ -316,6 +333,7 @@ def _append_items(self, item, deep, items=[]): da_item.setData(self.ITEM_TYPE_INC_ARG, self.ITEM_TYPE) da_item.setData(inc_file.path_or_str, self.DATA_FILE) da_item.setData(inc_file.inc_path, self.DATA_INC_FILE) + da_item.setData(inc_file.exists, self.DATA_FILE_EXISTS) da_item.setData(da_name, self.DATA_ARG_NAME) da_item.setToolTip("This argument is definded as default, but no set while include.") arg_item.appendRow(da_item) @@ -325,6 +343,7 @@ def _append_items(self, item, deep, items=[]): da_item.setData(self.ITEM_TYPE_INC_ARG, self.ITEM_TYPE) da_item.setData(inc_file.path_or_str, self.DATA_FILE) da_item.setData(inc_file.inc_path, self.DATA_INC_FILE) + da_item.setData(inc_file.exists, self.DATA_FILE_EXISTS) da_item.setData(da_name, self.DATA_ARG_NAME) arg_item.appendRow(da_item) inc_item.appendRow(arg_item) diff --git a/fkie_node_manager/src/fkie_node_manager/editor/text_edit.py b/fkie_node_manager/src/fkie_node_manager/editor/text_edit.py index 556abd21..df04aa3f 100644 --- a/fkie_node_manager/src/fkie_node_manager/editor/text_edit.py +++ b/fkie_node_manager/src/fkie_node_manager/editor/text_edit.py @@ -31,7 +31,6 @@ # POSSIBILITY OF SUCH DAMAGE. - from python_qt_binding.QtCore import QRegExp, Qt, Signal, QEvent from python_qt_binding.QtGui import QColor, QFont, QKeySequence, QTextCursor, QTextDocument import os @@ -318,7 +317,9 @@ def file_changed(self, mtime): def _strip_bad_parts(self, textblock, current_position): result = textblock - startidx = textblock.rfind('$(find ', 0, current_position) + startidx = textblock.rfind('$(dirname)', 0, current_position) + if startidx == -1: + startidx = textblock.rfind('$(find ', 0, current_position) if startidx == -1: startidx = textblock.rfind(' /', 0, current_position) if startidx == -1: @@ -351,67 +352,76 @@ def mouseReleaseEvent(self, event): if event.modifiers() == Qt.ControlModifier or event.modifiers() == Qt.ShiftModifier: cursor = self.cursorForPosition(event.pos()) try: - textblock = self._strip_bad_parts(cursor.block().text(), cursor.positionInBlock()) - for inc_file in find_included_files(textblock, False, False, search_in_ext=[]): - aval = inc_file.raw_inc_path - aitems = aval.split("'") - for search_for in aitems: - if not search_for: - continue - try: - rospy.logdebug("try to interpret: %s" % search_for) - args_in_name = get_arg_names(search_for) + textblock = self._strip_bad_parts( + cursor.block().text(), cursor.positionInBlock()) + r = self.parent.graph_view.get_included_files( + self.filename, textblock) + for inc_file, _raw_text, _line, file_exists in r: + try: + rospy.logdebug( + f"found included file: {inc_file}, exists: {file_exists}") + if file_exists: + event.setAccepted(True) + self.load_request_signal.emit(inc_file) + else: + rospy.logdebug( + f" included files does not exists, look for arguments in {inc_file}") + args_in_name = get_arg_names(inc_file) resolved_args = {} # if found arg in the name, try to detect values if args_in_name: - rospy.logdebug(" args %s in filename found, try to resolve..." % args_in_name) - resolved_args = self.parent.graph_view.get_include_args(args_in_name, search_for, self.filename) - if resolved_args: - params = {} - self._internal_args - # create parameter dialog - for key, val in resolved_args.items(): - values = list(val) - # add args defined in current file - if key in self._internal_args and self._internal_args[key] not in values: - values.append(self._internal_args[key]) - params[key] = {':type': 'string', ':value': values} - dia = ParameterDialog(params, store_geometry="open_launch_on_click") - dia.setFilterVisible(False) - dia.setWindowTitle('Select Parameter') - if dia.exec_(): - params = dia.getKeywords() - search_for = replace_arg(search_for, params) - else: - # canceled -> cancel interpretation - QTextEdit.mouseReleaseEvent(self, event) - return - # now resolve find-statements - rospy.logdebug(" send interpret request to daemon: %s" % search_for) - inc_files = nm.nmd().launch.get_interpreted_path(self.filename, text=[search_for]) - for path, exists in inc_files: - try: - rospy.logdebug(" received interpret request from daemon: %s, exists: %d" % (path, exists)) - if exists: - event.setAccepted(True) - self.load_request_signal.emit(path) + rospy.logdebug( + f" args {args_in_name} in filename found, try to resolve...") + resolved_args = self.parent.graph_view.get_include_args( + args_in_name, _raw_text, self.filename) + if resolved_args: + params = {} + self._internal_args + # create parameter dialog + for key, val in resolved_args.items(): + values = list(val) + # add args defined in current file + if key in self._internal_args and self._internal_args[key] not in values: + values.append( + self._internal_args[key]) + params[key] = { + ':type': 'string', ':value': values} + dia = ParameterDialog( + params, store_geometry="open_launch_on_click") + dia.setFilterVisible(False) + dia.setWindowTitle('Select Parameter') + if dia.exec_(): + params = dia.getKeywords() + inc_file = replace_arg( + inc_file, params) + self.load_request_signal.emit( + inc_file) else: - _filename, file_extension = os.path.splitext(path) - if file_extension in nm.settings().launch_view_file_ext: - # create a new file, if it does not exists - result = MessageBox.question(self, "File not exists", '\n\n'.join(["Create a new file?", path]), buttons=MessageBox.Yes | MessageBox.No) - if result == MessageBox.Yes: - content = '\n\n' if path.endswith('.launch') else '' - nm.nmd().file.save_file(path, content.encode(), 0) - event.setAccepted(True) - self.load_request_signal.emit(path) - except Exception as e: - MessageBox.critical(self, "Error", "File not found %s" % path, detailed_text=utf8(e)) - except exceptions.ResourceNotFound as not_found: - MessageBox.critical(self, "Error", "Resource not found %s" % search_for, detailed_text=utf8(not_found.error)) + # canceled -> cancel interpretation + QTextEdit.mouseReleaseEvent( + self, event) + return + else: + _filename, file_extension = os.path.splitext( + inc_file) + if file_extension in nm.settings().launch_view_file_ext: + # create a new file, if it does not exists + result = MessageBox.question(self, "File not exists", '\n\n'.join( + ["Create a new file?", inc_file]), buttons=MessageBox.Yes | MessageBox.No) + if result == MessageBox.Yes: + content = '\n\n' if inc_file.endswith( + '.launch') else '' + nm.nmd().file.save_file(inc_file, content.encode(), 0) + event.setAccepted(True) + self.load_request_signal.emit( + inc_file) + except (Exception, exceptions.ResourceNotFound) as e: + MessageBox.critical( + self, "Error", "File not found %s" % inc_file, detailed_text=utf8(e)) except Exception as err: print(traceback.format_exc()) - MessageBox.critical(self, "Error", "Error while request included file %s" % self.filename, detailed_text=utf8(err)) + MessageBox.critical( + self, "Error", "Error while request included file %s" % self.filename, detailed_text=utf8(err)) QTextEdit.mouseReleaseEvent(self, event) def mouseMoveEvent(self, event): diff --git a/fkie_node_manager_daemon/src/fkie_node_manager_daemon/common.py b/fkie_node_manager_daemon/src/fkie_node_manager_daemon/common.py index 27fdf9c1..ce7a73ae 100644 --- a/fkie_node_manager_daemon/src/fkie_node_manager_daemon/common.py +++ b/fkie_node_manager_daemon/src/fkie_node_manager_daemon/common.py @@ -46,6 +46,7 @@ PACKAGE_FILE = 'package.xml' EMPTY_PATTERN = re.compile('\b', re.I) INCLUDE_PATTERN = [r"\s*(\$\(find.*?\)[^\"]*)", + r"\s*(\$\(dirname\)[^\"]*)", r"file=\"(.*?)\"", r"textfile=\"(.*?)\"", r"binfile=\"(.*?)\"", @@ -257,7 +258,7 @@ def interpret_path(path, pwd='.'): Otherwise the parameter itself normalized by :py:func:`os.path.normpath` will be returned. :rtype: str ''' - result = path.strip() + result = path.strip().replace("$(dirname)", pwd) # try replace package name by package path pkg_pattern = re.compile(r"\$\(find (.*?)\)/|pkg:\/\/(.*?)/|package:\/\/(.*?)/") for groups in pkg_pattern.finditer(path): @@ -302,7 +303,7 @@ def replace_paths(text, pwd='.'): Like meth:interpret_path(), but replaces all matches in the text and retain other text. ''' result = text - path_pattern = re.compile(r"(\$\(find .*?\)/)|(pkg:\/\/.*?/)|(package:\/\/.*?/)") + path_pattern = re.compile(r"(\$\(dirname\)|\$\(find .*?\)/)|(pkg:\/\/.*?/)|(package:\/\/.*?/)") for groups in path_pattern.finditer(text): for index in range(groups.lastindex): path = groups.groups()[index] @@ -455,7 +456,8 @@ def find_included_files(string, search_in_ext=SEARCH_IN_EXT, resolve_args={}, unique_files=[], - rec_depth=0): + rec_depth=0, + filename=None): ''' If the `string` parameter is a valid file the content of this file will be parsed. In other case the `string` is parsed to find included files. @@ -492,6 +494,10 @@ def find_included_files(string, count_nl = content[match.start():match.end()].count('\n') content = content[:match.start()] + '\n' * count_nl + content[match.end():] match = comment_pattern.search(content, match.start()) + if filename is not None: + pwd = os.path.dirname(filename) + if '://' in pwd: + pwd = re.sub(r"^.*://[^/]*", "", pwd) inc_files_forward_args = [] # replace the arguments and detect arguments for include-statements resolve_args_intern = {}