├── .python-version ├── .gitattributes ├── templates ├── testbox │ ├── compacttext │ │ ├── logo.txt │ │ ├── legend.txt │ │ ├── bundle.txt │ │ ├── global_exception.txt │ │ └── results.txt │ └── text │ │ ├── logo.txt │ │ ├── legend.txt │ │ ├── bundle.txt │ │ ├── results.txt │ │ └── global_exception.txt ├── pagination.html ├── method_preview.html ├── completion_doc.html └── inline_documentation.html ├── src ├── formatting │ ├── __init__.py │ ├── method_chains.py │ └── misc.py ├── component_parser │ ├── __init__.py │ ├── cfml_properties.py │ ├── regex.py │ └── cfml_components.py ├── custom_tag_index │ ├── __init__.py │ └── custom_tag_index.py ├── events.py ├── plugins_ │ ├── plugin.py │ ├── custom_tags │ │ ├── __init__.py │ │ ├── documentation.py │ │ └── completions.py │ ├── cfdocs │ │ └── __init__.py │ ├── fw1 │ │ ├── __init__.py │ │ └── json │ │ │ ├── settings.json │ │ │ └── methods.json │ ├── applicationcfc │ │ ├── __init__.py │ │ ├── json │ │ │ ├── methods.json │ │ │ └── settings.json │ │ └── appcfc.py │ ├── testbox │ │ ├── __init__.py │ │ └── testbox_spec_outline.py │ ├── basecompletions │ │ └── __init__.py │ ├── cfcs │ │ ├── completions.py │ │ ├── __init__.py │ │ ├── di.py │ │ └── cfcs.py │ ├── in_file_completions │ │ ├── __init__.py │ │ └── in_file_completions.py │ ├── entities │ │ ├── __init__.py │ │ ├── completions.py │ │ └── documentation.py │ └── dotpaths │ │ └── __init__.py ├── __init__.py ├── cfml_plugins.py ├── component_index │ ├── __init__.py │ ├── navigate_to_method.py │ └── completions.py ├── documentation_helpers.py ├── goto_cfml_file.py ├── completions.py ├── commands │ ├── cfc_dotted_path.py │ ├── color_scheme_styles.py │ └── __init__.py ├── cfdocs.py ├── buffer_metadata.py └── method_preview.py ├── messages ├── 0.4.1.txt ├── 0.30.0.txt ├── 0.9.3.txt ├── 0.12.1.txt ├── 0.2.1.txt ├── 0.9.2.txt ├── 0.10.1.txt ├── 0.8.1.txt ├── 0.3.1.txt ├── 0.10.2.txt ├── 0.29.0.txt ├── 0.9.0.txt ├── 0.4.0.txt ├── 0.12.2.txt ├── 0.12.3.txt ├── 0.9.1.txt ├── install.txt ├── 0.12.0.txt ├── 0.23.0.txt ├── 0.14.0.txt ├── 0.2.0.txt ├── 0.11.0.txt ├── 0.3.0.txt ├── 0.5.1.txt ├── 0.31.0.txt ├── 0.17.0.txt ├── 0.4.2.txt ├── 0.6.0.txt ├── 0.25.0.txt ├── 0.28.0.txt ├── 0.8.0.txt ├── 0.19.0.txt ├── 0.22.0.txt ├── 0.5.0.txt ├── 0.10.0.txt ├── 0.27.0.txt ├── 0.7.0.txt ├── 0.15.0.txt ├── 0.18.0.txt ├── 0.24.0.txt ├── 0.26.0.txt ├── 0.16.0.txt ├── 0.20.0.txt ├── 0.13.0.txt └── 0.21.0.txt ├── menus ├── Context.sublime-menu ├── Side Bar.sublime-menu └── Main.sublime-menu ├── inputmaps ├── Default (OSX).sublime-mousemap ├── Default (Linux).sublime-mousemap └── Default (Windows).sublime-mousemap ├── metadata ├── symbols.tmPreferences ├── symbols-indexed.tmPreferences ├── symbols-banned.tmPreferences ├── cfscript-indent.tmPreferences ├── cfml.tmPreferences ├── cfscript.tmPreferences └── cfml-indent.tmPreferences ├── syntaxes ├── tests │ ├── syntax_test_rest_spread.cfm │ ├── syntax_test_lucee6.cfc │ ├── syntax_test_java.cfm │ ├── syntax_test_destructure.cfm │ ├── syntax_test_javadoc.cfc │ ├── syntax_test_lucee5.cfc │ ├── syntax_test_tag_attributes_cfml.cfm │ ├── syntax_test_sql_strings.cfm │ └── syntax_test_cfml_component.cfc ├── JavaScript (CFML).sublime-syntax ├── CFScript (CFML) Tags.sublime-syntax ├── testbox.sublime-syntax ├── HTML (CFML).sublime-syntax └── build_base_cfscript_syntax.py ├── settings └── CFML.sublime-settings ├── LICENSE.md ├── commands └── Default.sublime-commands ├── messages.json ├── color-schemes └── testbox.hidden-tmTheme └── cfml_plugin.py /.python-version: -------------------------------------------------------------------------------- 1 | 3.8 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /templates/testbox/compacttext/logo.txt: -------------------------------------------------------------------------------- 1 | Testbox 2 | -------------------------------------------------------------------------------- /src/formatting/__init__.py: -------------------------------------------------------------------------------- 1 | from .cfml_format import CfmlFormatCommand 2 | -------------------------------------------------------------------------------- /messages/0.4.1.txt: -------------------------------------------------------------------------------- 1 | CFML v0.4.1 Changelog: 2 | 3 | - fix for invalid JSON in OSX keymap file 4 | Sorry! 5 | -------------------------------------------------------------------------------- /menus/Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "caption": "Copy CFC Dotted Path", "command": "cfml_cfc_dotted_path" } 3 | ] 4 | -------------------------------------------------------------------------------- /menus/Side Bar.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "caption": "Copy CFC Dotted Path", "command": "cfml_sidebar_cfc_dotted_path", "args": {"files": []} } 3 | ] 4 | -------------------------------------------------------------------------------- /inputmaps/Default (OSX).sublime-mousemap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "button": "button1", "modifiers": ["ctrl", "alt"], 4 | "command": "cfml_goto_file" 5 | } 6 | ] -------------------------------------------------------------------------------- /inputmaps/Default (Linux).sublime-mousemap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "button": "button1", "modifiers": ["ctrl", "alt"], 4 | "command": "cfml_goto_file" 5 | } 6 | ] -------------------------------------------------------------------------------- /inputmaps/Default (Windows).sublime-mousemap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "button": "button1", "modifiers": ["ctrl", "alt"], 4 | "command": "cfml_goto_file" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /messages/0.30.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.30.0 Changelog: 2 | 3 | - ST4 support 4 | 5 | PLEASE RESTART SUBLIME TEXT TO ENSURE THESE CHANGES ARE LOADED CORRECTLY, THANKS! 6 | -------------------------------------------------------------------------------- /templates/pagination.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /messages/0.9.3.txt: -------------------------------------------------------------------------------- 1 | CFML v0.9.3 Changelog: 2 | 3 | - Adds SQL string highlighting in `queryExecute()` 4 | https://github.com/jcberquist/sublimetext-cfml/issues/24 5 | -------------------------------------------------------------------------------- /src/component_parser/__init__.py: -------------------------------------------------------------------------------- 1 | from .parser import Parser 2 | from .cfml_components import parse_cfc_file_string 3 | 4 | __all__ = ["Parser", "parse_cfc_file_string"] 5 | -------------------------------------------------------------------------------- /messages/0.12.1.txt: -------------------------------------------------------------------------------- 1 | CFML v0.12.1 Changelog: 2 | 3 | - Fixed inconsistent docblock parsing function return type 4 | https://github.com/jcberquist/sublimetext-cfml/issues/30 5 | -------------------------------------------------------------------------------- /messages/0.2.1.txt: -------------------------------------------------------------------------------- 1 | CFML v0.2.1 Changelog: 2 | 3 | - No changes, just a reminder that after the v0.2.0 update a restart 4 | of Sublime Text is needed for the new features to function properly -------------------------------------------------------------------------------- /messages/0.9.2.txt: -------------------------------------------------------------------------------- 1 | CFML v0.9.2 Changelog: 2 | 3 | - Added basic syntax highlighting for JavaDoc style comments 4 | 5 | - Standardized syntax highlighting meta scope order for function calls 6 | -------------------------------------------------------------------------------- /src/custom_tag_index/__init__.py: -------------------------------------------------------------------------------- 1 | from .custom_tags import CustomTags 2 | 3 | 4 | custom_tag_index = CustomTags() 5 | 6 | 7 | def _plugin_loaded(): 8 | custom_tag_index.sync_projects() 9 | -------------------------------------------------------------------------------- /messages/0.10.1.txt: -------------------------------------------------------------------------------- 1 | CFML v0.10.1 Changelog: 2 | 3 | - Match `void` correctly in a function declaration that has no storage modifier 4 | https://github.com/jcberquist/sublimetext-cfml/issues/27 5 | -------------------------------------------------------------------------------- /templates/testbox/compacttext/legend.txt: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------- 2 | Legend: (P) = Passed, (-) = Skipped, (X) = Exception/Error, (!) = Failure 3 | -------------------------------------------------------------------------------- /messages/0.8.1.txt: -------------------------------------------------------------------------------- 1 | CFML v0.8.1 Changelog: 2 | 3 | - Updated function and tag completions to use latest cfdocs.org data 4 | 5 | - Minor syntax file updates (tag and function lists, typo fixes, tweaked a regex) 6 | -------------------------------------------------------------------------------- /templates/testbox/text/logo.txt: -------------------------------------------------------------------------------- 1 | _____ _ ____ 2 | |_ _|__ ___| |_| __ ) _____ __ 3 | | |/ _ \/ __| __| _ \ / _ \ \/ / 4 | | | __/\__ \ |_| |_) | (_) > < 5 | |_|\___||___/\__|____/ \___/_/\_\ 6 | -------------------------------------------------------------------------------- /templates/testbox/text/legend.txt: -------------------------------------------------------------------------------- 1 | ============================================================= 2 | Legend: 3 | ============================================================= 4 | (P) = Passed 5 | (-) = Skipped 6 | (X) = Exception/Error 7 | (!) = Failure 8 | -------------------------------------------------------------------------------- /messages/0.3.1.txt: -------------------------------------------------------------------------------- 1 | CFML v0.3.1 Changelog: 2 | 3 | - Added `cfargument` to the list of tags that do not need a closing tag 4 | 5 | - Syntax now correctly allows for a function return type of an object array 6 | (e.g. public User[] function getUser(){}) 7 | -------------------------------------------------------------------------------- /messages/0.10.2.txt: -------------------------------------------------------------------------------- 1 | CFML v0.10.2 Changelog: 2 | 3 | - When indexing files, the plugin now specifies utf-8 encoding for file reads 4 | rather than relying on the system default. See the discussion as to why at 5 | https://github.com/jcberquist/sublimetext-cfml/issues/26 6 | -------------------------------------------------------------------------------- /messages/0.29.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.29.0 Changelog: 2 | 3 | - Various syntax highlighting fixes and updates. Tag island syntax highlighting is now supported 4 | 5 | - bug fix for HTML completions being duplicated 6 | 7 | PLEASE RESTART SUBLIME TEXT TO ENSURE THESE CHANGES ARE LOADED CORRECTLY, THANKS! 8 | -------------------------------------------------------------------------------- /messages/0.9.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.9.0 Changelog: 2 | 3 | - Lucee 5 syntax highlighting: 4 | abstract/final modifiers 5 | lambda functions 6 | static constructor 7 | static methods and properties 8 | 9 | - Syntax scope updates to stay in sync with official Sublime Text packages 10 | 11 | - Small bug fixes 12 | -------------------------------------------------------------------------------- /metadata/symbols.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | scope 5 | source.cfml meta.function.cfml entity.name.function 6 | settings 7 | 8 | showInSymbolList 9 | 1 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /templates/testbox/text/bundle.txt: -------------------------------------------------------------------------------- 1 | ============================================================= 2 | ${path} (${totalduration}) 3 | ============================================================= 4 | ->[Suites/Specs: ${totalsuites}/${totalspecs}] 5 | ->[Pass: ${totalpass}] 6 | ->[Failures: ${totalfail}] 7 | ->[Errors: ${totalerror}] 8 | ->[Skipped: ${totalskipped}] 9 | 10 | -------------------------------------------------------------------------------- /templates/testbox/compacttext/bundle.txt: -------------------------------------------------------------------------------- 1 | ================================================================================= 2 | ${path} (${totalduration}) [Suites/Specs: ${totalsuites}/${totalspecs}] 3 | [Passed: ${totalpass}] [Failed: ${totalfail}] [Errors: ${totalerror}] [Skipped: ${totalskipped}] 4 | --------------------------------------------------------------------------------- 5 | -------------------------------------------------------------------------------- /metadata/symbols-indexed.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | scope 5 | source.cfml meta.function.cfml entity.name.function 6 | settings 7 | 8 | showInIndexedSymbolList 9 | 1 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /syntaxes/tests/syntax_test_rest_spread.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | a = {...b}; 4 | 5 | a = [...b]; 6 | 7 | 8 | function test(a, ...rest) { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /messages/0.4.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.4.0 Changelog: 2 | 3 | - Added support for ColdFusion style tags in cfscript 4 | e.g. 'cfcontent( type="text/html" );' 5 | 6 | - Added menu items for key binding files to the package settings menu 7 | 8 | - bug fixes for incorrect syntax highlighting, for more see 9 | https://github.com/jcberquist/sublimetext-cfml/commit/3f73f74c2ce7dd8b3d04fbf000a3c3e66fec108f 10 | -------------------------------------------------------------------------------- /templates/testbox/text/results.txt: -------------------------------------------------------------------------------- 1 | ============================================================= 2 | Global Stats (${totalduration}) 3 | ============================================================= 4 | ->[Bundles/Suites/Specs: ${totalbundles}/${totalsuites}/${totalspecs}] 5 | ->[Pass: ${totalpass}] 6 | ->[Failures: ${totalfail}] 7 | ->[Errors: ${totalerror}] 8 | ->[Skipped: ${totalskipped}] 9 | 10 | -------------------------------------------------------------------------------- /messages/0.12.2.txt: -------------------------------------------------------------------------------- 1 | CFML v0.12.2 Changelog: 2 | 3 | - Better error handling when indexing components 4 | The file path of a component that causes an indexing error 5 | is printed to the console along with the error traceback. 6 | 7 | - Added a command to the command palette that can be used to 8 | trigger a re-indexing of the project in the active window: 9 | `CFML: Index Active Project` 10 | -------------------------------------------------------------------------------- /messages/0.12.3.txt: -------------------------------------------------------------------------------- 1 | CFML v0.12.3 Changelog: 2 | 3 | - Updated attribute regex to correctly handle attributes 4 | having value strings that extend over multiple lines. 5 | https://github.com/jcberquist/sublimetext-cfml/issues/32 6 | 7 | - Indexed components that have `persistent` set to true now 8 | have accessors generated for their properties. 9 | https://github.com/jcberquist/sublimetext-cfml/issues/33 10 | -------------------------------------------------------------------------------- /templates/testbox/text/global_exception.txt: -------------------------------------------------------------------------------- 1 | GLOBAL BUNDLE EXCEPTION 2 | -> ${type}:${message}:${detail} 3 | ============================================================= 4 | STACKTRACE 5 | ============================================================= 6 | ${stacktrace} 7 | ============================================================= 8 | END STACKTRACE 9 | ============================================================= 10 | -------------------------------------------------------------------------------- /src/events.py: -------------------------------------------------------------------------------- 1 | event_listeners = {} 2 | 3 | 4 | def subscribe(event_name, callback): 5 | if event_name not in event_listeners: 6 | event_listeners[event_name] = [] 7 | event_listeners[event_name].append(callback) 8 | 9 | 10 | def trigger(event_name, *event_args): 11 | if event_name in event_listeners: 12 | for callback in event_listeners[event_name]: 13 | callback(*event_args) 14 | -------------------------------------------------------------------------------- /messages/0.9.1.txt: -------------------------------------------------------------------------------- 1 | CFML v0.9.1 Changelog: 2 | 3 | - Fixed a bug that caused the plugin to incorrectly determine the name of the 4 | folder that it was installed in. This came about due to recent changes in 5 | how plugins are loaded by ST (in build 3112, I think). Thanks to @mjhagen 6 | for reporting the issue to me. 7 | 8 | - various syntax highlighting updates, see the recent commits on GitHub for 9 | more details 10 | -------------------------------------------------------------------------------- /messages/install.txt: -------------------------------------------------------------------------------- 1 | CFML (ColdFusion/Lucee) package for Sublime Text 3 2 | https://github.com/jcberquist/sublimetext-cfml 3 | 4 | Thank you for installing this package. Please see the readme at the GitHub 5 | repository for more information about the features of this package. 6 | 7 | The GitHub repository is also the place to raise any issues you encounter. 8 | 9 | *IMPORTANT: Sublime Text 3 must be restarted for this package to function properly.* -------------------------------------------------------------------------------- /messages/0.12.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.12.0 Changelog: 2 | 3 | - Added `argumentCollection` completion when entering function call parameters 4 | https://github.com/jcberquist/sublimetext-cfml/issues/29 5 | 6 | - Component indexing has been updated to try and capture docblocks as well as 7 | inline hints. Hints, if present, will be displayed in the inline documentation 8 | for indexed components and methods. 9 | 10 | - Syntax highlighting bug fix 11 | -------------------------------------------------------------------------------- /messages/0.23.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.23.0 Changelog: 2 | 3 | - Updated some syntax highlighting scope names to match preferred scope names 4 | in default syntaxes. This will require a RESTART of Sublime Text to load 5 | properly, sorry! 6 | 7 | - Updated cfdocs.org popup styling 8 | 9 | - Added `DROP` to the list of strings that trigger SQL string highlighting 10 | https://github.com/jcberquist/sublimetext-cfml/issues/35#issuecomment-308840280 11 | -------------------------------------------------------------------------------- /src/plugins_/plugin.py: -------------------------------------------------------------------------------- 1 | class CFMLPlugin: 2 | def get_completion_docs(self, cfml_view): 3 | return None 4 | 5 | def get_completions(self, cfml_view): 6 | return None 7 | 8 | def get_goto_cfml_file(self, cfml_view): 9 | return None 10 | 11 | def get_inline_documentation(self, cfml_view, doc_type): 12 | return None 13 | 14 | def get_method_preview(self, cfml_view): 15 | return None 16 | -------------------------------------------------------------------------------- /metadata/symbols-banned.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | scope 5 | source.cfml meta.function-call, source.cfml meta.instance.constructor 6 | settings 7 | 8 | showInIndexedSymbolList 9 | 0 10 | showInSymbolList 11 | 0 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /messages/0.14.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.14.0 Changelog: 2 | 3 | - after typing `arguments.` in a function body you will now be offered 4 | available function arguments as completions 5 | https://github.com/jcberquist/sublimetext-cfml/issues/37 6 | 7 | - CFScript formatting bug fixes 8 | https://github.com/jcberquist/sublimetext-cfml/issues/40 9 | 10 | - Tag completions were updated to match latest cfdocs.org tag data 11 | https://github.com/jcberquist/sublimetext-cfml/pull/41 12 | -------------------------------------------------------------------------------- /messages/0.2.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.2.0 Changelog: 2 | 3 | - Added auto insert of closing tag when `>` is pressed 4 | This can be enabled via the `cfml_auto_insert_closing_tag` setting 5 | See the readme for more detail - https://github.com/jcberquist/sublimetext-cfml 6 | 7 | - Added ability to insert an extra newline + indent when `ENTER` is pressed between a CFML tag pair 8 | This can be enabled via the `cfml_between_tag_pair` setting 9 | See the readme for more detail - https://github.com/jcberquist/sublimetext-cfml -------------------------------------------------------------------------------- /templates/testbox/compacttext/global_exception.txt: -------------------------------------------------------------------------------- 1 | GLOBAL BUNDLE EXCEPTION 2 | -> ${type}:${message}:${detail} 3 | --------------------------------------------------------------------------------- 4 | STACKTRACE 5 | --------------------------------------------------------------------------------- 6 | ${stacktrace} 7 | --------------------------------------------------------------------------------- 8 | END STACKTRACE 9 | --------------------------------------------------------------------------------- 10 | -------------------------------------------------------------------------------- /templates/testbox/compacttext/results.txt: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------- 2 | | Passed | Failed | Errored | Skipped | Time | Bundles | Suites | Specs | 3 | --------------------------------------------------------------------------------- 4 | | ${totalpass} | ${totalfail} | ${totalerror} | ${totalskipped} | ${totalduration} | ${totalbundles} | ${totalsuites} | ${totalspecs} | 5 | --------------------------------------------------------------------------------- 6 | -------------------------------------------------------------------------------- /messages/0.11.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.11.0 Changelog: 2 | 3 | - Allow cfc folder variable names in .sublime-project files to contain periods 4 | https://github.com/jcberquist/sublimetext-cfml/issues/28 5 | 6 | - `testbox_tests_root` .sublime-project setting now supports relative paths just 7 | as the various `path` settings in a sublime project file do (relative to the 8 | location of the sublime project file). 9 | 10 | - Updates to work with changes introduced in the latest ST dev builds 11 | 12 | - Various bug fixes, see the recent commits on GitHub for more details 13 | -------------------------------------------------------------------------------- /src/plugins_/custom_tags/__init__.py: -------------------------------------------------------------------------------- 1 | from .completions import get_completions, get_goto_cfml_file 2 | from .documentation import get_inline_documentation 3 | 4 | from .. import plugin 5 | 6 | 7 | class CFMLPlugin(plugin.CFMLPlugin): 8 | def get_completions(self, cfml_view): 9 | return get_completions(cfml_view) 10 | 11 | def get_goto_cfml_file(self, cfml_view): 12 | return get_goto_cfml_file(cfml_view) 13 | 14 | def get_inline_documentation(self, cfml_view, doc_type): 15 | return get_inline_documentation(cfml_view, doc_type) 16 | -------------------------------------------------------------------------------- /src/plugins_/cfdocs/__init__.py: -------------------------------------------------------------------------------- 1 | from .. import plugin 2 | 3 | from .cfdocs import ( 4 | get_inline_documentation, 5 | get_completion_docs, 6 | get_goto_cfml_file 7 | ) 8 | 9 | 10 | class CFMLPlugin(plugin.CFMLPlugin): 11 | def get_completion_docs(self, cfml_view): 12 | return get_completion_docs(cfml_view) 13 | 14 | def get_inline_documentation(self, cfml_view, doc_type): 15 | return get_inline_documentation(cfml_view, doc_type) 16 | 17 | def get_goto_cfml_file(self, cfml_view): 18 | return get_goto_cfml_file(cfml_view) 19 | -------------------------------------------------------------------------------- /syntaxes/tests/syntax_test_lucee6.cfc: -------------------------------------------------------------------------------- 1 | // SYNTAX TEST "Packages/CFML/syntaxes/CFML.sublime-syntax" 2 | component { 3 | // <- source.cfml.script meta.class.declaration.cfml storage.type.class.cfml 4 | } 5 | 6 | component name="Sub" { 7 | // <- source.cfml.script meta.class.declaration.cfml storage.type.class.cfml 8 | function t() { 9 | var inline=new component { 10 | // ^^^ meta.instance.constructor.cfml keyword.operator.word.new.cfml 11 | // ^^^^^^^^^ meta.instance.constructor.cfml storage.type.class.cfml 12 | }; 13 | } 14 | } -------------------------------------------------------------------------------- /syntaxes/JavaScript (CFML).sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | name: JavaScript (CFML) 4 | scope: source.js.cfml 5 | version: 2 6 | hidden: true 7 | 8 | extends: Packages/JavaScript/JavaScript.sublime-syntax 9 | 10 | contexts: 11 | literal-string-template-begin: 12 | - meta_include_prototype: false 13 | - match: (?:({{identifier_name}})\s*)?(\`) 14 | captures: 15 | 1: variable.function.tagged-template.js 16 | 2: meta.string.template.js string.quoted.other.js punctuation.definition.string.begin.js 17 | set: literal-string-template-content 18 | - include: immediately-pop 19 | -------------------------------------------------------------------------------- /src/plugins_/fw1/__init__.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from . import fw1 3 | from .. import plugin 4 | 5 | 6 | class CFMLPlugin(plugin.CFMLPlugin): 7 | def get_completions(self, cfml_view): 8 | if cfml_view.type == "script": 9 | return fw1.get_script_completions(cfml_view) 10 | elif cfml_view.type == "dot": 11 | return fw1.get_dot_completions(cfml_view) 12 | return None 13 | 14 | def get_inline_documentation(self, cfml_view, doc_type): 15 | return fw1.get_inline_documentation(cfml_view, doc_type) 16 | 17 | 18 | def _plugin_loaded(): 19 | sublime.set_timeout_async(fw1.load) 20 | -------------------------------------------------------------------------------- /messages/0.3.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.3.0 Changelog: 2 | 3 | - Added tmPreferences file that marks CFML files as source files for sidebar icons 4 | This ensures that the file_type_source icon in themes is applied to CFML files 5 | 6 | See https://github.com/jcberquist/sublimetext-cfml/blob/master/metadata/cfml-icon.tmPreferences 7 | If a copy of this file is placed in your User package folder, you can override 8 | `file_type_source` with another icon type, depending on what your active theme supports. 9 | For example, Material Theme (https://github.com/equinusocio/material-theme) has two 10 | icon types for CFML files: `file_type_cfc` and `file_type_cfm`. -------------------------------------------------------------------------------- /messages/0.5.1.txt: -------------------------------------------------------------------------------- 1 | CFML v0.5.1 Changelog: 2 | 3 | - Added 'cfdump', 'cfinclude', and 'cfprocessingdirective' to the default list 4 | of tags that do not receive a closing tag 5 | 6 | - The default mouse binding on the Mac for opening component files has been 7 | changed from CMD+ALT+Click to CTRL+ALT+Click. This was done because 8 | CMD+ALT+Click is the default mouse binding for selecting text by column 9 | on the Mac. 10 | 11 | - The click to open component file and F1 documentation commands should now work 12 | on any quoted string containing a dot path to a component. (Assuming the correct 13 | mapping has been set up in a project file.) 14 | -------------------------------------------------------------------------------- /settings/CFML.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "auto_complete_triggers": [ 3 | {"selector": "source.cfml.script - string", "characters": "."}, 4 | {"selector": "source.cfml.script meta.function-call.support.createcomponent.cfml string.quoted", "characters": "."}, 5 | {"selector": "meta.class.inheritance.cfml", "characters": "."}, 6 | {"selector": "meta.tag.cfml - string - source.cfml.script", "characters": " "}, 7 | {"selector": "meta.tag.custom.cfml - string - source.cfml.script", "characters": ": "}, 8 | {"selector": "meta.tag.script.cfml - string, meta.tag.script.cf.cfml - string, meta.class.declaration.cfml - string", "characters": " "} 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /src/plugins_/applicationcfc/__init__.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from . import appcfc 3 | from .. import plugin 4 | 5 | 6 | def _plugin_loaded(): 7 | sublime.set_timeout_async(appcfc.load) 8 | 9 | 10 | class CFMLPlugin(plugin.CFMLPlugin): 11 | def get_completions(self, cfml_view): 12 | if cfml_view.type == 'script': 13 | return appcfc.get_script_completions(cfml_view) 14 | elif cfml_view.type == 'dot': 15 | return appcfc.get_dot_completions(cfml_view) 16 | return None 17 | 18 | def get_inline_documentation(self, cfml_view, doc_type): 19 | return appcfc.get_inline_documentation(cfml_view, doc_type) 20 | -------------------------------------------------------------------------------- /syntaxes/tests/syntax_test_java.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | classInstance = java { public class class1 { } } 4 | 5 | 6 | function test(int i) a="b" type="java" { return i*2; } 7 | 8 | 9 | 10 | 11 | 12 | import java.io.*; 13 | public class Harmless { } 14 | -------------------------------------------------------------------------------- /src/plugins_/applicationcfc/json/methods.json: -------------------------------------------------------------------------------- 1 | { 2 | "onAbort": "(string targetPage) {$0}", 3 | "onApplicationEnd": "(struct applicationScope) {$0}", 4 | "onApplicationStart": "() {$0}", 5 | "onCFCRequest": "(string cfcName, string methodName, struct args) {$0}", 6 | "onDebug": "(struct debuggingData) {$0}", 7 | "onError": "(struct exception, string eventName) {$0}", 8 | "onMissingTemplate": "(string targetPage) {$0}", 9 | "onRequest": "(string targetPage) {$0}", 10 | "onRequestEnd": "(string targetPage) {$0}", 11 | "onRequestStart": "(string targetPage) {$0}", 12 | "onSessionEnd": "(struct applicationScope, struct sessionScope) {$0}", 13 | "onSessionStart": "() {$0}" 14 | } -------------------------------------------------------------------------------- /messages/0.31.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.31.0 Changelog: 2 | 3 | - Updated syntax highlighting to work with the updated features in the Sublime 4 | Text 4 syntax highlighting engine, as well as the latest default syntax 5 | highlighting files released in stable build 4186 6 | 7 | Please note: these changes are significant and thus might introduce syntax 8 | highlighting errors, but the latest JavaScript syntax file bundled with 9 | Sublime Text causes the CFML syntax to break completely, so I decided to 10 | release this now to allow CFML syntax highlighting to continue to 11 | function. 12 | 13 | PLEASE RESTART SUBLIME TEXT TO ENSURE THESE CHANGES ARE LOADED CORRECTLY, THANKS! 14 | -------------------------------------------------------------------------------- /metadata/cfscript-indent.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | name 5 | CFScript Indent 6 | scope 7 | source.cfml.script 8 | settings 9 | 10 | decreaseIndentPattern 11 | ^(.*\*/)?\s*\}.*$ 12 | increaseIndentPattern 13 | ^.*\{[^}"']*$ 14 | 15 | bracketIndentNextLinePattern 16 | (?x) 17 | ^ \s* \b(if|while|else)\b [^;]* $ 18 | | ^ \s* \b(for)\b .* $ 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /messages/0.17.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.17.0 Changelog: 2 | 3 | - Added completions for built-in function parameters where there is list of 4 | possible values available in cfdocs.org data. For example, when entering an 5 | algorithm for the `hmac()` function, a list of possible algorithms will be 6 | offered. 7 | 8 | - This release will be the last release that supports Sublime Text build 3083. At 9 | this point there have been a number of beta releases beyond 3083, so everyone 10 | has access to a ST version that supports the newer `.sublime-syntax` format for 11 | syntax highlighting. It is time consuming to support the `.tmLanguage` variant of 12 | the CFML syntax, and it no longer seems necessary. 13 | -------------------------------------------------------------------------------- /messages/0.4.2.txt: -------------------------------------------------------------------------------- 1 | CFML v0.4.2 Changelog: 2 | 3 | - Fixed some bugs in the Testbox test runner code 4 | 5 | - After a tag attribute value completion has been inserted (between ""), 6 | the cursor is now automatically advanced past the second double quote 7 | 8 | - Added a package setting, "cfdocs_path", which is for use when you have a 9 | copy of the cfdocs.org GitHub repository on your file system (or at least 10 | its data directory). It should be set to the local file system path to the 11 | data directory of the cfdocs.org git repository. For example: 12 | 13 | "cfdocs_path": "C:/github/cfdocs/data/en/" 14 | 15 | When this is set, the F1 documentation command will load cfdocs.org data from 16 | the file system instead of fetching it via http request. 17 | -------------------------------------------------------------------------------- /src/plugins_/testbox/__init__.py: -------------------------------------------------------------------------------- 1 | from . import test_runner 2 | from . import testbox 3 | from .test_runner import TestboxCommand 4 | from .testbox_spec_outline import TestboxSpecOutlineCommand 5 | from .. import plugin 6 | 7 | 8 | class CFMLPlugin(plugin.CFMLPlugin): 9 | def get_completions(self, cfml_view): 10 | if cfml_view.type == "script": 11 | return testbox.get_script_completions(cfml_view) 12 | elif cfml_view.type == "dot": 13 | return testbox.get_dot_completions(cfml_view) 14 | return None 15 | 16 | def get_inline_documentation(self, cfml_view, doc_type): 17 | return testbox.get_inline_documentation(cfml_view, doc_type) 18 | 19 | 20 | def _plugin_loaded(): 21 | test_runner._plugin_loaded() 22 | testbox._plugin_loaded() 23 | -------------------------------------------------------------------------------- /messages/0.6.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.6.0 Changelog: 2 | 3 | - The CFScript syntax has been overhauled. 4 | It is now a mod on top of Will Bond's amazing JavaScript syntax rewrite at 5 | https://github.com/sublimehq/Packages/blob/master/JavaScript/JavaScript.sublime-syntax 6 | 7 | The new syntax is more accurate, and file parsing is significantly faster. The 8 | previous version of the CFScript syntax took ~150ms to parse FW/1's `one.cfc` 9 | (at 2883 lines) on my desktop and ~210ms on my laptop. The new version takes 10 | ~45ms on my desktop and ~70ms on my laptop. 11 | 12 | - Unquoted tag attribute values should now correctly highlight CFScript contained 13 | between `#` characters 14 | 15 | - The matching of ColdFusion style tags in script has been restricted to match only 16 | actual tag names 17 | -------------------------------------------------------------------------------- /syntaxes/tests/syntax_test_destructure.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | var [ a, b ] = [ 1, 2 ]; 4 | 5 | [ a, b ] = [ 1, 2 ]; 6 | 7 | 8 | var { a, b } = { a: 1, b: 2 }; 9 | 10 | 11 | ({ a, b } = { a: 1, b: 2 }); 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | from . import cfml_plugins 2 | from . import completions 3 | from . import component_index 4 | from . import custom_tag_index 5 | from . import events 6 | from . import goto_cfml_file 7 | from . import inline_documentation 8 | from . import method_preview 9 | from . import utils 10 | from . import commands 11 | from . import formatting 12 | 13 | command_list = [] 14 | 15 | 16 | def _plugin_loaded(): 17 | for k, v in globals().items(): 18 | try: 19 | if "_plugin_loaded" in v.__dict__: 20 | v._plugin_loaded() 21 | except Exception: 22 | pass 23 | 24 | 25 | # load commands 26 | for k in dir(): 27 | try: 28 | v = globals()[k] 29 | for a in dir(v): 30 | if a.endswith("Command"): 31 | command_list.append(v.__dict__[a]) 32 | except Exception: 33 | pass 34 | -------------------------------------------------------------------------------- /src/plugins_/basecompletions/__init__.py: -------------------------------------------------------------------------------- 1 | from .. import plugin 2 | from . import basecompletions 3 | 4 | 5 | class CFMLPlugin(plugin.CFMLPlugin): 6 | def get_completions(self, cfml_view): 7 | if cfml_view.type == "script": 8 | return basecompletions.get_script_completions(cfml_view) 9 | elif cfml_view.type == "dot": 10 | return basecompletions.get_dot_completions(cfml_view) 11 | elif cfml_view.type == "tag_attributes": 12 | return basecompletions.get_tag_attributes(cfml_view) 13 | elif cfml_view.type == "tag": 14 | return basecompletions.get_tags(cfml_view) 15 | return None 16 | 17 | def get_inline_documentation(self, cfml_view, doc_type): 18 | return basecompletions.get_inline_documentation(cfml_view, doc_type) 19 | 20 | 21 | def _plugin_loaded(): 22 | basecompletions.load_completions() 23 | -------------------------------------------------------------------------------- /messages/0.25.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.25.0 Changelog: 2 | 3 | - Added a TestBox Spec component outline command. Similar to the symbol list, 4 | this command collects all the title param strings in a spec component and 5 | displays them in an indented list for easy navigation and search. My thanks 6 | to Eric Peterson (@elpete) for suggesting and testing this. 7 | 8 | In order to use this you need to run the `testbox_spec_outline` command when 9 | a TestBox Spec component is open. I have not added a default key binding for 10 | this command, but an example binding you can add to your user key binding 11 | file is given here: 12 | 13 | { 14 | "keys": ["ctrl+alt+t"], 15 | "command": "testbox_spec_outline", 16 | "context": [ 17 | {"key": "selector", "operand": "source.cfml", "operator": "equal"} 18 | ] 19 | } 20 | 21 | - Syntax highlighting fixes, see the recent commits on GitHub for more details 22 | -------------------------------------------------------------------------------- /syntaxes/CFScript (CFML) Tags.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | name: CFScript (CFML) Tags 4 | scope: source.cfml.script.tags 5 | version: 2 6 | hidden: true 7 | 8 | extends: CFScript (CFML).sublime-syntax 9 | 10 | contexts: 11 | angle-bracket-pop: 12 | - match: (?=/?>) 13 | pop: 1 14 | 15 | expression-break: 16 | - meta_prepend: true 17 | - include: angle-bracket-pop 18 | 19 | expressions: 20 | - meta_prepend: true 21 | - include: angle-bracket-pop 22 | 23 | expressions-no-comma: 24 | - meta_prepend: true 25 | - include: angle-bracket-pop 26 | 27 | parenthesized-expression: 28 | - match: \( 29 | scope: punctuation.section.group.begin.cfml 30 | set: 31 | - meta_scope: meta.group.cfml 32 | - match: \) 33 | scope: punctuation.section.group.end.cfml 34 | pop: 1 35 | - include: angle-bracket-pop 36 | - match: (?=\S) 37 | push: expression 38 | -------------------------------------------------------------------------------- /messages/0.28.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.28.0 Changelog: 2 | 3 | - Updated the "CFML: Toggle Color Scheme Styles" command to use of the new 4 | `.sublime-color-scheme` format and customizations. See the readme for more 5 | information as well as the official documentation: 6 | https://www.sublimetext.com/docs/3/color_schemes.html#customization 7 | 8 | - Hovering over a CFML tag attribute or using F1 on it will now show specifically 9 | the documentation for that tag attribute instead of for the tag in general. 10 | 11 | - When completion docs are enabled, tag attribute documentation will now be shown 12 | while entering in tag attribute values. 13 | 14 | - Syntax highlighting support has been added for ordered structs and ACF2018 15 | typed arrays. 16 | 17 | - Basic function completions have been updated to be based on a more recent version 18 | of cfdocs.org function data. 19 | 20 | PLEASE RESTART SUBLIME TEXT TO ENSURE THESE CHANGES ARE LOADED CORRECTLY, THANKS! 21 | -------------------------------------------------------------------------------- /src/plugins_/cfcs/completions.py: -------------------------------------------------------------------------------- 1 | from . import cfcs 2 | 3 | 4 | def get_dot_completions(cfml_view): 5 | if not cfml_view.project_name or len(cfml_view.dot_context) == 0: 6 | return None 7 | # check for known cfc name 8 | cfc_name, cfc_name_region = cfcs.search_dot_context_for_cfc( 9 | cfml_view.project_name, cfml_view.dot_context 10 | ) 11 | if cfc_name: 12 | return cfml_view.CompletionList( 13 | cfcs.get_cfc_completions(cfml_view.project_name, cfc_name), 1, True 14 | ) 15 | 16 | # also check for getter being used to access property 17 | symbol = cfml_view.dot_context[-1] 18 | if symbol.is_function and symbol.name.startswith("get"): 19 | if cfcs.has_cfc(cfml_view.project_name, symbol.name[3:]): 20 | return cfml_view.CompletionList( 21 | cfcs.get_cfc_completions(cfml_view.project_name, symbol.name[3:]), 22 | 1, 23 | True, 24 | ) 25 | 26 | return None 27 | -------------------------------------------------------------------------------- /src/cfml_plugins.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | from .plugins_.plugin import CFMLPlugin 3 | 4 | directory = [ 5 | "applicationcfc", 6 | "basecompletions", 7 | "cfcs", 8 | "cfdocs", 9 | "custom_tags", 10 | "dotpaths", 11 | "entities", 12 | "fw1", 13 | "in_file_completions", 14 | "testbox", 15 | ] 16 | 17 | plugins = [] 18 | 19 | 20 | for p in directory: 21 | m = importlib.import_module(".plugins_." + p, __package__) 22 | globals()[p] = m 23 | for a in dir(m): 24 | v = m.__dict__[a] 25 | if a.endswith("Command"): 26 | globals()[a] = v 27 | elif a == "CFMLPlugin": 28 | try: 29 | if v.__bases__ and issubclass(v, CFMLPlugin): 30 | plugins.append(v()) 31 | except AttributeError: 32 | pass 33 | 34 | 35 | def _plugin_loaded(): 36 | for p in directory: 37 | m = globals()[p] 38 | if "_plugin_loaded" in m.__dict__: 39 | m._plugin_loaded() 40 | -------------------------------------------------------------------------------- /src/plugins_/in_file_completions/__init__.py: -------------------------------------------------------------------------------- 1 | from .. import plugin 2 | from . import in_file_completions 3 | from .documentation import ( 4 | get_inline_documentation, 5 | get_goto_cfml_file, 6 | get_completion_docs, 7 | get_method_preview, 8 | ) 9 | 10 | 11 | class CFMLPlugin(plugin.CFMLPlugin): 12 | def get_completion_docs(self, cfml_view): 13 | return get_completion_docs(cfml_view) 14 | 15 | def get_completions(self, cfml_view): 16 | if cfml_view.type == "script": 17 | return in_file_completions.get_script_completions(cfml_view) 18 | elif cfml_view.type == "dot": 19 | return in_file_completions.get_dot_completions(cfml_view) 20 | return None 21 | 22 | def get_goto_cfml_file(self, cfml_view): 23 | return get_goto_cfml_file(cfml_view) 24 | 25 | def get_inline_documentation(self, cfml_view, doc_type): 26 | return get_inline_documentation(cfml_view, doc_type) 27 | 28 | def get_method_preview(self, cfml_view): 29 | return get_method_preview(cfml_view) 30 | -------------------------------------------------------------------------------- /metadata/cfml.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | name 5 | cfml 6 | scope 7 | embedding.cfml -source.cfml.script, embedding.cfml text.html.cfml 8 | settings 9 | 10 | shellVariables 11 | 12 | 13 | name 14 | TM_COMMENT_START 15 | value 16 | <!--- 17 | 18 | 19 | name 20 | TM_COMMENT_END 21 | value 22 | ---> 23 | 24 | 25 | name 26 | TM_LINE_TERMINATOR 27 | value 28 | > 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/plugins_/cfcs/__init__.py: -------------------------------------------------------------------------------- 1 | from .cfcs import build_project_cfcs 2 | from .completions import get_dot_completions 3 | from .documentation import ( 4 | get_inline_documentation, 5 | get_goto_cfml_file, 6 | get_completions_doc, 7 | get_method_preview, 8 | ) 9 | from .di import CfmlDiPropertyCommand 10 | from ...component_index import component_index 11 | from .. import plugin 12 | 13 | 14 | class CFMLPlugin(plugin.CFMLPlugin): 15 | def get_completion_docs(self, cfml_view): 16 | return get_completions_doc(cfml_view) 17 | 18 | def get_completions(self, cfml_view): 19 | if cfml_view.type == "dot": 20 | return get_dot_completions(cfml_view) 21 | return None 22 | 23 | def get_goto_cfml_file(self, cfml_view): 24 | return get_goto_cfml_file(cfml_view) 25 | 26 | def get_inline_documentation(self, cfml_view, doc_type): 27 | return get_inline_documentation(cfml_view, doc_type) 28 | 29 | def get_method_preview(self, cfml_view): 30 | return get_method_preview(cfml_view) 31 | 32 | 33 | component_index.subscribe(build_project_cfcs) 34 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /messages/0.8.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.8.0 Changelog: 2 | 3 | - Support for completions and documentation in the active CFC file 4 | 5 | When editing a CFC, completions for the methods of that component are 6 | now offered; this includes offering completions after typing `this.`. 7 | If the component extends another component and that parent component is 8 | contained in a project index, then you will also be offered completions 9 | from the parent component, as well as completions after typing `super.`. 10 | 11 | The F1 documentation command and CTRL-ALT-Click can be used on component 12 | method calls, as well as on the `this` and `super` keywords. 13 | 14 | - Source code preview when using the F1 documentation command on CFC methods has 15 | been added. This is somewhat experimental, and feedback is welcome. Please note 16 | that it will currently truncate longer functions in order to avoid crashing 17 | Sublime Text. 18 | 19 | - Fixed several issues reported on GitHub 20 | 21 | - Updated regex patterns used in syntax to remove those not compatible with Sublime 22 | Text's new regex engine 23 | 24 | - Bug fix for default custom tag project data 25 | -------------------------------------------------------------------------------- /syntaxes/tests/syntax_test_javadoc.cfc: -------------------------------------------------------------------------------- 1 | // SYNTAX TEST "Packages/CFML/syntaxes/CFML.sublime-syntax" 2 | /** 3 | This is a description of the component 4 | // <- embedding.cfml source.cfml.script comment.block.documentation.cfml text.html 5 | // ^ -text.html 6 | * @attribute and some hint text 7 | // <- embedding.cfml source.cfml.script comment.block.documentation.cfml 8 | // <- keyword.other.documentation.cfml punctuation.definition.keyword.cfml 9 | // ^ -punctuation.definition.keyword.cfml 10 | // ^ -keyword.other.documentation.cfml 11 | // ^ text.html 12 | // ^ -text.html 13 | @another.attribute and some hint text 14 | // <- embedding.cfml source.cfml.script comment.block.documentation.cfml keyword.other.documentation.cfml punctuation.definition.keyword.cfml 15 | // <- -punctuation.definition.keyword.cfml 16 | // ^ -keyword.other.documentation.cfml 17 | // ^ text.html 18 | // ^ -text.html 19 | */ 20 | component { 21 | 22 | /**/ a; 23 | // ^ source.cfml.script variable.other.readwrite.cfml - comment 24 | 25 | } 26 | -------------------------------------------------------------------------------- /templates/method_preview.html: -------------------------------------------------------------------------------- 1 | 2 | 46 |
47 | 51 |
${body}
52 |
53 | ${pagination} 54 | 55 | -------------------------------------------------------------------------------- /messages/0.19.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.19.0 Changelog: 2 | 3 | - Removed `cfml-icon.tmPreferences` which set a default sidebar icon for 4 | CFML files. This package is now supported by the "zz File Icons" package 5 | (https://packagecontrol.io/packages/zz%20File%20Icons) and specifying an 6 | icon in the package conflicted with it. 7 | 8 | - When running TestBox tests via the build menu, the root folder(s) of your 9 | project will be searched for a `box.json` file containing a testbox runner 10 | setting, if you do not specify a testbox runner in your project file. 11 | Please see https://github.com/jcberquist/sublimetext-cfml#testbox for more 12 | details. IMPORTANT, if you have been using the TestBox runner, this update 13 | includes a BREAKING CHANGE with regard to the structure of the TestBox 14 | settings. See also: 15 | https://github.com/jcberquist/sublimetext-cfml/issues/54 16 | 17 | - Custom tag indexing support has been updated to allow the omission of the 18 | `prefix` key when specifying custom tag folders in your project file. When 19 | this is done, custom tags in that folder will be offered as `` 20 | style completions. 21 | https://github.com/jcberquist/sublimetext-cfml/issues/55 22 | -------------------------------------------------------------------------------- /src/plugins_/entities/__init__.py: -------------------------------------------------------------------------------- 1 | from .completions import ( 2 | build_project_entities, 3 | get_script_completions, 4 | get_dot_completions, 5 | ) 6 | from .documentation import ( 7 | get_inline_documentation, 8 | get_goto_cfml_file, 9 | get_completions_doc, 10 | get_method_preview, 11 | ) 12 | from ...component_index import component_index 13 | from .. import plugin 14 | 15 | 16 | class CFMLPlugin(plugin.CFMLPlugin): 17 | def get_completion_docs(self, cfml_view): 18 | return get_completions_doc(cfml_view) 19 | 20 | def get_completions(self, cfml_view): 21 | if cfml_view.type == "script": 22 | return get_script_completions(cfml_view) 23 | elif cfml_view.type == "dot": 24 | return get_dot_completions(cfml_view) 25 | return None 26 | 27 | def get_goto_cfml_file(self, cfml_view): 28 | return get_goto_cfml_file(cfml_view) 29 | 30 | def get_inline_documentation(self, cfml_view, doc_type): 31 | return get_inline_documentation(cfml_view, doc_type) 32 | 33 | def get_method_preview(self, cfml_view): 34 | return get_method_preview(cfml_view) 35 | 36 | 37 | component_index.subscribe(build_project_entities) 38 | -------------------------------------------------------------------------------- /src/component_index/__init__.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from .component_index import ComponentIndex 3 | from .documentation import ( 4 | build_documentation, 5 | build_method_documentation, 6 | build_method_preview, 7 | build_method_preview_doc, 8 | build_function_call_params_doc, 9 | ) 10 | from .completions import build_file_completions 11 | from .navigate_to_method import CfmlNavigateToMethodCommand 12 | 13 | 14 | __all__ = ["CfmlNavigateToMethodCommand", "builders", "component_index"] 15 | 16 | 17 | build_functions = [ 18 | "build_documentation", 19 | "build_method_documentation", 20 | "build_method_preview", 21 | "build_method_preview_doc", 22 | "build_function_call_params_doc", 23 | "build_file_completions", 24 | ] 25 | 26 | 27 | Builders = namedtuple("Builders", build_functions) 28 | 29 | 30 | component_index = ComponentIndex() 31 | builders = Builders( 32 | build_documentation, 33 | build_method_documentation, 34 | build_method_preview, 35 | build_method_preview_doc, 36 | build_function_call_params_doc, 37 | build_file_completions, 38 | ) 39 | 40 | 41 | def _plugin_loaded(): 42 | component_index.init_parser() 43 | component_index.sync_projects() 44 | -------------------------------------------------------------------------------- /metadata/cfscript.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | name 5 | cfscript 6 | scope 7 | source.cfml.script 8 | settings 9 | 10 | shellVariables 11 | 12 | 13 | name 14 | TM_COMMENT_START 15 | value 16 | // 17 | 18 | 19 | name 20 | TM_COMMENT_START_2 21 | value 22 | /* 23 | 24 | 25 | name 26 | TM_COMMENT_END_2 27 | value 28 | */ 29 | 30 | 31 | name 32 | TM_LINE_TERMINATOR 33 | value 34 | ; 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /messages/0.22.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.22.0 Changelog: 2 | 3 | - Moved from separate menu entries for opening default package setting files and 4 | user package setting files to one entry for each that opens the default and 5 | user files side by side (as Sublime Text itself now does for its settings 6 | files). 7 | 8 | - Inline documentation popups now make use of styling that adapts to the current 9 | color scheme. (e.g. Color schemes with a dark background will now get popups 10 | with a dark background as well.) You can disable this behavior by setting the 11 | package setting `adaptive_doc_styles` to false in your user package settings. 12 | 13 | - Added a new testbox reporter: "compacttext". There are now two options for the 14 | text output when tests are run, "text" and "compacttext". This is controlled 15 | by the setting `testbox.reporter` and can be set in your package settings, project 16 | settings, or in a box.json file at the root of your project. The "compacttext" 17 | style output is courtesy of work done by John Whish (@aliaspooryorik). 18 | 19 | - Added `TRUNCATE TABLE` and `BULK INSERT` to the list of strings that trigger 20 | SQL string highlighting 21 | https://github.com/jcberquist/sublimetext-cfml/issues/35#issuecomment-294524021 22 | -------------------------------------------------------------------------------- /messages/0.5.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.5.0 Changelog: 2 | 3 | - Breaking change: 4 | The per project "model_completion_folders" setting has been replaced with a "cfc_folders" setting. 5 | This update includes improvements to how CFCs are indexed, and allows for greater customization of 6 | the variable names for which it will provide CFC method completions. For complete information on 7 | this update please see: 8 | https://github.com/jcberquist/sublimetext-cfml#cfc-indexing-and-dot-paths 9 | 10 | The short version: 11 | If you were using "model_completion_folders", to restore the current behavior you will need to make 12 | the following change: 13 | 14 | from 15 | "model_completion_folders": [ "C:/full/path/to/model" ] 16 | 17 | to 18 | "cfc_folders": [{"path": "C:/full/path/to/model", "variable_names": ["{cfc}","{cfc}{cfc_folder_singularized}"], "accessors": false}] 19 | 20 | - Added support for per project mappings 21 | These are used to convert file system paths to CFC dot paths and the reverse. They are also used 22 | in conjunction with CFC folder indexing to resolve dot paths in the extends attribute of indexed 23 | CFCs. For complete information on this feature please see: 24 | https://github.com/jcberquist/sublimetext-cfml#cfc-indexing-and-dot-paths 25 | -------------------------------------------------------------------------------- /messages/0.10.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.10.0 Changelog: 2 | 3 | - Added `cflocation` to the default list of tags that do not need a closing tag 4 | https://github.com/jcberquist/sublimetext-cfml/issues/25 5 | 6 | - Relative paths are now supported in `.sublime-project` files for paths specified 7 | for mappings, cfc folders, and custom tags. When a relative path is used, it is 8 | understood to be relative to the location of the `.sublime-project` file. 9 | (Just as Sublime Text itself resolves relative folder paths in project files.) 10 | https://github.com/jcberquist/sublimetext-cfml/issues/26 11 | 12 | - Added syntax highlighting support for function parameter hints and metadata 13 | specified inline with the parameter (e.g. `required string varName hint="hint"`) 14 | 15 | - Added a new command (bound by default to SHIFT+ALT+d), that when run will insert a 16 | property into a component based on a project's indexed component variable names. 17 | If the cursor's current position can be resolved to a component name, that name will 18 | be set in the property, otherwise, a list of the project's indexed component names will 19 | be offered from which a component name can be chosen. See `Inject Property Command` in 20 | https://github.com/jcberquist/sublimetext-cfml#cfc-indexing-and-dot-paths 21 | -------------------------------------------------------------------------------- /messages/0.27.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.27.0 Changelog: 2 | 3 | - Using `CTRL+ALT+Left Click` on builtin functions and tags will now open the 4 | cfdocs.org page for that function or tag in your default browser. 5 | 6 | BREAKING CHANGE 7 | 8 | - The CFScript formatting feature has been substantially overhauled. 9 | 10 | The format settings file structure has been flattened so that it no longer 11 | contains any nested objects. Instead those settings are now contained in top 12 | level keys that use periods to denote namespaces. (So, for example, you will 13 | see keys such as "array.padding_inside".) This change allows for more 14 | granular overriding of the default settings. 15 | 16 | In addition, this change will make the format settings compatible with 17 | "PackageDev" (https://packagecontrol.io/packages/PackageDev). PackageDev 18 | provides completions and popup docs for settings files, as well as more 19 | targeted syntax highlighting. (For this reason I highly recommend it, even if 20 | you don't intend to develop any ST packages.) 21 | 22 | However, this means that if you are currently formatting CFScript with 23 | this package, your current user defined settings WILL NOT WORK until you 24 | update them to this new structure. 25 | 26 | Please read through the default settings file to see the updated formatting 27 | options. 28 | -------------------------------------------------------------------------------- /src/plugins_/dotpaths/__init__.py: -------------------------------------------------------------------------------- 1 | from .completions import ( 2 | build_project_map, 3 | get_script_completions, 4 | get_dot_completions, 5 | get_tag_attributes, 6 | ) 7 | from .documentation import ( 8 | get_inline_documentation, 9 | get_goto_cfml_file, 10 | get_completions_doc, 11 | get_method_preview, 12 | ) 13 | from ...component_index import component_index 14 | 15 | from .. import plugin 16 | 17 | 18 | class CFMLPlugin(plugin.CFMLPlugin): 19 | def get_completion_docs(self, cfml_view): 20 | return get_completions_doc(cfml_view) 21 | 22 | def get_completions(self, cfml_view): 23 | if cfml_view.type == "script": 24 | return get_script_completions(cfml_view) 25 | elif cfml_view.type == "dot": 26 | return get_dot_completions(cfml_view) 27 | elif cfml_view.type == "tag_attributes": 28 | return get_tag_attributes(cfml_view) 29 | return None 30 | 31 | def get_goto_cfml_file(self, cfml_view): 32 | return get_goto_cfml_file(cfml_view) 33 | 34 | def get_inline_documentation(self, cfml_view, doc_type): 35 | return get_inline_documentation(cfml_view, doc_type) 36 | 37 | def get_method_preview(self, cfml_view): 38 | return get_method_preview(cfml_view) 39 | 40 | 41 | component_index.subscribe(build_project_map) 42 | -------------------------------------------------------------------------------- /syntaxes/testbox.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | name: TestBox 4 | scope: testbox 5 | hidden: true 6 | contexts: 7 | main: 8 | - match: '^\|(?=\s\d+\s+\|)' 9 | push: [skipped, error, failure, success] 10 | - match: 'Pass:|\[Passed:' 11 | push: success 12 | - match: 'Failures:|\[Failed:' 13 | push: failure 14 | - match: 'Errors:|\[Errors:' 15 | push: error 16 | - match: 'Skipped:|\[Skipped:' 17 | push: skipped 18 | - match: \(([+P])\) 19 | captures: 20 | 1: testbox.success 21 | - match: \((!)\) 22 | captures: 23 | 1: testbox.failure 24 | - match: \((X)\) 25 | captures: 26 | 1: testbox.error 27 | - match: \((-)\) 28 | captures: 29 | 1: testbox.skipped 30 | - match: \S.*testbox[\\/]system.*:\d+$ 31 | scope: testbox.stack.testbox 32 | - match: \S.*:\d+$ 33 | scope: testbox.stack 34 | success: 35 | - match: '[1-9]\d*' 36 | scope: testbox.success 37 | - match: \S 38 | pop: true 39 | failure: 40 | - match: '[1-9]\d*' 41 | scope: testbox.failure 42 | - match: \S 43 | pop: true 44 | error: 45 | - match: '[1-9]\d*' 46 | scope: testbox.error 47 | - match: \S 48 | pop: true 49 | skipped: 50 | - match: '[1-9]\d*' 51 | scope: testbox.skipped 52 | - match: \S 53 | pop: true 54 | -------------------------------------------------------------------------------- /messages/0.7.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.7.0 Changelog: 2 | 3 | - adds basic support for custom tags as used with a cfimport tag and a custom tag prefix 4 | 5 | The completions are offered on a per project basis. To set them up add a "custom_tag_folders" 6 | setting to your `.sublime-project` file. It should be an array of JSON objects where each object 7 | has two required keys: "path" and "prefix". "path" should be a full file path to a folder 8 | containing custom tags, and "prefix" should be the prefix you intend to import them with. 9 | 10 | For example: 11 | 12 | "custom_tag_folders": [ 13 | {"path": "/path/to/tag/folder", "prefix": "page"} 14 | ] 15 | 16 | Once this is done, "page" will be offered as a custom tag prefix completion, and then after 17 | entering `` (case insensitive) in custom tag files in order to determine whether or not 26 | to auto close the custom tag. 27 | -------------------------------------------------------------------------------- /src/component_index/navigate_to_method.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | from .. import utils 4 | 5 | 6 | class CfmlNavigateToMethodCommand(sublime_plugin.WindowCommand): 7 | 8 | def run(self, file_path, href): 9 | 10 | if len(file_path) > 0: 11 | index_locations = self.window.lookup_symbol_in_index(href) 12 | 13 | for full_path, project_path, rowcol in index_locations: 14 | if utils.format_lookup_file_path(full_path) == file_path: 15 | row, col = rowcol 16 | self.window.open_file(full_path + ":" + str(row) + ":" + str(col), sublime.ENCODED_POSITION | sublime.FORCE_GROUP) 17 | break 18 | else: 19 | # might be a setter, so for now just open the file 20 | self.window.open_file(file_path) 21 | else: 22 | # this symbol should be in active view 23 | view = self.window.active_view() 24 | functions = view.find_by_selector("meta.function.declaration.cfml entity.name.function.cfml") 25 | for funct_region in functions: 26 | if view.substr(funct_region).lower() == href.lower(): 27 | view.sel().clear() 28 | r = sublime.Region(funct_region.begin()) 29 | view.sel().add(r) 30 | view.show(r) 31 | break 32 | -------------------------------------------------------------------------------- /messages/0.15.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.15.0 Changelog: 2 | 3 | - `writeOutput` key binding now outputs a semicolon after the parentheses 4 | matching the `writeDump` key binding 5 | https://github.com/jcberquist/sublimetext-cfml/pull/44 6 | 7 | - Fixed syntax highlighting and cfc completions for function parameter types 8 | containing component dot paths 9 | https://github.com/jcberquist/sublimetext-cfml/issues/45 10 | 11 | - Component completions no longer include private methods in contexts where 12 | those methods are not callable 13 | https://github.com/jcberquist/sublimetext-cfml/issues/43 14 | 15 | - Added basic method completion support for variables referencing instantiated 16 | components. 17 | 18 | For example, given `myVar = new path.to.component()`, after typing `mycfc.`, 19 | completions will be offered for `path/to/component.cfc`. 20 | 21 | This support is somewhat limited, and it may collide with the completions 22 | offered by the already extant cfc completions using `cfc_folders`. If you wish 23 | to disable it you can set the package setting `instantiated_component_completions` 24 | to `false` in your user package settings. 25 | 26 | In order for these completions to work, the component in question must be 27 | indexed in a project `cfc_folders` array and there must be a mapping specified in 28 | the project `mappings` array such that the path to the component can be deciphered. 29 | (If pressing F1 on `new path.to.component()` brings up the component documentation, 30 | then things are setup properly.) 31 | -------------------------------------------------------------------------------- /messages/0.18.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.18.0 Changelog: 2 | 3 | - Removed `.tmLanguage` support from the package. The previous tagged release 4 | (v0.17.0) will be the last release containing `.tmLanguage` syntax files. 5 | 6 | - Fixed an issue where, when indexing custom tag files, a file read failure on 7 | an individual file was preventing the custom tag index from being generated. 8 | https://github.com/jcberquist/sublimetext-cfml/issues/53 9 | 10 | - Persistent components are now indexed via their entity names when they are 11 | contained in a project `cfc_folders` array. This index is used to offer 12 | entity name completions when using the ORM functions `entityNew()`, 13 | `entityLoad()`, and `entityLoadByPK()`. 14 | https://github.com/jcberquist/sublimetext-cfml/issues/51 15 | 16 | Also, using that same index, method completion support for variables 17 | referencing persistent components has been added. For example, given 18 | `myVar = entityLoadByPK("myentity", 1)`, after typing `myVar.`, completions 19 | will be offered for the `myentity.cfc` component. (If the `entityname` 20 | attribute is specified, it does takes precedence over the file name in 21 | determining the component entity name.) This support also extends to the F1 22 | documentation command, as well as the CTRL+ALT+click go to source command. 23 | https://github.com/jcberquist/sublimetext-cfml/issues/50 24 | 25 | If you wish to disable this behavior you can set the package setting 26 | `instantiated_component_completions` to `false` in your user package settings. 27 | -------------------------------------------------------------------------------- /src/documentation_helpers.py: -------------------------------------------------------------------------------- 1 | CARD_TEMPLATE = """ 2 |
3 |
{}
4 |
{}
5 |
6 | """ 7 | 8 | HEADER_TEMPLATE = """ 9 | {key}{value} 10 | """ 11 | 12 | 13 | def header(key, value="", scope=""): 14 | args = {"key": key, "value": ""} 15 | if len(value): 16 | args["value"] = ": " + span_wrap(value, scope) 17 | return HEADER_TEMPLATE.format(**args) 18 | 19 | 20 | def param_header(param): 21 | key = param["name"] 22 | value = param["type"] if "type" in param and param["type"] else "" 23 | html = header(key, value, "storage.type") 24 | 25 | if "required" in param and param["required"]: 26 | html += '  Required ' 27 | return html 28 | 29 | 30 | def card(header="", body=""): 31 | html = CARD_TEMPLATE.format(header.strip(), body.strip()) 32 | html = html.replace('
\n', "") 33 | if '
' in html: 34 | html = html.replace( 35 | '
', '
' 36 | ) 37 | html = html.replace('
\n', "") 38 | return html 39 | 40 | 41 | def span_wrap(txt, selector): 42 | return '' + txt + "" 43 | 44 | 45 | def clean_html(txt): 46 | return ( 47 | txt.replace("<", "<") 48 | .replace(">", ">") 49 | .replace("\n ", "
") 50 | .replace("\n", "
") 51 | ) 52 | -------------------------------------------------------------------------------- /commands/Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "CFML: Settings", 4 | "command": "cfml_edit_settings", 5 | "args": { 6 | "file": "${packages}/{cfml_package_name}/settings/cfml_package.sublime-settings", 7 | "default": "{\n\t$0\n}\n" 8 | } 9 | }, 10 | { 11 | "caption": "CFML: Key Bindings", 12 | "command": "cfml_edit_settings", 13 | "args": { 14 | "file": "${packages}/{cfml_package_name}/inputmaps/Default ($platform).sublime-keymap", 15 | "default": "[\n\t$0\n]\n" 16 | } 17 | }, 18 | { 19 | "caption": "CFML: Mouse Bindings", 20 | "command": "cfml_edit_settings", 21 | "args": { 22 | "file": "${packages}/{cfml_package_name}/inputmaps/Default ($platform).sublime-mousemap", 23 | "default": "[\n\t$0\n]\n" 24 | } 25 | }, 26 | { 27 | "caption": "CFML: Format CFScript - Settings", 28 | "command": "cfml_edit_settings", 29 | "args": { 30 | "file": "${packages}/{cfml_package_name}/settings/cfml_format.sublime-settings", 31 | "default": "{\n\t$0\n}\n" 32 | } 33 | }, 34 | { 35 | "caption": "CFML: Format CFScript", 36 | "command": "cfml_format", 37 | "args": { 38 | "current_method": false 39 | } 40 | }, 41 | { 42 | "caption": "CFML: Index Active Project", 43 | "command": "cfml_index_project" 44 | }, 45 | { 46 | "caption": "CFML: Toggle Color Scheme Styles", 47 | "command": "cfml_color_scheme_styles" 48 | } 49 | ] 50 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "install": "messages/install.txt", 3 | "0.2.0": "messages/0.2.0.txt", 4 | "0.2.1": "messages/0.2.1.txt", 5 | "0.3.0": "messages/0.3.0.txt", 6 | "0.3.1": "messages/0.3.1.txt", 7 | "0.4.0": "messages/0.4.0.txt", 8 | "0.4.1": "messages/0.4.1.txt", 9 | "0.4.2": "messages/0.4.2.txt", 10 | "0.5.0": "messages/0.5.0.txt", 11 | "0.5.1": "messages/0.5.1.txt", 12 | "0.6.0": "messages/0.6.0.txt", 13 | "0.7.0": "messages/0.7.0.txt", 14 | "0.8.0": "messages/0.8.0.txt", 15 | "0.8.1": "messages/0.8.1.txt", 16 | "0.9.0": "messages/0.9.0.txt", 17 | "0.9.1": "messages/0.9.1.txt", 18 | "0.9.2": "messages/0.9.2.txt", 19 | "0.9.3": "messages/0.9.3.txt", 20 | "0.10.0": "messages/0.10.0.txt", 21 | "0.10.1": "messages/0.10.1.txt", 22 | "0.10.2": "messages/0.10.2.txt", 23 | "0.11.0": "messages/0.11.0.txt", 24 | "0.12.0": "messages/0.12.0.txt", 25 | "0.12.1": "messages/0.12.1.txt", 26 | "0.12.2": "messages/0.12.2.txt", 27 | "0.12.3": "messages/0.12.3.txt", 28 | "0.13.0": "messages/0.13.0.txt", 29 | "0.14.0": "messages/0.14.0.txt", 30 | "0.15.0": "messages/0.15.0.txt", 31 | "0.16.0": "messages/0.16.0.txt", 32 | "0.17.0": "messages/0.17.0.txt", 33 | "0.18.0": "messages/0.18.0.txt", 34 | "0.19.0": "messages/0.19.0.txt", 35 | "0.20.0": "messages/0.20.0.txt", 36 | "0.21.0": "messages/0.21.0.txt", 37 | "0.22.0": "messages/0.22.0.txt", 38 | "0.23.0": "messages/0.23.0.txt", 39 | "0.24.0": "messages/0.24.0.txt", 40 | "0.25.0": "messages/0.25.0.txt", 41 | "0.26.0": "messages/0.26.0.txt", 42 | "0.27.0": "messages/0.27.0.txt", 43 | "0.28.0": "messages/0.28.0.txt", 44 | "0.29.0": "messages/0.29.0.txt", 45 | "0.30.0": "messages/0.30.0.txt", 46 | "0.31.0": "messages/0.31.0.txt" 47 | } 48 | -------------------------------------------------------------------------------- /messages/0.24.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.24.0 Changelog: 2 | 3 | - Updated cfdocs.org popups to include display of param types. This was 4 | contributed by Matthew Brown (@KamasamaK) in release v0.23.1 but didn't get 5 | noted in the changelog. 6 | https://github.com/jcberquist/sublimetext-cfml/pull/81 7 | 8 | - There has been a major update to the syntax highlighting files that 9 | incorporates work done by Thomas Smith (@Thom1729) on the default JavaScript 10 | syntax (see https://github.com/sublimehq/Packages/issues/1009). The CFScript 11 | syntax is now able to exactly match expressions, and determine when they 12 | terminate. This allows for significantly greater precision when parsing CFML 13 | files. For example, consider the following line: 14 | 15 | function go( param = a + b c ) {} 16 | 17 | Correctly parsing syntax like that to identify `a + b` as an expression and 18 | `c` as metadata (tag attribute effectively) was basically impossible 19 | before, but is easy now. 20 | 21 | My thanks to Thomas Smith for letting me use his work and for helping me out 22 | with it. 23 | 24 | Please note that most of the work on this update was done using Sublime Text 25 | build 3142, which has had several bug fixes to the syntax highlighting engine 26 | since 3126. The tests I have in place do pass on 3126, but please let me know 27 | if you encounter any issues when using that build. 28 | 29 | - The CFScript syntax now distinguishes prefix, postfix, and binary operators. 30 | Whitespace formatting of operators has been updated to take advantage of 31 | this. (E.g. `i = -1;` will no longer get formatted to `i = - 1;` when 32 | formatting with spacing around binary operators.) 33 | -------------------------------------------------------------------------------- /messages/0.26.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.26.0 Changelog: 2 | 3 | - The format in which inline document popups are displayed has been simplified 4 | and standardized. With this change, the setting `adaptive_doc_styles` no 5 | longer has any effect and has been removed. Also, when viewing the popup for a 6 | method of a component, the code preview is no longer displayed by default, but 7 | it can be added back by setting `cfml_doc_method_preview` to true in your CFML 8 | package settings. 9 | 10 | - A new method preview command has been added that is bound to SHIFT+F1 by 11 | default. When the cursor is on a method that can be previewed (one where you 12 | can get method documentation) and the command is run, it will display the 13 | preview right below the method call. The preview can be closed by pressing 14 | SHIFT+F1 again or by clicking on the `x`. 15 | 16 | - A basic indent settings file has been added for CFScript. At the moment it is 17 | a clone of the one included in the default packages for JavaScript. This 18 | should provide a better experience when the cursor is between curly braces 19 | `{}` and enter is pressed. 20 | 21 | - In an effort to reduce false positives when indexing components, the indexer 22 | now tries to identify comment and string ranges in component files, so that 23 | commented out functions and the like are no longer identified as valid 24 | functions. This does add some overhead to the time it takes to index files, so 25 | a cache has been added. The cache is stored in a sqlite3 database that is 26 | stored in your User package folder at `./CFML/cfc_index_cache.sqlite`. 27 | 28 | PLEASE RESTART SUBLIME TEXT TO ENSURE THESE CHANGES ARE LOADED CORRECTLY, THANKS! 29 | -------------------------------------------------------------------------------- /src/custom_tag_index/custom_tag_index.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | attribute_regex = re.compile(r"\battributes\.(\w+)\b(?!\s*\()", re.I) 5 | end_tag_regex = re.compile( 6 | r"thistag\.executionmode\s+(?:eq|is|==)\s+[\"']end[\"']", re.I 7 | ) 8 | alt_end_tag_regex = re.compile(r"") 9 | 10 | 11 | def index(custom_tag_path): 12 | custom_tags = {} 13 | for path, directories, filenames in os.walk(custom_tag_path): 14 | for filename in filenames: 15 | if filename.endswith(".cfm") or filename.endswith(".cfc"): 16 | full_file_path = path.replace("\\", "/") + "/" + filename 17 | file_index = index_file(full_file_path) 18 | if file_index: 19 | custom_tags[full_file_path] = file_index 20 | return custom_tags 21 | 22 | 23 | def index_file(full_file_path): 24 | try: 25 | with open(full_file_path, "r", encoding="utf-8") as f: 26 | file_string = f.read() 27 | except Exception: 28 | print("CFML: unable to read file - " + full_file_path) 29 | return None 30 | 31 | file_index = {"tag_name": full_file_path.split("/").pop()[:-4]} 32 | file_index.update(parse_cfm_file_string(file_string)) 33 | return file_index 34 | 35 | 36 | def parse_cfm_file_string(file_string): 37 | tag_index = {"has_end_tag": False} 38 | tag_index["attributes"] = list( 39 | sorted(set([attr.lower() for attr in re.findall(attribute_regex, file_string)])) 40 | ) 41 | if re.search(end_tag_regex, file_string): 42 | tag_index["has_end_tag"] = True 43 | elif re.search(alt_end_tag_regex, file_string): 44 | tag_index["has_end_tag"] = True 45 | return tag_index 46 | -------------------------------------------------------------------------------- /templates/completion_doc.html: -------------------------------------------------------------------------------- 1 | 2 | 74 |

${header}

75 |
${arguments}
76 |
${body}
77 |
${links}
78 | ${pagination} 79 | 80 | -------------------------------------------------------------------------------- /syntaxes/tests/syntax_test_lucee5.cfc: -------------------------------------------------------------------------------- 1 | // SYNTAX TEST "Packages/CFML/syntaxes/CFML.sublime-syntax" 2 | abstract component { 3 | // <- embedding.cfml source.cfml.script meta.class.declaration.cfml storage.modifier.cfml 4 | 5 | static { 6 | //^ meta.block.static.cfml keyword.control.static.cfml 7 | staticValue = 5; 8 | } 9 | 10 | static final test = 1; 11 | //^^^^^^ storage.modifier.cfml 12 | 13 | private final numeric function testStatic(){ 14 | return static.staticValue; 15 | // ^ variable.language.scope.cfml 16 | } 17 | 18 | 19 | abstract function getFile(); 20 | // ^ storage.modifier.cfml 21 | final function getDirectory() { 22 | // ^ storage.modifier.cfml 23 | return getDirectoryFromPath(getFile()); 24 | } 25 | 26 | function test() { 27 | test.class::staticMethod(); 28 | // ^^^^^^^^^^ entity.name.class.cfml 29 | // ^^ punctuation.accessor.static.cfml 30 | // ^^^^^^^^^^^^ meta.function-call.method.static.cfml variable.function.static.cfml 31 | test.class::staticVal; 32 | // ^^^^^^^^^^ entity.name.class.cfml 33 | // ^^ punctuation.accessor.static.cfml 34 | // ^^^^^^^^^ meta.property.cfml 35 | } 36 | 37 | func(object::staticMethod()); 38 | // ^^^^^^ entity.name.class.cfml 39 | // ^^ punctuation.accessor.static.cfml 40 | 41 | bleh = [bar::foo()]; 42 | // ^^^ entity.name.class.cfml 43 | // ^^ punctuation.accessor.static.cfml 44 | 45 | ``` 46 | //^^^ source.cfml.script meta.class.body.cfml punctuation.definition.raw.code-fence.begin.cfml 47 | 48 | // ^^^^^ embedding.cfml entity.name.tag 49 | ``` 50 | //^^^ punctuation.definition.raw.code-fence.end.cfml 51 | } 52 | // <- embedding.cfml source.cfml.script meta.class.body.cfml punctuation.section.block.end.cfml 53 | 54 | -------------------------------------------------------------------------------- /messages/0.16.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.16.0 Changelog: 2 | 3 | - Added attribute name scope to component `extends` keyword 4 | https://github.com/jcberquist/sublimetext-cfml/issues/49 5 | 6 | - Added options to control the verboseness of completions for built-in functions 7 | as well as for completions from indexed components. There are two new package 8 | settings for these: `cfml_bif_completions` and `cfml_cfc_completions`. 9 | Both of these have three options: `basic`, `required`, and `full`. When set 10 | to `basic`, completions using only the function name will be inserted. When set 11 | to `required`, only required arguments will be included in the completion, and 12 | no argument types. Setting these to `full` will cause all arguments to be 13 | included in the completions, and argument types will be included as well. 14 | 15 | This last setting (`full`) matches the behavior of the completions till now - 16 | HEADS UP, I have changed the default completion style to `required`. To get the 17 | previous completion style back, you just need to override these settings in your 18 | user package settings file. 19 | 20 | - Added completion docs that display alongside the auto-complete when entering 21 | function call parameters. These docs are available when entering parameters for 22 | built-in functions, as well as parameters for indexed components. 23 | 24 | For the built-in functions, the documentation from cfdocs.org for that function 25 | parameter is displayed. For indexed components, the doc includes the parameter 26 | type, whether it is required, and its default value, if it has one. If the 27 | parameter has a docblock hint, that will be displayed as well. 28 | 29 | These completion docs are enabled by default, but you can turn them off by setting 30 | the package setting `cfml_completion_docs` to false in your user package settings. 31 | -------------------------------------------------------------------------------- /src/plugins_/custom_tags/documentation.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | from ...custom_tag_index import custom_tag_index 3 | from ... import utils 4 | 5 | 6 | SIDE_COLOR = "color(var(--orangish) blend(var(--background) 60%))" 7 | 8 | 9 | def get_inline_documentation(cfml_view, doc_type): 10 | if not cfml_view.project_name: 11 | return None 12 | 13 | if cfml_view.view.match_selector(cfml_view.position, "meta.tag.custom.cfml"): 14 | tag_name = utils.get_tag_name(cfml_view.view, cfml_view.position) 15 | 16 | file_path, tag_info = custom_tag_index.get_index_by_tag_name( 17 | cfml_view.project_name, tag_name 18 | ) 19 | if file_path: 20 | doc, callback = get_documentation( 21 | cfml_view.view, tag_name, file_path, tag_info 22 | ) 23 | return cfml_view.Documentation(None, doc, callback, 2) 24 | 25 | return None 26 | 27 | 28 | def get_documentation(view, tag_name, file_path, tag_info): 29 | custom_tag_doc = {"side_color": SIDE_COLOR, "html": {}} 30 | custom_tag_doc["html"]["links"] = [] 31 | 32 | custom_tag_doc["html"]["header"] = tag_name 33 | custom_tag_doc["html"]["description"] = ( 34 | 'path: ' 35 | + file_path 36 | + "" 37 | ) 38 | 39 | custom_tag_doc["html"]["body"] = "
" 40 | custom_tag_doc["html"]["body"] += ( 41 | "Closing tag: " 42 | + ("true" if tag_info["has_end_tag"] else "false") 43 | + "
" 44 | ) 45 | custom_tag_doc["html"]["body"] += "Attributes: " + ", ".join( 46 | tag_info["attributes"] 47 | ) 48 | 49 | callback = partial(on_navigate, view, file_path) 50 | return custom_tag_doc, callback 51 | 52 | 53 | def on_navigate(view, file_path, href): 54 | view.window().open_file(file_path) 55 | -------------------------------------------------------------------------------- /src/plugins_/fw1/json/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": [ 3 | "framework" 4 | ], 5 | "variables.framework": [ 6 | "action", 7 | "applicationKey", 8 | "base", 9 | "baseURL", 10 | "cacheFileExists", 11 | "cfcbase", 12 | "controllersFolder", 13 | "decodeRequestBody", 14 | "defaultItem", 15 | "defaultSection", 16 | "defaultSubsystem", 17 | "diComponent", 18 | "diConfig", 19 | "diEngine", 20 | "diLocations", 21 | "diOverrideAllowed", 22 | "environments", 23 | "error", 24 | "generateSES", 25 | "home", 26 | "layoutsFolder", 27 | "maxNumContextsPreserved", 28 | "missingview", 29 | "noLowerCase", 30 | "optionsAccessControl", 31 | "password", 32 | "perResourceError", 33 | "preflightOptions", 34 | "preserveKeyURLKey", 35 | "reload", 36 | "reloadApplicationOnEveryRequest", 37 | "resourceRouteTemplates", 38 | "routes", 39 | "routesCaseSensitive", 40 | "SESOmitIndex", 41 | "siteWideLayoutSubsystem", 42 | "subsystemDelimiter", 43 | "subsystems", 44 | "subsystemsFolder", 45 | "trace", 46 | "unhandledErrorCaught", 47 | "unhandledExtensions", 48 | "unhandledPaths", 49 | "usingSubsystems", 50 | "viewsFolder" 51 | ], 52 | "variables.framework.diconfig": [ 53 | "constants", 54 | "exclude", 55 | "initMethod", 56 | "liberal", 57 | "loadListener", 58 | "omitDefaultedProperties", 59 | "omitDirectoryAliases", 60 | "omitTypedProperties", 61 | "recurse", 62 | "singletonPattern", 63 | "singulars", 64 | "strict", 65 | "transientPattern", 66 | "transients" 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /src/goto_cfml_file.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | import sublime 3 | import sublime_plugin 4 | from . import utils 5 | from . import cfml_plugins 6 | from .cfml_view import CfmlView 7 | 8 | 9 | def get_cfml_files(cfml_view): 10 | cfml_files = [] 11 | 12 | for p in cfml_plugins.plugins: 13 | gotocfmlfile = p.get_goto_cfml_file(cfml_view) 14 | if gotocfmlfile: 15 | cfml_files.append(gotocfmlfile) 16 | 17 | return cfml_files 18 | 19 | 20 | def open_file_at_symbol(view, file_path, symbol): 21 | index_locations = view.window().lookup_symbol_in_index(symbol) 22 | if file_path[1] == ":": 23 | file_path = "/" + file_path[0] + file_path[2:] 24 | 25 | for full_path, project_path, rowcol in index_locations: 26 | if utils.format_lookup_file_path(full_path) == file_path: 27 | row, col = rowcol 28 | view.window().open_file( 29 | full_path + ":" + str(row) + ":" + str(col), 30 | sublime.ENCODED_POSITION | sublime.FORCE_GROUP, 31 | ) 32 | break 33 | else: 34 | # if symbol can't be found in the index, go ahead and open the file 35 | view.window().open_file(file_path) 36 | 37 | 38 | class CfmlGotoFileCommand(sublime_plugin.TextCommand): 39 | def run(self, edit, event): 40 | pt = self.view.window_to_text((event["x"], event["y"])) 41 | cfml_view = CfmlView(self.view, pt) 42 | cfml_files = get_cfml_files(cfml_view) 43 | if len(cfml_files) > 0: 44 | if cfml_files[0].symbol: 45 | open_file_at_symbol( 46 | self.view, cfml_files[0].file_path, cfml_files[0].symbol 47 | ) 48 | else: 49 | if cfml_files[0].file_path.startswith("http"): 50 | webbrowser.open_new_tab(cfml_files[0].file_path) 51 | else: 52 | self.view.window().open_file(cfml_files[0].file_path) 53 | 54 | def want_event(self): 55 | return True 56 | -------------------------------------------------------------------------------- /src/completions.py: -------------------------------------------------------------------------------- 1 | import sublime_plugin 2 | from . import inline_documentation 3 | from . import utils 4 | from . import cfml_plugins 5 | from .cfml_view import CfmlView 6 | 7 | 8 | def get_completions(view, position, prefix): 9 | cfml_view = CfmlView(view, position, prefix) 10 | if not cfml_view.type: 11 | return None 12 | 13 | completion_lists = [] 14 | minimum_priority = 0 15 | docs = [] 16 | 17 | for p in cfml_plugins.plugins: 18 | completionlist = p.get_completions(cfml_view) 19 | if completionlist: 20 | completion_lists.append(completionlist) 21 | if completionlist.exclude_lower_priority: 22 | minimum_priority = completionlist.priority 23 | 24 | if utils.get_setting("cfml_completion_docs"): 25 | for p in cfml_plugins.plugins: 26 | inline_doc = p.get_completion_docs(cfml_view) 27 | if inline_doc: 28 | docs.append(inline_doc) 29 | 30 | full_completion_list = [] 31 | for completionlist in completion_lists: 32 | full_completion_list.extend(completionlist.completions) 33 | 34 | if len(docs) > 0: 35 | inline_documentation.display_documentation(view, docs, "completion_doc", 0) 36 | return full_completion_list 37 | 38 | 39 | class CfmlUpdateCompletionDocCommand(sublime_plugin.TextCommand): 40 | def run(self, edit): 41 | self.view.run_command("insert_snippet", {"contents": ","}) 42 | if inline_documentation.doc_window == "completion_doc": 43 | position = self.view.sel()[0].begin() 44 | cfml_view = CfmlView(self.view, position) 45 | docs = [] 46 | for p in cfml_plugins.plugins: 47 | inline_doc = p.get_completion_docs(cfml_view) 48 | if inline_doc: 49 | docs.append(inline_doc) 50 | 51 | if len(docs) > 0: 52 | inline_documentation.display_documentation( 53 | self.view, docs, "completion_doc", 0 54 | ) 55 | else: 56 | self.view.hide_popup() 57 | -------------------------------------------------------------------------------- /src/plugins_/custom_tags/completions.py: -------------------------------------------------------------------------------- 1 | from ...custom_tag_index import custom_tag_index 2 | from ... import utils 3 | 4 | 5 | def get_completions(cfml_view): 6 | if cfml_view.type == "tag": 7 | return get_tags(cfml_view) 8 | elif cfml_view.type == "tag_attributes": 9 | return get_tag_attributes(cfml_view) 10 | return None 11 | 12 | 13 | def get_tags(cfml_view): 14 | if cfml_view.previous_char == "<": 15 | completion_list = custom_tag_index.get_prefix_completions( 16 | cfml_view.project_name 17 | ) 18 | if completion_list: 19 | return cfml_view.CompletionList(completion_list, 0, False) 20 | elif cfml_view.previous_char == ":": 21 | prefix = cfml_view.view.substr(cfml_view.view.word(cfml_view.prefix_start - 1)) 22 | completion_list = custom_tag_index.get_tag_completions( 23 | cfml_view.project_name, prefix 24 | ) 25 | if completion_list: 26 | return cfml_view.CompletionList(completion_list, 0, False) 27 | return None 28 | 29 | 30 | def get_tag_attributes(cfml_view): 31 | if not cfml_view.tag_name: 32 | return None 33 | 34 | if cfml_view.project_name in custom_tag_index.data: 35 | if ":" in cfml_view.tag_name or cfml_view.tag_name.lower().startswith("cf_"): 36 | completion_list = custom_tag_index.get_tag_attribute_completions( 37 | cfml_view.project_name, cfml_view.tag_name 38 | ) 39 | if completion_list: 40 | return cfml_view.CompletionList(completion_list, 0, False) 41 | return None 42 | 43 | 44 | def get_goto_cfml_file(cfml_view): 45 | if not cfml_view.project_name: 46 | return None 47 | 48 | if cfml_view.view.match_selector(cfml_view.position, "meta.tag.custom.cfml"): 49 | tag_name = utils.get_tag_name(cfml_view.view, cfml_view.position) 50 | 51 | file_path, tag_info = custom_tag_index.get_index_by_tag_name( 52 | cfml_view.project_name, tag_name 53 | ) 54 | if file_path: 55 | return cfml_view.GotoCfmlFile(file_path, None) 56 | 57 | return None 58 | -------------------------------------------------------------------------------- /messages/0.20.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.20.0 Changelog: 2 | 3 | - Added the following tags to the default list of tags that do not receive a 4 | closing tag: `cfcontent`, `cfparam`, `cfflush`, `cffile`, `cfdirectory`, 5 | `cfheader`, `cfhttpparam`, `cflog`, and `cfimage`. 6 | https://github.com/jcberquist/sublimetext-cfml/pull/58 7 | https://github.com/jcberquist/sublimetext-cfml/pull/59 8 | 9 | - Component method completions are no longer of the form `method():returntype` 10 | by default, as it turns out this format blocks Sublime Text from including 11 | local buffer completions in the completion list. Instead the completion names 12 | now only include the method name. It is possible to get the old completion 13 | name style back via a new package setting `cfc_completion_names` - the 14 | two options for it are "basic" and "full". (If this setting is changed, you 15 | will need to run the `CFML: Index Active Project` command via the command 16 | palette for the change to take effect for indexed components.) If you do want 17 | to use the old completion style a plugin such as `All Autocomplete` can be 18 | used as a workaround to get local buffer completions back into the completion 19 | list. See the issue below for more discussion: 20 | https://github.com/jcberquist/sublimetext-cfml/issues/57 21 | 22 | - For Sublime Text builds 3116+ running the documentation command on mouse hover 23 | is now supported. This is enabled by default, but can be disabled by setting 24 | the package setting `cfml_hover_docs` to false in your user package settings. 25 | 26 | - The documentation command now highlights the relevant regions of the code 27 | to which the displayed documentation pertains. This is enabled by default, 28 | but can be disabled by setting the package setting 29 | `inline_doc_regions_highlight` to false in your user package settings. 30 | 31 | - When using the CFML formatting command to format the current function, it 32 | will now format the function declaration as well as the function body. 33 | 34 | - When formatting a function call that contains an anonymous function, e.g.: 35 | 36 | myArray.map(function(item) { 37 | return item.key; 38 | }); 39 | 40 | the anonymous function will be kept inline with the opening parenthesis, 41 | instead of always being placed on a new line. 42 | -------------------------------------------------------------------------------- /syntaxes/HTML (CFML).sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | name: CFML 4 | scope: text.html.cfml 5 | version: 2 6 | hidden: true 7 | 8 | extends: Packages/HTML/HTML.sublime-syntax 9 | 10 | variables: 11 | cfml_attribute_name: '[_[:alpha:]][[:alnum:]_\-:]*' 12 | 13 | contexts: 14 | ## JavaScript 15 | script-javascript-content: 16 | - meta_include_prototype: false 17 | - match: \s*(( 27 | escape_captures: 28 | 0: meta.tag.sgml.cdata.html punctuation.definition.tag.end.html 29 | - match: '{{script_content_begin}}' 30 | captures: 31 | 1: comment.block.html punctuation.definition.comment.begin.html 32 | pop: 1 # make sure to match only once 33 | embed: scope:source.js.cfml 34 | embed_scope: source.js.embedded.html 35 | escape: '{{script_content_end}}' 36 | escape_captures: 37 | 1: source.js.embedded.html 38 | 2: comment.block.html punctuation.definition.comment.end.html 39 | 3: source.js.embedded.html 40 | 4: comment.block.html punctuation.definition.comment.end.html 41 | 42 | tag-event-attribute-value: 43 | - match: \" 44 | scope: meta.string.html string.quoted.double.html punctuation.definition.string.begin.html 45 | embed: scope:source.js.cfml 46 | embed_scope: meta.string.html meta.interpolation.html source.js.embedded.html 47 | escape: \" 48 | escape_captures: 49 | 0: meta.string.html string.quoted.double.html punctuation.definition.string.end.html 50 | - match: \' 51 | scope: meta.string.html string.quoted.single.html punctuation.definition.string.begin.html 52 | embed: scope:source.js.cfml 53 | embed_scope: meta.string.html meta.interpolation.html source.js.embedded.html 54 | escape: \' 55 | escape_captures: 56 | 0: meta.string.html string.quoted.single.html punctuation.definition.string.end.html 57 | - include: else-pop 58 | 59 | -------------------------------------------------------------------------------- /messages/0.13.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.13.0 Changelog: 2 | 3 | - CFScript strings starting with uppercase SQL keywords are now highlighted as SQL. 4 | https://github.com/jcberquist/sublimetext-cfml/issues/35 5 | 6 | - Added (beta) CFScript code formatting commands 7 | 8 | The settings for these commands are found in a new settings file: 9 | `cfml_format.sublime-settings`. You can find the default settings via the menu 10 | under `Package Settings -> CFML -> Format Settings - Default` or from the 11 | command palette under `CFML: Format Settings - Default`. Please open that 12 | file to see the various formatting options available. These settings can be 13 | overridden in your user format settings file, which you can open from the 14 | same locations. 15 | 16 | There are two new key bindings that work with these formatting commansds: 17 | `SHIFT+ALT+F` and `CTRL+ALT+SHIFT+F` (`CMD+ALT+SHIFT+F` on OSX). The first 18 | executes a default set of commands (specified in the settings file), while 19 | the second calls up a menu from which a formatting command can be selected. 20 | The `SHIFT+ALT+F` command formats the current method when in a component - 21 | though if any text is selected it formats only that text, and in `.cfm` 22 | files it formats all CFScript in the file. To format an entire component the 23 | `Format Full Component` menu command can be used. All of the individual 24 | commands listed in the menu operate on the whole file unless text is selected. 25 | 26 | PLEASE NOTE: 27 | The reason formatting is limited by default to the current method in components 28 | is that the formatting is CPU intensive and is currently run on the main thread, 29 | meaning Sublime Text will "freeze" while formatting is ongoing. I tested this on 30 | FW/1's `one.cfc` - to format the full file with the default set of commands takes 31 | ~900ms on my machine. @mjhagen tested this for me on a slower processor - it took 32 | ~700ms to format around 800 lines of code. On smaller blocks of code, though, 33 | performance should be fine. 34 | 35 | I have looked into doing this in an asynchronous fashion, but it seems that text 36 | buffer updates have to be run on the main thread, so I have yet to find a 37 | satisfactory async approach. 38 | 39 | This is definitely a beta feature, so please do report if you use these formatting 40 | commands and they do something unexpected or eat your code :) 41 | -------------------------------------------------------------------------------- /syntaxes/tests/syntax_test_tag_attributes_cfml.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | type = "string" default = "hello"> 28 | 29 | 30 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/component_parser/cfml_properties.py: -------------------------------------------------------------------------------- 1 | # This module, via index(), takes in a cfml source file string and 2 | # returns a python dict of the property names mapped to a dict 3 | # containing data about the property. 4 | 5 | # The structure looks like this: 6 | # { 7 | # "property_name": { 8 | # "type": "any/string/numeric/etc", 9 | # "getter": True/False/None 10 | # "setter": True/False/None 11 | # }, 12 | # ... 13 | # } 14 | 15 | import re 16 | from . import cfml_functions, regex, ranges 17 | 18 | 19 | 20 | def index(file_string, cfscript_range=None): 21 | if cfscript_range is None: 22 | cfscript_range = ranges.RangeWalker(file_string, 0, 'cfscript').walk() 23 | 24 | properties = {} 25 | for property_match in re.finditer(regex.cfml_property, file_string): 26 | 27 | if cfscript_range.is_in_range(property_match.start(), ranges.NON_SCRIPT_RANGES): 28 | continue 29 | 30 | property_string = property_match.group() 31 | 32 | if not property_string.startswith(' 2 | 3 | thisQuery = queryExecute("SELECT * from myTable WHERE myColumn = 1", "SELECT * from myTable WHERE myColumn = 1"); 4 | 5 | 6 | thisQuery = queryExecute(params = {}, sql = "SELECT * FROM myTable"); 7 | 8 | var test = "FROM myTable WHERE test = '#obj.property#'" 9 | 10 | 11 | 12 | sql = "select *"; 13 | 14 | sql = " 15 | select 16 | 17 | "; 18 | sql = " 19 | select top(2) 20 | 21 | "; 22 | sql = "select a.b.c from"; 23 | 24 | sql = "select a.b.c,"; 25 | 26 | sql = "select a.b.c as columnAlias,"; 27 | 28 | sql = "from 29 | 30 | "; 31 | sql = "from table q 32 | 33 | "; 34 | sql = "where 35 | 36 | "; 37 | sql = "where a.b = 'astring'"; 38 | 39 | 40 | sql = "group by a.b, 41 | 42 | "; 43 | sql = "inner join a.table"; 44 | 45 | 46 | sql = "left outer join " 47 | 48 | 49 | 50 | 51 | not_sql = "select a.b.c"; 52 | 53 | not_sql = "from table q"; 54 | 55 | not_sql = "group by a.b"; 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/commands/cfc_dotted_path.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | from os.path import dirname 4 | from .. import utils 5 | 6 | 7 | def cfc_files(files): 8 | return [ 9 | file_path for file_path in files if file_path.split(".")[-1].lower() == "cfc" 10 | ] 11 | 12 | 13 | def get_project_info(window): 14 | project_data = window.project_data() 15 | project_path = ( 16 | dirname(window.project_file_name()) if window.project_file_name() else None 17 | ) 18 | return project_data, project_path 19 | 20 | 21 | def get_dotted_paths(window, file_path): 22 | dotted_paths = [] 23 | normalized_path = utils.normalize_path(file_path) 24 | project_data, project_path = get_project_info(window) 25 | 26 | if "mappings" in project_data: 27 | for mapping in project_data["mappings"]: 28 | normalized_mapping = utils.normalize_mapping(mapping, project_path) 29 | if normalized_path.startswith(normalized_mapping["path"]): 30 | mapped_path = normalized_mapping["mapping"] + normalized_path.replace( 31 | normalized_mapping["path"], "" 32 | ) 33 | path_parts = mapped_path.split("/")[1:] 34 | dotted_paths.append(".".join(path_parts)[:-4]) 35 | 36 | # fall back to folders if no mappings matched 37 | if len(dotted_paths) == 0: 38 | for folder in project_data["folders"]: 39 | relative_path = normalized_path.replace( 40 | utils.normalize_path(folder["path"], project_path), "" 41 | ) 42 | if relative_path != normalized_path: 43 | path_parts = relative_path.split("/")[1:] 44 | dotted_paths.append(".".join(path_parts)[:-4]) 45 | 46 | return dotted_paths 47 | 48 | 49 | def copy_path(window, files): 50 | def on_done(i): 51 | if i != -1: 52 | sublime.set_clipboard(dotted_paths[i]) 53 | sublime.status_message("CFML: copied cfc dotted path") 54 | 55 | dotted_paths = get_dotted_paths(window, cfc_files(files)[0]) 56 | 57 | if len(dotted_paths) > 1: 58 | window.show_quick_panel(dotted_paths, on_done) 59 | else: 60 | on_done(0) 61 | 62 | 63 | class CfmlCfcDottedPathCommand(sublime_plugin.TextCommand): 64 | def run(self, edit): 65 | if len(self.view.file_name()) > 0: 66 | copy_path(self.view.window(), [self.view.file_name()]) 67 | 68 | def is_visible(self): 69 | return ( 70 | self.view.file_name() is not None 71 | and len(cfc_files([self.view.file_name()])) == 1 72 | ) 73 | 74 | 75 | class CfmlSidebarCfcDottedPathCommand(sublime_plugin.WindowCommand): 76 | def run(self, files): 77 | copy_path(self.window, files) 78 | 79 | def is_visible(self, files): 80 | return len(cfc_files(files)) == 1 81 | -------------------------------------------------------------------------------- /src/formatting/method_chains.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from .. import utils 3 | 4 | 5 | def format_method_chains(cfml_format): 6 | singleline_max_col = cfml_format.get_setting("singleline_max_col") 7 | break_after = cfml_format.get_setting("method_chains.break_after") 8 | inline = cfml_format.get_setting("method_chains.inline") 9 | 10 | substitutions = [] 11 | method_chains = find_method_chains(cfml_format) 12 | 13 | for method_chain, method_chain_strs in reversed(method_chains): 14 | start_point = ( 15 | utils.get_previous_character(cfml_format.view, method_chain[0].begin() - 1) 16 | + 1 17 | ) 18 | target_region = sublime.Region(start_point, method_chain[-1].end()) 19 | inline_str = ".".join(method_chain_strs) 20 | inline_method_cols = cfml_format.pt_column( 21 | start_point 22 | ) + cfml_format.text_columns(inline_str) 23 | 24 | # we need to break if total methods or total column size is greater than settings 25 | will_break = ( 26 | (break_after is not None and break_after < len(method_chain_strs)) 27 | or ( 28 | singleline_max_col is not None 29 | and singleline_max_col < inline_method_cols 30 | ) 31 | or ("\n" in inline_str) 32 | ) 33 | 34 | formatted_str = "" 35 | for i, method_str in enumerate(method_chain_strs): 36 | # inline if method count and max column are within settings 37 | # if will_break then inline until start methods _or_ max column reached 38 | if not will_break or (inline is not None and inline > i): 39 | formatted_str += "." + method_str 40 | else: 41 | formatted_str += "\n" + "." + method_str 42 | substitutions.append((target_region, formatted_str)) 43 | 44 | return substitutions 45 | 46 | 47 | def find_method_chains(cfml_format): 48 | method_chains = [] 49 | regions = cfml_format.find_by_selector( 50 | "source.cfml.script meta.function-call.method -meta.function-call.method.static" 51 | ) 52 | 53 | if len(regions) == 0: 54 | return method_chains 55 | 56 | current_chain = [regions[0]] 57 | current_chain_strs = [cfml_format.view.substr(regions[0])] 58 | 59 | for r in regions[1:]: 60 | if ( 61 | utils.get_next_character(cfml_format.view, current_chain[-1].end()) 62 | == r.begin() - 1 63 | ): 64 | current_chain.append(r) 65 | current_chain_strs.append(cfml_format.view.substr(r)) 66 | else: 67 | method_chains.append((current_chain, current_chain_strs)) 68 | current_chain = [r] 69 | current_chain_strs = [cfml_format.view.substr(r)] 70 | 71 | method_chains.append((current_chain, current_chain_strs)) 72 | return method_chains 73 | -------------------------------------------------------------------------------- /messages/0.21.0.txt: -------------------------------------------------------------------------------- 1 | CFML v0.21.0 Changelog: 2 | 3 | - Added a right click context menu item (Copy CFC Dotted Path) for when the 4 | active view is a component, that copies the dot path to that component to 5 | the clipboard. This functions in the same way is the context menu item when 6 | right clicking on a CFC file in the side bar. 7 | 8 | - Syntax highlighting changes to remove string scope coloring from cfscript 9 | embedded in strings. For example, in the following code: 10 | 11 | mystring = 'something #struct.key#'; 12 | 13 | `key` used to be colored as if it were a string, but this should no longer be 14 | the case. 15 | 16 | - Miscellaneous bug fixes - see the recent GitHub commits for more information 17 | 18 | 19 | It looks to me like I missed out on actually including the v0.20.0 changelog 20 | when I pushed that version out, sorry! I have copied it in below, in case you 21 | haven't seen it. 22 | 23 | CFML v0.20.0 Changelog: 24 | 25 | - Added the following tags to the default list of tags that do not receive a 26 | closing tag: `cfcontent`, `cfparam`, `cfflush`, `cffile`, `cfdirectory`, 27 | `cfheader`, `cfhttpparam`, `cflog`, and `cfimage`. 28 | https://github.com/jcberquist/sublimetext-cfml/pull/58 29 | https://github.com/jcberquist/sublimetext-cfml/pull/59 30 | 31 | - Component method completions are no longer of the form `method():returntype` 32 | by default, as it turns out this format blocks Sublime Text from including 33 | local buffer completions in the completion list. Instead the completion names 34 | now only include the method name. It is possible to get the old completion 35 | name style back via a new package setting `cfc_completion_names` - the 36 | two options for it are "basic" and "full". (If this setting is changed, you 37 | will need to run the `CFML: Index Active Project` command via the command 38 | palette for the change to take effect for indexed components.) If you do want 39 | to use the old completion style a plugin such as `All Autocomplete` can be 40 | used as a workaround to get local buffer completions back into the completion 41 | list. See the issue below for more discussion: 42 | https://github.com/jcberquist/sublimetext-cfml/issues/57 43 | 44 | - For Sublime Text builds 3116+ running the documentation command on mouse hover 45 | is now supported. This is enabled by default, but can be disabled by setting 46 | the package setting `cfml_hover_docs` to false in your user package settings. 47 | 48 | - The documentation command now highlights the relevant regions of the code 49 | to which the displayed documentation pertains. This is enabled by default, 50 | but can be disabled by setting the package setting 51 | `inline_doc_regions_highlight` to false in your user package settings. 52 | 53 | - When using the CFML formatting command to format the current function, it 54 | will now format the function declaration as well as the function body. 55 | 56 | - When formatting a function call that contains an anonymous function, e.g.: 57 | 58 | myArray.map(function(item) { 59 | return item.key; 60 | }); 61 | 62 | the anonymous function will be kept inline with the opening parenthesis, 63 | instead of always being placed on a new line. 64 | -------------------------------------------------------------------------------- /metadata/cfml-indent.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | name 5 | cfml-indent 6 | scope 7 | embedding.cfml source.cfml -source.cfml.script, embedding.cfml text.html 8 | settings 9 | 10 | decreaseIndentPattern 11 | ]*+]*> 15 | | ---?> 16 | | \} 17 | | [^<>]*+]*> 18 | ) 19 | ]]> 20 | batchDecreaseIndentPattern 21 | ]*+]*> 25 | | ---?> 26 | | \} 27 | | [^<>]*+]*> 28 | ) 29 | ]]> 30 | increaseIndentPattern 31 | ) 36 | | .*<(?!\?| 37 | (?i:area|base|br|col|frame|hr|html|img|input|link|meta|param|cfabort| 38 | cfargument|cfbreak|cfcontent|cfcontinue|cfcookie|cfdirectory|cfdump| 39 | cfexecute|cfexit|cffile|cfflush|cfheader|cfhttpparam|cfimage|cfimport| 40 | cfindex|cfinclude|cfinput|cfinvokeargument|cflocation|cflog|cfloginuser| 41 | cflogout|cfmailparam|cfobject|cfobjectcache|cfparam|cfprocessingdirective| 42 | cfprocparam|cfprocresult|cfproperty|cfqueryparam|cfrethrow|cfreturn| 43 | cfschedule|cfsearch|cfset|cfsetting|cfthrow 44 | )\b 45 | |[^>]*/>) 46 | (?[A-Za-z0-9-]+)(?=\s|>)\b[^>]*>(?!.*\s*>) 47 | ) 48 | ]]> 49 | batchIncreaseIndentPattern 50 | ) 55 | | .*<(?!\?| 56 | (?i:area|base|br|col|frame|hr|html|img|input|link|meta|param|cfabort| 57 | cfargument|cfbreak|cfcontent|cfcontinue|cfcookie|cfdirectory|cfdump| 58 | cfexecute|cfexit|cffile|cfflush|cfheader|cfhttpparam|cfimage|cfimport| 59 | cfindex|cfinclude|cfinput|cfinvokeargument|cflocation|cflog|cfloginuser| 60 | cflogout|cfmailparam|cfobject|cfobjectcache|cfparam|cfprocessingdirective| 61 | cfprocparam|cfprocresult|cfproperty|cfqueryparam|cfrethrow|cfreturn| 62 | cfschedule|cfsearch|cfset|cfsetting|cfthrow 63 | )\b 64 | |[^>]*/>) 65 | (?[A-Za-z0-9-]+)(?=\s|>)\b[^>]*>(?!.*\s*>) 66 | ) 67 | ]]> 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/plugins_/applicationcfc/json/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "this": [ 3 | "applicationTimeout", 4 | "authCookie", 5 | "bufferOutput", 6 | "cache", 7 | "cacheFunction", 8 | "cacheInclude", 9 | "cacheObject", 10 | "cacheQuery", 11 | "cacheResource", 12 | "cacheTemplate", 13 | "chartStyleDirectory", 14 | "clientCluster", 15 | "clientManagement", 16 | "clientStorage", 17 | "clientTimeout", 18 | "compileExtForInclude", 19 | "componentPaths", 20 | "compression", 21 | "customTagPaths", 22 | "datasource", 23 | "datasources", 24 | "debuggingIPAddresses", 25 | "defaultDatasource", 26 | "enableRobustException", 27 | "googleMapKey", 28 | "invokeImplicitAccessor", 29 | "javaSettings", 30 | "localMode", 31 | "locale", 32 | "loginStorage", 33 | "mappings", 34 | "name", 35 | "ormEnabled", 36 | "ormSettings", 37 | "requestTimeout", 38 | "resourceCharset", 39 | "restSettings", 40 | "s3", 41 | "sameFormFieldsAsArray", 42 | "scopeCascading", 43 | "scriptProtect", 44 | "secureJSON", 45 | "secureJSONPrefix", 46 | "security", 47 | "serialization", 48 | "serverSideFormValidation", 49 | "sessionCluster", 50 | "sessionCookie", 51 | "sessionManagement", 52 | "sessionStorage", 53 | "sessionTimeout", 54 | "sessionType", 55 | "setClientCookies", 56 | "setDomainCookies", 57 | "smtpServerSettings", 58 | "strictNumberValidation", 59 | "suppressRemoteComponentContent", 60 | "timeout", 61 | "timezone", 62 | "triggerDataMember", 63 | "typeChecking", 64 | "webCharset", 65 | "welcomeFileList", 66 | "wstype" 67 | ], 68 | "this.authcookie": [ 69 | "disableUpdate", 70 | "timeout" 71 | ], 72 | "this.cache": [ 73 | "useInternalQueryCache", 74 | "querySize" 75 | ], 76 | "this.datasource": [ 77 | "name", 78 | "username", 79 | "password" 80 | ], 81 | "this.defaultdatasource": [ 82 | "name", 83 | "username", 84 | "password" 85 | ], 86 | "this.inmemoryfilesystem": [ 87 | "enabled", 88 | "size" 89 | ], 90 | "this.javasettings": [ 91 | "loadColdFusionClassPath", 92 | "loadPaths", 93 | "reloadOnChange" 94 | ], 95 | "this.ormsettings": [ 96 | "autoGenMap", 97 | "autoManageSession", 98 | "cacheConfig", 99 | "cacheProvider", 100 | "catalog", 101 | "cfcLocation", 102 | "datasource", 103 | "dbCreate", 104 | "dialect", 105 | "eventHandling", 106 | "flushAtRequestEnd", 107 | "logSQL", 108 | "namingStrategy", 109 | "ormConfig", 110 | "saveMapping", 111 | "schema", 112 | "secondaryCacheEnabled", 113 | "skipCFCWithError", 114 | "sqlScript", 115 | "useDBForMapping" 116 | ], 117 | "this.restsettings": [ 118 | "cfcLocation", 119 | "skipCFCWithError" 120 | ], 121 | "this.s3": [ 122 | "accessKeyId", 123 | "awsSecretKey", 124 | "defaultLocation" 125 | ], 126 | "this.security": [ 127 | "antiSamyPolicy" 128 | ], 129 | "this.serialization": [ 130 | "preserveCaseForStructKey", 131 | "preserveCaseForQueryColumn", 132 | "serializeQueryAs" 133 | ], 134 | "this.sessioncookie": [ 135 | "disableUpdate", 136 | "domain", 137 | "httpOnly", 138 | "secure", 139 | "timeout" 140 | ], 141 | "this.smtpserversettings": [ 142 | "password", 143 | "server", 144 | "username" 145 | ] 146 | } -------------------------------------------------------------------------------- /src/commands/color_scheme_styles.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import os 4 | 5 | 6 | def get_rule_dicts(): 7 | dicts = { 8 | "cfml_tag_style": { 9 | "name": "cfml tag", 10 | "scope": "entity.name.tag.cfml, entity.name.tag.script.cfml", 11 | }, 12 | "cfml_tag_attribute_style": { 13 | "name": "cfml tag attribute", 14 | "scope": "entity.other.attribute-name.cfml", 15 | }, 16 | } 17 | return dicts 18 | 19 | 20 | def get_cfml_rules(): 21 | cfml_settings = sublime.load_settings("cfml_package.sublime-settings") 22 | style_dicts = get_rule_dicts() 23 | rules = [] 24 | for style_setting_key in style_dicts: 25 | style_setting = cfml_settings.get(style_setting_key) 26 | if style_setting: 27 | settings_to_inject = { 28 | k: style_setting[k] 29 | for k in ["foreground", "font_style"] 30 | if k in style_setting 31 | } 32 | style_dicts[style_setting_key].update(settings_to_inject) 33 | rules.append(style_dicts[style_setting_key]) 34 | return rules 35 | 36 | 37 | def get_user_color_scheme(color_scheme_file): 38 | try: 39 | src_str = sublime.load_resource(color_scheme_file) 40 | user_color_scheme = sublime.decode_value(src_str) 41 | if "rules" not in user_color_scheme: 42 | user_color_scheme["rules"] = [] 43 | return user_color_scheme 44 | except IOError: 45 | return {"rules": []} 46 | 47 | 48 | def remove_file_if_exists(file_path): 49 | if os.path.isfile(file_path): 50 | try: 51 | os.remove(file_path) 52 | except Exception: 53 | print("CFML: unable to remove file: " + file_path) 54 | return False 55 | return True 56 | 57 | 58 | def toggle(): 59 | preferences = sublime.load_settings("Preferences.sublime-settings") 60 | color_scheme = os.path.basename(preferences.get("color_scheme")) 61 | color_scheme_file = os.path.splitext(color_scheme)[0] + ".sublime-color-scheme" 62 | user_color_scheme = get_user_color_scheme("Packages/User/" + color_scheme_file) 63 | user_color_scheme_path = os.path.join( 64 | sublime.packages_path(), "User", color_scheme_file 65 | ) 66 | 67 | non_cfml_rules = [ 68 | row 69 | for row in user_color_scheme["rules"] 70 | if "scope" not in row or not row["scope"].endswith("cfml") 71 | ] 72 | has_cfml_rules = len(user_color_scheme["rules"]) > len(non_cfml_rules) 73 | 74 | if has_cfml_rules: 75 | user_color_scheme["rules"] = non_cfml_rules 76 | else: 77 | user_color_scheme["rules"].extend(get_cfml_rules()) 78 | 79 | with open(user_color_scheme_path, "w") as f: 80 | f.write(sublime.encode_value(user_color_scheme, True)) 81 | 82 | if len(user_color_scheme.keys()) == 1 and len(user_color_scheme["rules"]) == 0: 83 | print("CFML: Packages/User/{} is now empty.".format(color_scheme_file)) 84 | 85 | 86 | class CfmlColorSchemeStylesCommand(sublime_plugin.ApplicationCommand): 87 | def run(self): 88 | if int(sublime.version()) < 3176: 89 | sublime.error_message( 90 | "Color scheme customization is only available in Sublime Text builds >= 3176" 91 | ) 92 | return 93 | toggle() 94 | -------------------------------------------------------------------------------- /src/plugins_/entities/completions.py: -------------------------------------------------------------------------------- 1 | from ...component_index import component_index 2 | from . import entity_utils 3 | 4 | 5 | projects = {} 6 | 7 | 8 | def build_project_entities(project_name): 9 | global projects 10 | entities = component_index.get_entities(project_name) 11 | completions = [] 12 | for key in entities: 13 | completions.append( 14 | (entities[key]["entity_name"] + "\tentity", entities[key]["entity_name"]) 15 | ) 16 | projects[project_name] = completions 17 | 18 | 19 | def get_script_completions(cfml_view): 20 | if not cfml_view.project_name or cfml_view.project_name not in projects: 21 | return None 22 | 23 | if cfml_view.view.match_selector( 24 | cfml_view.position, 25 | "meta.function-call.support.entity.cfml meta.function-call.arguments.support.cfml string.quoted", 26 | ): 27 | if is_entityname_param(cfml_view.function_call_params): 28 | completions = projects[cfml_view.project_name] 29 | if len(completions) > 0: 30 | return cfml_view.CompletionList(completions, 0, False) 31 | 32 | return None 33 | 34 | 35 | def get_dot_completions(cfml_view): 36 | if not cfml_view.project_name or len(cfml_view.dot_context) == 0: 37 | return None 38 | 39 | entity_selector = "meta.function-call.support.entity.cfml" 40 | entity_methods = ["entitynew", "entityload", "entityloadbypk"] 41 | entity_name = None 42 | 43 | if cfml_view.dot_context[ 44 | 0 45 | ].name in entity_methods and cfml_view.view.match_selector( 46 | cfml_view.prefix_start - 2, entity_selector 47 | ): 48 | entity_name = entity_utils.get_entity_name( 49 | cfml_view.view.substr(cfml_view.dot_context[0].args_region), 50 | cfml_view.dot_context[0].name, 51 | ) 52 | 53 | elif entity_utils.is_possible_entity(cfml_view.dot_context): 54 | entity_tuple = entity_utils.find_entity_by_var_assignment( 55 | cfml_view, cfml_view.prefix_start, cfml_view.dot_context[0].name 56 | ) 57 | if entity_tuple[0] is not None: 58 | entity_name = entity_tuple[0] 59 | 60 | if entity_name: 61 | completions = get_completions_by_entity_name( 62 | cfml_view.project_name, entity_name 63 | ) 64 | if completions: 65 | return cfml_view.CompletionList(completions, 0, False) 66 | 67 | return None 68 | 69 | 70 | def is_entityname_param(function_call_params): 71 | if function_call_params.named_params: 72 | active_name = ( 73 | function_call_params.params[function_call_params.current_index][0] or "" 74 | ) 75 | return active_name.lower() == "entityname" 76 | 77 | return function_call_params.current_index == 0 78 | 79 | 80 | def get_completions_by_entity_name(project_name, entity_name): 81 | comp = component_index.get_completions_by_entity_name(project_name, entity_name) 82 | 83 | if comp: 84 | filtered_completions = [] 85 | for completion in comp["functions"]: 86 | if not completion.private: 87 | hint = "method" if completion.hint == "method" else entity_name 88 | filtered_completions.append( 89 | (completion.key + "\t" + hint, completion.content) 90 | ) 91 | return filtered_completions 92 | 93 | return None 94 | -------------------------------------------------------------------------------- /src/component_parser/regex.py: -------------------------------------------------------------------------------- 1 | import re 2 | from collections import namedtuple 3 | 4 | 5 | component = r""" 6 | (?:/\*\*((?:\*(?!/)|[^*])*)\*/\s+)? 7 | ( 8 | (?:{]*) 12 | """ 13 | component = re.compile(component, re.I | re.X | re.S) 14 | Component = namedtuple('Component', 'script docblock attributes') 15 | 16 | 17 | script_function = r""" 18 | (?:/\*\*((?:\*(?!/)|[^*])*)\*/\s+)? 19 | (?:\b(private|package|public|remote|static|final|abstract)\s+)? 20 | (?:\b(private|package|public|remote|static|final|abstract)\s+)? 21 | (?:\b([A-Za-z0-9_\.$]+)\s+)? 22 | function\s+ 23 | ([_$a-zA-Z][$\w]*)\s* 24 | (?=\() 25 | """ 26 | script_function = re.compile(script_function, re.I | re.X | re.S) 27 | ScriptFunction = namedtuple( 28 | 'ScriptFunction', 29 | 'docblock storage_slot_1 storage_slot_2 returntype name parameters attributes' 30 | ) 31 | 32 | 33 | script_parameter = r""" 34 | (?:(required)\s+)? 35 | (?:\b([\w.]+)\b\s+)? 36 | (\b\w+\b) 37 | (?:\s*=\s*(\{[^\}]*\}|\[[^\]]*\]|\([^\)]*\)|(?:(?!\b\w+\s*=).)+))? 38 | (.*)? 39 | """ 40 | script_parameter = re.compile(script_parameter, re.I | re.S | re.X) 41 | ScriptParameter = namedtuple( 42 | 'ScriptParameter', 43 | 'required type name default attributes' 44 | ) 45 | 46 | 47 | attribute = r""" 48 | \b(\w+)\b(?:\s*=\s*(?:(['"])(.*?)(\2)|([a-z0-9:.]+)))? 49 | """ 50 | attribute = re.compile(attribute, re.I | re.X | re.S) 51 | Attribute = namedtuple('Attribute', 'key quote_start value quote_end unquoted_value') 52 | 53 | 54 | docblock = r""" 55 | \n\s*(?:\*\s*)?(?:@(\w+)(?:\.(\w+))?)?\s*(\S.*) 56 | """ 57 | docblock = re.compile(docblock, re.I | re.X) 58 | Docblock = namedtuple('Dockblock', 'key subkey value') 59 | 60 | 61 | strings = r""" 62 | "[^"]*"|'[^']*' 63 | """ 64 | strings = re.compile(strings, re.X) 65 | 66 | 67 | function_attributes = r""" 68 | \)([^)]*)$ 69 | """ 70 | function_attributes = re.compile(function_attributes, re.X | re.S) 71 | 72 | function_block = r""" 73 | 74 | """ 75 | function_block = re.compile(function_block, re.X | re.I | re.S) 76 | 77 | function_start_tag = r""" 78 | ]*)> 79 | """ 80 | function_start_tag = re.compile(function_start_tag, re.X | re.I) 81 | 82 | function_end_tag = r""" 83 | 84 | """ 85 | function_end_tag = re.compile(function_end_tag, re.X | re.I) 86 | 87 | argument_tag = r""" 88 | ]*)> 89 | """ 90 | argument_tag = re.compile(argument_tag, re.X | re.I) 91 | 92 | cfml_property = r""" 93 | ^\s* 94 | (?:]*) 97 | """ 98 | cfml_property = re.compile(cfml_property, re.X | re.I | re.M) 99 | 100 | property_type_name = r""" 101 | \A[\s\n]* 102 | (?!\b\w+\s*=) 103 | (?:(\w+)\s+)? 104 | \b(\w+)\b 105 | """ 106 | property_type_name = re.compile(property_type_name, re.X) 107 | 108 | string_quoted_single = r""" 109 | '[^']*' 110 | """ 111 | string_quoted_single = re.compile(string_quoted_single, re.X) 112 | 113 | string_quoted_double = r""" 114 | "[^"]*" 115 | """ 116 | string_quoted_double = re.compile(string_quoted_double, re.X) 117 | 118 | line_comment = r""" 119 | //[^\r\n]\r?\n 120 | """ 121 | line_comment = re.compile(line_comment, re.X) 122 | 123 | multiline_comment = r""" 124 | /\*.*?\*\/ 125 | """ 126 | multiline_comment = re.compile(multiline_comment, re.X | re.S) 127 | 128 | tag_comment = r""" 129 | 130 | """ 131 | tag_comment = re.compile(tag_comment, re.X | re.S) 132 | -------------------------------------------------------------------------------- /templates/inline_documentation.html: -------------------------------------------------------------------------------- 1 | 2 | 147 |
148 |

