/src/cfml_view.py

https://github.com/jcberquist/sublimetext-cfml · Python · 267 lines · 216 code · 46 blank · 5 comment · 37 complexity · 889497799b0278ded73c776ece7c4ead MD5 · raw file

  1. import re
  2. import sublime
  3. from collections import defaultdict
  4. from collections import namedtuple
  5. from . import utils
  6. from . import buffer_metadata
  7. CompletionList = namedtuple(
  8. "CompletionList", "completions priority exclude_lower_priority"
  9. )
  10. Documentation = namedtuple(
  11. "Documentation", "doc_regions doc_html_variables on_navigate priority"
  12. )
  13. MethodPreview = namedtuple(
  14. "MethodPreview", "preview_regions preview_html_variables on_navigate priority"
  15. )
  16. CompletionDoc = namedtuple(
  17. "CompletionDoc", "doc_regions doc_html_variables on_navigate"
  18. )
  19. GotoCfmlFile = namedtuple("GotoCfmlFile", "file_path symbol")
  20. class CfmlFunctionCallParams:
  21. param_regex = re.compile(r"^(?:([\w]+)\s*=\s*)?(.*)$", re.M | re.S)
  22. def __init__(self, cfml_view, position):
  23. self.support = False
  24. self.method = False
  25. self.dot_context = None
  26. self.named_params = False
  27. self.current_index = None
  28. self.params = []
  29. self.function_name, self.function_region, self.params_region = cfml_view.get_function_call(
  30. position
  31. )
  32. if (
  33. "support"
  34. in cfml_view.view.scope_name(self.function_region.begin())
  35. .strip()
  36. .split(" ")[-1]
  37. ):
  38. self.support = True
  39. prev_pt = self.function_region.begin() - 1
  40. if cfml_view.view.match_selector(
  41. prev_pt, "embedding.cfml source.cfml.script punctuation.accessor.cfml"
  42. ):
  43. self.method = True
  44. self.dot_context = cfml_view.dot_context = cfml_view.get_dot_context(
  45. prev_pt
  46. )
  47. start_scope_list = (
  48. cfml_view.view.scope_name(self.params_region.begin())
  49. .strip()
  50. .split(" ")[:-1]
  51. )
  52. separator_scope = " ".join(start_scope_list) + " "
  53. last_key = start_scope_list[-2].replace("meta.", "punctuation.separator.") + " "
  54. for scope_name in ["entity.", "createcomponent.", "createjavaobject."]:
  55. last_key = last_key.replace(scope_name, "")
  56. separator_scope += last_key
  57. start = self.params_region.begin() + 1
  58. for pt in range(self.params_region.begin() + 1, self.params_region.end()):
  59. if pt == position:
  60. self.current_index = len(self.params)
  61. if cfml_view.view.scope_name(pt) == separator_scope:
  62. current_element = cfml_view.view.substr(
  63. sublime.Region(start, pt)
  64. ).strip()
  65. param = re.match(CfmlFunctionCallParams.param_regex, current_element)
  66. self.params.append(param.groups())
  67. start = pt + 1
  68. final_element = cfml_view.view.substr(sublime.Region(start, pt)).strip()
  69. if len(final_element) > 0 or start != self.params_region.begin() + 1:
  70. param = re.match(CfmlFunctionCallParams.param_regex, final_element)
  71. self.params.append(param.groups())
  72. if len(self.params) > 0:
  73. self.named_params = self.params[0][0] is not None
  74. def __repr__(self):
  75. return repr(
  76. (
  77. self.support,
  78. self.method,
  79. self.function_name,
  80. self.function_region,
  81. self.params_region,
  82. self.dot_context,
  83. self.named_params,
  84. self.current_index,
  85. self.params,
  86. )
  87. )
  88. class CfmlView:
  89. def __init__(self, view, position, prefix=""):
  90. self.view = view
  91. self.prefix = prefix
  92. self.position = position
  93. self.function_call_params = None
  94. self.tag_name = None
  95. self.tag_attribute_name = None
  96. self.tag_in_script = False
  97. self.tag_location = None
  98. self._cache = defaultdict(dict)
  99. self.CompletionList = CompletionList
  100. self.Documentation = Documentation
  101. self.CompletionDoc = CompletionDoc
  102. self.MethodPreview = MethodPreview
  103. self.GotoCfmlFile = GotoCfmlFile
  104. self.prefix_start = self.position - len(self.prefix)
  105. self.determine_type()
  106. # continue processing only if we know the type
  107. if self.type:
  108. self.set_base_info()
  109. self.view_metadata = buffer_metadata.get_cached_view_metadata(view)
  110. def set_base_info(self):
  111. self.file_path = utils.normalize_path(self.view.file_name())
  112. self.file_name = (
  113. self.file_path.split("/").pop().lower() if self.file_path else None
  114. )
  115. self.project_name = utils.get_project_name(self.view)
  116. self.previous_char = self.view.substr(self.prefix_start - 1)
  117. def determine_type(self):
  118. base_script_scope = "embedding.cfml source.cfml.script"
  119. self.type = None
  120. # tag completions
  121. if self.view.match_selector(
  122. self.prefix_start, "embedding.cfml - source.cfml.script"
  123. ):
  124. self.type = "tag"
  125. self.set_tag_info()
  126. # dot completions (member and model function completions)
  127. elif self.view.match_selector(
  128. self.prefix_start - 1, base_script_scope + " punctuation.accessor.cfml"
  129. ):
  130. self.type = "dot"
  131. self.set_dot_context()
  132. self.function_call_params = self.get_function_call_params(self.position)
  133. # tag in script attribute completions
  134. elif self.view.match_selector(
  135. self.prefix_start,
  136. base_script_scope
  137. + " meta.tag, "
  138. + base_script_scope
  139. + " meta.class.declaration",
  140. ):
  141. self.type = "tag_attributes"
  142. self.set_tag_info(True)
  143. # script completions
  144. elif self.view.match_selector(
  145. self.prefix_start, "embedding.cfml source.cfml.script"
  146. ):
  147. self.type = "script"
  148. self.function_call_params = self.get_function_call_params(self.position)
  149. def set_dot_context(self):
  150. self.dot_context = self.get_dot_context(self.prefix_start - 1)
  151. def set_tag_info(self, tag_in_script=False):
  152. self.tag_in_script = tag_in_script
  153. if self.view.match_selector(
  154. self.prefix_start,
  155. "meta.tag - punctuation.definition.tag.begin, meta.class.declaration.cfml",
  156. ):
  157. if self.view.match_selector(
  158. self.prefix_start - 1,
  159. "punctuation.definition.tag.begin, entity.name.tag",
  160. ):
  161. self.tag_location = "tag_name"
  162. elif self.view.match_selector(
  163. self.prefix_start, "entity.other.attribute-name.cfml"
  164. ):
  165. self.tag_location = "tag_attribute_name"
  166. else:
  167. self.tag_location = "tag_attributes"
  168. if self.view.match_selector(
  169. self.prefix_start, "source.cfml.script meta.class.declaration"
  170. ):
  171. self.tag_name = "component"
  172. else:
  173. self.tag_name = utils.get_tag_name(self.view, self.prefix_start)
  174. if self.tag_in_script and not self.tag_name.startswith("cf"):
  175. self.tag_name = "cf" + self.tag_name
  176. if self.tag_location != "tag_name":
  177. self.tag_attribute_name = utils.get_tag_attribute_name(
  178. self.view, self.prefix_start
  179. )
  180. self.type = "tag_attributes"
  181. def get_dot_context(self, pt, cachable=True):
  182. if not cachable or pt not in self._cache["get_dot_context"]:
  183. self._cache["get_dot_context"][pt] = utils.get_dot_context(self.view, pt)
  184. return self._cache["get_dot_context"][pt]
  185. def get_struct_context(self, pt, cachable=True):
  186. if not cachable or pt not in self._cache["get_function"]:
  187. self._cache["get_struct_context"][pt] = utils.get_struct_context(
  188. self.view, pt
  189. )
  190. return self._cache["get_struct_context"][pt]
  191. def get_struct_var_assignment(self, pt):
  192. struct_context = self.get_struct_context(pt)
  193. variable_name = ".".join([symbol.name for symbol in reversed(struct_context)])
  194. return variable_name
  195. def get_function(self, pt, cachable=True):
  196. if not cachable or pt not in self._cache["get_function"]:
  197. self._cache["get_function"][pt] = utils.get_function(self.view, pt)
  198. return self._cache["get_function"][pt]
  199. def get_function_call(self, pt, support=False, cachable=True):
  200. cache_key = (pt, support)
  201. if not cachable or cache_key not in self._cache["get_function_call"]:
  202. self._cache["get_function_call"][cache_key] = utils.get_function_call(
  203. self.view, pt, support
  204. )
  205. return self._cache["get_function_call"][cache_key]
  206. def get_function_call_params(self, pt):
  207. if self.view.match_selector(
  208. pt, "source.cfml.script meta.function-call.parameters"
  209. ):
  210. return CfmlFunctionCallParams(self, pt)
  211. return None
  212. def get_string_metadata(self, file_string):
  213. return buffer_metadata.parse_cfc_file_string(file_string)
  214. def find_variable_assignment(self, position, variable_name, cachable=True):
  215. cache_key = (position, variable_name)
  216. if not cachable or cache_key not in self._cache["find_variable_assignment"]:
  217. var_assignment = utils.find_variable_assignment(
  218. self.view, position, variable_name
  219. )
  220. self._cache["find_variable_assignment"][cache_key] = var_assignment
  221. return self._cache["find_variable_assignment"][cache_key]