utils.gd 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. @tool
  2. extends Node
  3. class_name ModToolUtils
  4. # Utility functions used across the ModTool.
  5. # ! Not used currently. This can overwrite existing text very easily if the wrong script is shown in the text editor.
  6. static func reload_script(script: Script, mod_tool_store: ModToolStore) -> void:
  7. var pending_reloads := mod_tool_store.pending_reloads
  8. if script.resource_path in pending_reloads:
  9. var source_code_from_disc := FileAccess.open(script.resource_path, FileAccess.READ).get_as_text()
  10. var script_editor := EditorInterface.get_script_editor()
  11. var text_edit: CodeEdit = script_editor.get_current_editor().get_base_editor()
  12. var column := text_edit.get_caret_column()
  13. var row := text_edit.get_caret_line()
  14. var scroll_position_h := text_edit.get_h_scroll_bar().value
  15. var scroll_position_v := text_edit.get_v_scroll_bar().value
  16. text_edit.text = source_code_from_disc
  17. text_edit.set_caret_column(column)
  18. text_edit.set_caret_line(row)
  19. text_edit.scroll_horizontal = scroll_position_h
  20. text_edit.scroll_vertical = scroll_position_v
  21. text_edit.tag_saved_version()
  22. pending_reloads.erase(script.resource_path)
  23. # Takes a file path and an array of file extensions [.txt, .tscn, ..]
  24. static func is_file_extension(path: String, excluded_extensions: PackedStringArray) -> bool:
  25. var is_extension := false
  26. for extension in excluded_extensions:
  27. var file_name := path.get_file()
  28. if(extension in file_name):
  29. is_extension = true
  30. break
  31. else:
  32. is_extension = false
  33. return is_extension
  34. # Returns the content of the file from the given path as a string.
  35. static func file_get_as_text(path: String) -> String:
  36. var file_access := FileAccess.open(path, FileAccess.READ)
  37. var content := file_access.get_as_text()
  38. file_access.close()
  39. return content
  40. # Copies a file from a given src to the specified dst path.
  41. # src = path/to/file.extension
  42. # dst = other/path/to/file.extension
  43. static func file_copy(src: String, dst: String) -> void:
  44. var dst_dir := dst.get_base_dir()
  45. if not DirAccess.dir_exists_absolute(dst_dir):
  46. DirAccess.make_dir_recursive_absolute(dst_dir)
  47. DirAccess.copy_absolute(src, dst)
  48. # Log error messages
  49. static func output_error(message) -> void:
  50. printerr("ModTool Error: " + str(message))
  51. static func output_info(message) -> void:
  52. print("ModTool: " + str(message))
  53. static func save_to_manifest_json(manifest_data: ModManifest, path_manifest: String) -> bool:
  54. var is_success := _ModLoaderFile._save_string_to_file(
  55. manifest_data.to_json(),
  56. path_manifest
  57. )
  58. if is_success:
  59. output_info("Successfully saved manifest.json file!")
  60. return is_success
  61. static func make_dir_recursive(dst_dir: String) -> bool:
  62. var error := DirAccess.make_dir_recursive_absolute(dst_dir)
  63. if not error == OK:
  64. output_error("Failed creating directory at %s with error \"%s\"" % [dst_dir, error_string(error)])
  65. return false
  66. return true
  67. # Takes a directory path to get removed.
  68. # https://www.davidepesce.com/2019/11/04/essential-guide-to-godot-filesystem-api/
  69. static func remove_recursive(path: String) -> void:
  70. var directory := DirAccess.open(path)
  71. if not directory:
  72. print("Error removing " + path)
  73. return
  74. # List directory content
  75. directory.list_dir_begin()
  76. var file_name := directory.get_next()
  77. while file_name != "":
  78. if directory.current_is_dir():
  79. remove_recursive(path + "/" + file_name)
  80. else:
  81. directory.remove(file_name)
  82. file_name = directory.get_next()
  83. # Remove current path
  84. directory.remove(path)
  85. static func check_for_hooked_script(script_paths: Array[String], mod_tool_store: ModToolStore) -> int:
  86. var count := 0
  87. for script_path in script_paths:
  88. if mod_tool_store.hooked_scripts.has(script_path):
  89. count += 1
  90. return count
  91. static func quote_string(string: String) -> String:
  92. var settings: EditorSettings = EditorInterface.get_editor_settings()
  93. if settings.get_setting("text_editor/completion/use_single_quotes"):
  94. return "'%s'" % string
  95. return "\"%s\"" % string
  96. static func script_has_method(script_path: String, method: String) -> bool:
  97. var script: Script = load(script_path)
  98. for script_method in script.get_script_method_list():
  99. if script_method.name == method:
  100. return true
  101. if method in script.source_code:
  102. return true
  103. return false
  104. static func get_index_at_method_end(method_name: String, text: String) -> int:
  105. var starting_index := text.rfind(method_name)
  106. # Find the end of the method
  107. var next_method_line_index := text.find("func ", starting_index)
  108. var method_end := -1
  109. if next_method_line_index == -1:
  110. # Backtrack empty lines from the end of the file
  111. method_end = text.length() -1
  112. else:
  113. # Get the line before the next function line
  114. method_end = text.rfind("\n", next_method_line_index)
  115. # Backtrack to the last non-empty line
  116. var last_non_empty_line_index := method_end
  117. while last_non_empty_line_index > starting_index:
  118. last_non_empty_line_index -= 1
  119. # Remove spaces, tabs and newlines (whitespace) to check if the line really is empty
  120. if text[last_non_empty_line_index].rstrip("\t\n "):
  121. break # encountered a filled line
  122. return last_non_empty_line_index +1
  123. # Slightly modified version of:
  124. # https://gist.github.com/willnationsdev/00d97aa8339138fd7ef0d6bd42748f6e
  125. # Removed .import from the extension filter.
  126. # p_match is a string that filters the list of files.
  127. # If p_match_is_regex is false, p_match is directly string-searched against the FILENAME.
  128. # If it is true, a regex object compiles p_match and runs it against the FILEPATH.
  129. static func get_flat_view_dict(
  130. p_dir := "res://",
  131. p_match := "",
  132. p_match_file_extensions: Array[StringName] = [],
  133. p_match_is_regex := false,
  134. include_empty_dirs := false,
  135. ignored_dirs: Array[StringName] = []
  136. ) -> PackedStringArray:
  137. var data: PackedStringArray = []
  138. var regex: RegEx
  139. if p_match_is_regex:
  140. regex = RegEx.new()
  141. var _compile_error: int = regex.compile(p_match)
  142. if not regex.is_valid():
  143. return data
  144. var dirs := [p_dir]
  145. var first := true
  146. while not dirs.is_empty():
  147. var dir_name : String = dirs.back()
  148. var dir := DirAccess.open(dir_name)
  149. dirs.pop_back()
  150. if dir_name.lstrip("res://").get_slice("/", 0) in ignored_dirs:
  151. continue
  152. if dir:
  153. var _dirlist_error: int = dir.list_dir_begin()
  154. var file_name := dir.get_next()
  155. if include_empty_dirs and not dir_name == p_dir:
  156. data.append(dir_name)
  157. while file_name != "":
  158. if not dir_name == "res://":
  159. first = false
  160. # ignore hidden, temporary, or system content
  161. if not file_name.begins_with(".") and not file_name.get_extension() == "tmp":
  162. # If a directory, then add to list of directories to visit
  163. if dir.current_is_dir():
  164. dirs.push_back(dir.get_current_dir() + "/" + file_name)
  165. # If a file, check if we already have a record for the same name
  166. else:
  167. var path := dir.get_current_dir() + ("/" if not first else "") + file_name
  168. # grab all
  169. if not p_match and not p_match_file_extensions:
  170. data.append(path)
  171. # grab matching strings
  172. elif not p_match_is_regex and p_match and file_name.contains(p_match):
  173. data.append(path)
  174. # garb matching file extension
  175. elif p_match_file_extensions and file_name.get_extension() in p_match_file_extensions:
  176. data.append(path)
  177. # grab matching regex
  178. elif p_match_is_regex:
  179. var regex_match := regex.search(path)
  180. if regex_match != null:
  181. data.append(path)
  182. # Move on to the next file in this directory
  183. file_name = dir.get_next()
  184. # We've exhausted all files in this directory. Close the iterator.
  185. dir.list_dir_end()
  186. return data