${header}

149 |
${body}
150 |
${links}
151 |
152 | ${pagination} 153 | 154 | -------------------------------------------------------------------------------- /src/component_index/completions.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from .. import utils 3 | 4 | CfcCompletion = namedtuple("CfcCompletion", "key hint content file_path accessor private") 5 | 6 | # note that the completion tuples include 4 elements, so they will need to be modified before 7 | # being returned by subscribers 8 | 9 | 10 | def build(project_name, file_paths, get_extended_metadata_by_file_path): 11 | completions = {} 12 | for file_path in file_paths: 13 | extended_metadata = get_extended_metadata_by_file_path(project_name, file_path) 14 | completions[file_path] = build_file_completions(extended_metadata) 15 | return completions 16 | 17 | 18 | def build_file_completions(extended_metadata): 19 | file_completions = {} 20 | for completion_type in ["basic", "required", "full"]: 21 | comps = {"constructor": None} 22 | comps["functions"] = make_completions(extended_metadata["functions"], extended_metadata["function_file_map"], completion_type) 23 | 24 | if "init" in extended_metadata["functions"]: 25 | constructor_meta = extended_metadata["functions"]["init"] 26 | constructor_loc = extended_metadata["function_file_map"]["init"] 27 | comps["constructor"] = make_completion(constructor_meta, constructor_loc, completion_type) 28 | 29 | file_completions[completion_type] = comps 30 | 31 | return file_completions 32 | 33 | 34 | def make_completions(funct_meta, funct_cfcs, completion_type): 35 | return [make_completion(funct_meta[funct_key], funct_cfcs[funct_key], completion_type) for funct_key in sorted(funct_meta)] 36 | 37 | 38 | def make_completion(funct, cfc_file_path, completion_type): 39 | key = funct["name"] 40 | hint = "method" 41 | if utils.get_setting("cfc_completion_names") == "full": 42 | key += "()" 43 | if funct["meta"]["returntype"]: 44 | key += ":" + funct["meta"]["returntype"] 45 | hint = cfc_file_path.split("/").pop().split(".")[0] 46 | 47 | return CfcCompletion( 48 | key, 49 | hint, 50 | funct["name"] + "(" + make_arguments_string(funct["meta"]["parameters"], completion_type) + ")", 51 | cfc_file_path, 52 | funct["implicit"], 53 | funct["meta"]["access"] == "private" 54 | ) 55 | 56 | 57 | def make_arguments_string(parameters, completion_type): 58 | index = 1 59 | delim = "" 60 | arguments_string = "" 61 | 62 | if completion_type == "basic": 63 | return arguments_string 64 | 65 | for argument_params in parameters: 66 | if not argument_params: 67 | continue 68 | if argument_params["required"] or index == 1: 69 | arguments_string += delim + "${" + str(index) + ":" + make_argument_string(argument_params, completion_type) + "}" 70 | index += 1 71 | elif completion_type == "required": 72 | break 73 | else: 74 | arguments_string += "${" + str(index) + ":, ${" + str(index + 1) + ":" + make_argument_string(argument_params, completion_type) + "}}" 75 | index += 2 76 | delim = ', ' 77 | return arguments_string 78 | 79 | 80 | def make_argument_string(argument_params, completion_type): 81 | if completion_type == "required": 82 | return argument_params["name"] 83 | 84 | argument_string = "required " if argument_params["required"] else "" 85 | if argument_params["type"]: 86 | argument_string += argument_params["type"] + " " 87 | argument_string += argument_params["name"] 88 | if argument_params["default"]: 89 | argument_string += "=" + argument_params["default"] 90 | return argument_string 91 | -------------------------------------------------------------------------------- /color-schemes/testbox.hidden-tmTheme: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | name 5 | TestBox 6 | settings 7 | 8 | 9 | settings 10 | 11 | background 12 | #0F0F0F 13 | caret 14 | #c0c5ce 15 | foreground 16 | #CDD3DE 17 | invisibles 18 | #65737e 19 | lineHighlight 20 | #65737e55 21 | selection 22 | #4f5b66 23 | 24 | 25 | 26 | name 27 | TestBox Success 28 | scope 29 | testbox.success 30 | settings 31 | 32 | fontStyle 33 | bold 34 | foreground 35 | #008000 36 | 37 | 38 | 39 | name 40 | TestBox Failure 41 | scope 42 | testbox.failure 43 | settings 44 | 45 | fontStyle 46 | bold 47 | foreground 48 | #FFA500 49 | 50 | 51 | 52 | name 53 | TestBox Error 54 | scope 55 | testbox.error 56 | settings 57 | 58 | fontStyle 59 | bold 60 | foreground 61 | #FF0000 62 | 63 | 64 | 65 | name 66 | TestBox Skipped 67 | scope 68 | testbox.skipped 69 | settings 70 | 71 | fontStyle 72 | bold 73 | foreground 74 | #0000FF 75 | 76 | 77 | 78 | name 79 | Stack 80 | scope 81 | testbox.stack 82 | settings 83 | 84 | fontStyle 85 | bold 86 | foreground 87 | 88 | 89 | 90 | 91 | name 92 | TestBox Stack 93 | scope 94 | testbox.stack.testbox 95 | settings 96 | 97 | fontStyle 98 | italic 99 | foreground 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/plugins_/in_file_completions/in_file_completions.py: -------------------------------------------------------------------------------- 1 | from ... import utils, component_index 2 | 3 | 4 | def get_script_completions(cfml_view): 5 | completions = component_index.build_file_completions(cfml_view.view_metadata)[ 6 | utils.get_setting("cfml_cfc_completions") 7 | ] 8 | completions = [ 9 | make_completion(completion, cfml_view.file_path) 10 | for completion in completions["functions"] 11 | ] 12 | if len(completions) > 0: 13 | return cfml_view.CompletionList(completions, 0, False) 14 | return None 15 | 16 | 17 | def get_dot_completions(cfml_view): 18 | if len(cfml_view.dot_context) == 0: 19 | return None 20 | 21 | for symbol in cfml_view.dot_context: 22 | if not symbol.is_function: 23 | if symbol.name == "this": 24 | completions = component_index.build_file_completions( 25 | cfml_view.view_metadata 26 | )[utils.get_setting("cfml_cfc_completions")] 27 | completions = [ 28 | make_completion(completion, cfml_view.file_path) 29 | for completion in completions["functions"] 30 | ] 31 | return cfml_view.CompletionList(completions, 0, False) 32 | 33 | if len(cfml_view.dot_context) == 1 and symbol.name == "arguments": 34 | current_function_body = utils.get_current_function_body( 35 | cfml_view.view, cfml_view.position, component_method=False 36 | ) 37 | if current_function_body: 38 | function = cfml_view.get_function(current_function_body.begin() - 1) 39 | meta = cfml_view.get_string_metadata( 40 | cfml_view.view.substr(function[2]) + "{}" 41 | ) 42 | if "functions" in meta and function[0] in meta["functions"]: 43 | args = meta["functions"][function[0]]["meta"]["parameters"] 44 | completions = [ 45 | (arg["name"] + "\targuments", arg["name"]) for arg in args 46 | ] 47 | return cfml_view.CompletionList(completions, 0, False) 48 | 49 | if ( 50 | symbol.name == "super" 51 | and cfml_view.project_name 52 | and cfml_view.view_metadata["extends"] 53 | ): 54 | comp = component_index.component_index.get_completions_by_dot_path( 55 | cfml_view.project_name, cfml_view.view_metadata["extends"] 56 | ) 57 | 58 | if not comp and cfml_view.file_path: 59 | extends_file_path = component_index.component_index.resolve_path( 60 | cfml_view.project_name, 61 | cfml_view.file_path, 62 | cfml_view.view_metadata["extends"], 63 | ) 64 | comp = component_index.component_index.get_completions_by_file_path( 65 | cfml_view.project_name, extends_file_path 66 | ) 67 | 68 | if comp: 69 | completions = [ 70 | (completion.key + "\t" + completion.hint, completion.content) 71 | for completion in comp["functions"] 72 | ] 73 | return cfml_view.CompletionList(completions, 0, False) 74 | 75 | return None 76 | 77 | 78 | def make_completion(comp, file_path): 79 | hint = "this" 80 | if len(comp.file_path) > 0 and comp.file_path != file_path: 81 | hint = comp.hint 82 | return (comp.key + "\t" + hint, comp.content) 83 | -------------------------------------------------------------------------------- /src/cfdocs.py: -------------------------------------------------------------------------------- 1 | import json 2 | import time 3 | import urllib.request 4 | import urllib.error 5 | import certifi 6 | from collections import deque 7 | from . import utils 8 | 9 | CFDOCS_BASE_URL = "https://raw.githubusercontent.com/foundeo/cfdocs/master/data/en/" 10 | CFDOCS_HTTP_ERROR_MESSAGE = """ 11 |

HTTP requests to GitHub seem to be failing at the moment. This means that 12 | cfdocs.org documentation is not currently available.

13 |

To avoid seeing this error message on mouse hover you can set the `cfml_hover_docs` 14 | setting to false in your user package settings.

15 |

Please note that it is possible to load cfdocs.org data from a local drive by cloning or 16 | downloading the cfdocs.org repo (https://github.com/foundeo/cfdocs) and using the `cfdocs_path` 17 | package setting to point to the data folder of the repository.

18 | """ 19 | 20 | cfdocs_cache = {} 21 | cfdocs_failed_requests = deque() 22 | 23 | 24 | def get_cfdoc(function_or_tag): 25 | if utils.get_setting("cfdocs_path"): 26 | return load_cfdoc(function_or_tag) 27 | return fetch_cfdoc(function_or_tag) 28 | 29 | 30 | def load_cfdoc(function_or_tag): 31 | global cfdocs_cache 32 | file_path = function_or_tag + ".json" 33 | if file_path not in cfdocs_cache: 34 | full_file_path = ( 35 | utils.normalize_path(utils.get_setting("cfdocs_path")) + "/" + file_path 36 | ) 37 | try: 38 | with open(full_file_path, "r", encoding="utf-8") as f: 39 | json_string = f.read() 40 | except Exception: 41 | data = {"error_message": "Unable to read " + function_or_tag + ".json"} 42 | return data, False 43 | try: 44 | data = json.loads(json_string) 45 | except ValueError as e: 46 | data = { 47 | "error_message": "Unable to decode " 48 | + function_or_tag 49 | + ".json
ValueError: " 50 | + str(e) 51 | } 52 | return data, False 53 | 54 | cfdocs_cache[file_path] = data 55 | 56 | return cfdocs_cache[file_path], True 57 | 58 | 59 | def fetch_cfdoc(function_or_tag): 60 | global cfdocs_cache, cfdocs_failed_requests 61 | file_path = function_or_tag + ".json" 62 | 63 | if file_path not in cfdocs_cache: 64 | while ( 65 | len(cfdocs_failed_requests) 66 | and int(time.time() - cfdocs_failed_requests[0]) > 1800 67 | ): 68 | cfdocs_failed_requests.popleft() 69 | 70 | if len(cfdocs_failed_requests) > 2: 71 | data = {"error_message": CFDOCS_HTTP_ERROR_MESSAGE} 72 | return data, False 73 | 74 | full_url = CFDOCS_BASE_URL + file_path 75 | try: 76 | json_string = urllib.request.urlopen(full_url, cafile=certifi.where()).read().decode("utf-8") 77 | except urllib.error.HTTPError as e: 78 | cfdocs_failed_requests.append(time.time()) 79 | data = { 80 | "error_message": "Unable to fetch " 81 | + function_or_tag 82 | + ".json
" 83 | + str(e) 84 | } 85 | return data, False 86 | 87 | try: 88 | data = json.loads(json_string) 89 | except ValueError as e: 90 | data = { 91 | "error_message": "Unable to decode " 92 | + function_or_tag 93 | + ".json
ValueError: " 94 | + str(e) 95 | } 96 | return data, False 97 | 98 | cfdocs_cache[file_path] = data 99 | 100 | return cfdocs_cache[file_path], True 101 | -------------------------------------------------------------------------------- /syntaxes/build_base_cfscript_syntax.py: -------------------------------------------------------------------------------- 1 | import json 2 | import pathlib 3 | import re 4 | 5 | def wrap_regex(regex, indent=8): 6 | return re.sub(r"(.{80}[^|]*)", r"\1\n%s" % (" " * indent), regex) 7 | 8 | 9 | def generate_function_regex(): 10 | functions = pathlib.Path( 11 | "../src/plugins_/basecompletions/json/cfml_functions.json" 12 | ).read_text() 13 | keys = [k.lower() for k in json.loads(functions)] 14 | prefixes = [ 15 | 'array', 16 | 'struct', 17 | 'component', 18 | 'cache', 19 | 'entity', 20 | 'image', 21 | 'date', 22 | 'transaction', 23 | 'xml', 24 | 'spreadsheet', 25 | 'orm', 26 | 'object', 27 | 'list', 28 | 'query', 29 | 'create', 30 | 'get', 31 | 'url', 32 | 'is', 33 | 'store', 34 | 'to', 35 | 'replace', 36 | ] 37 | 38 | prefixed = {} 39 | non_prefixed = [] 40 | 41 | for item in sorted(keys): 42 | for prefix in prefixes: 43 | if item.startswith(prefix) and len(item) > len(prefix): 44 | prefixed.setdefault(prefix, []).append(item[len(prefix) :]) 45 | break 46 | else: 47 | non_prefixed.append(item) 48 | 49 | func_list = [] 50 | 51 | for prefix in sorted(prefixed): 52 | string = prefix + "(?:" + "|".join(prefixed[prefix]) + ")" 53 | func_list.append(string) 54 | 55 | func_list.extend(non_prefixed) 56 | 57 | return wrap_regex("|".join(func_list)) 58 | 59 | 60 | def generate_member_function_regex(): 61 | 62 | member_functions = set() 63 | 64 | data = pathlib.Path( 65 | "../src/plugins_/basecompletions/json/cfml_member_functions.json" 66 | ).read_text() 67 | data = json.loads(data) 68 | 69 | 70 | for member_type in data: 71 | methods = [m.lower() for m in data[member_type].keys()] 72 | member_functions.update(methods) 73 | 74 | return wrap_regex("|".join(sorted(list(member_functions)))) 75 | 76 | 77 | def generate_tag_regex(): 78 | tags_to_filter = [ 79 | 'abort', 80 | 'admin', 81 | 'case', 82 | 'catch', 83 | 'component', 84 | 'continue', 85 | 'defaultcase', 86 | 'else', 87 | 'elseif', 88 | 'exit', 89 | 'finally', 90 | 'function', 91 | 'if', 92 | 'interface', 93 | 'print', 94 | 'rethrow', 95 | 'retry', 96 | 'return', 97 | 'script', 98 | 'servlet', 99 | 'servletparam', 100 | 'set', 101 | 'sleep', 102 | 'switch', 103 | 'try', 104 | 'while', 105 | ] 106 | tags = pathlib.Path( 107 | "../src/plugins_/basecompletions/json/cfml_tags.json" 108 | ).read_text() 109 | tags = [t.lower()[2:] for t in json.loads(tags).keys() if t.lower()[2:] not in tags_to_filter] 110 | return wrap_regex("|".join(sorted(tags))) 111 | 112 | support_functions = generate_function_regex() 113 | member_functions = generate_member_function_regex() 114 | tags_in_script = generate_tag_regex() 115 | 116 | syntax = f""" 117 | %YAML 1.2 118 | --- 119 | name: CFScript (CFML) Base 120 | scope: source.cfml.script.base 121 | version: 2 122 | hidden: true 123 | 124 | variables: 125 | support_functions: |- 126 | (?x)(?i) 127 | (?: 128 | {support_functions} 129 | ) 130 | 131 | member_functions: |- 132 | (?x)(?i) 133 | (?: 134 | {member_functions} 135 | ) 136 | 137 | tags_in_script: |- 138 | (?x)(?i) 139 | (?: 140 | {tags_in_script} 141 | ) 142 | 143 | contexts: 144 | main: 145 | - match: . 146 | """ 147 | 148 | pathlib.Path('CFScript (CFML) Base.sublime-syntax').write_text(syntax.lstrip()) 149 | -------------------------------------------------------------------------------- /syntaxes/tests/syntax_test_cfml_component.cfc: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | extends="base.test" 6 | 7 | 8 | 9 | 10 | 11 | extends='base.test' 12 | 13 | 14 | 15 | 16 | 17 | extends=base.test 18 | 19 | 20 | 21 | 22 | > 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
Alert!
45 | 46 | 47 |
48 | 49 | 50 | 51 |
52 | 53 |
54 | 55 | -------------------------------------------------------------------------------- /src/commands/__init__.py: -------------------------------------------------------------------------------- 1 | import sublime_plugin 2 | from .. import utils 3 | from ..component_index import component_index 4 | from ..custom_tag_index import custom_tag_index 5 | from .cfc_dotted_path import CfmlCfcDottedPathCommand, CfmlSidebarCfcDottedPathCommand 6 | from .controller_view_toggle import CfmlToggleControllerViewCommand 7 | from .color_scheme_styles import CfmlColorSchemeStylesCommand 8 | 9 | 10 | class CloseCfmlTagCommand(sublime_plugin.TextCommand): 11 | def run(self, edit): 12 | for sel in self.view.sel(): 13 | pt = sel.begin() 14 | cfml_only = self.view.match_selector(pt, "string") 15 | last_open_tag = utils.get_last_open_tag( 16 | self.view, pt - 1, cfml_only, custom_tag_index 17 | ) 18 | if last_open_tag: 19 | if len(self.view.sel()) == 1: 20 | self.view.run_command( 21 | "insert", {"characters": "/" + last_open_tag + ">"} 22 | ) 23 | else: 24 | self.view.insert(edit, pt, "/" + last_open_tag + ">") 25 | else: 26 | # if there is no open tag print "/" 27 | self.view.insert(edit, pt, "/") 28 | 29 | 30 | class CfmlAutoInsertClosingTagCommand(sublime_plugin.TextCommand): 31 | def run(self, edit): 32 | pt = self.view.sel()[0].begin() 33 | if utils.get_setting("cfml_auto_insert_closing_tag"): 34 | tag_name = utils.get_tag_name(self.view, pt) 35 | if tag_name: 36 | is_custom_tag = self.view.match_selector(pt, "meta.tag.custom.cfml") 37 | if is_custom_tag: 38 | closing_custom_tags = custom_tag_index.get_closing_custom_tags( 39 | utils.get_project_name(self.view) 40 | ) 41 | has_closing_tag = tag_name in closing_custom_tags 42 | else: 43 | cfml_non_closing_tags = utils.get_setting("cfml_non_closing_tags") 44 | has_closing_tag = tag_name not in cfml_non_closing_tags 45 | if has_closing_tag: 46 | next_char = utils.get_next_character(self.view, pt) 47 | tag_close_search_region = self.view.find("", pt) 48 | if next_char != tag_close_search_region.begin(): 49 | self.view.run_command( 50 | "insert_snippet", {"contents": ">$0"} 51 | ) 52 | return 53 | self.view.run_command("insert_snippet", {"contents": ">"}) 54 | 55 | 56 | class CfmlBetweenTagPairCommand(sublime_plugin.TextCommand): 57 | def run(self, edit): 58 | pt = self.view.sel()[0].begin() 59 | cfml_between_tag_pair = utils.get_setting("cfml_between_tag_pair") 60 | if cfml_between_tag_pair in [ 61 | "newline", 62 | "indent", 63 | ] and utils.between_cfml_tag_pair(self.view, pt): 64 | self.view.run_command( 65 | "insert_snippet", 66 | { 67 | "contents": "\n" 68 | + ("\t" if cfml_between_tag_pair == "indent" else "") 69 | + "$0\n" 70 | }, 71 | ) 72 | return 73 | self.view.run_command("insert_snippet", {"contents": "\n"}) 74 | 75 | 76 | class CfmlEditSettingsCommand(sublime_plugin.WindowCommand): 77 | def run(self, file, default): 78 | package_file = file.replace("{cfml_package_name}", __name__.split(".")[0]) 79 | self.window.run_command( 80 | "edit_settings", {"base_file": package_file, "default": default} 81 | ) 82 | 83 | 84 | class CfmlIndexProjectCommand(sublime_plugin.WindowCommand): 85 | def run(self): 86 | project_name = utils.get_project_name_from_window(self.window) 87 | if project_name: 88 | custom_tag_index.resync_project(project_name) 89 | component_index.resync_project(project_name) 90 | -------------------------------------------------------------------------------- /src/plugins_/cfcs/di.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | from . import cfcs, documentation 4 | from ...cfml_view import CfmlView 5 | 6 | 7 | class CfmlDiPropertyCommand(sublime_plugin.TextCommand): 8 | def run(self, edit, property_name=None): 9 | 10 | pt = self.view.sel()[0].begin() 11 | 12 | if not self.view.match_selector(pt, "source.cfml meta.class"): 13 | return 14 | 15 | if property_name: 16 | self.insert_property(edit, property_name) 17 | return 18 | 19 | cfml_view = CfmlView(self.view, pt) 20 | 21 | if not cfml_view.project_name: 22 | return 23 | 24 | cfc_info, metadata, function_name, regions = documentation.find_cfc(cfml_view) 25 | 26 | if cfc_info: 27 | self.insert_property(edit, cfc_info["name"]) 28 | else: 29 | cfc_list = cfcs.get_cfc_list(cfml_view.project_name) 30 | 31 | def callback(i): 32 | if i > -1: 33 | self.view.run_command( 34 | "cfml_di_property", {"property_name": cfc_list[i]} 35 | ) 36 | 37 | self.view.window().show_quick_panel( 38 | cfc_list, callback, flags=sublime.MONOSPACE_FONT 39 | ) 40 | 41 | def insert_property(self, edit, property_name): 42 | di_property = self.get_setting("di_property") 43 | is_script = ( 44 | len(self.view.find_by_selector("source.cfml.script meta.class.body.cfml")) 45 | > 0 46 | ) 47 | property_string = ( 48 | di_property.get("script_template", "") 49 | if is_script 50 | else di_property.get("tag_template", "") 51 | ) 52 | 53 | if "{name}" not in property_string: 54 | return 55 | 56 | properties = self.view.find_by_selector("meta.tag.property.cfml") 57 | property_names = [ 58 | self.view.substr(r).lower() 59 | for r in self.view.find_by_selector("meta.tag.property.name.cfml") 60 | ] 61 | 62 | if property_name.lower() in property_names: 63 | return 64 | 65 | if len(properties) > 0: 66 | indent_region = sublime.Region( 67 | self.view.line(properties[-1]).begin(), properties[-1].begin() 68 | ) 69 | indent_string = "\n" + self.view.substr(indent_region) 70 | injection_pt = properties[-1].end() 71 | else: 72 | tab_size = self.view.settings().get("tab_size") 73 | translate_tabs_to_spaces = self.view.settings().get( 74 | "translate_tabs_to_spaces" 75 | ) 76 | indent_string = "\n\n" + ( 77 | " " * tab_size if translate_tabs_to_spaces else "\t" 78 | ) 79 | injection_pt = self.view.find_by_selector("source.cfml meta.class.body")[ 80 | 0 81 | ].begin() 82 | 83 | if is_script: 84 | injection_pt += 1 85 | property_string = indent_string + property_string.replace( 86 | "{name}", property_name 87 | ) 88 | self.view.insert(edit, injection_pt, property_string) 89 | 90 | if di_property.get("sort_properties", False): 91 | properties = self.view.find_by_selector("meta.tag.property.cfml") 92 | property_names = self.view.find_by_selector("meta.tag.property.name.cfml") 93 | 94 | if len(properties) != len(property_names): 95 | return 96 | 97 | sorted_properties = [ 98 | self.view.substr(r) 99 | for r, name in sorted( 100 | zip(properties, property_names), 101 | reverse=True, 102 | key=lambda x: self.view.substr(x[1]), 103 | ) 104 | ] 105 | 106 | for i, r in enumerate(reversed(properties)): 107 | self.view.replace(edit, r, sorted_properties[i]) 108 | 109 | def get_setting(self, setting_key): 110 | if ( 111 | self.view.window().project_file_name() 112 | and setting_key in self.view.window().project_data() 113 | ): 114 | return self.view.window().project_data()[setting_key] 115 | package_settings = sublime.load_settings("cfml_package.sublime-settings") 116 | return package_settings.get(setting_key) 117 | -------------------------------------------------------------------------------- /src/plugins_/testbox/testbox_spec_outline.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | from ... import utils 4 | 5 | testbox_function_names = [ 6 | "describe", 7 | "it", 8 | "feature", 9 | "scenario", 10 | "story", 11 | "given", 12 | "when", 13 | "then", 14 | ] 15 | func_call_scope = "meta.function-call.cfml variable.function.cfml" 16 | string_scope = "meta.function-call.cfml meta.function-call.arguments.cfml meta.string" 17 | func_param_name_scope = "meta.function-call.cfml meta.function-call.arguments.cfml entity.other.function-parameter.cfml" 18 | 19 | 20 | class TestboxSpecOutlineCommand(sublime_plugin.TextCommand): 21 | def run(self, edit): 22 | 23 | # sanity check 24 | if not self.view.match_selector(0, "embedding.cfml"): 25 | return 26 | 27 | self.outline = [] 28 | self.outline_regions = [] 29 | self.selected_index = 0, None 30 | self.current_regions = [r for r in self.view.sel()] 31 | self.viewport_position = self.view.viewport_position() 32 | 33 | # collect title parameters 34 | title_params = {} 35 | for r in self.view.find_by_selector(func_param_name_scope): 36 | if self.view.substr(r).lower() == "title": 37 | title_params[r.begin()] = r 38 | 39 | # collect strings 40 | string_descriptions = { 41 | r.begin(): r for r in self.view.find_by_selector(string_scope) 42 | } 43 | 44 | # build outline 45 | for r in self.view.find_by_selector(func_call_scope): 46 | func_name = self.view.substr(r).lower() 47 | 48 | # continue if not a testbox func call 49 | if func_name not in testbox_function_names: 50 | continue 51 | 52 | # find the `(` after the func name 53 | next_pt = utils.get_next_character(self.view, r.end()) 54 | 55 | # find the next pt - could be string start or named title param 56 | next_pt = utils.get_next_character(self.view, next_pt + 1) 57 | 58 | # check for named title param 59 | if next_pt in title_params: 60 | # get `=` 61 | next_pt = utils.get_next_character( 62 | self.view, title_params[next_pt].end() 63 | ) 64 | # look for title string 65 | next_pt = utils.get_next_character(self.view, next_pt + 1) 66 | 67 | if next_pt in string_descriptions: 68 | # looks like a something we care about 69 | string_region = string_descriptions[next_pt] 70 | depth = ( 71 | self.view.scope_name(r.begin()).count("meta.function.body.cfml") - 1 72 | ) 73 | indent = " " * depth 74 | self.outline.append( 75 | indent + func_name + ": " + self.view.substr(string_region)[1:-1] 76 | ) 77 | self.outline_regions.append(string_region) 78 | 79 | distance = self.distance(string_region) 80 | if self.selected_index[1] is None or distance < self.selected_index[1]: 81 | self.selected_index = len(self.outline_regions) - 1, distance 82 | 83 | if len(self.outline) == 0: 84 | return 85 | 86 | self.view.window().show_quick_panel( 87 | self.outline, 88 | self.on_done, 89 | selected_index=self.selected_index[0], 90 | on_highlight=self.on_highlight, 91 | ) 92 | 93 | def distance(self, region): 94 | pt = self.current_regions[0].begin() 95 | if region.contains(pt): 96 | return 0 97 | if pt < region.begin(): 98 | return region.begin() - pt 99 | return pt - region.end() 100 | 101 | def refresh(self): 102 | # workaround the selection updates not being drawn 103 | self.view.add_regions("force_refresh", []) 104 | self.view.erase_regions("force_refresh") 105 | 106 | def on_done(self, i): 107 | if i == -1: 108 | # nothing selected, reset 109 | self.view.sel().clear() 110 | self.view.sel().add_all(self.current_regions) 111 | self.view.set_viewport_position(self.viewport_position) 112 | 113 | def on_highlight(self, i): 114 | self.view.sel().clear() 115 | self.view.sel().add(self.outline_regions[i]) 116 | self.view.show_at_center(self.outline_regions[i]) 117 | self.refresh() 118 | -------------------------------------------------------------------------------- /src/formatting/misc.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from ..cfml_plugins import basecompletions 3 | 4 | 5 | def format_properties(cfml_format): 6 | sort_by = cfml_format.get_setting("format_properties.sort_by") 7 | 8 | if sort_by != "name": 9 | return [] 10 | 11 | properties = cfml_format.find_by_selector( 12 | "source.cfml.script meta.tag.property.cfml" 13 | ) 14 | property_names = cfml_format.find_by_selector( 15 | "source.cfml.script meta.tag.property.name.cfml" 16 | ) 17 | 18 | if len(properties) != len(property_names): 19 | return 20 | 21 | sorted_properties = [ 22 | r 23 | for r, name in sorted( 24 | zip(properties, property_names), key=lambda x: cfml_format.view.substr(x[1]) 25 | ) 26 | ] 27 | replacements = [ 28 | (r, cfml_format.view.substr(sorted_r)) 29 | for r, sorted_r in zip(properties, sorted_properties) 30 | ] 31 | 32 | return replacements 33 | 34 | 35 | def normalize_builtin_functions(cfml_format): 36 | setting = cfml_format.get_setting("normalize_builtin_functions") 37 | substitutions = [] 38 | 39 | if setting is None: 40 | return substitutions 41 | 42 | function_name_map = { 43 | funct.lower(): funct for funct in basecompletions.basecompletions.function_names 44 | } 45 | 46 | for r in cfml_format.find_by_selector("source.cfml.script support.function.cfml"): 47 | funct = cfml_format.view.substr(r).lower() 48 | substitutions.append((r, function_name_map[funct])) 49 | 50 | return substitutions 51 | 52 | 53 | def normalize_strings(cfml_format): 54 | substitutions = [] 55 | 56 | selectors = { 57 | "script": [ 58 | { 59 | "base": "", 60 | "suffix": " -meta.tag.script.cfml -meta.class.declaration.cfml -meta.function.declaration.cfml", 61 | }, 62 | {"base": "meta.function.parameters", "suffix": ""}, 63 | ], 64 | "script-tag-attributes": [ 65 | {"base": "meta.tag.script.cfml", "suffix": ""}, 66 | {"base": "meta.class.declaration.cfml", "suffix": ""}, 67 | { 68 | "base": "meta.function.declaration.cfml", 69 | "suffix": " -meta.function.parameters", 70 | }, 71 | ], 72 | } 73 | 74 | def build_selector(key, selector): 75 | full_selector_list = [] 76 | for d in selectors[key]: 77 | full_selector_list.append( 78 | "source.cfml.script " + d["base"] + " " + selector + d["suffix"] 79 | ) 80 | return ",".join(full_selector_list) 81 | 82 | for key in selectors: 83 | key_setting = cfml_format.get_setting("normalize_strings." + key) 84 | 85 | if not key_setting or key_setting not in ["single", "double"]: 86 | continue 87 | 88 | quote = '"' if key_setting == "double" else "'" 89 | escaped_quote = '"' if key_setting == "single" else "'" 90 | opposite = "single" if key_setting == "double" else "double" 91 | 92 | start_selector = ( 93 | "string.quoted." 94 | + opposite 95 | + ".cfml punctuation.definition.string.begin.cfml" 96 | ) 97 | end_selector = ( 98 | "string.quoted." + opposite + ".cfml punctuation.definition.string.end.cfml" 99 | ) 100 | escaped_char_selector = ( 101 | "string.quoted." + opposite + ".cfml constant.character.escape.quote.cfml" 102 | ) 103 | meta_string_selector = ( 104 | "meta.string.quoted." 105 | + opposite 106 | + ".cfml -punctuation.definition.string -constant.character.escape.quote.cfml" 107 | ) 108 | 109 | for r in cfml_format.find_by_selector(build_selector(key, start_selector)): 110 | substitutions.append((r, quote)) 111 | 112 | for r in cfml_format.find_by_selector(build_selector(key, end_selector)): 113 | substitutions.append((r, quote)) 114 | 115 | for r in cfml_format.find_by_selector( 116 | build_selector(key, escaped_char_selector) 117 | ): 118 | substitutions.append((r, escaped_quote * int(r.size() / 2))) 119 | 120 | for r in cfml_format.find_by_selector( 121 | build_selector(key, meta_string_selector) 122 | ): 123 | for pt in range(r.begin(), r.end()): 124 | if cfml_format.view.substr(pt) == quote: 125 | substitutions.append((sublime.Region(pt, pt + 1), (quote * 2))) 126 | 127 | return substitutions 128 | -------------------------------------------------------------------------------- /src/component_parser/cfml_components.py: -------------------------------------------------------------------------------- 1 | # This module, via index(), takes in a directory path and recursively 2 | # browses it and its subfolders looking for ".cfc" files, and then uses the 3 | # functions module to index the functions from those files. 4 | # It assumes that all such files in the directory are component files, and does 5 | # not check that the files actually define components. It returns a dict of 6 | # normalized file paths mapped to a named tuple containing function metadata 7 | # and completions. 8 | 9 | # The returned dict structure looks like this: 10 | # { 11 | # "/path/to/mycfc.cfc": { 12 | # "accessors": true/false, 13 | # "entityname": entityname, 14 | # "extends": extends, 15 | # "functions": metadata, 16 | # "initmethod": initmethod, 17 | # "persistent": true/false, 18 | # "properties": metadata 19 | # }, 20 | # ... 21 | # } 22 | 23 | 24 | import re 25 | from . import cfml_functions, cfml_properties, regex, ranges 26 | 27 | 28 | def parse_cfc_file_string(file_string): 29 | cfscript_range = ranges.RangeWalker(file_string, 0, 'cfscript').walk() 30 | cfc_index = {} 31 | 32 | component_search = re.search(regex.component, file_string) 33 | 34 | if component_search: 35 | component = regex.Component( 36 | component_search.group(2).startswith('component'), 37 | component_search.group(1), 38 | component_search.group(3) 39 | ) 40 | 41 | if component.docblock: 42 | docblock = cfml_functions.parse_docblock(component.docblock) 43 | for key in docblock: 44 | for d in docblock[key]: 45 | full_key = d.key.lower() + '.' + d.subkey.lower() if len(d.subkey) > 0 else d.key.lower() 46 | cfc_index[full_key] = d.value.strip() 47 | 48 | cfc_index.update(cfml_functions.parse_attributes(component.attributes)) 49 | 50 | for key in ["extends", "initmethod", "entityname"]: 51 | if key not in cfc_index: 52 | cfc_index[key] = None 53 | 54 | for key in ["accessors", "persistent"]: 55 | if key in cfc_index and cfc_index[key].lower() in ["true", "yes"]: 56 | cfc_index[key] = True 57 | else: 58 | cfc_index[key] = False 59 | 60 | properties = cfml_properties.index(file_string, cfscript_range) 61 | functions = cfml_functions.index(file_string, cfscript_range) 62 | 63 | cfc_index["properties"] = prop_metadata_dict(properties) 64 | cfc_index["functions"] = get_accessors_metadata_dict(cfc_index["accessors"] or cfc_index["persistent"], properties) 65 | cfc_index["functions"].update(funct_metadata_dict(functions)) 66 | 67 | return cfc_index 68 | 69 | 70 | def funct_metadata_dict(functions): 71 | meta = {} 72 | for function_name in functions: 73 | meta[function_name.lower()] = { 74 | "name": function_name, 75 | "meta": functions[function_name], 76 | "implicit": False 77 | } 78 | return meta 79 | 80 | 81 | def prop_metadata_dict(properties): 82 | meta = {} 83 | for prop_name in properties: 84 | meta[prop_name.lower()] = { 85 | "name": prop_name, 86 | "meta": properties[prop_name] 87 | } 88 | return meta 89 | 90 | 91 | def get_accessors_metadata_dict(cfc_accessors, properties): 92 | """ 93 | build a dict for each property that has a getter and/or a setter 94 | these will be used as a base for explicit functions to be merged into 95 | """ 96 | meta = {} 97 | for prop_name in properties: 98 | attrs = properties[prop_name] 99 | cased_prop_name = prop_name[0].upper() + prop_name[1:] 100 | 101 | # getter 102 | if attrs["getter"] or (attrs["getter"] is None and cfc_accessors): 103 | funct_meta = {"access": "public", "parameters": []} 104 | funct_meta["returntype"] = attrs["type"] 105 | meta["get" + prop_name.lower()] = { 106 | "name": "get" + cased_prop_name, 107 | "meta": funct_meta, 108 | "implicit": True 109 | } 110 | 111 | # setter 112 | if attrs["setter"] or (attrs["setter"] is None and cfc_accessors): 113 | funct_meta = {"access": "public", "parameters": []} 114 | arg = {"default": None} 115 | arg["name"] = prop_name 116 | arg["required"] = True 117 | arg["type"] = attrs["type"] 118 | funct_meta["returntype"] = "void" 119 | funct_meta["parameters"].append(arg) 120 | meta["set" + prop_name.lower()] = { 121 | "name": "set" + cased_prop_name, 122 | "meta": funct_meta, 123 | "implicit": True 124 | } 125 | 126 | return meta 127 | -------------------------------------------------------------------------------- /src/buffer_metadata.py: -------------------------------------------------------------------------------- 1 | import timeit 2 | from . import utils 3 | from . import events 4 | from .component_parser import parse_cfc_file_string 5 | from .component_index import component_index 6 | 7 | 8 | buffer_metadata_cache = {} 9 | 10 | 11 | def get_minimal_file_string(view): 12 | min_string = "" 13 | 14 | tag_component_regions = view.find_by_selector("meta.class.cfml") 15 | 16 | if len(tag_component_regions) > 0: 17 | min_string += view.substr(tag_component_regions[0]) + "\n" 18 | current_funct = "" 19 | for r in view.find_by_selector( 20 | "meta.function.cfml, meta.function.body.tag.cfml meta.tag.argument.cfml" 21 | ): 22 | text = view.substr(r) 23 | if text.lower().startswith(" 0: 24 | min_string += current_funct + "\n" 25 | current_funct = "" 26 | current_funct += text + "\n" 27 | min_string += current_funct + "\n" 28 | else: 29 | script_selectors = [ 30 | ("comment.block.documentation.cfml -meta.class", "\n"), 31 | ("meta.class.declaration.cfml", " {\n"), 32 | ("meta.tag.property.cfml", ";\n"), 33 | ] 34 | 35 | for selector, separator in script_selectors: 36 | for r in view.find_by_selector(selector): 37 | min_string += view.substr(r) + separator 38 | 39 | funct_regions = "meta.class.body.cfml comment.block.documentation.cfml, meta.function.declaration.cfml -meta.function.body.cfml" 40 | for r in view.find_by_selector(funct_regions): 41 | string = view.substr(r) 42 | min_string += string + ("\n" if string.endswith("*/") else "{ }\n") 43 | 44 | min_string += "}" 45 | 46 | return min_string 47 | 48 | 49 | def get_cached_view_metadata(view): 50 | if view.buffer_id() in buffer_metadata_cache: 51 | return buffer_metadata_cache[view.buffer_id()][1] 52 | return get_view_metadata(view) 53 | 54 | 55 | def get_view_metadata(view): 56 | start_time = timeit.default_timer() 57 | file_string = get_minimal_file_string(view) 58 | base_meta = parse_cfc_file_string(file_string) 59 | 60 | if utils.get_setting("cfml_log_in_file_parse_time"): 61 | parse_time = timeit.default_timer() - start_time 62 | message = "CFML: parsed {} in {}ms" 63 | print(message.format(view.file_name() or "file", round(parse_time * 1000))) 64 | 65 | extended_meta = dict(base_meta) 66 | extended_meta.update( 67 | { 68 | "functions": {}, 69 | "function_file_map": {}, 70 | "properties": {}, 71 | "property_file_map": {}, 72 | } 73 | ) 74 | 75 | file_path = utils.normalize_path(view.file_name()) if view.file_name() else "" 76 | project_name = utils.get_project_name(view) 77 | if project_name and base_meta["extends"]: 78 | extends_file_path = component_index.resolve_path( 79 | project_name, file_path, base_meta["extends"] 80 | ) 81 | root_meta = component_index.get_extended_metadata_by_file_path( 82 | project_name, extends_file_path 83 | ) 84 | if root_meta: 85 | for key in [ 86 | "functions", 87 | "function_file_map", 88 | "properties", 89 | "property_file_map", 90 | ]: 91 | extended_meta[key].update(root_meta[key]) 92 | 93 | extended_meta["functions"].update(base_meta["functions"]) 94 | extended_meta["function_file_map"].update( 95 | {funct_key: file_path for funct_key in base_meta["functions"]} 96 | ) 97 | extended_meta["properties"].update(base_meta["properties"]) 98 | extended_meta["property_file_map"].update( 99 | {prop_key: file_path for prop_key in base_meta["properties"]} 100 | ) 101 | 102 | buffer_metadata_cache[view.buffer_id()] = timeit.default_timer(), extended_meta 103 | 104 | return extended_meta 105 | 106 | 107 | def on_view_loaded(view): 108 | if not view.match_selector(0, "embedding.cfml"): 109 | return 110 | get_view_metadata(view) 111 | 112 | 113 | def on_view_modified(view): 114 | if not view.match_selector(0, "embedding.cfml"): 115 | return 116 | 117 | if view.buffer_id() in buffer_metadata_cache: 118 | last_updated, meta = buffer_metadata_cache[view.buffer_id()] 119 | if timeit.default_timer() - last_updated < 0.5: 120 | return 121 | get_view_metadata(view) 122 | 123 | 124 | def on_view_closed(view): 125 | if view.buffer_id() in buffer_metadata_cache: 126 | del buffer_metadata_cache[view.buffer_id()] 127 | 128 | 129 | events.subscribe("on_load_async", on_view_loaded) 130 | events.subscribe("on_modified_async", on_view_modified) 131 | events.subscribe("on_close", on_view_closed) 132 | -------------------------------------------------------------------------------- /src/method_preview.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import webbrowser 4 | from . import utils 5 | from . import cfml_plugins 6 | from .cfml_view import CfmlView 7 | 8 | 9 | PREVIEW_TEMPLATE = "" 10 | PAGINATION_TEMPLATE = "" 11 | phantom_sets_by_buffer = {} 12 | 13 | 14 | def get_method_previews(cfml_view): 15 | previews = [] 16 | 17 | for p in cfml_plugins.plugins: 18 | preview = p.get_method_preview(cfml_view) 19 | if preview: 20 | previews.append(preview) 21 | 22 | return previews 23 | 24 | 25 | def _plugin_loaded(): 26 | global PREVIEW_TEMPLATE, PAGINATION_TEMPLATE 27 | root_path = "Packages/" + utils.get_plugin_name() + "/templates" 28 | PREVIEW_TEMPLATE = sublime.load_resource( 29 | root_path + "/method_preview.html" 30 | ).replace("\r", "") 31 | PAGINATION_TEMPLATE = sublime.load_resource(root_path + "/pagination.html").replace( 32 | "\r", "" 33 | ) 34 | 35 | 36 | def build_pagination(current_index, total_pages): 37 | pagination_variables = { 38 | "current_page": str(current_index + 1), 39 | "total_pages": str(total_pages), 40 | } 41 | 42 | previous_index = current_index - 1 if current_index > 0 else total_pages - 1 43 | pagination_variables["prev"] = "page_" + str(previous_index) 44 | 45 | next_index = current_index + 1 if current_index < total_pages - 1 else 0 46 | pagination_variables["next"] = "page_" + str(next_index) 47 | 48 | return sublime.expand_variables(PAGINATION_TEMPLATE, pagination_variables) 49 | 50 | 51 | def close_phantom(view): 52 | global phantom_sets_by_buffer 53 | buffer_id = view.buffer_id() 54 | if buffer_id in phantom_sets_by_buffer: 55 | view.erase_phantoms("cfml_method_preview") 56 | view.erase_regions("cfml_method_preview") 57 | del phantom_sets_by_buffer[buffer_id] 58 | 59 | 60 | def get_on_navigate(view, docs, current_index, pt): 61 | def on_navigate(href): 62 | if href.startswith("page_"): 63 | new_index = int(href.split("_").pop()) 64 | display_previews(view, docs, pt, new_index) 65 | elif href == "__close__": 66 | close_phantom(view) 67 | elif docs[current_index].on_navigate: 68 | docs[current_index].on_navigate(href) 69 | else: 70 | webbrowser.open_new_tab(href) 71 | 72 | return on_navigate 73 | 74 | 75 | def generate_previews(docs, current_index): 76 | preview_html_variables = dict(docs[current_index].preview_html_variables["html"]) 77 | preview_html_variables["pagination"] = ( 78 | build_pagination(current_index, len(docs)) if len(docs) > 1 else "" 79 | ) 80 | html = sublime.expand_variables(PREVIEW_TEMPLATE, preview_html_variables) 81 | return html, docs[current_index].preview_regions 82 | 83 | 84 | def merge_regions(regions): 85 | merged_regions = [] 86 | for region in sorted(regions): 87 | if len(merged_regions) > 0 and merged_regions[-1].contains(region): 88 | continue 89 | elif len(merged_regions) > 0 and merged_regions[-1].end() == region.begin(): 90 | merged_regions[-1] = sublime.Region( 91 | merged_regions[-1].begin(), region.end() 92 | ) 93 | else: 94 | merged_regions.append(region) 95 | return merged_regions 96 | 97 | 98 | def display_previews(view, previews, pt=-1, current_index=0): 99 | global phantom_sets_by_buffer 100 | buffer_id = view.buffer_id() 101 | 102 | if ( 103 | buffer_id in phantom_sets_by_buffer 104 | and pt != phantom_sets_by_buffer[buffer_id][1] 105 | ): 106 | return 107 | 108 | preview_html, preview_regions = generate_previews(previews, current_index) 109 | on_navigate = get_on_navigate(view, previews, current_index, pt) 110 | view.add_regions( 111 | "cfml_method_preview", 112 | merge_regions(preview_regions), 113 | "source", 114 | flags=sublime.DRAW_NO_FILL, 115 | ) 116 | 117 | phantom_set = sublime.PhantomSet(view, "cfml_method_preview") 118 | phantom_sets_by_buffer[buffer_id] = (phantom_set, pt) 119 | phantom = sublime.Phantom( 120 | view.line(pt), preview_html, sublime.LAYOUT_BLOCK, on_navigate 121 | ) 122 | phantom_set.update([phantom]) 123 | 124 | 125 | class CfmlPreviewMethodCommand(sublime_plugin.TextCommand): 126 | def run(self, edit, pt=None): 127 | buffer_id = self.view.buffer_id() 128 | if buffer_id in phantom_sets_by_buffer: 129 | close_phantom(self.view) 130 | return 131 | 132 | position = pt if pt else self.view.sel()[0].begin() 133 | cfml_view = CfmlView(self.view, position) 134 | previews = get_method_previews(cfml_view) 135 | if len(previews) > 0: 136 | display_previews( 137 | self.view, 138 | sorted(previews, key=lambda doc: doc.priority, reverse=True), 139 | position, 140 | ) 141 | -------------------------------------------------------------------------------- /src/plugins_/applicationcfc/appcfc.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import json 3 | from ... import utils 4 | 5 | SIDE_COLOR = "color(#4C9BB0 blend(var(--background) 60%))" 6 | 7 | 8 | appcfc = {"settings": {}, "settings_docs": {}, "methods": {}, "methods_docs": {}} 9 | 10 | 11 | def load(): 12 | global appcfc 13 | for key in appcfc: 14 | data = load_json_data(key) 15 | if key == "settings": 16 | data = {k: make_setting_completions(k, data[k]) for k in data} 17 | elif key == "methods": 18 | data = [ 19 | (key + "\tapplication.cfc", key + data[key]) 20 | for key in sorted(data.keys()) 21 | ] 22 | appcfc[key] = data 23 | 24 | 25 | def make_setting_completions(prefix, source_list): 26 | return [(key + "\t" + prefix, key) for key in sorted(source_list)] 27 | 28 | 29 | def load_json_data(filename): 30 | json_data = sublime.load_resource( 31 | "Packages/" 32 | + utils.get_plugin_name() 33 | + "/src/plugins_/applicationcfc/json/" 34 | + filename 35 | + ".json" 36 | ) 37 | return json.loads(json_data) 38 | 39 | 40 | def get_dot_completions(cfml_view): 41 | if ( 42 | cfml_view.file_name == "application.cfc" 43 | and len(cfml_view.dot_context) > 0 44 | and cfml_view.dot_context[-1].name == "this" 45 | ): 46 | key = ".".join([symbol.name for symbol in reversed(cfml_view.dot_context)]) 47 | if key in appcfc["settings"]: 48 | return cfml_view.CompletionList(appcfc["settings"][key], 0, False) 49 | 50 | return None 51 | 52 | 53 | def get_script_completions(cfml_view): 54 | if cfml_view.file_name == "application.cfc": 55 | if cfml_view.view.match_selector( 56 | cfml_view.position, 57 | "meta.class.body.cfml -meta.function -meta.struct-literal", 58 | ): 59 | return cfml_view.CompletionList(appcfc["methods"], 0, False) 60 | 61 | key = cfml_view.get_struct_var_assignment(cfml_view.position) 62 | if key and key in appcfc["settings"]: 63 | return cfml_view.CompletionList(appcfc["settings"][key], 0, False) 64 | 65 | return None 66 | 67 | 68 | def get_inline_documentation(cfml_view, doc_type): 69 | if cfml_view.file_name != "application.cfc": 70 | return None 71 | 72 | # settings 73 | context = [] 74 | word_region = cfml_view.view.word(cfml_view.position) 75 | 76 | if cfml_view.view.match_selector(cfml_view.position, "meta.property"): 77 | context = cfml_view.get_dot_context(word_region.begin() - 1) 78 | 79 | if cfml_view.view.match_selector( 80 | cfml_view.position, "meta.class.body.cfml meta.struct-literal.cfml" 81 | ): 82 | context = cfml_view.get_struct_context(cfml_view.position) 83 | 84 | if len(context) > 0: 85 | key = ".".join([symbol.name for symbol in reversed(context)]) 86 | if cfml_view.view.match_selector( 87 | cfml_view.position, 88 | "meta.property, meta.struct-literal.key.cfml, variable.other.readwrite.cfml", 89 | ): 90 | key += "." + cfml_view.view.substr(word_region).lower() 91 | if key in appcfc["settings_docs"]: 92 | return cfml_view.Documentation( 93 | [word_region], 94 | get_documentation(key, appcfc["settings_docs"][key]), 95 | None, 96 | 1, 97 | ) 98 | parent_key = ".".join(key.split(".")[:-1]) 99 | if parent_key in appcfc["settings_docs"]: 100 | return cfml_view.Documentation( 101 | [word_region, context[0].name_region], 102 | get_documentation(parent_key, appcfc["settings_docs"][parent_key]), 103 | None, 104 | 1, 105 | ) 106 | 107 | # methods 108 | if cfml_view.view.match_selector( 109 | cfml_view.position, "meta.function.cfml, meta.function.declaration.cfml" 110 | ): 111 | function_name, function_name_region, function_region = cfml_view.get_function( 112 | cfml_view.position 113 | ) 114 | region = sublime.Region(function_name_region.begin(), function_region.end()) 115 | if function_name in appcfc["methods_docs"]: 116 | return cfml_view.Documentation( 117 | [region], 118 | get_documentation(function_name, appcfc["methods_docs"][function_name]), 119 | None, 120 | 1, 121 | ) 122 | 123 | return None 124 | 125 | 126 | def get_documentation(key, metadata): 127 | appcfc_doc = {"side_color": SIDE_COLOR, "html": {}} 128 | appcfc_doc["html"]["header"] = metadata["header"] 129 | appcfc_doc["html"]["body"] = metadata["body"] 130 | appcfc_doc["html"]["links"] = [ 131 | { 132 | "href": "http://cfdocs.org/application-cfc", 133 | "text": "cfdocs.org/application-cfc", 134 | } 135 | ] 136 | appcfc_doc["html"]["links"].extend(metadata["links"]) 137 | return appcfc_doc 138 | -------------------------------------------------------------------------------- /src/plugins_/cfcs/cfcs.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import os 3 | from ...component_index import component_index 4 | from ... import utils 5 | 6 | projects = {} 7 | 8 | 9 | def build_project_cfcs(project_name): 10 | cfc_index = {} 11 | cfc_folders = component_index.get_project_data(project_name).get("cfc_folders", []) 12 | for cfc_folder in cfc_folders: 13 | if cfc_folder.get("variable_names", []): 14 | accessors = cfc_folder.get("accessors", True) 15 | if not isinstance(accessors, bool): 16 | accessors = True 17 | cfc_index.update( 18 | build_cfcs( 19 | cfc_folder["path"], 20 | cfc_folder["variable_names"], 21 | accessors, 22 | project_name, 23 | ) 24 | ) 25 | projects[project_name] = cfc_index 26 | 27 | 28 | def build_cfcs(root_path, cfc_names, accessors, project_name): 29 | folder_cfc_index = {} 30 | root_dir = utils.normalize_path(root_path, os.path.dirname(project_name)) 31 | for file_path in component_index.get_file_paths(project_name): 32 | if not file_path.startswith(root_dir): 33 | continue 34 | for cfc_name in get_cfc_names(root_dir, cfc_names, project_name, file_path): 35 | folder_cfc_index[cfc_name.lower()] = { 36 | "name": cfc_name, 37 | "file_path": file_path, 38 | "accessors": accessors, 39 | } 40 | return folder_cfc_index 41 | 42 | 43 | def get_cfc_names(root_dir, cfc_names, project_name, file_path): 44 | cfc_path, file_ext = os.path.splitext(file_path) 45 | cfc_path_parts = cfc_path.replace(root_dir, "").split("/") 46 | metadata = component_index.get_metadata_by_file_path(project_name, file_path) 47 | path_keys = {} 48 | path_keys["cfc"] = cfc_path_parts[-1] 49 | path_keys["entity_name"] = ( 50 | metadata["entityname"] if metadata["entityname"] else path_keys["cfc"] 51 | ) 52 | path_keys["cfc_folder"] = ( 53 | uppercase(cfc_path_parts[-2]) if len(cfc_path_parts) > 1 else "" 54 | ) 55 | path_keys["cfc_folder_singularized"] = ( 56 | path_keys["cfc_folder"] 57 | if len(path_keys["cfc_folder"]) == 0 or path_keys["cfc_folder"][-1] != "s" 58 | else path_keys["cfc_folder"][:-1] 59 | ) 60 | return [get_cfc_name(cfc_name, path_keys) for cfc_name in cfc_names] 61 | 62 | 63 | def get_cfc_name(cfc_name, path_keys): 64 | for path_key in path_keys: 65 | cfc_name = cfc_name.replace("{" + path_key + "}", path_keys[path_key]) 66 | return cfc_name 67 | 68 | 69 | def get_cfc_list(project_name): 70 | if project_name in projects: 71 | return [ 72 | projects[project_name][key]["name"] 73 | for key in sorted(projects[project_name]) 74 | ] 75 | return [] 76 | 77 | 78 | def has_cfc(project_name, cfc_name): 79 | if project_name in projects: 80 | return cfc_name.lower() in projects[project_name] 81 | return False 82 | 83 | 84 | def search_dot_context_for_cfc(project_name, dot_context): 85 | stack = [] 86 | regions = [] 87 | for symbol in dot_context: 88 | if not symbol.is_function: 89 | stack.append(symbol.name) 90 | regions.append(symbol.name_region) 91 | else: 92 | if len(stack) > 0: 93 | break 94 | 95 | if len(stack) > 0: 96 | stack.reverse() 97 | regions.reverse() 98 | for i in range(len(stack)): 99 | cfc_name = ".".join(stack[i:]) 100 | if has_cfc(project_name, cfc_name): 101 | return cfc_name, sublime.Region(regions[i].begin(), regions[-1].end()) 102 | return None, None 103 | 104 | 105 | def get_cfc_info(project_name, cfc_name): 106 | return projects[project_name][cfc_name.lower()] 107 | 108 | 109 | def get_cfc_completions(project_name, cfc_name): 110 | cfc_info = get_cfc_info(project_name, cfc_name) 111 | cfc_completions = component_index.get_completions_by_file_path( 112 | project_name, cfc_info["file_path"] 113 | )["functions"] 114 | filtered_completions = [] 115 | for completion in cfc_completions: 116 | if not completion.private and ( 117 | cfc_info["accessors"] or not completion.accessor 118 | ): 119 | filtered_completions.append(get_completion(completion, cfc_info)) 120 | return filtered_completions 121 | 122 | 123 | def get_cfc_metadata(project_name, cfc_name): 124 | file_path = get_cfc_info(project_name, cfc_name)["file_path"] 125 | return component_index.get_extended_metadata_by_file_path(project_name, file_path) 126 | 127 | 128 | def get_completion(completion, cfc_info): 129 | hint = completion.hint 130 | if hint != "method" and cfc_info["file_path"] == completion.file_path: 131 | hint = cfc_info["name"] 132 | return completion.key + "\t" + hint, completion.content 133 | 134 | 135 | def uppercase(string): 136 | if len(string) == 0: 137 | return "" 138 | return string[0].upper() + string[1:] 139 | -------------------------------------------------------------------------------- /src/plugins_/fw1/json/methods.json: -------------------------------------------------------------------------------- 1 | { 2 | "calls": { 3 | "abortController():void": "()", 4 | "actionSpecifiesSubsystem():boolean": "( ${1:action} )", 5 | "addRoute():void": "( ${1:routes}${2:, ${3:target}}${4:, ${5:methods}}${6:, ${7:statusCode}} )", 6 | "buildCustomURL():string": "( ${1:uri} )", 7 | "buildURL():string": "( ${1:action}${2:, ${3:path}}${4:, ${5:queryString}} )", 8 | "controller():void": "( ${1:action} )", 9 | "customizeViewOrLayoutPath():string": "( ${1:pathInfo}${2:, ${3:type}}${4:, ${5:fullPath}} )", 10 | "disableFrameworkTrace():void": "()", 11 | "disableLayout():void": "()", 12 | "enableFrameworkTrace():void": "()", 13 | "enableLayout():void": "()", 14 | "frameworkTrace():void": "( ${1:[}${2:, ${3:]}} )", 15 | "getAction():string": "()", 16 | "getBaseURL():string": "()", 17 | "getBeanFactory():any": "( ${1:subsystem} )", 18 | "getCGIRequestMethod():string": "()", 19 | "getConfig():struct": "()", 20 | "getDefaultBeanFactory():any": "()", 21 | "getDefaultSubsystem():string": "()", 22 | "getEnvVar():string": "( ${1:name} )", 23 | "getEnvironment():string": "()", 24 | "getFrameworkTrace():array": "()", 25 | "getFullyQualifiedAction():string": "( ${1:action} )", 26 | "getHostname():string": "()", 27 | "getItem():string": "( ${1:action} )", 28 | "getResourceRouteTemplates():array": "()", 29 | "getRoute():string": "()", 30 | "getRoutePath():string": "()", 31 | "getRoutes():array": "()", 32 | "getSection():string": "( ${1:action} )", 33 | "getSectionAndItem():string": "( ${1:action} )", 34 | "getSubsystem():string": "( ${1:action} )", 35 | "getSubsystemBase():string": "()", 36 | "getSubsystemBeanFactory():any": "( ${1:subsystem} )", 37 | "getSubsystemConfig():struct": "( ${1:subsystem} )", 38 | "getSubsystemSectionAndItem():string": "( ${1:action} )", 39 | "hasBeanFactory():boolean": "()", 40 | "hasDefaultBeanFactory():boolean": "()", 41 | "hasSubsystemBeanFactory():boolean": "( ${1:subsystem} )", 42 | "isCurrentAction():boolean": "( ${1:action} )", 43 | "isFrameworkReloadRequest():boolean": "()", 44 | "isUnhandledRequest():boolean": "( ${1:targetPath} )", 45 | "layout():string": "( ${1:path}${2:, ${3:body}} )", 46 | "onMissingView():string": "( ${1:rc} )", 47 | "onPopulateError():void": "( ${1:cfc}${2:, ${3:property}}${4:, ${5:rc}} )", 48 | "populate():any": "( ${1:cfc}${2:, ${3:keys}}${4:, ${5:trustKeys}}${6:, ${7:trim}}${8:, ${9:deep}}${10:, ${11:properties}} )", 49 | "processRoutes():struct": "( ${1:path}${2:, ${3:routes}}${4:, ${5:httpMethod}} )", 50 | "redirect():void": "( ${1:action}${2:, ${3:preserve}}${4:, ${5:append}}${6:, ${7:path}}${8:, ${9:queryString}}${10:, ${11:statusCode}}${12:, ${13:header}} )", 51 | "redirectCustomURL():void": "( ${1:uri}${2:, ${3:preserve}}${4:, ${5:statusCode}}${6:, ${7:header}} )", 52 | "renderData():any": "()", 53 | "renderer():any": "()", 54 | "setBeanFactory():void": "( ${1:factory} )", 55 | "setLayout():void": "( ${1:action}${2:, ${3:suppressOtherLayouts}} )", 56 | "setSubsystemBeanFactory():void": "( ${1:subsystem}${2:, ${3:factory}} )", 57 | "setView():void": "( ${1:action} )", 58 | "setupApplication():void": "()", 59 | "setupEnvironment():void": "( ${1:env} )", 60 | "setupRequest():void": "()", 61 | "setupResponse():void": "( ${1:rc} )", 62 | "setupSession():void": "()", 63 | "setupSubsystem():void": "( ${1:subsystem} )", 64 | "setupTraceRender():void": "( ${1:output} )", 65 | "setupView():void": "( ${1:rc} )", 66 | "usingSubsystems():boolean": "()", 67 | "view():string": "( ${1:path}${2:, ${3:args}}${4:, ${5:missingView}} )" 68 | }, 69 | "definitions": { 70 | "customizeViewOrLayoutPath():string": "( pathInfo, type, fullPath ) {$0}", 71 | "getBaseURL():string": "() {$0}", 72 | "getEnvironment():string": "() {$0}", 73 | "getResourceRouteTemplates():array": "() {$0}", 74 | "getRoutes():array": "() {$0}", 75 | "isUnhandledRequest():boolean": "( targetPath ) {$0}", 76 | "onMissingView():string": "( rc ) {$0}", 77 | "onPopulateError():void": "( cfc, property, rc ) {$0}", 78 | "setupApplication():void": "() {$0}", 79 | "setupEnvironment():void": "( env ) {$0}", 80 | "setupRequest():void": "() {$0}", 81 | "setupResponse():void": "( rc ) {$0}", 82 | "setupSession():void": "() {$0}", 83 | "setupSubsystem():void": "( subsystem ) {$0}", 84 | "setupTraceRender():void": "( output ) {$0}", 85 | "setupView():void": "( rc ) {$0}" 86 | }, 87 | "renderdata": { 88 | "data():builder": "( ${1:payload} )", 89 | "type():builder": "( ${1:contentType} )", 90 | "header():builder": "( ${1:name}, ${2:value} )", 91 | "statusCode():builder": "( ${1:code} )", 92 | "statusText():builder": "( ${1:message} )", 93 | "jsonpCallback():builder": "( ${1:callback} )" 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/plugins_/entities/documentation.py: -------------------------------------------------------------------------------- 1 | from ...component_index import component_index 2 | from . import entity_utils 3 | 4 | 5 | def get_inline_documentation(cfml_view, doc_type): 6 | if not cfml_view.project_name: 7 | return None 8 | 9 | entity_name, function_name, regions = entity_utils.find_entity( 10 | cfml_view, cfml_view.position 11 | ) 12 | 13 | if entity_name: 14 | file_path_info = component_index.get_file_path_by_entity_name( 15 | cfml_view.project_name, entity_name 16 | ) 17 | if file_path_info is None: 18 | return None 19 | 20 | if function_name: 21 | metadata = component_index.get_extended_metadata_by_file_path( 22 | cfml_view.project_name, file_path_info["file_path"] 23 | ) 24 | if function_name in metadata["functions"]: 25 | doc, callback = component_index.get_method_documentation( 26 | cfml_view.view, 27 | cfml_view.project_name, 28 | file_path_info["file_path"], 29 | function_name, 30 | file_path_info["entity_name"], 31 | metadata["functions"][function_name]["name"], 32 | ) 33 | return cfml_view.Documentation(regions, doc, callback, 2) 34 | 35 | doc, callback = component_index.get_documentation( 36 | cfml_view.view, 37 | cfml_view.project_name, 38 | file_path_info["file_path"], 39 | file_path_info["entity_name"], 40 | ) 41 | return cfml_view.Documentation(regions, doc, callback, 2) 42 | 43 | return None 44 | 45 | 46 | def get_method_preview(cfml_view): 47 | if not cfml_view.project_name: 48 | return None 49 | 50 | entity_name, function_name, regions = entity_utils.find_entity( 51 | cfml_view, cfml_view.position 52 | ) 53 | 54 | if entity_name: 55 | file_path_info = component_index.get_file_path_by_entity_name( 56 | cfml_view.project_name, entity_name 57 | ) 58 | if file_path_info is None: 59 | return None 60 | 61 | if function_name: 62 | doc, callback = component_index.get_method_preview( 63 | cfml_view.view, 64 | cfml_view.project_name, 65 | file_path_info["file_path"], 66 | function_name, 67 | ) 68 | return cfml_view.MethodPreview(regions, doc, callback, 2) 69 | 70 | return None 71 | 72 | 73 | def get_goto_cfml_file(cfml_view): 74 | if not cfml_view.project_name: 75 | return None 76 | 77 | entity_name, function_name, region = entity_utils.find_entity( 78 | cfml_view, cfml_view.position 79 | ) 80 | 81 | if entity_name: 82 | file_path_info = component_index.get_file_path_by_entity_name( 83 | cfml_view.project_name, entity_name 84 | ) 85 | if file_path_info is None: 86 | return None 87 | if function_name: 88 | metadata = component_index.get_extended_metadata_by_file_path( 89 | cfml_view.project_name, file_path_info["file_path"] 90 | ) 91 | if function_name in metadata["functions"]: 92 | return cfml_view.GotoCfmlFile( 93 | metadata["function_file_map"][function_name], 94 | metadata["functions"][function_name]["name"], 95 | ) 96 | else: 97 | return cfml_view.GotoCfmlFile(file_path_info["file_path"], None) 98 | 99 | return None 100 | 101 | 102 | def get_completions_doc(cfml_view): 103 | if ( 104 | not cfml_view.project_name 105 | or not cfml_view.function_call_params 106 | or not cfml_view.function_call_params.method 107 | ): 108 | return None 109 | 110 | if len(cfml_view.function_call_params.dot_context) != 1: 111 | return None 112 | 113 | start_pt = cfml_view.function_call_params.dot_context[0].name_region.begin() 114 | entity_name, temp_function_name, region = entity_utils.find_entity( 115 | cfml_view, start_pt 116 | ) 117 | 118 | if entity_name: 119 | file_path_info = component_index.get_file_path_by_entity_name( 120 | cfml_view.project_name, entity_name 121 | ) 122 | if file_path_info is None: 123 | return None 124 | function_name = cfml_view.function_call_params.function_name 125 | metadata = component_index.get_extended_metadata_by_file_path( 126 | cfml_view.project_name, file_path_info["file_path"] 127 | ) 128 | if cfml_view.function_call_params.function_name in metadata["functions"]: 129 | doc, callback = component_index.get_function_call_params_doc( 130 | cfml_view.project_name, 131 | file_path_info["file_path"], 132 | cfml_view.function_call_params, 133 | file_path_info["entity_name"], 134 | metadata["functions"][function_name]["name"], 135 | ) 136 | return cfml_view.CompletionDoc(None, doc, callback) 137 | 138 | return None 139 | -------------------------------------------------------------------------------- /cfml_plugin.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | from HTML import html_completions 4 | from .src import command_list, completions, events, utils, _plugin_loaded 5 | 6 | for command in command_list: 7 | globals()[command.__name__] = command 8 | 9 | 10 | def plugin_loaded(): 11 | _plugin_loaded() 12 | 13 | 14 | class CfmlEventListener(sublime_plugin.EventListener): 15 | def on_load_async(self, view): 16 | events.trigger("on_load_async", view) 17 | 18 | def on_close(self, view): 19 | events.trigger("on_close", view) 20 | 21 | def on_modified_async(self, view): 22 | events.trigger("on_modified_async", view) 23 | 24 | def on_post_save_async(self, view): 25 | if not view.file_name(): 26 | print( 27 | "CFML: file was saved and closed - it is not possible to determine the file path." 28 | ) 29 | return 30 | events.trigger("on_post_save_async", view) 31 | 32 | def on_post_text_command(self, view, command_name, args): 33 | if command_name == "commit_completion": 34 | pos = view.sel()[0].begin() 35 | 36 | if view.match_selector( 37 | pos, 38 | "meta.tag.cfml -source.cfml.script, meta.tag.script.cfml, meta.tag.script.cf.cfml, meta.class.declaration.cfml -meta.class.inheritance.cfml", 39 | ): 40 | if view.substr(pos - 1) in [" ", '"', "'", "="]: 41 | view.run_command("auto_complete", {"api_completions_only": True}) 42 | elif view.substr(pos) == '"': 43 | # an attribute completion was most likely just inserted 44 | # advance cursor past double quote character 45 | view.run_command("move", {"by": "characters", "forward": True}) 46 | 47 | if view.substr(pos - 1) == ":" and view.match_selector( 48 | pos - 1, "meta.tag.custom.cfml -source.cfml.script" 49 | ): 50 | view.run_command("auto_complete", {"api_completions_only": True}) 51 | 52 | if view.substr(pos - 1) == "." and view.match_selector( 53 | pos - 1, 54 | "meta.function-call.support.createcomponent.cfml string.quoted, entity.other.inherited-class.cfml, meta.instance.constructor.cfml", 55 | ): 56 | view.run_command("auto_complete", {"api_completions_only": True}) 57 | 58 | def on_post_window_command(self, window, command_name, args): 59 | events.trigger("on_post_window_command", window, command_name, args) 60 | 61 | def on_query_completions(self, view, prefix, locations): 62 | if not view.match_selector(locations[0], "embedding.cfml"): 63 | return None 64 | 65 | return completions.get_completions(view, locations[0], prefix) 66 | 67 | def on_hover(self, view, point, hover_zone): 68 | if hover_zone != sublime.HOVER_TEXT: 69 | return 70 | 71 | if not view.match_selector(point, "embedding.cfml"): 72 | return 73 | 74 | view.run_command( 75 | "cfml_inline_documentation", {"pt": point, "doc_type": "hover_doc"} 76 | ) 77 | 78 | 79 | class CustomHtmlTagCompletions(html_completions.HtmlTagCompletions): 80 | """ 81 | There is no text.html scope in bodies, so this 82 | allows the HTML completions to still function there 83 | 84 | uses Default Packages HtmlTagCompletions code 85 | """ 86 | 87 | def on_query_completions(self, view, prefix, locations): 88 | if not utils.get_setting("html_completions_in_tag_components"): 89 | return None 90 | 91 | # Only trigger within CFML tag component functions 92 | selector = "meta.class.body.tag.cfml meta.function.body.tag.cfml -source.cfml.script -source.sql" 93 | if not view.match_selector(locations[0], selector): 94 | return None 95 | 96 | pt = locations[0] - len(prefix) - 1 97 | ch = view.substr(pt) 98 | 99 | if ch == '&': 100 | return self.entity_completions 101 | 102 | if ch == '<': 103 | # If the caret is within tag, complete only tag names. 104 | # see: https://github.com/sublimehq/sublime_text/issues/3508 105 | if view.match_selector(locations[0], "meta.tag"): 106 | return self.tag_name_completions 107 | return self.tag_completions 108 | 109 | # Note: Exclude opening punctuation to enable abbreviations 110 | # if the caret is located directly in front of a html tag. 111 | if view.match_selector(locations[0], "meta.function.body.tag.cfml meta.tag - meta.string - punctuation.definition.tag.begin"): 112 | if ch in ' \f\n\t': 113 | return self.attribute_completions(view, locations[0], prefix) 114 | return None 115 | 116 | if view.match_selector(locations[0], "meta.function.body.tag.cfml - meta.tag, meta.function.body.tag.cfml punctuation.definition.tag.begin"): 117 | # Expand tag and attribute abbreviations 118 | return self.expand_tag_attributes(view, locations) or self.tag_abbreviations 119 | 120 | return None 121 | --------------------------------------------------------------------------------