├── .python-version
├── run_tests.sh
├── tests
├── __init__.py
├── test_json.py
└── validate_json_format.py
├── bh_modules
├── __init__.py
├── lowercase.py
├── pyquotes.py
├── phpkeywords.py
├── cmakekeywords.py
├── erlangcase.py
├── pascalkeywords.py
├── s840d_gcode.py
├── luakeywords.py
├── s840d_hmi.py
├── mdcode.py
├── foldbracket.py
├── bashsupport.py
├── tagnameselect.py
├── latexenvironments.py
├── swapbrackets.py
├── swapquotes.py
├── bracketremove.py
├── rubykeywords.py
├── tagattrselect.py
├── bracketselect.py
└── tags.py
├── icons
├── icon_version.json
├── dot.png
├── tag.png
├── cross.png
├── hash.png
├── minus.png
├── plus.png
├── quote.png
├── star.png
├── bookmark.png
├── circle.png
├── question.png
├── dot_small.png
├── hash_small.png
├── plus_small.png
├── star_small.png
├── tag_small.png
├── angle_bracket.png
├── circle_small.png
├── cross_small.png
├── curly_bracket.png
├── double_quote.png
├── minus_small.png
├── quote_small.png
├── round_bracket.png
├── single_quote.png
├── bookmark_small.png
├── question_small.png
├── square_bracket.png
├── angle_bracket_open.png
├── curly_bracket_open.png
├── double_quote_close.png
├── double_quote_open.png
├── double_quote_small.png
├── round_bracket_open.png
├── single_quote_close.png
├── single_quote_open.png
├── single_quote_small.png
├── angle_bracket_close.png
├── angle_bracket_small.png
├── curly_bracket_close.png
├── curly_bracket_small.png
├── double_quote_offset.png
├── round_bracket_close.png
├── round_bracket_small.png
├── single_quote_offset.png
├── square_bracket_close.png
├── square_bracket_open.png
├── square_bracket_small.png
├── double_quote_open_small.png
├── single_quote_open_small.png
├── angle_bracket_close_small.png
├── angle_bracket_open_small.png
├── curly_bracket_close_small.png
├── curly_bracket_open_small.png
├── double_quote_close_small.png
├── double_quote_offset_open.png
├── double_quote_offset_small.png
├── round_bracket_close_small.png
├── round_bracket_open_small.png
├── single_quote_close_small.png
├── single_quote_offset_open.png
├── single_quote_offset_small.png
├── square_bracket_open_small.png
├── square_bracket_close_small.png
├── double_quote_offset_open_small.png
└── single_quote_offset_open_small.png
├── .gitignore
├── docs
├── src
│ ├── markdown
│ │ ├── .snippets
│ │ │ ├── refs.md
│ │ │ ├── abbr.md
│ │ │ └── links.md
│ │ ├── images
│ │ │ ├── popup1.png
│ │ │ ├── popup2.png
│ │ │ ├── Example1.png
│ │ │ └── unmatched_popup.png
│ │ ├── about
│ │ │ ├── license.md
│ │ │ └── contributing.md
│ │ ├── index.md
│ │ ├── extended-regex.md
│ │ ├── usage.md
│ │ └── installation.md
│ ├── requirements.txt
│ └── dictionary
│ │ └── en-custom.txt
└── theme
│ └── announce.html
├── .github
├── FUNDING.yml
├── CONTRIBUTING.md
├── PULL_REQUEST_TEMPLATE.md
├── workflows
│ ├── deploy.yml
│ └── build.yml
├── ISSUE_TEMPLATE.md
└── labels.yml
├── messages.json
├── .compat_flag_3067
├── dependencies.json
├── messages
├── recent.md
└── install.md
├── .prospector.yml
├── Default.sublime-keymap
├── bh_logging.py
├── tox.ini
├── bh_remove.py
├── readme.md
├── bh_swapping.py
├── bh_swapping.sublime-settings
├── .pyspelling.yml
├── mkdocs.yml
├── quickstart.md
├── bh_tag.sublime-settings
├── bh_wrapping.sublime-settings
├── Example.sublime-keymap
├── bh_plugin.py
├── support.py
├── Default.sublime-commands
├── CHANGES.md
├── bh_popup.py
└── bh_wrapping.py
/.python-version:
--------------------------------------------------------------------------------
1 | 3.13
2 |
--------------------------------------------------------------------------------
/run_tests.sh:
--------------------------------------------------------------------------------
1 | py.test .
2 | flake8 .
3 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | """Unit Tests."""
2 |
--------------------------------------------------------------------------------
/bh_modules/__init__.py:
--------------------------------------------------------------------------------
1 | """BH Modules."""
2 |
--------------------------------------------------------------------------------
/icons/icon_version.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .DS_Store
3 | site/*
4 | build/*
5 | .cache/*
6 | .tox/*
7 |
--------------------------------------------------------------------------------
/docs/src/markdown/.snippets/refs.md:
--------------------------------------------------------------------------------
1 | --8<--
2 | links.md
3 | abbr.md
4 | --8<--
5 |
--------------------------------------------------------------------------------
/icons/dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/dot.png
--------------------------------------------------------------------------------
/icons/tag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/tag.png
--------------------------------------------------------------------------------
/icons/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/cross.png
--------------------------------------------------------------------------------
/icons/hash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/hash.png
--------------------------------------------------------------------------------
/icons/minus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/minus.png
--------------------------------------------------------------------------------
/icons/plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/plus.png
--------------------------------------------------------------------------------
/icons/quote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/quote.png
--------------------------------------------------------------------------------
/icons/star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/star.png
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: facelessuser
2 | custom:
3 | - "https://www.paypal.me/facelessuser"
4 |
--------------------------------------------------------------------------------
/icons/bookmark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/bookmark.png
--------------------------------------------------------------------------------
/icons/circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/circle.png
--------------------------------------------------------------------------------
/icons/question.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/question.png
--------------------------------------------------------------------------------
/icons/dot_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/dot_small.png
--------------------------------------------------------------------------------
/icons/hash_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/hash_small.png
--------------------------------------------------------------------------------
/icons/plus_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/plus_small.png
--------------------------------------------------------------------------------
/icons/star_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/star_small.png
--------------------------------------------------------------------------------
/icons/tag_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/tag_small.png
--------------------------------------------------------------------------------
/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "install": "messages/install.md",
3 | "2.33.0": "messages/recent.md"
4 | }
5 |
--------------------------------------------------------------------------------
/icons/angle_bracket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/angle_bracket.png
--------------------------------------------------------------------------------
/icons/circle_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/circle_small.png
--------------------------------------------------------------------------------
/icons/cross_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/cross_small.png
--------------------------------------------------------------------------------
/icons/curly_bracket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/curly_bracket.png
--------------------------------------------------------------------------------
/icons/double_quote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/double_quote.png
--------------------------------------------------------------------------------
/icons/minus_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/minus_small.png
--------------------------------------------------------------------------------
/icons/quote_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/quote_small.png
--------------------------------------------------------------------------------
/icons/round_bracket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/round_bracket.png
--------------------------------------------------------------------------------
/icons/single_quote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/single_quote.png
--------------------------------------------------------------------------------
/icons/bookmark_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/bookmark_small.png
--------------------------------------------------------------------------------
/icons/question_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/question_small.png
--------------------------------------------------------------------------------
/icons/square_bracket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/square_bracket.png
--------------------------------------------------------------------------------
/docs/src/markdown/.snippets/abbr.md:
--------------------------------------------------------------------------------
1 | *[BH]: BracketHighlighter
2 | *[ST2]: Sublime Text 2
3 | *[ST3]: Sublime Text 3
4 |
--------------------------------------------------------------------------------
/icons/angle_bracket_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/angle_bracket_open.png
--------------------------------------------------------------------------------
/icons/curly_bracket_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/curly_bracket_open.png
--------------------------------------------------------------------------------
/icons/double_quote_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/double_quote_close.png
--------------------------------------------------------------------------------
/icons/double_quote_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/double_quote_open.png
--------------------------------------------------------------------------------
/icons/double_quote_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/double_quote_small.png
--------------------------------------------------------------------------------
/icons/round_bracket_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/round_bracket_open.png
--------------------------------------------------------------------------------
/icons/single_quote_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/single_quote_close.png
--------------------------------------------------------------------------------
/icons/single_quote_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/single_quote_open.png
--------------------------------------------------------------------------------
/icons/single_quote_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/single_quote_small.png
--------------------------------------------------------------------------------
/icons/angle_bracket_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/angle_bracket_close.png
--------------------------------------------------------------------------------
/icons/angle_bracket_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/angle_bracket_small.png
--------------------------------------------------------------------------------
/icons/curly_bracket_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/curly_bracket_close.png
--------------------------------------------------------------------------------
/icons/curly_bracket_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/curly_bracket_small.png
--------------------------------------------------------------------------------
/icons/double_quote_offset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/double_quote_offset.png
--------------------------------------------------------------------------------
/icons/round_bracket_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/round_bracket_close.png
--------------------------------------------------------------------------------
/icons/round_bracket_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/round_bracket_small.png
--------------------------------------------------------------------------------
/icons/single_quote_offset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/single_quote_offset.png
--------------------------------------------------------------------------------
/icons/square_bracket_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/square_bracket_close.png
--------------------------------------------------------------------------------
/icons/square_bracket_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/square_bracket_open.png
--------------------------------------------------------------------------------
/icons/square_bracket_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/square_bracket_small.png
--------------------------------------------------------------------------------
/icons/double_quote_open_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/double_quote_open_small.png
--------------------------------------------------------------------------------
/icons/single_quote_open_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/single_quote_open_small.png
--------------------------------------------------------------------------------
/docs/src/markdown/images/popup1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/docs/src/markdown/images/popup1.png
--------------------------------------------------------------------------------
/docs/src/markdown/images/popup2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/docs/src/markdown/images/popup2.png
--------------------------------------------------------------------------------
/icons/angle_bracket_close_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/angle_bracket_close_small.png
--------------------------------------------------------------------------------
/icons/angle_bracket_open_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/angle_bracket_open_small.png
--------------------------------------------------------------------------------
/icons/curly_bracket_close_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/curly_bracket_close_small.png
--------------------------------------------------------------------------------
/icons/curly_bracket_open_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/curly_bracket_open_small.png
--------------------------------------------------------------------------------
/icons/double_quote_close_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/double_quote_close_small.png
--------------------------------------------------------------------------------
/icons/double_quote_offset_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/double_quote_offset_open.png
--------------------------------------------------------------------------------
/icons/double_quote_offset_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/double_quote_offset_small.png
--------------------------------------------------------------------------------
/icons/round_bracket_close_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/round_bracket_close_small.png
--------------------------------------------------------------------------------
/icons/round_bracket_open_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/round_bracket_open_small.png
--------------------------------------------------------------------------------
/icons/single_quote_close_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/single_quote_close_small.png
--------------------------------------------------------------------------------
/icons/single_quote_offset_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/single_quote_offset_open.png
--------------------------------------------------------------------------------
/icons/single_quote_offset_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/single_quote_offset_small.png
--------------------------------------------------------------------------------
/icons/square_bracket_open_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/square_bracket_open_small.png
--------------------------------------------------------------------------------
/docs/src/markdown/images/Example1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/docs/src/markdown/images/Example1.png
--------------------------------------------------------------------------------
/icons/square_bracket_close_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/square_bracket_close_small.png
--------------------------------------------------------------------------------
/icons/double_quote_offset_open_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/double_quote_offset_open_small.png
--------------------------------------------------------------------------------
/icons/single_quote_offset_open_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/icons/single_quote_offset_open_small.png
--------------------------------------------------------------------------------
/docs/src/requirements.txt:
--------------------------------------------------------------------------------
1 | mkdocs_pymdownx_material_extras>=2.0
2 | mkdocs-git-revision-date-localized-plugin
3 | mkdocs-minify-plugin
4 | pyspelling
5 |
--------------------------------------------------------------------------------
/docs/src/markdown/images/unmatched_popup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/facelessuser/BracketHighlighter/HEAD/docs/src/markdown/images/unmatched_popup.png
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Please follow this link to see Contributing & Support documentation: http://facelessuser.github.io/BracketHighlighter/contributing/.
2 |
--------------------------------------------------------------------------------
/.compat_flag_3067:
--------------------------------------------------------------------------------
1 | Temporary file so that Jon of Sublime Text can avoid loading older BH on ST versions >= 3067. There was a nasty hang and crash when Jon updated ST3 to version 3067.
2 |
--------------------------------------------------------------------------------
/dependencies.json:
--------------------------------------------------------------------------------
1 | {
2 | "*": {
3 | ">=3000": [
4 | "backrefs"
5 | ],
6 | ">=3124": [
7 | "backrefs",
8 | "mdpopups"
9 | ]
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/bh_modules/lowercase.py:
--------------------------------------------------------------------------------
1 | """Ensure brackets are lower case."""
2 |
3 |
4 | def validate(name, bracket, bracket_side, bfr):
5 | """Check if bracket is lowercase."""
6 |
7 | return bfr[bracket.begin:bracket.end].islower()
8 |
--------------------------------------------------------------------------------
/docs/theme/announce.html:
--------------------------------------------------------------------------------
1 | Sponsorship
2 | is now available!
3 |
4 | {% set icon = "octicons/heart-fill-16" %}
5 | {% include ".icons/" ~ icon ~ ".svg" %}
6 |
7 |
--------------------------------------------------------------------------------
/messages/recent.md:
--------------------------------------------------------------------------------
1 | # BracketHighlighter
2 |
3 | New release!
4 |
5 | A restart might be required. If you see issues immediately after the update
6 | please try restarting.
7 |
8 | ## 2.33.0
9 |
10 | - **NEW**: Release special branch for Sublime Text 4201+.
11 |
--------------------------------------------------------------------------------
/messages/install.md:
--------------------------------------------------------------------------------
1 | # BracketHighlighter
2 |
3 | Welcome to BracketHighlighter!
4 |
5 | A restart of Sublime Text is recommended to ensure all dependencies get
6 | loaded properly.
7 |
8 | For a quick start guide, please go to
9 | `Preferences->Package Settings->BracketHighlighter->Quick Start Guide`.
10 |
--------------------------------------------------------------------------------
/.prospector.yml:
--------------------------------------------------------------------------------
1 | strictness: medium
2 | doc-warnings: true
3 | test-warnings: false
4 | max-line-length: 120
5 | ignore-patterns:
6 | - docs
7 | - docs_theme
8 | - site(\\|/).*\.py
9 | dodgy:
10 | run: false
11 | pep257:
12 | disable:
13 | - D202
14 | - D203
15 | - D401
16 | pylint:
17 | disable:
18 | - import-error
19 |
--------------------------------------------------------------------------------
/bh_modules/pyquotes.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 |
8 |
9 | def compare(name, first, second, bfr):
10 | """Pair the appropriate open bracket with its close."""
11 |
12 | return bfr[first.begin:first.end] == bfr[second.begin:second.end]
13 |
--------------------------------------------------------------------------------
/bh_modules/phpkeywords.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 |
8 |
9 | def compare(name, first, second, bfr):
10 | """Pair the appropriate open bracket with its close."""
11 |
12 | return "end" + bfr[first.begin:first.end].lower() == bfr[second.begin:second.end].lower()
13 |
--------------------------------------------------------------------------------
/bh_modules/cmakekeywords.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 |
8 |
9 | def compare(name, first, second, bfr):
10 | """Pair the appropriate open bracket with its close."""
11 |
12 | return "end" + bfr[first.begin:first.end].lower() == bfr[second.begin:second.end].lower()
13 |
--------------------------------------------------------------------------------
/bh_modules/erlangcase.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | from BracketHighlighter.bh_plugin import import_module
8 | lowercase = import_module("bh_modules.lowercase")
9 |
10 |
11 | def validate(*args):
12 | """Check if bracket is lowercase."""
13 | return lowercase.validate(*args)
14 |
--------------------------------------------------------------------------------
/bh_modules/pascalkeywords.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 |
8 |
9 | def compare(name, first, second, bfr):
10 | """Differentiate 'repeat..until' from '*..end' brackets."""
11 | brackets = (bfr[first.begin:first.end], bfr[second.begin:second.end])
12 | return (brackets[0] == "repeat") ^ (brackets[1] == "end")
13 |
--------------------------------------------------------------------------------
/Default.sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | // Navigate tabstops in wrapped selection
3 | {
4 | "keys": ["tab"],
5 | "command": "bh_next_wrap_sel",
6 | "context":
7 | [
8 | {
9 | "operand": true,
10 | "operator": "equal",
11 | "match_all": true,
12 | "key": "bh_wrapping"
13 | }
14 | ]
15 | }
16 | ]
17 |
--------------------------------------------------------------------------------
/bh_logging.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | import sublime
8 |
9 |
10 | def log(msg):
11 | """Standard log."""
12 |
13 | print("BracketHighlighter: %s" % msg)
14 |
15 |
16 | def debug(msg):
17 | """Debug log."""
18 |
19 | if sublime.load_settings("bh_core.sublime-settings").get('debug_enable', False):
20 | log(msg)
21 |
--------------------------------------------------------------------------------
/bh_modules/s840d_gcode.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Deathaxe
5 | License: MIT
6 | """
7 |
8 |
9 | def compare(name, first, second, bfr):
10 | """Ensure correct open is paired with correct close."""
11 |
12 | o = bfr[first.begin:first.end].lower()
13 | c = bfr[second.begin:second.end].lower()
14 |
15 | match = False
16 | if o == "repeat" and c == "until":
17 | match = True
18 | elif c == "end" + o:
19 | match = True
20 | return match
21 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | skipsdist=True
3 | envlist =
4 | py37,p38,py39,py310,p311,lint
5 |
6 | [testenv]
7 | deps=
8 | pytest
9 | commands=
10 | py.test .
11 |
12 | [testenv:documents]
13 | deps=
14 | -rdocs/src/requirements.txt
15 | commands=
16 | mkdocs build --clean --verbose --strict
17 | pyspelling
18 |
19 | [testenv:lint]
20 | deps=
21 | flake8
22 | flake8_docstrings
23 | pep8-naming
24 | flake8-mutable
25 | flake8-builtins
26 | commands=
27 | flake8 "{toxinidir}"
28 |
29 | [flake8]
30 | ignore=D202,D203,D401,W504,W606,E741,N818
31 | max-line-length=120
32 | exclude=site/*.py,.tox/*
33 |
--------------------------------------------------------------------------------
/bh_modules/luakeywords.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | from BracketHighlighter.bh_plugin import import_module
8 | lowercase = import_module("bh_modules.lowercase")
9 |
10 |
11 | def validate(*args):
12 | """Check if bracket is lowercase."""
13 | return lowercase.validate(*args)
14 |
15 |
16 | def compare(name, first, second, bfr):
17 | """Differentiate 'repeat..until' from '*..end' brackets."""
18 | brackets = (bfr[first.begin:first.end], bfr[second.begin:second.end])
19 | return (brackets[0] == "repeat") ^ (brackets[1] == "end")
20 |
--------------------------------------------------------------------------------
/bh_modules/s840d_hmi.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Deathaxe
5 | License: MIT
6 | """
7 |
8 | S840D_HMI_CLASSES = ("//a", "//b", "//g", "//m", "//s")
9 |
10 |
11 | def compare(name, first, second, bfr):
12 | """Ensure correct open is paired with correct close."""
13 |
14 | o = bfr[first.begin:first.end].lower()
15 | c = bfr[second.begin:second.end].lower()
16 |
17 | match = False
18 | # classes
19 | if o in S840D_HMI_CLASSES and c == "//end":
20 | match = True
21 | # methods
22 | elif c == "end_" + o or c == 'end' + o:
23 | match = True
24 | return match
25 |
--------------------------------------------------------------------------------
/bh_modules/mdcode.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 |
8 |
9 | def post_match(view, name, style, first, second, center, bfr, threshold):
10 | """Ensure that backticks that do not contribute inside the inline or block regions are not highlighted."""
11 |
12 | if first is not None and second is not None:
13 | diff = first.size() - second.size()
14 | if diff > 0:
15 | first = first.move(first.begin, first.end - diff)
16 | elif diff < 0:
17 | second = second.move(second.begin - diff, second.end)
18 | return first, second, style
19 |
--------------------------------------------------------------------------------
/docs/src/markdown/.snippets/links.md:
--------------------------------------------------------------------------------
1 | [aprosopo]: https://github.com/facelessuser/Aprosopo
2 | [backrefs]: http://facelessuser.github.io/backrefs/
3 | [keymap]: https://github.com/facelessuser/BracketHighlighter/blob/master/Example.sublime-keymap
4 | [mkdocs]: http://www.mkdocs.org
5 | [mkdocs-material]: https://github.com/squidfunk/mkdocs-material
6 | [regreplace]: https://github.com/facelessuser/RegReplace
7 | [package-control]: https://packagecontrol.io/
8 | [package-control-install]: https://packagecontrol.io/installation
9 | [pymdown-extensions]: https://github.com/facelessuser/pymdown-extensions
10 | [scopes]: http://facelessuser.github.io/sublime-markdown-popups/textmate_scopes/
11 | [template]: https://github.com/facelessuser/BracketHighlighter/blob/master/.github/ISSUE_TEMPLATE.md
12 |
--------------------------------------------------------------------------------
/bh_modules/foldbracket.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | from BracketHighlighter import bh_plugin
8 | import sublime
9 |
10 |
11 | class FoldBrackets(bh_plugin.BracketPluginCommand):
12 | """Fold bracket plugin."""
13 |
14 | def run(self, edit, name):
15 | """Fold the content between the bracket."""
16 |
17 | content = sublime.Region(self.left.end, self.right.begin)
18 | new_content = [content]
19 | if content.size() > 0:
20 | if self.view.fold(content) is False:
21 | new_content = self.view.unfold(content)
22 | self.selection = new_content
23 |
24 |
25 | def plugin():
26 | """Make plugin available."""
27 |
28 | return FoldBrackets
29 |
--------------------------------------------------------------------------------
/bh_modules/bashsupport.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | from BracketHighlighter.bh_plugin import import_module
8 | lowercase = import_module("bh_modules.lowercase")
9 |
10 | BASH_KEYWORDS = ("select", "for", "while", "until")
11 |
12 |
13 | def validate(*args):
14 | """Check if bracket is lowercase."""
15 | return lowercase.validate(*args)
16 |
17 |
18 | def compare(name, first, second, bfr):
19 | """Ensure correct open is paired with correct close."""
20 |
21 | o = bfr[first.begin:first.end]
22 | c = bfr[second.begin:second.end]
23 |
24 | match = False
25 | if o == "if" and c == "fi":
26 | match = True
27 | elif o in BASH_KEYWORDS and c == "done":
28 | match = True
29 | elif o == "case" and c == "esac":
30 | match = True
31 | return match
32 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Thanks you for contributing to this project! Make sure you've read: http://facelessuser.github.io/BracketHighlighter/contributing/. Please follow the guidelines below.
2 |
3 | - Please describe the change in as much detail as possible so I can understand what is being added or modified.
4 |
5 | - If you are solving a bug that does not already have an issue, please describe the bug in detail and provide info on how to reproduce if applicable (this is good for me and others to reference later when verifying the issue has been resolved).
6 |
7 | - Please reference and link related open bugs or feature requests in this pull if applicable.
8 |
9 | - Make sure you've documented or updated the existing documentation if introducing a new feature or modifying the behavior of an existing feature that a user needs to be aware of. I will not accept new features if you have not provided documentation describing the feature.
10 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: deploy
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'st3-*'
7 | - 'st4-*'
8 |
9 | jobs:
10 |
11 | documents:
12 |
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v4
17 | with:
18 | fetch-depth: 0
19 | - name: Set up Python
20 | uses: actions/setup-python@v4
21 | with:
22 | python-version: 3.11
23 | - name: Install dependencies
24 | run: |
25 | python -m pip install --upgrade pip setuptools
26 | python -m pip install -r docs/src/requirements.txt
27 | - name: Deploy documents
28 | run: |
29 | git config user.name facelessuser
30 | git config user.email "${{ secrets.GH_EMAIL }}"
31 | git remote add gh-token "https://${{ secrets.GH_TOKEN }}@github.com/facelessuser/BracketHighlighter.git"
32 | git fetch gh-token && git fetch gh-token gh-pages:gh-pages
33 | python -m mkdocs gh-deploy -v --clean --remote-name gh-token
34 | git push gh-token gh-pages
35 |
--------------------------------------------------------------------------------
/bh_modules/tagnameselect.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | import sublime
8 | from BracketHighlighter import bh_plugin
9 | from BracketHighlighter.bh_plugin import import_module
10 | tags = import_module("bh_modules.tags")
11 |
12 |
13 | class TagNameSelect(bh_plugin.BracketPluginCommand):
14 | """Tag name select plugin."""
15 |
16 | def run(self, edit, name):
17 | """Select tag name."""
18 |
19 | if self.left.size() > 1:
20 | tag_settings = sublime.load_settings("bh_tag.sublime-settings")
21 | tag_mode = tags.get_tag_mode(self.view, tag_settings.get("tag_mode", []))
22 | tag_name = tag_settings.get('tag_name')[tag_mode]
23 | region1 = self.view.find(tag_name, self.left.begin)
24 | region2 = self.view.find(tag_name, self.right.begin)
25 | self.selection = [region1, region2]
26 |
27 |
28 | def plugin():
29 | """Make plugin available."""
30 |
31 | return TagNameSelect
32 |
--------------------------------------------------------------------------------
/bh_modules/latexenvironments.py:
--------------------------------------------------------------------------------
1 | """Compare environments to ensure they have matching names."""
2 | import re
3 |
4 | BEGIN_RE = re.compile(r"\\begin\{([^\}]*)\}")
5 | END_RE = re.compile(r"\\end\{([^\}]*)\}")
6 |
7 | BEGIN_LEN = len("\\begin{")
8 | END_LEN = len("\\end{")
9 | BRACKET_LEN = len("}")
10 |
11 |
12 | def highlighting(view, name, style, left, right):
13 | """Highlight only the environment name."""
14 | if left is not None:
15 | left = left.move(left.begin + BEGIN_LEN, left.end - BRACKET_LEN)
16 | if right is not None:
17 | right = right.move(right.begin + END_LEN, right.end - BRACKET_LEN)
18 |
19 | return left, right
20 |
21 |
22 | def compare(name, first, second, bfr):
23 | """Ensure both environments have the same name."""
24 | first_match = BEGIN_RE.match(bfr[first.begin:first.end])
25 | second_match = END_RE.match(bfr[second.begin:second.end])
26 | # although it should never happen, avoid errors from not matched regex
27 | if not (first_match and second_match):
28 | return False
29 |
30 | return first_match.group(1) == second_match.group(1)
31 |
--------------------------------------------------------------------------------
/bh_modules/swapbrackets.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | import sublime
8 | from BracketHighlighter.bh_plugin import import_module
9 | BracketRemove = import_module("bh_modules.bracketremove", "BracketRemove")
10 |
11 |
12 | class SwapBrackets(BracketRemove):
13 | """Swap bracket plugin."""
14 |
15 | def run(self, edit, name, remove_content=False, remove_indent=False, remove_block=False):
16 | """Remove then replace the bracket and adjust indentation if desired."""
17 |
18 | offset = self.left.toregion().size()
19 | selection = [sublime.Region(self.left.begin, self.right.begin - offset)]
20 | left = self.left.move(self.left.end, self.left.end)
21 | right = self.right.move(self.right.begin, self.right.begin)
22 | super(SwapBrackets, self).run(edit, name)
23 | self.selection = selection
24 | self.left = left
25 | self.right = right
26 | self.nobracket = False
27 |
28 |
29 | def plugin():
30 | """Make plugin available."""
31 |
32 | return SwapBrackets
33 |
--------------------------------------------------------------------------------
/docs/src/markdown/about/license.md:
--------------------------------------------------------------------------------
1 | # License
2 |
3 | Released under the MIT license.
4 |
5 | Copyright (c) 2013 - 2025 Isaac Muse
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
9 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
10 | persons to whom the Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
13 | Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
16 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 |
--------------------------------------------------------------------------------
/tests/test_json.py:
--------------------------------------------------------------------------------
1 | """Test JSON."""
2 | import unittest
3 | from . import validate_json_format
4 | import os
5 | import fnmatch
6 |
7 |
8 | class TestSettings(unittest.TestCase):
9 | """Test JSON settings."""
10 |
11 | def _get_json_files(self, pattern, folder='.'):
12 | """Get JSON files."""
13 |
14 | for root, dirnames, filenames in os.walk(folder):
15 | for filename in fnmatch.filter(filenames, pattern):
16 | yield os.path.join(root, filename)
17 | dirnames = [d for d in dirnames if d not in ('.svn', '.git', '.tox')]
18 |
19 | def test_json_settings(self):
20 | """Test each JSON file."""
21 |
22 | patterns = (
23 | '*.sublime-settings',
24 | '*.sublime-keymap',
25 | '*.sublime-commands',
26 | '*.sublime-menu',
27 | '*.sublime-theme',
28 | '*.sublime-color-scheme'
29 | )
30 |
31 | for pattern in patterns:
32 | for f in self._get_json_files(pattern):
33 | self.assertFalse(
34 | validate_json_format.CheckJsonFormat(False, True).check_format(f),
35 | "%s does not comform to expected format!" % f
36 | )
37 |
--------------------------------------------------------------------------------
/docs/src/dictionary/en-custom.txt:
--------------------------------------------------------------------------------
1 | API
2 | Aprosopo
3 | BH
4 | Backrefs
5 | BoundInCode
6 | BracketHighligher
7 | BracketHighlighter
8 | CMFL
9 | CMake
10 | CSS
11 | CSSedit
12 | Changelog
13 | ColdFusion
14 | Control's
15 | Customizable
16 | Deathaxe
17 | Django
18 | EmojiOne
19 | Erlang
20 | GitHub
21 | GitLab
22 | JSON
23 | JavaScript
24 | Jun
25 | LaTeX
26 | Lua
27 | MERCHANTABILITY
28 | Mar
29 | MdPopups
30 | MkDocs
31 | NONINFRINGEMENT
32 | OCaml
33 | OSX
34 | Offscreen
35 | PHP
36 | PLIST
37 | PyMdown
38 | RegReplace
39 | Regex
40 | Sublime's
41 | SublimeBrackets
42 | SublimeTagmatcher
43 | Swappable
44 | TextMate
45 | Twemoji
46 | allowlist
47 | backticks
48 | blocklist
49 | bool
50 | boolean
51 | builtins
52 | changelog
53 | cmd
54 | ctrl
55 | customizable
56 | dev
57 | docstrings
58 | ebeweber
59 | emoji
60 | extendable
61 | facelessuser
62 | gforcada
63 | gitlab
64 | installable
65 | ish
66 | matcher
67 | minimap
68 | mkdocs
69 | multi
70 | multiline
71 | offscreen
72 | plugin
73 | plugins
74 | popup
75 | popup's
76 | popups
77 | pre
78 | preprocessors
79 | pycqa
80 | pymdown
81 | pyparadigm
82 | pyparadigm's
83 | pytest
84 | regex
85 | renderer
86 | requesters
87 | screenshot
88 | squidfunk
89 | sublicense
90 | tuple
91 | un
92 | unescaping
93 | unneeded
94 | versa
95 | whitelist
96 | whitespace
97 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'master'
7 | tags:
8 | - '**'
9 | pull_request:
10 | branches:
11 | - '**'
12 |
13 | jobs:
14 | tests:
15 |
16 | env:
17 | TOXENV: py311
18 |
19 | runs-on: ubuntu-latest
20 |
21 | steps:
22 | - uses: actions/checkout@v4
23 | - name: Set up Python
24 | uses: actions/setup-python@v4
25 | with:
26 | python-version: 3.11
27 | - name: Install dependencies
28 | run: |
29 | python -m pip install --upgrade pip setuptools tox
30 | - name: Tests
31 | run: |
32 | python -m tox
33 |
34 | lint:
35 |
36 | env:
37 | TOXENV: lint
38 |
39 | runs-on: ubuntu-latest
40 |
41 | steps:
42 | - uses: actions/checkout@v4
43 | - name: Set up Python
44 | uses: actions/setup-python@v4
45 | with:
46 | python-version: 3.11
47 | - name: Install dependencies
48 | run: |
49 | python -m pip install --upgrade pip setuptools tox
50 | - name: Lint
51 | run: |
52 | python -m tox
53 |
54 | documents:
55 | env:
56 | TOXENV: documents
57 |
58 | runs-on: ubuntu-latest
59 |
60 | steps:
61 | - uses: actions/checkout@v4
62 | - name: Set up Python
63 | uses: actions/setup-python@v4
64 | with:
65 | python-version: 3.11
66 | - name: Install dependencies
67 | run: |
68 | python -m pip install --upgrade pip setuptools tox
69 | - name: Install Aspell
70 | run: |
71 | sudo apt-get install aspell aspell-en
72 | - name: Build documents
73 | run: |
74 | python -m tox
75 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Please read and fill out this template by replacing the instructions with appropriate information. If the template is not followed, the issue will be marked `Invalid` and closed.
2 |
3 | Before submitting an issue search past issues and read the area of the [documentation](http://facelessuser.github.io/BracketHighlighter/) related to your specific question, issue, or request.
4 |
5 | ---
6 |
7 | ## Description
8 |
9 | ... what is the issue / request ?
10 |
11 | > Vague issues/requests will be marked with `Insufficient Details` for about a week. If not corrected, they will be marked `Stale` for about a week and then closed.
12 |
13 | > For feature requests or proposals:
14 |
15 | > - Clearly define in as much detail as possible how you imagine the feature to work.
16 | > - Examples are also appreciated.
17 |
18 | > For bugs and support questions:
19 |
20 | > - Describe the bug/question in as much detail as possible to make it clear what is wrong or what you do not > understand.
21 | > - Provide errors from console (if available).
22 | > - Pictures or screencasts can also be used to clarify what the issue is or what the question is.
23 | > - Provide links to 3rd party syntax highlighting package you are using if applicable.
24 |
25 | ## Support Info
26 |
27 | ...
28 |
29 | > Run the following command from the menu: `Preferences->Package Settings->BracketHighlighter->Support Info`. Post the result here.
30 |
31 | ## Steps to Reproduce Issue
32 |
33 | 1. First step...
34 | 2. Second step...
35 | 3. Third step...
36 |
37 | > Provide steps to reproduce the issue. Pictures are fine, but also provide code/text I can copy and paste in order to reproduce. Omit for feature requests and feature proposals.
38 |
--------------------------------------------------------------------------------
/bh_remove.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | import sublime_plugin
8 | from collections import namedtuple
9 |
10 | MENU = namedtuple("Menu", "simple content block block_indent")(
11 | "Remove Brackets",
12 | "Remove Brackets and Content",
13 | "Remove Brackets: Block",
14 | "Remove Brackets: Indented Block"
15 | )
16 |
17 |
18 | class BhRemoveBracketsCommand(sublime_plugin.TextCommand):
19 | """Command to remove current highlighted brackets and optionally content."""
20 |
21 | def remove_brackets(self, value):
22 | """Perform removal of brackets."""
23 |
24 | if value != -1:
25 | menu_item = MENU[value]
26 | indent = menu_item == MENU.block_indent
27 | block = menu_item == MENU.block or menu_item == MENU.block_indent
28 | content = menu_item == MENU.content
29 |
30 | self.view.run_command(
31 | "bh_key",
32 | {
33 | "plugin": {
34 | "type": ["__all__"],
35 | "command": "bh_modules.bracketremove",
36 | "args": {
37 | "remove_indent": indent,
38 | "remove_block": block,
39 | "remove_content": content
40 | }
41 | }
42 | }
43 | )
44 |
45 | def run(self, edit):
46 | """Show menu of removal options."""
47 |
48 | self.window = self.view.window()
49 | self.window.show_quick_panel(
50 | list(MENU),
51 | self.remove_brackets
52 | )
53 |
--------------------------------------------------------------------------------
/docs/src/markdown/index.md:
--------------------------------------------------------------------------------
1 | # About BracketHighlighter
2 |
3 | ## Overview
4 |
5 | BracketHighlighter matches a variety of brackets such as: `[]`, `()`, `{}`, `""`, `''`, `#!xml `, and even
6 | custom brackets.
7 |
8 | This was originally forked from pyparadigm's _SublimeBrackets_ and _SublimeTagmatcher_ (both are no longer available). I
9 | forked his repositories to fix a number issues I add some features I had wanted. I also wanted to improve the
10 | efficiency of the matching. Moving forward, I have thrown away all of the code and have completely rewritten the entire
11 | code base to allow for more flexibility, faster matching, and a more feature rich experience.
12 |
13 | 
14 |
15 | ## Feature List
16 |
17 | - Customizable to highlight almost any bracket.
18 | - Customizable bracket highlight style.
19 | - High visibility bracket highlight mode.
20 | - Selectively disable or enable specific matching of tags, brackets, or quotes.
21 | - Selectively use an allowlist or blocklist for matching specific tags, brackets, or quotes based on language.
22 | - When bound to a shortcut, allow options to show line count and char count between match in the status bar.
23 | - Highlight basic brackets within strings.
24 | - Works with multi-select.
25 | - Configurable custom gutter icons.
26 | - Toggle bracket escape mode for string brackets (regex|string).
27 | - Bracket plugins that can jump between bracket ends, select content, etc.
28 |
29 | ## Credits
30 |
31 | - pyparadigm: for his original efforts with SublimeBrackets and SublimeTagmatcher which originally BracketHighlighter
32 | was built off of and the inspiration for this project.
33 | - BoundInCode: for his Tag icon.
34 |
--------------------------------------------------------------------------------
/bh_modules/swapquotes.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | from BracketHighlighter import bh_plugin
8 | import sublime
9 |
10 |
11 | class SwapQuotes(bh_plugin.BracketPluginCommand):
12 | """Swap quotes plugin."""
13 |
14 | def escaped(self, idx):
15 | """Check if character is an escape char."""
16 |
17 | view = self.view
18 | escaped = False
19 | while idx >= 0 and view.substr(idx) == '\\':
20 | escaped = ~escaped
21 | idx -= 1
22 | return escaped
23 |
24 | def run(self, edit, name):
25 | """
26 | Swap double or single quotes with each other.
27 |
28 | Handle escaping or unescaping like quotes or
29 | unlike quotes respectively.
30 | """
31 |
32 | view = self.view
33 | quote = view.substr(self.left.begin)
34 | if quote != "'" and quote != '"':
35 | return
36 | new = "'" if (quote == '"') else '"'
37 | old = quote
38 | begin = self.left.end
39 | end = self.right.begin
40 | content_end = self.right.begin
41 |
42 | view.replace(edit, self.left.toregion(), view.substr(self.left.toregion()).replace(old, new))
43 | view.replace(edit, self.right.toregion(), view.substr(self.right.toregion()).replace(old, new))
44 |
45 | offset = 0
46 | while begin < end + offset:
47 | char = view.substr(begin)
48 | if char == old and self.escaped(begin - 1):
49 | view.replace(edit, sublime.Region(begin - 1, begin), '')
50 | offset -= 1
51 | content_end -= 1
52 | elif char == new and not self.escaped(begin - 1):
53 | view.insert(edit, begin, "\\")
54 | offset += 1
55 | content_end += 1
56 | begin += 1
57 |
58 | self.right = self.right.move(content_end, end + offset)
59 | self.selection = [sublime.Region(content_end)]
60 |
61 |
62 | def plugin():
63 | """Make plugin available."""
64 |
65 | return SwapQuotes
66 |
--------------------------------------------------------------------------------
/bh_modules/bracketremove.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | from BracketHighlighter import bh_plugin
8 | import re
9 | import sublime
10 |
11 |
12 | class BracketRemove(bh_plugin.BracketPluginCommand):
13 | """Bracket remove plugin."""
14 |
15 | def decrease_indent_level(self, edit, row_first, row_last):
16 | """Decrease indent level on removal."""
17 |
18 | tab_size = self.view.settings().get("tab_size", 4)
19 | indents = re.compile(r"^(?:\t| {%d}| *)((?:\t| {%d}| )*)([\s\S]*)" % (tab_size, tab_size))
20 | if not self.single_line:
21 | for x in reversed(range(row_first, row_last + 1)):
22 | line = self.view.full_line(self.view.text_point(x, 0))
23 | text = self.view.substr(line)
24 | m = indents.match(text)
25 | if m:
26 | self.view.replace(edit, line, m.group(1) + m.group(2))
27 |
28 | def run(self, edit, name, remove_content=False, remove_indent=False, remove_block=False):
29 | """Remove the given bracket and adjust its indentation if desired."""
30 |
31 | if remove_content:
32 | self.view.replace(edit, sublime.Region(self.left.begin, self.right.end), "")
33 | else:
34 | row_first = self.view.rowcol(self.left.end)[0] + 1
35 | row_last = self.view.rowcol(self.right.begin)[0] - 1
36 | self.single_line = not row_first <= row_last
37 | if remove_block and not self.single_line:
38 | self.view.replace(edit, self.view.full_line(self.right.toregion()), "")
39 | else:
40 | self.view.replace(edit, self.right.toregion(), "")
41 | if remove_indent:
42 | self.decrease_indent_level(edit, row_first, row_last)
43 | if remove_block and not self.single_line:
44 | self.view.replace(edit, self.view.full_line(self.left.toregion()), "")
45 | else:
46 | self.view.replace(edit, self.left.toregion(), "")
47 |
48 | self.left = None
49 | self.right = None
50 | self.nobracket = True
51 |
52 |
53 | def plugin():
54 | """Make plugin available."""
55 |
56 | return BracketRemove
57 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | [![Donate via PayPal][donate-image]][donate-link]
2 | [![Build][github-ci-image]][github-ci-link]
3 | [![Package Control Downloads][pc-image]][pc-link]
4 | ![License][license-image]
5 | # BracketHighlighter
6 |
7 | Bracket Highlighter matches a variety of brackets such as: `[]`, `()`, `{}`, `""`, `''`, ``, and even custom
8 | brackets.
9 |
10 | This was originally forked from pyparadigm's _SublimeBrackets_ and _SublimeTagmatcher_ (both are no longer available). I
11 | forked this to fix some issues I had and to add some features I had wanted. I also wanted to improve the efficiency of
12 | the matching.
13 |
14 | Moving forward, I have thrown away all of the code and have completely rewritten the entire code base to allow for a
15 | more flexibility, faster, and more feature rich experience.
16 |
17 | 
18 |
19 | # Feature List
20 |
21 | - Customizable to highlight almost any bracket.
22 | - Customizable bracket highlight style.
23 | - High visibility bracket highlight mode.
24 | - Selectively disable or enable specific matching of tags, brackets, or quotes.
25 | - Selectively use an allowlist or blocklist for matching specific tags, brackets, or quotes based on language.
26 | - When bound to a shortcut, allow option to show line count and char count between match in the status bar.
27 | - Highlight basic brackets within strings.
28 | - Works with multi-select.
29 | - Configurable custom gutter icons.
30 | - Toggle bracket escape mode for string brackets (regex|string).
31 | - Bracket plugins that can jump between bracket ends, select content, remove brackets and/or content, wrap selections
32 | with brackets, swap brackets, swap quotes (handling quote escaping between the main quotes), fold/unfold content
33 | between brackets, toggle through tag attribute selection, select both the opening and closing tag name to change both
34 | simultaneously, etc.
35 |
36 | # Documentation
37 |
38 | https://facelessuser.github.io/BracketHighlighter/
39 |
40 | # License
41 |
42 | Released under the MIT license.
43 |
44 | [github-ci-image]: https://github.com/facelessuser/BracketHighlighter/workflows/build/badge.svg?branch=master&event=push
45 | [github-ci-link]: https://github.com/facelessuser/BracketHighlighter/actions?query=workflow%3Abuild+branch%3Amaster
46 | [pc-image]: https://img.shields.io/packagecontrol/dt/BracketHighlighter.svg?labelColor=333333&logo=sublime%20text
47 | [pc-link]: https://packagecontrol.io/packages/BracketHighlighter
48 | [license-image]: https://img.shields.io/badge/license-MIT-blue.svg?labelColor=333333
49 | [donate-image]: https://img.shields.io/badge/Donate-PayPal-3fabd1?logo=paypal
50 | [donate-link]: https://www.paypal.me/facelessuser
51 |
--------------------------------------------------------------------------------
/bh_modules/rubykeywords.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | import re
8 |
9 | RE_DEF = re.compile(r"\s*(?:(?:private|public|protected)\s+)?(def).*?")
10 | RE_DEF_ENDLESS = re.compile(
11 | r"^[ \t]*((?:private|public|protected)\s+)?def\s+(?:[\w\.=]+)[?!]?\s*(\([^)]+\)\s*|\s+)=",
12 | re.M
13 | )
14 | RE_KEYWORD = re.compile(r"(\s*\b)[\w\W]*")
15 | SPECIAL_KEYWORDS = ('do',)
16 | NORMAL_KEYWORDS = ('for', 'until', 'unless', 'while', 'class', 'module', 'if', 'begin', 'case')
17 | RE_PREVIOUS = re.compile(r'.*([^\s])\s*=$')
18 |
19 |
20 | def validate(view, name, bracket, bracket_side, bfr):
21 | """Check if bracket is lowercase."""
22 |
23 | b = bfr[bracket.begin:bracket.end]
24 | if bracket_side == 1 and b.strip() == '=':
25 | if view.match_selector(bracket.end, 'meta.function.parameters.default-value.ruby, string, comment'):
26 | return False
27 | view.score_selector
28 | left = max(0, bracket.begin - 1)
29 | previous = bfr[left:bracket.end]
30 | m = RE_PREVIOUS.match(previous)
31 | if m:
32 | s = left + m.start(1)
33 | selector = 'meta.function.ruby entity.name.function.ruby, meta.function.parameters.ruby'
34 | if not view.match_selector(s, selector):
35 | return False
36 | return True
37 |
38 |
39 | def compare(name, first, second, bfr):
40 | """Differentiate 'repeat..until' from '*..end' brackets."""
41 |
42 | brackets = (bfr[first.begin:first.end], bfr[second.begin:second.end])
43 | if brackets[1].lstrip() == '=' and RE_DEF.match(brackets[0]):
44 | return True
45 | if brackets[1] == 'end':
46 | return True
47 | return False
48 |
49 |
50 | def post_match(view, name, style, first, second, center, bfr, threshold):
51 | """Strip whitespace from being targeted with highlight."""
52 |
53 | if first is not None:
54 | # Strip whitespace from the beginning of first bracket
55 | open_bracket = bfr[first.begin:first.end]
56 | if open_bracket not in SPECIAL_KEYWORDS:
57 | open_bracket_stripped = open_bracket.strip()
58 | if open_bracket_stripped not in NORMAL_KEYWORDS:
59 | m = RE_DEF.match(open_bracket)
60 | if m:
61 | first = first.move(first.begin + m.start(1), first.begin + m.end(1))
62 | if second and bfr[second.begin:second.end].lstrip() == '=':
63 | second = first
64 | else:
65 | m = RE_KEYWORD.match(open_bracket)
66 | if m:
67 | first = first.move(first.begin + m.end(1), first.end)
68 | return first, second, style
69 |
--------------------------------------------------------------------------------
/bh_swapping.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2020 Isaac Muse
5 | License: MIT
6 | """
7 | import sublime
8 | import sublime_plugin
9 | from . import bh_wrapping
10 |
11 |
12 | class SwapBrackets(bh_wrapping.WrapBrackets):
13 | """
14 | Swap Base Class.
15 |
16 | Swap base is derived from the wrap base.
17 | """
18 |
19 | def wrap(self, wrap_entry):
20 | """Setup for wrapping."""
21 |
22 | if wrap_entry < 0:
23 | return
24 |
25 | self._style = ["inline"]
26 |
27 | self.brackets = self._brackets[wrap_entry]
28 | self.wrap_brackets(0)
29 |
30 |
31 | class SwapBracketsCommand(sublime_plugin.TextCommand):
32 | """Swap bracket command."""
33 |
34 | def finalize(self, callback):
35 | """Execute post wrap callback."""
36 |
37 | if self.view is not None:
38 | if not self.view.settings().get("bracket_highlighter.busy", False):
39 | callback()
40 | else:
41 | sublime.set_timeout(lambda: self.finalize(callback), 100)
42 |
43 | def swap_brackets(self, value):
44 | """Swap the brackets."""
45 |
46 | if value < 0:
47 | return
48 |
49 | self.brackets = self.wrap._brackets[value]
50 |
51 | self.view.run_command(
52 | "bh_async_key" if self._async else "bh_key",
53 | {
54 | "plugin": {
55 | "type": ["__all__"],
56 | "command": "bh_modules.swapbrackets"
57 | }
58 | }
59 | )
60 |
61 | self.view = self.window.active_view()
62 |
63 | if self._async:
64 | sublime.set_timeout(lambda: self.finalize(lambda: self.wrap.wrap(value)), 100)
65 | else:
66 | self.finalize(self.wrap.wrap(value))
67 |
68 | def run(self, edit, **kwargs):
69 | """Initiate the swap."""
70 |
71 | self._async = kwargs.get('async', False)
72 | self.window = self.view.window()
73 | self.wrap = SwapBrackets(self.view, "bh_swapping.sublime-settings", "swapping")
74 |
75 | if len(self.wrap._menu):
76 | self.window.show_quick_panel(
77 | self.wrap._menu,
78 | self.swap_brackets
79 | )
80 |
81 | def is_enabled(self, **kwargs):
82 | """Check if command is enabled."""
83 |
84 | settings = self.view.settings()
85 | return bool(
86 | not settings.get('bracket_highlighter.ignore', False) and
87 | (
88 | not settings.get('is_widget') or
89 | sublime.load_settings("bh_core.sublime-settings").get('search_in_widgets', False)
90 | )
91 | )
92 |
--------------------------------------------------------------------------------
/docs/src/markdown/extended-regex.md:
--------------------------------------------------------------------------------
1 | # Extended Regex Guide
2 |
3 | ## Overview
4 |
5 | BH uses Python's Re regular expression engine, but it also adds some additional back references to aid in the creation
6 | of bracket patterns. This is done with a custom wrapper called Backrefs that was originally written for
7 | [RegReplace][regreplace].
8 |
9 | Backrefs was written to add various additional back references that are known to some regex engines, but not to Python's
10 | Re. Backrefs adds: `\p`, `\P`, `\u`, `\U`, `\l`, `\L`, `\Q` or `\E` (though `\u` and `\U` are replaced with `\c` and
11 | `\C`).
12 |
13 | You can read more about Backrefs' features in [Backrefs' documentation][backrefs].
14 |
15 | ## Getting the Latest Backrefs
16 |
17 | It is not always clear when Package Control updates dependencies. So to force dependency updates, you can run Package
18 | Control's `Satisfy Dependencies` command which will update to the latest release.
19 |
20 | ## Using Backrefs in BracketHighlighter Plugins
21 |
22 | You can import Backrefs into a `bh_plugin`:
23 |
24 | ```python
25 | from backrefs as bre
26 | ```
27 |
28 | Backrefs does provide a wrapper for all of Re's normal functions such as `match`, `sub`, etc., but it is recommended to
29 | pre-compile your search patterns **and** your replace patterns for the best performance; especially if you plan on
30 | reusing the same pattern multiple times. As Re does cache a certain amount of the non-compiled calls, you will be
31 | spared from some of the performance hit, but Backrefs does not cache the pre-processing of search and replace patterns.
32 |
33 | To use pre-compiled functions, you compile the search pattern with `compile_search`. If you want to take advantage of
34 | replace Backrefs, you need to compile the replace pattern as well. Notice the compiled pattern is fed into the replace
35 | pattern; you can feed the replace compiler the string representation of the search pattern as well, but the compiled
36 | pattern will be faster and is the recommended way.
37 |
38 | ```py
39 | pattern = bre.compile_search(r'somepattern', flags)
40 | replace = bre.compile_replace(pattern, r'\1 some replace pattern')
41 | ```
42 |
43 | Then you can use the complied search pattern and replace
44 |
45 | ```py
46 | text = pattern.sub(replace, r'sometext')
47 | ```
48 |
49 | or
50 |
51 | ```py
52 | m = pattern.match(r'sometext')
53 | if m:
54 | text = replace(m) # similar to m.expand(template)
55 | ```
56 |
57 | To use the non-compiled search/replace functions, you call them just them as you would in Re; the names are the same.
58 | Methods like `sub` and `subn` will compile the replace pattern on the fly if given a string.
59 |
60 | ```python
61 | for m in bre.finditer(r'somepattern', 'some text', bre.UNICODE | bre.DOTALL):
62 | # do something
63 | ```
64 |
65 | If you want to replace without compiling, you can use the `expand` method.
66 |
67 | ```python
68 | m = bre.match(r'sometext')
69 | if m:
70 | text = bre.expand(m, r'replace pattern')
71 | ```
72 |
--------------------------------------------------------------------------------
/bh_swapping.sublime-settings:
--------------------------------------------------------------------------------
1 | {
2 | "swapping": [
3 | {
4 | "enabled": true, "language_list": [], "language_filter": "allowlist", "entries": [
5 | {"name": "<> Angle", "brackets": ["<", ">${BH_SEL}"]}
6 | ]
7 | },
8 | {
9 | "enabled": true, "language_list": ["Plain text"], "language_filter": "blocklist", "entries": [
10 | {"name": "{} Curly", "brackets": ["{", "}${BH_SEL}"]}
11 | ]
12 | },
13 | {
14 | "enabled": true, "language_list": ["Plain text"], "language_filter": "blocklist", "entries": [
15 | {"name": "() Round", "brackets": ["(", ")${BH_SEL}"]}
16 | ]
17 | },
18 | {
19 | "enabled": true, "language_list": ["Plain text"], "language_filter": "blocklist", "entries": [
20 | {"name": "[] Square", "brackets": ["[", "]${BH_SEL}"]}
21 | ]
22 | },
23 | {
24 | "enabled": true, "language_list": ["HTML", "HTML 5", "XML", "PHP", "CFML", "HTML+CFML", "ColdFusion", "ColdFusionCFC"], "language_filter": "allowlist", "entries": [
25 | {"name": "HTML/XML Tag", "brackets": ["<${BH_SEL:NAME}>", "${BH_SEL:NAME}>"]}
26 | ]
27 | },
28 | {
29 | "enabled": true, "language_list": ["Markdown", "Multimarkdown", "GithubFlavoredMarkdown", "Markdown Extended"], "language_filter": "allowlist", "entries": [
30 | {"name": "Markdown: Bold", "brackets": ["**", "**${BH_SEL}"]},
31 | {"name": "Markdown: Italic", "brackets": ["_", "_${BH_SEL}"]},
32 | {"name": "Markdown: Monospace", "brackets": ["`", "`${BH_SEL}"]}
33 | ]
34 | },
35 | {
36 | "enabled": true, "language_list": ["LaTeX", "LaTeX (TikZ)", "knitr (Rnw)"], "language_filter": "allowlist", "entries": [
37 | {"name": "LaTeX Environment", "brackets": ["\\begin{${BH_SEL:NAME}}", "\\end{${BH_SEL:NAME}}"]}
38 | ]
39 | },
40 | {
41 | "enabled": true, "language_list": ["C++", "C", "C Improved"], "language_filter": "allowlist", "entries": [
42 | {"name": "C/C++: #if", "brackets": ["#if ${BH_SEL}", "#endif"]},
43 | {"name": "C/C++: #if, #else", "brackets": ["#if${BH_SEL}", "#else\n${BH_TAB:/* CODE */}\n#endif"]},
44 | {"name": "C/C++: #if, #elif", "brackets": ["#if${BH_SEL}", "#elif ${BH_TAB:/* CONDITION */}\n${BH_TAB:/* CODE */}\n#endif"]},
45 | {"name": "C/C++: #ifdef", "brackets": ["#ifdef${BH_SEL}", "#endif"]},
46 | {"name": "C/C++: #ifdef, #else", "brackets": ["#ifdef${BH_SEL}", "#else\n${BH_TAB:/* CODE */}\n#endif"]},
47 | {"name": "C/C++: #ifndef", "brackets": ["#ifndef${BH_SEL}", "#endif"]},
48 | {"name": "C/C++: #ifndef, #else", "brackets": ["#ifndef${BH_SEL}", "#else\n${BH_TAB:/* CODE */}\n#endif"]}
49 | ]
50 | }
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/.pyspelling.yml:
--------------------------------------------------------------------------------
1 | matrix:
2 | - name: settings
3 | sources:
4 | - '**/*.sublime-settings'
5 | aspell:
6 | lang: en
7 | dictionary:
8 | wordlists:
9 | - docs/src/dictionary/en-custom.txt
10 | output: build/dictionary/settings.dic
11 | pipeline:
12 | - pyspelling.filters.cpp:
13 | prefix: 'st'
14 | group_comments: true
15 | line_comments: false
16 | - pyspelling.filters.context:
17 | context_visible_first: true
18 | escapes: '\\[\\`]'
19 | delimiters:
20 | # Ignore multiline content between fences (fences can have 3 or more back ticks)
21 | # ```
22 | # content
23 | # ```
24 | - open: '(?s)^(?P *`{3,})$'
25 | close: '^(?P=open)$'
26 | # Ignore text between inline back ticks
27 | - open: '(?P`+)'
28 | close: '(?P=open)'
29 | - pyspelling.filters.url:
30 |
31 | - name: mkdocs
32 | sources:
33 | - site/**/*.html
34 | aspell:
35 | lang: en
36 | dictionary:
37 | wordlists:
38 | - docs/src/dictionary/en-custom.txt
39 | output: build/dictionary/mkdocs.dic
40 | pipeline:
41 | - pyspelling.filters.html:
42 | comments: false
43 | attributes:
44 | - title
45 | - alt
46 | ignores:
47 | - 'code, pre, a.magiclink, span.keys'
48 | - '.MathJax_Preview, .md-nav__link, .md-footer-custom-text, .md-source__repository, .headerlink, .md-icon'
49 | - '.md-social__link'
50 | - pyspelling.filters.url:
51 |
52 | - name: markdown
53 | sources:
54 | - readme.md
55 | aspell:
56 | lang: en
57 | dictionary:
58 | wordlists:
59 | - docs/src/dictionary/en-custom.txt
60 | output: build/dictionary/markdown.dic
61 | pipeline:
62 | - pyspelling.filters.markdown:
63 | - pyspelling.filters.html:
64 | comments: false
65 | attributes:
66 | - title
67 | - alt
68 | ignores:
69 | - :is(code, pre)
70 | - pyspelling.filters.url:
71 |
72 | - name: python
73 | sources:
74 | - './**/*.py'
75 | aspell:
76 | lang: en
77 | dictionary:
78 | wordlists:
79 | - docs/src/dictionary/en-custom.txt
80 | output: build/dictionary/python.dic
81 | pipeline:
82 | - pyspelling.filters.python:
83 | group_comments: true
84 | - pyspelling.flow_control.wildcard:
85 | allow:
86 | - py-comment
87 | - pyspelling.filters.context:
88 | context_visible_first: true
89 | delimiters:
90 | # Ignore lint (noqa) and coverage (pragma) as well as shebang (#!)
91 | - open: '^(?: *(?:noqa\b|pragma: no cover)|!)'
92 | close: '$'
93 | # Ignore Python encoding string -*- encoding stuff -*-
94 | - open: '^ *-\*-'
95 | close: '-\*-$'
96 | - pyspelling.filters.context:
97 | context_visible_first: true
98 | escapes: '\\[\\`]'
99 | delimiters:
100 | # Ignore multiline content between fences (fences can have 3 or more back ticks)
101 | # ```
102 | # content
103 | # ```
104 | - open: '(?s)^(?P *`{3,})$'
105 | close: '^(?P=open)$'
106 | # Ignore text between inline back ticks
107 | - open: '(?P`+)'
108 | close: '(?P=open)'
109 | - pyspelling.filters.url:
110 |
--------------------------------------------------------------------------------
/.github/labels.yml:
--------------------------------------------------------------------------------
1 | template: 'facelessuser:master-labels:labels.yml:master'
2 |
3 | # Wildcard labels
4 |
5 | brace_expansion: true
6 | extended_glob: true
7 | minus_negate: false
8 |
9 | rules:
10 | - labels: ['C: infrastructure']
11 | patterns: ['*|!@(*.md|*.py|*.sublime-@(keymap|menu|settings|commands))', '@(requirements|.github)/**']
12 |
13 | - labels: ['C: source']
14 | patterns: ['**/@(*.py|*.sublime-@(keymap|menu|settings|commands))|!tests']
15 |
16 | - labels: ['C: docs']
17 | patterns: ['**/*.md|docs/**']
18 |
19 | - labels: ['C: tests']
20 | patterns: ['tests/**']
21 |
22 | - labels: ['C: plugins']
23 | patterns: ['bh_modules/**|bh_plugin.py']
24 |
25 | - labels: ['C: swapping']
26 | patterns: ['**/*swapping*']
27 |
28 | - labels: ['C: wrapping']
29 | patterns: ['**/*wrapping*']
30 |
31 | - labels: ['C: core']
32 | patterns: ['**/*core*']
33 |
34 | - labels: ['C: logging']
35 | patterns: ['bh_logging.py']
36 |
37 | - labels: ['C: popups']
38 | patterns: ['bh_popup.py']
39 |
40 | - labels: ['C: regions']
41 | patterns: ['bh_regions.py']
42 |
43 | - labels: ['C: remove']
44 | patterns: ['bh_remove.py']
45 |
46 | - labels: ['C: rules']
47 | patterns: ['bh_rules.py']
48 |
49 | - labels: ['C: search']
50 | patterns: ['bh_search.py']
51 |
52 | - labels: ['C: tags']
53 | patterns: ['**/bh_modules/tag@(s|attrselect|nameselect).py|bh_tag.sublime-settings']
54 |
55 | - labels: ['C: settings']
56 | patterns: ['*.sublime-@(keymap|menu|settings|commands)']
57 |
58 | - labels: ['C: icons']
59 | patterns: ['**/icons/*']
60 |
61 | # Label management
62 |
63 | labels:
64 | - name: 'C: plugins'
65 | renamed: plugins
66 | color: subcategory
67 | description: Related to plugins.
68 |
69 | - name: 'C: swapping'
70 | renamed: swapping
71 | color: subcategory
72 | description: Related to the swapping library.
73 |
74 | - name: 'C: wrapping'
75 | renamed: wrapping
76 | color: subcategory
77 | description: Related to the wrapping library.
78 |
79 | - name: 'C: core'
80 | renamed: core
81 | color: subcategory
82 | description: Related to the core code.
83 |
84 | - name: 'C: logging'
85 | renamed: logging
86 | color: subcategory
87 | description: Related to logging
88 |
89 | - name: 'C: popups'
90 | renamed: popups
91 | color: subcategory
92 | description: Related to popups
93 |
94 | - name: 'C: regions'
95 | renamed: regions
96 | color: subcategory
97 | description: Related to regions.
98 |
99 | - name: 'C: remove'
100 | renamed: remove
101 | color: subcategory
102 | description: Related to bracket removal.
103 |
104 | - name: 'C: rules'
105 | renamed: rules
106 | color: subcategory
107 | description: Related to bracket rules.
108 |
109 | - name: 'C: search'
110 | renamed: search
111 | color: subcategory
112 | description: Related to search.
113 |
114 | - name: 'C: tags'
115 | renamed: tags
116 | color: subcategory
117 | description: Related to tag handling.
118 |
119 | - name: 'C: settings'
120 | renamed: settings
121 | color: subcategory
122 | description: Related to Sublime settings files.
123 |
124 | - name: 'C: icons'
125 | renamed: icons
126 | color: subcategory
127 | description: Bracket icons.
128 |
--------------------------------------------------------------------------------
/bh_modules/tagattrselect.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | import sublime
8 | from BracketHighlighter import bh_plugin
9 | from BracketHighlighter.bh_plugin import import_module
10 | tags = import_module("bh_modules.tags")
11 |
12 |
13 | class SelectAttr(bh_plugin.BracketPluginCommand):
14 | """Select attribute plugin."""
15 |
16 | def run(self, edit, name, direction='right'):
17 | """
18 | Select next attribute in the given direction.
19 |
20 | Wrap when the end is hit.
21 | """
22 |
23 | if self.left.size() <= 1:
24 | return
25 | tag_settings = sublime.load_settings("bh_tag.sublime-settings")
26 | tag_mode = tags.get_tag_mode(self.view, tag_settings.get("tag_mode", []))
27 | tag_name = tag_settings.get('tag_name')[tag_mode]
28 | attr_name = tag_settings.get('attributes')[tag_mode]
29 | tname = self.view.find(tag_name, self.left.begin)
30 | current_region = self.selection[0]
31 | current_pt = self.selection[0].b
32 | region = self.view.find(attr_name, tname.b)
33 | selection = self.selection
34 |
35 | if direction == 'left':
36 | last = None
37 |
38 | # Keep track of last attribute
39 | if region is not None and current_pt <= region.b and region.b < self.left.end:
40 | last = region
41 |
42 | while region is not None and region.b < self.left.end:
43 | # Select attribute until you have closest to the left of selection
44 | if (
45 | current_pt > region.b or
46 | (
47 | current_pt <= region.b and current_region.a >= region.a and not
48 | (
49 | region.a == current_region.a and region.b == current_region.b
50 | )
51 | )
52 | ):
53 | selection = [region]
54 | last = None
55 | # Update last attribute
56 | elif last is not None:
57 | last = region
58 | region = self.view.find(attr_name, region.b)
59 | # Wrap right
60 | if last is not None:
61 | selection = [last]
62 | else:
63 | first = None
64 | # Keep track of first attribute
65 | if region is not None and region.b < self.left.end:
66 | first = region
67 |
68 | while region is not None and region.b < self.left.end:
69 | # Select closest attribute to the right of the selection
70 | if (
71 | current_pt < region.b or
72 | (
73 | current_pt <= region.b and current_region.a >= region.a and not
74 | (
75 | region.a == current_region.a and region.b == current_region.b
76 | )
77 | )
78 | ):
79 | selection = [region]
80 | first = None
81 | break
82 | region = self.view.find(attr_name, region.b)
83 | # Wrap left
84 | if first is not None:
85 | selection = [first]
86 | self.selection = selection
87 |
88 |
89 | def plugin():
90 | """Make plugin available."""
91 |
92 | return SelectAttr
93 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: BracketHighlighter Documentation
2 | site_url: https://facelessuser.github.io/BracketHighlighter
3 | repo_url: https://github.com/facelessuser/BracketHighlighter
4 | edit_uri: tree/master/docs/src/markdown
5 | site_description: A customizable Sublime Text plugin that highlights matching brackets.
6 | copyright: |
7 | Copyright © 2013 - 2025 Isaac Muse
8 |
9 | docs_dir: docs/src/markdown
10 | theme:
11 | name: material
12 | custom_dir: docs/theme
13 | icon:
14 | logo: material/book-open-page-variant
15 | palette:
16 | scheme: dracula
17 | primary: deep purple
18 | accent: deep purple
19 | font:
20 | text: Roboto
21 | code: Roboto Mono
22 | features:
23 | - navigation.tabs
24 | - navigation.top
25 |
26 | nav:
27 | - Home:
28 | - BracketHighlighter: index.md
29 | - Installation: installation.md
30 | - Basic Usage: usage.md
31 | - Customizing: customize.md
32 | - Extended Regex Guide: extended-regex.md
33 | - About:
34 | - Contributing & Support: about/contributing.md
35 | - Changelog: https://github.com/facelessuser/BracketHighlighter/blob/master/CHANGES.md
36 | - License: about/license.md
37 |
38 | markdown_extensions:
39 | - markdown.extensions.toc:
40 | slugify: !!python/name:pymdownx.slugs.uslugify
41 | permalink: ""
42 | - markdown.extensions.smarty:
43 | smart_quotes: false
44 | - pymdownx.betterem:
45 | - markdown.extensions.attr_list:
46 | - markdown.extensions.tables:
47 | - markdown.extensions.abbr:
48 | - markdown.extensions.footnotes:
49 | - markdown.extensions.md_in_html:
50 | - pymdownx.superfences:
51 | - pymdownx.highlight:
52 | extend_pygments_lang:
53 | - name: php-inline
54 | lang: php
55 | options:
56 | startinline: true
57 | - name: pycon3
58 | lang: pycon
59 | options:
60 | python3: true
61 | - pymdownx.inlinehilite:
62 | - pymdownx.magiclink:
63 | repo_url_shortener: true
64 | repo_url_shorthand: true
65 | social_url_shorthand: true
66 | user: facelessuser
67 | repo: BracketHighlighter
68 | - pymdownx.tilde:
69 | - pymdownx.caret:
70 | - pymdownx.smartsymbols:
71 | - pymdownx.emoji:
72 | emoji_index: !!python/name:material.extensions.emoji.twemoji
73 | emoji_generator: !!python/name:material.extensions.emoji.to_svg
74 | - pymdownx.escapeall:
75 | hardbreak: true
76 | nbsp: true
77 | - pymdownx.tasklist:
78 | custom_checkbox: true
79 | - pymdownx.arithmatex:
80 | - pymdownx.mark:
81 | - pymdownx.striphtml:
82 | - pymdownx.snippets:
83 | base_path:
84 | - docs/src/markdown/.snippets
85 | auto_append:
86 | - refs.md
87 | - pymdownx.keys:
88 | separator: "\uff0b"
89 | - pymdownx.saneheaders:
90 | - pymdownx.blocks.admonition:
91 | types:
92 | - new
93 | - settings
94 | - note
95 | - abstract
96 | - info
97 | - tip
98 | - success
99 | - question
100 | - warning
101 | - failure
102 | - danger
103 | - bug
104 | - example
105 | - quote
106 | - pymdownx.blocks.details:
107 | types:
108 | - name: details-new
109 | class: new
110 | - name: details-settings
111 | class: settings
112 | - name: details-note
113 | class: note
114 | - name: details-abstract
115 | class: abstract
116 | - name: details-info
117 | class: info
118 | - name: details-tip
119 | class: tip
120 | - name: details-success
121 | class: success
122 | - name: details-question
123 | class: question
124 | - name: details-warning
125 | class: warning
126 | - name: details-failure
127 | class: failure
128 | - name: details-danger
129 | class: danger
130 | - name: details-bug
131 | class: bug
132 | - name: details-example
133 | class: example
134 | - name: details-quote
135 | class: quote
136 | - pymdownx.blocks.html:
137 | - pymdownx.blocks.definition:
138 | - pymdownx.blocks.tab:
139 | alternate_style: True
140 | - pymdownx.fancylists:
141 | inject_style: true
142 | - pymdownx.blocks.caption:
143 |
144 | extra:
145 | social:
146 | - icon: fontawesome/brands/github
147 | link: https://github.com/facelessuser
148 |
149 | plugins:
150 | - search
151 | - git-revision-date-localized
152 | - mkdocs_pymdownx_material_extras
153 | - minify:
154 | minify_html: true
155 |
--------------------------------------------------------------------------------
/quickstart.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | {: width=769, height=270}
4 |
5 | BracketHighlighter is designed to auto highlight your brackets in various source files. In general, if you are using
6 | one of the common languages, BracketHighlighter will work out of the box.
7 |
8 | It is advised that you disable Sublime's default bracket and tag matcher in your `Preferences.sublime-settings` file
9 | or you will have matching conflicts:
10 |
11 | ```js
12 | "match_brackets": false,
13 | "match_brackets_angle": false,
14 | "match_brackets_braces": false,
15 | "match_brackets_content": false,
16 | "match_brackets_square": false,
17 | "match_tags": false
18 | ```
19 |
20 | BracketHighlighter also contains a number
21 | of extensions that allows for deleting, replacing, or even wrapping brackets; check out the [documentation](http://facelessuser.github.io/BracketHighlighter/usage/) to learn
22 | more about basic usage.
23 |
24 | BracketHighlighter (if you are on the latest version of Sublime Text 3) also will show a popup on screen if you mouse
25 | over a bracket and the other matching pair is offscreen. The popup will show the offscreen bracket with surrounding
26 | lines and allow you to jump to the offscreen bracket.
27 |
28 | {: width=300, height=179}
29 |
30 | # Where It Doesn't Work
31 |
32 | BracketHighlighter excludes plain text by default. The free form nature of plain text and lack of syntax highlight
33 | scoping makes it more difficult to detect intended brackets.
34 |
35 | Some language might not be supported yet, but they can be added via pull requests. Check out the [documentation](http://facelessuser.github.io/BracketHighlighter/customize/#configuring-brackets) to
36 | learn about adding bracket rules and take a look at the default [settings file](sub://Packages/BracketHighlighter/bh_core.sublime-settings) to see examples.
37 |
38 | # My Language Isn't Supported
39 |
40 | BracketHighlighter supports numerous different languages and specialty bracket types, but your language might not be
41 | supported yet. The most common requested enhancement for BracketHighlighter is for new rules to support for a new a
42 | previously unsupported language. I, like you, am proficient in very specific languages. I probably don’t use your
43 | favorite language or there would already be a support for it. I don’t have time to learn the nuances of your language.
44 | For these reasons, support for new language brackets requires pull requests from the community.
45 |
46 | Though I will not personally implement rules for your favorite language, I am more than willing to offer suggestions
47 | and guidance to help those who may struggle to create rules for their specific language of interest.
48 |
49 | If you find a bug in a supported language I am familiar with, I am happy to address the issue. If the bug is found in
50 | a language I am not proficient in, it is likely I will defer fixes to the community. I may offer suggestions and
51 | encourage the issue creator to do the actual testing as they will be more familiar with the language.
52 |
53 | # Customizing
54 |
55 | BracketHighlighter can be tweaked to show specific brackets with specific colors and styles. Due to the way sublime
56 | handles colored regions, the method of specifying specific colors can be cumbersome, but it is all [documented](http://facelessuser.github.io/BracketHighlighter/customize/#configuring-highlight-style).
57 |
58 | # Sometimes I See a Question Mark
59 |
60 | BracketHighlighter's auto highlighting does not scan the entire file all at once, but it scans a small window for
61 | performance reasons. If you see a question mark, it may be simply that the search threshold has been reached. You
62 | can run a bracket match with no threshold from the command palette or the offscreen bracket popup dialog (if using
63 | the latest version of Sublime Text 3). If you are dissatisfied with the default small threshold, it can be increased
64 | in the settings file. Be mindful that extremely large thresholds may affect performance. I personally use a value of
65 | `10000`.
66 |
67 | {: width=486, height=210}
68 |
69 | # I Need Help!
70 |
71 | That's okay. Bugs are sometimes introduced or discovered in existing code. Sometimes the documentation isn't clear.
72 | Support can be found over on the [official repo](https://github.com/facelessuser/BracketHighlighter/issues). Make sure to first search the documentation and previous issues
73 | before opening a new issue. And when creating a new issue, make sure to fill in the provided issue template. Please
74 | be courteous and kind in your interactions.
75 |
--------------------------------------------------------------------------------
/bh_tag.sublime-settings:
--------------------------------------------------------------------------------
1 | {
2 | /* If you add a new key to one of these settings, you must add one to all! */
3 |
4 | // This is where you initially define a new tag mode.
5 | // This new tag_mode key must be used in all other settings in this file
6 | // to set up its specific settings.
7 | // Determine which style of tag-matching to use in which syntax.
8 | "tag_mode": [
9 | {"mode": "xml", "syntax": ["XML"]},
10 | {
11 | "mode": "xhtml",
12 | "syntax": [
13 | "HTML",
14 | "HTML 5",
15 | "PHP",
16 | "HTML (Jinja Templates)",
17 | "HTML (Jinja2)",
18 | "HTML (Rails)",
19 | "HTML (Twig)",
20 | "HTML (Django)",
21 | "Jinja",
22 | "laravel-blade",
23 | "blade",
24 | "Handlebars",
25 | "AngularJS",
26 | "Java Server Pages (JSP)"
27 | ],
28 | "first_line": "^[ \\t]*<\\?xml"
29 | },
30 | {
31 | "mode": "html",
32 | "syntax": [
33 | "HTML",
34 | "HTML 5",
35 | "PHP",
36 | "HTML (Jinja Templates)",
37 | "HTML (Jinja2)",
38 | "HTML (Rails)",
39 | "HTML (Twig)",
40 | "HTML (Django)",
41 | "Jinja",
42 | "laravel-blade",
43 | "blade",
44 | "Handlebars",
45 | "AngularJS",
46 | "Java Server Pages (JSP)"
47 | ]
48 | },
49 | {
50 | "mode": "cfml",
51 | "syntax": ["CFML", "HTML+CFML", "ColdFusion", "ColdFusionCFC"]
52 | }
53 | ],
54 |
55 | // Style to use for matched tags
56 | "tag_style": {
57 | "xml": "tag",
58 | "xhtml": "tag",
59 | "html": "tag",
60 | "cfml": "tag"
61 | },
62 |
63 | // Scopes to exclude from tag searches.
64 | "tag_scope_exclude": {
65 | "xhtml": ["string", "comment"],
66 | "html": ["string", "comment"],
67 | "cfml": ["string", "comment"]
68 | },
69 |
70 | // Optional closing HTML tags. You can use 'null' if it does not require a pattern.
71 | "optional_tag_patterns": {
72 | "xml": null,
73 | "xhtml": null,
74 | "html": "colgroup|dd|dt|li|option|optgroup|p|td|tfoot|th|thead|tr",
75 | "cfml": "cf.+|colgroup|dd|dt|li|option|optgroup|p|td|tfoot|th|thead|tr"
76 | },
77 |
78 | // Tags that never have a closing. You can use 'null' if it does not require a pattern.
79 | "void_tag_patterns": {
80 | "xml": null,
81 | "xhtml": null,
82 | "html": "area|base|basefont|br|col|embed|frame|hr|img|input|isindex|keygen|link|meta|param|source|track|wbr",
83 | "cfml": "area|base|basefont|br|col|embed|frame|hr|img|input|isindex|keygen|link|meta|param|source|track|wbr"
84 | },
85 |
86 | // Self closing tags. Single tags that are closed like this
87 | "self_closing_tag_patterns": {
88 | "xml": "[\\w:\\.\\-]+",
89 | "xhtml": "[\\w:\\.\\-]+",
90 | "html": "circle|ellipse|line|path|polygon|polyline|rect|stop|use",
91 | "cfml": "cf.+"
92 | },
93 |
94 | // Regex for tag name. Do not use capturing groups.
95 | "tag_name":
96 | {
97 | "xml": "[\\w:\\.\\-]+",
98 | "xhtml": "[\\w:\\.\\-]+",
99 | "html": "[\\w:\\.\\-]+",
100 | "cfml": "[\\w:\\.\\-]+"
101 | },
102 |
103 | // HTML attributes. Do not use capturing groups.
104 | "attributes":
105 | {
106 | "xml": "[\\w:][-\\w\\d:.]*(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'))?",
107 | "xhtml": "[\\w:@][-\\w\\d:.]*(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'))?",
108 | "html": "[\\w:@][-\\w\\d:.]*(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s\"'`=<>]+))?",
109 | "cfml": "[\\w:][-\\w\\d:.]*(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s\"'`=<>]+))?"
110 | },
111 |
112 | // Regex for start/opening tag. Use a capturing group for tag name and self closing indicator '/' only.
113 | // Attributes and tag names are inserted using python string formatting:
114 | // the keyword 'attributes' and `tag_name` are used.
115 | "start_tag": {
116 | "xml": "<(%(tag_name)s)(?:(?:\\s+|(?<=['\"]))%(attributes)s)*\\s*(/?)>",
117 | "xhtml": "<(%(tag_name)s)(?:(?:\\s+|(?<=['\"]))%(attributes)s)*\\s*(/?)>",
118 | "html": "<(%(tag_name)s)(?:(?:\\s+|(?<=['\"]))%(attributes)s)*\\s*(/?)>",
119 | "cfml": "<(%(tag_name)s)(?:(?:(?:\\s+|(?<=['\"]))%(attributes)s)*|(?:(?<=cfif)|(?<=cfelseif))[^>]+)\\s*(/?)>"
120 | },
121 |
122 | // Regex for end/closing tag. Only use a capturing group for name.
123 | "end_tag": {
124 | "xml": "([\\w\\:\\.\\-]+)[^>]*>",
125 | "xhtml": "([\\w\\:\\.\\-]+)[^>]*>",
126 | "html": "([\\w\\:\\.\\-]+)[^>]*>",
127 | "cfml": "([\\w\\:\\.\\-]+)[^>]*>"
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/bh_wrapping.sublime-settings:
--------------------------------------------------------------------------------
1 | {
2 | "wrapping": [
3 | {
4 | "enabled": true, "language_list": [], "language_filter": "allowlist", "entries": [
5 | {"name": "<> Angle", "brackets": ["<", ">${BH_SEL}"]}
6 | ]
7 | },
8 | {
9 | "enabled": true, "language_list": ["Plain text"], "language_filter": "blocklist", "entries": [
10 | {"name": "{} Curly", "brackets": ["{", "}${BH_SEL}"], "insert_style": ["inline", "block", "indent_block"]}
11 | ]
12 | },
13 | {
14 | "enabled": true, "language_list": ["Plain text"], "language_filter": "blocklist", "entries": [
15 | {"name": "() Round", "brackets": ["(", ")${BH_SEL}"], "insert_style": ["inline", "block", "indent_block"]}
16 | ]
17 | },
18 | {
19 | "enabled": true, "language_list": ["Plain text"], "language_filter": "blocklist", "entries": [
20 | {"name": "[] Square", "brackets": ["[", "]${BH_SEL}"], "insert_style": ["inline", "block", "indent_block"]}
21 | ]
22 | },
23 | {
24 | "enabled": true, "language_list": ["Plain text"], "language_filter": "blocklist", "entries": [
25 | {"name": "'' Single Quotes", "brackets": ["'", "'${BH_SEL}"], "insert_style": ["inline"]},
26 | {"name": "\"\" Double Quotes", "brackets": ["\"", "\"${BH_SEL}"], "insert_style": ["inline"]}
27 | ]
28 | },
29 | {
30 | "enabled": true, "language_list": ["Python", "PythonImproved"], "language_filter": "allowlist", "entries": [
31 | {"name": "'''''' Triple Single Quotes", "brackets": ["'''", "'''${BH_SEL}"], "insert_style": ["inline", "block"]},
32 | {"name": "\"\"\"\"\"\" Triple Double Quotes", "brackets": ["\"\"\"", "\"\"\"${BH_SEL}"], "insert_style": ["inline", "block"]}
33 | ]
34 | },
35 | {
36 | "enabled": true, "language_list": ["HTML", "HTML 5", "XML", "PHP", "CFML", "HTML+CFML", "ColdFusion", "ColdFusionCFC"], "language_filter": "allowlist", "entries": [
37 | {"name": "HTML/XML Tag", "brackets": ["<${BH_SEL:NAME}>", "${BH_SEL:NAME}>"], "insert_style": ["inline", "block", "indent_block"]}
38 | ]
39 | },
40 | {
41 | "enabled": true, "language_list": ["Markdown", "Multimarkdown", "GithubFlavoredMarkdown", "Markdown Extended"], "language_filter": "allowlist", "entries": [
42 | {"name": "Markdown: Bold", "brackets": ["**", "**${BH_SEL}"]},
43 | {"name": "Markdown: Italic", "brackets": ["_", "_${BH_SEL}"]},
44 | {"name": "Markdown: Monospace", "brackets": ["`", "`${BH_SEL}"]}
45 | ]
46 | },
47 | {
48 | "enabled": true, "language_list": ["LaTeX", "LaTeX (TikZ)", "knitr (Rnw)"], "language_filter": "allowlist", "entries": [
49 | {"name": "LaTeX Environment", "brackets": ["\\begin{${BH_SEL:NAME}}", "\\end{${BH_SEL:NAME}}"], "insert_style": ["block"]}
50 | ]
51 | },
52 | {
53 | "enabled": true, "language_list": ["C++", "C", "C Improved"], "language_filter": "allowlist", "entries": [
54 | {"name": "C/C++: #if", "brackets": ["#if ${BH_SEL:/* CONDITION */}", "#endif"], "insert_style": ["block"]},
55 | {"name": "C/C++: #if, #else", "brackets": ["#if ${BH_SEL:/* CONDITION */}", "#else\n${BH_TAB:/* CODE */}\n#endif"], "insert_style": ["block"]},
56 | {"name": "C/C++: #if, #elif", "brackets": ["#if ${BH_SEL:/* CONDITION */}", "#elif ${BH_TAB:/* CONDITION */}\n${BH_TAB:/* CODE */}\n#endif"], "insert_style": ["block"]},
57 | {"name": "C/C++: #ifdef", "brackets": ["#ifdef ${BH_SEL:/* DEFINE */}", "#endif"], "insert_style": ["block"]},
58 | {"name": "C/C++: #ifdef, #else", "brackets": ["#ifdef ${BH_SEL:/* DEFINE */}", "#else\n${BH_TAB:/* CODE */}\n#endif"], "insert_style": ["block"]},
59 | {"name": "C/C++: #ifndef", "brackets": ["#ifndef ${BH_SEL:/* DEFINE */}", "#endif"], "insert_style": ["block"]},
60 | {"name": "C/C++: #ifndef, #else", "brackets": ["#ifndef ${BH_SEL:/* DEFINE */}", "#else\n${BH_TAB:/* CODE */}\n#endif"], "insert_style": ["block"]}
61 | ]
62 | },
63 | {
64 | "enabled": true, "language_list": ["Ruby"], "language_filter": "allowlist", "entries": [
65 | {"name": "Ruby: if", "brackets": ["if ${BH_SEL:CONDITION}", "end"], "insert_style": ["indent_block"]},
66 | {"name": "Ruby: until", "brackets": ["until ${BH_SEL:CONDITION}", "end"], "insert_style": ["indent_block"]},
67 | {"name": "Ruby: while", "brackets": ["while ${BH_SEL:CONDITION}", "end"], "insert_style": ["indent_block"]},
68 | {"name": "Ruby: do", "brackets": ["do ${BH_SEL:CONDITION}", "end"], "insert_style": ["indent_block"]},
69 | {"name": "Ruby: def", "brackets": ["def ${BH_SEL:NAME}", "end"], "insert_style": ["indent_block"]}
70 | ]
71 | },
72 | {
73 | "enabled": true, "language_list": ["CSS"], "language_filter": "allowlist", "entries": [
74 | {"name": "CSS: @group", "brackets": ["/* @group ${BH_SEL:NAME} */", "/* @end */"], "insert_style": ["block"]}
75 | ]
76 | }
77 | ]
78 | }
79 |
--------------------------------------------------------------------------------
/Example.sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | // Toggle Global Enable
3 | {
4 | "keys": ["ctrl+alt+super+e"],
5 | "command": "bh_toggle_enable"
6 | },
7 | // Search to end of file for bracket
8 | {
9 | "keys": ["ctrl+alt+super+b"],
10 | "command": "bh_key",
11 | "args":
12 | {
13 | "lines" : true
14 | }
15 | },
16 | // Go to left bracket
17 | {
18 | "keys": ["ctrl+alt+super+up"],
19 | "command": "bh_key",
20 | "args":
21 | {
22 | "no_outside_adj": null,
23 | "no_block_mode": null,
24 | "lines" : true,
25 | "plugin":
26 | {
27 | "command": "bh_modules.bracketselect",
28 | "args": {"select": "left"}
29 | }
30 | }
31 | },
32 | // Go to right bracket
33 | {
34 | "keys": ["ctrl+alt+super+down"],
35 | "command": "bh_key",
36 | "args":
37 | {
38 | "no_outside_adj": null,
39 | "no_block_mode": null,
40 | "lines" : true,
41 | "plugin":
42 | {
43 | "command": "bh_modules.bracketselect",
44 | "args": {"select": "right"}
45 | }
46 | }
47 | },
48 | // Select to left bracket
49 | {
50 | "keys": ["ctrl+alt+shift+super+up"],
51 | "command": "bh_key",
52 | "args":
53 | {
54 | "no_outside_adj": null,
55 | "no_block_mode": null,
56 | "lines" : true,
57 | "plugin":
58 | {
59 | "command": "bh_modules.bracketselect",
60 | "args": {"select": "left", "extend": true}
61 | }
62 | }
63 | },
64 | // Select to right bracket
65 | {
66 | "keys": ["ctrl+alt+shift+super+down"],
67 | "command": "bh_key",
68 | "args":
69 | {
70 | "no_outside_adj": null,
71 | "no_block_mode": null,
72 | "lines" : true,
73 | "plugin":
74 | {
75 | "command": "bh_modules.bracketselect",
76 | "args": {"select": "right", "extend": true}
77 | }
78 | }
79 | },
80 | // Remove brackets
81 | {
82 | "keys": ["ctrl+alt+super+r"],
83 | "command": "bh_remove_brackets"
84 | },
85 | // Toggle string escape mode for sub bracket search in strings
86 | {
87 | "keys": ["ctrl+alt+super+x"],
88 | "command": "bh_toggle_string_escape_mode"
89 | },
90 | // Jump to matching bracket
91 | {
92 | "keys": ["ctrl+m"],
93 | "command": "bh_key",
94 | "args": {
95 | "plugin": {
96 | "args": {"select": "right", "alternate": true},
97 | "command": "bh_modules.bracketselect"
98 | },
99 | "lines": true,
100 | "no_outside_adj": null
101 | }
102 | },
103 | // Select text between brackets
104 | {
105 | "keys": ["ctrl+alt+super+s"],
106 | "command": "bh_key",
107 | "args":
108 | {
109 | "no_outside_adj": null,
110 | "lines" : true,
111 | "plugin":
112 | {
113 | "command": "bh_modules.bracketselect"
114 | }
115 | }
116 | },
117 | // Select text including brackets
118 | {
119 | "keys": ["ctrl+alt+super+d"],
120 | "command": "bh_key",
121 | "args":
122 | {
123 | "no_outside_adj": null,
124 | "lines" : true,
125 | "plugin":
126 | {
127 | "command": "bh_modules.bracketselect",
128 | "args": {"always_include_brackets": true}
129 | }
130 | }
131 | },
132 | // Select tag name of HTML/XML tag (both opening name and closing)
133 | {
134 | "keys": ["ctrl+alt+super+t"],
135 | "command": "bh_key",
136 | "args":
137 | {
138 | "plugin":
139 | {
140 | "type": ["cfml", "html", "angle"],
141 | "command": "bh_modules.tagnameselect"
142 | }
143 | }
144 | },
145 | // Select the attribute to the right of the cursor (will wrap inside the tag)
146 | {
147 | "keys": ["ctrl+alt+super+right"],
148 | "command": "bh_key",
149 | "args":
150 | {
151 | "plugin":
152 | {
153 | "type": ["cfml", "html", "angle"],
154 | "command": "bh_modules.tagattrselect",
155 | "args": {"direction": "right"}
156 | }
157 | }
158 | },
159 | // Select the attribute to the left of the cursor (will wrap inside the tag)
160 | {
161 | "keys": ["ctrl+alt+super+left"],
162 | "command": "bh_key",
163 | "args":
164 | {
165 | "plugin":
166 | {
167 | "type": ["cfml", "html", "angle"],
168 | "command": "bh_modules.tagattrselect",
169 | "args": {"direction": "left"}
170 | }
171 | }
172 | },
173 | // Convert single quote string to double quoted string and vice versa
174 | // Will handle escaping or unescaping quotes within the string
175 | {
176 | "keys": ["ctrl+alt+super+q"],
177 | "command": "bh_key",
178 | "args":
179 | {
180 | "lines" : true,
181 | "plugin":
182 | {
183 | "type": ["single_quote", "double_quote", "py_single_quote", "py_double_quote"],
184 | "command": "bh_modules.swapquotes"
185 | }
186 | }
187 | },
188 | // Fold contents between brackets
189 | {
190 | "keys": ["ctrl+alt+super+["],
191 | "command": "bh_key",
192 | "args":
193 | {
194 | "plugin": {
195 | "command" : "bh_modules.foldbracket"
196 | }
197 | }
198 | },
199 | // Swap brackets with another type
200 | {
201 | "keys": ["ctrl+alt+super+e"],
202 | "command": "swap_brackets"
203 | },
204 | // Surround selection with brackets from quick panel
205 | {
206 | "keys": ["ctrl+alt+super+w"],
207 | "command": "wrap_brackets"
208 | },
209 | // Toggle high visibility mode
210 | {
211 | "keys": ["ctrl+alt+super+v"],
212 | "command": "bh_toggle_high_visibility"
213 | }
214 | ]
215 |
--------------------------------------------------------------------------------
/docs/src/markdown/usage.md:
--------------------------------------------------------------------------------
1 | # Basic Usage
2 |
3 | ## Overview
4 |
5 | Out of the box, BH will highlight brackets (or defined brackets like start and end blocks) surrounding the cursor. BH
6 | will also put opening and closing icons in the gutter of the corresponding line containing open or closing bracket.
7 |
8 | It is advised that you disable Sublime's default bracket and tag matcher in your `Preferences.sublime-settings` file or
9 | you will have matching conflicts:
10 |
11 | Sublime's bracket highlighter can clash with BH's and it advisable to disable Sublime's bracket matching visualization.
12 | If this is desired, it can be done by creating a file in your user folder with the same name as your current color
13 | scheme using the extension `.sublime-color-scheme` (even if you are overriding a `tmTheme` color scheme) and adding the
14 | following to that file:
15 |
16 | ```js
17 | {
18 | "variables": {},
19 | "globals":
20 | {
21 | "bracket_contents_options": "none",
22 | "brackets_options": "none",
23 | "tags_options": "none"
24 | },
25 | "rules": []
26 | }
27 | ```
28 |
29 | /// note | Outdated Recommendation
30 | Previously, it was recommended to disable the following features, but this also disabled bracket related features
31 | in "expand selections" and such. This is no longer recommended.
32 |
33 | ```js
34 | "match_brackets": false,
35 | "match_brackets_angle": false,
36 | "match_brackets_braces": false,
37 | "match_brackets_content": false,
38 | "match_brackets_square": false,
39 | "match_tags": false
40 | ```
41 | ///
42 |
43 | If you are using Sublime Text build 3124+, a new feature has been added which shows a popup when you mouse over a
44 | bracket that has its matching bracket pair off screen. It will show where the other bracket is located with line
45 | context and provide a link to jump to the other bracket. When mousing over a bracket in which the match could not be
46 | found, a popup explaining why this might occur will be shown and give the option to click a link which will perform a
47 | search without thresholds to see if it can find the brackets when restraints are removed.
48 |
49 | ## Built-in Supported brackets
50 |
51 | BH supports a variety of brackets out of the box; here are some examples:
52 |
53 | - round
54 | - square
55 | - curly
56 | - angle
57 | - single and double quotes
58 | - Python single, double, and triple quotes (Unicode and raw)
59 | - Django Python templates with mixed HTML, CSS, and JavaScript
60 | - JavaScript regex
61 | - Perl regex
62 | - Ruby regex
63 | - Markdown bold, italic, and code blocks
64 | - CSSedit groups
65 | - Ruby conditional statements
66 | - C/C++ compiler switches
67 | - PHP conditional keywords
68 | - PHP angle brackets ``
69 | - Erlang conditional statements
70 | - HTML, ColdFusion, XML, and various other template tags
71 | - Bash conditional and looping constructs
72 | - Fish conditional and looping constructs
73 | - Lua
74 | - Pascal
75 | - Elixir
76 |
77 | Within supported regex and strings, BH can also highlight basic sub brackets between the matched quotes: `(), [], {}`.
78 |
79 | ## General Commands
80 |
81 | BH has a couple of additional features built-in which are found in the command palette.
82 |
83 | ### Toggle Global Enable
84 |
85 | The `bh_toggle_enable` command enables and disables BH globally.
86 |
87 | ### Toggle String Bracket Escape Mode
88 |
89 | `bh_toggle_string_escape_mode` toggles BH's recognition mode of escaped sub brackets in strings and regex. The modes
90 | are *string escape* mode and *regex escape* mode.
91 |
92 | ### Find Matching Offscreen Brackets
93 |
94 | When `show_offscreen_bracket_popup` is enabled, mousing over an on screen bracket, or invoking the `bh_offscreen_popup`
95 | command, will show a popup on the screen that reveals the location of the matching offscreen bracket(s) (only available
96 | for Sublime Text 3 versions that support this). The cursor needs to be between a matching pair of brackets.
97 |
98 | ## Bracket Plugin Commands
99 |
100 | BH is also extendable via plugins and provides a number of built-in Bracket Plugins that take advantage of BH's matching
101 | to provide additional features. Most plugin features are available via the command palette. To see how to configure
102 | shortcuts, see the [`Example.sublime-keymap`][keymap] file.
103 |
104 | ### Bracket Select Plugin
105 |
106 | The Bracket Select plugin selects the content between the brackets or moves the selection to the opening or closing
107 | bracket. Behavior is slightly modified for tags. When the `extend` argument is set to `true`, the Bracket Select
108 | plugin extends the current selection to the left/right bracket when jumping to the chosen bracket.
109 |
110 | ### Swap Brackets Plugin
111 |
112 | The Swap Brackets plugin can swap the current brackets to another type of bracket. When selected, it will displays the
113 | bracket options that are allowed for the current language. Allowed brackets are defined in `bh_swapping.sublime-settings`.
114 |
115 | ### Wrap Brackets Plugin
116 |
117 | The Wrap Brackets plugin wraps selected text with a bracket pair. When selected, it will display the bracket options
118 | that are allowed for the current language. Allowed brackets are defined in `bh_wrapping.sublime-settings`.
119 |
120 | ### Bracket Remove Plugin
121 |
122 | The Bracket Remove plugin removes the surrounding brackets around the cursor.
123 |
124 | ### Fold Bracket Plugin
125 |
126 | The Fold Bracket plugin folds the content of the current surrounding brackets.
127 |
128 | ### Swap Quotes Plugin
129 |
130 | The Swaps Quotes plugin swaps the quote style of surrounding quotes from double to single or vice versa. It also
131 | handles escaping and un-escaping of sub quotes.
132 |
133 | ### Tag Plugin
134 |
135 | The Tag plugin Provides extra logic to target and highlight XML/HTML tags. To use BH's built-in HTML highlighting in
136 | your HTML-like template language of choice, add it to the list in `bh_tag.sublime_settings`.
137 |
138 | ### Tag Attribute Select Plugin
139 |
140 | The Tag Attribute plugin can cycle through the tag attributes of the selected tag.
141 |
142 | ### Tag Name Select Plugin
143 |
144 | Tag Name Select plugin selects the opening and closing tag name of the current selected tag.
145 |
146 | ## Keyboard Shortcuts
147 |
148 | BH provides no keyboard shortcuts in order to avoid shortcut conflicts, but you can view the included
149 | [`Example.sublime-keymap`][keymap] file to get an idea how to set up your own.
150 |
--------------------------------------------------------------------------------
/bh_modules/bracketselect.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | from BracketHighlighter import bh_plugin
8 | import sublime
9 |
10 | DEFAULT_TAGS = ["cfml", "html", "angle"]
11 |
12 |
13 | class SelectBracket(bh_plugin.BracketPluginCommand):
14 | """Select Bracket plugin."""
15 |
16 | def run(self, edit, name, select='', tags=None, always_include_brackets=False, alternate=False, extend=False):
17 | """
18 | Select the content between brackets.
19 |
20 | If "extend" is set to true, extend the current
21 | selection up to (and including) the left/right bracket
22 | when jumping to the left/right bracket.
23 |
24 | If "always_include_brackets" is enabled,
25 | include the brackets as well.
26 | If the content is already selected, expand to the parent.
27 | """
28 |
29 | self.tags = DEFAULT_TAGS if tags is None else tags
30 | self.current_left, self.current_right = self.selection[0].begin(), self.selection[0].end()
31 | first, last = self.left.end, self.right.begin
32 |
33 | if select == 'left':
34 | first, last = self.select_left(name, first, last, alternate, extend)
35 | elif select == 'right':
36 | first, last = self.select_right(name, first, last, alternate, extend)
37 | elif first == self.current_left and last == self.current_right or always_include_brackets:
38 | first, last = self.select_expand(first, last)
39 |
40 | self.selection = [sublime.Region(first, last)]
41 |
42 | def select_left(self, name, first, last, alternate, extend):
43 | """Select the left bracket."""
44 |
45 | if name in self.tags and self.left.size() > 1:
46 | first, last = self.left.begin + 1, self.left.begin + 1
47 | if first == self.current_left and last == self.current_right:
48 | self.refresh_match = True
49 | if alternate:
50 | first, last = self.right.begin + 1, self.right.begin + 1
51 | else:
52 | first, last = self.left.begin, self.left.begin
53 | else:
54 | if extend:
55 | anchor, cursor = self.selection[0].a, self.selection[0].b
56 |
57 | if first == cursor:
58 | self.refresh_match = True
59 | if alternate:
60 | first = anchor
61 | last = self.right.begin
62 | else:
63 | first = anchor
64 | last = self.left.begin
65 | elif first == anchor:
66 | self.refresh_match = True
67 | first, last = self.left.end, self.left.end
68 | else:
69 | self.refresh_match = True
70 | first = anchor
71 | last = self.left.end
72 | else:
73 | first, last = self.left.end, self.left.end
74 | if first == self.current_left and last == self.current_right:
75 | self.refresh_match = True
76 | if alternate:
77 | first, last = self.right.begin, self.right.begin
78 | else:
79 | first, last = self.left.begin, self.left.begin
80 | return first, last
81 |
82 | def select_right(self, name, first, last, alternate, extend):
83 | """Select the right bracket."""
84 |
85 | if self.left.end != self.right.end:
86 | if name in self.tags and self.left.size() > 1:
87 | first, last = self.right.begin + 1, self.right.begin + 1
88 | if first == self.current_left and last == self.current_right:
89 | self.refresh_match = True
90 | if alternate:
91 | first, last = self.left.begin + 1, self.left.begin + 1
92 | else:
93 | first, last = self.right.end, self.right.end
94 | else:
95 | if extend:
96 | anchor, cursor = self.selection[0].a, self.selection[0].b
97 |
98 | if last == cursor:
99 | self.refresh_match = True
100 | if alternate:
101 | first = anchor
102 | last = self.left.end
103 | else:
104 | first = anchor
105 | last = self.right.end
106 | elif last == anchor:
107 | self.refresh_match = True
108 | first, last = self.right.begin, self.right.begin
109 | else:
110 | self.refresh_match = True
111 | first = anchor
112 | last = self.right.begin
113 | else:
114 | first, last = self.right.begin, self.right.begin
115 | if first == self.current_left and last == self.current_right:
116 | self.refresh_match = True
117 | if alternate:
118 | first, last = self.left.end, self.left.end
119 | else:
120 | first, last = self.right.end, self.right.end
121 | else:
122 | # Select the first because there is no second bracket.
123 | if name in self.tags and self.left.size() > 1:
124 | first, last = self.left.begin + 1, self.left.begin + 1
125 | if first == self.current_left and last == self.current_right:
126 | self.refresh_match = True
127 | if not alternate:
128 | first, last = self.left.end, self.left.end
129 | else:
130 | first, last = self.right.end, self.right.end
131 | if first == self.current_left and last == self.current_right:
132 | self.refresh_match = True
133 | if not alternate:
134 | first, last = self.right.end, self.right.end
135 | return first, last
136 |
137 | def select_expand(self, first, last):
138 | """Expand content selection."""
139 |
140 | first, last = self.left.begin, self.right.end
141 | self.refresh_match = True
142 | return first, last
143 |
144 |
145 | def plugin():
146 | """Make plugin available."""
147 |
148 | return SelectBracket
149 |
--------------------------------------------------------------------------------
/bh_plugin.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | import sublime
8 | import sublime_plugin
9 | from os.path import normpath, join
10 | from collections import namedtuple
11 | import sys
12 | import traceback
13 | import re
14 | from .bh_logging import log
15 |
16 |
17 | class Payload(object):
18 | """Plugin payload."""
19 |
20 | status = False
21 | plugin = None
22 | args = None
23 |
24 | @classmethod
25 | def clear(cls):
26 | """Clear payload."""
27 |
28 | cls.status = False
29 | cls.plugin = None
30 | cls.args = None
31 |
32 |
33 | class BracketRegion (namedtuple('BracketRegion', ['begin', 'end'])):
34 | """Bracket regions for plugins."""
35 |
36 | def move(self, begin, end):
37 | """Move bracket region to different points."""
38 |
39 | return self._replace(begin=begin, end=end)
40 |
41 | def size(self):
42 | """Get the size of the region."""
43 |
44 | return abs(self.begin - self.end)
45 |
46 | def toregion(self):
47 | """Convert to sublime region."""
48 |
49 | return sublime.Region(self.begin, self.end)
50 |
51 |
52 | def is_bracket_region(obj):
53 | """Check if object is a `BracketRegion`."""
54 |
55 | return isinstance(obj, BracketRegion)
56 |
57 |
58 | def sublime_format_path(pth):
59 | """Format path for Sublime internally."""
60 | m = re.match(r"^([A-Za-z]{1}):(?:/|\\)(.*)", pth)
61 | if sublime.platform() == "windows" and m is not None:
62 | pth = m.group(1) + "/" + m.group(2)
63 | return pth.replace("\\", "/")
64 |
65 |
66 | def load_modules(obj, loaded):
67 | """Load bracket plugin modules."""
68 |
69 | plib = obj.get("plugin_library")
70 | if plib is None:
71 | return
72 |
73 | try:
74 | module = _import_module(plib, loaded)
75 | obj["compare"] = getattr(module, "compare", None)
76 | obj["post_match"] = getattr(module, "post_match", None)
77 | obj["validate"] = getattr(module, "validate", None)
78 | obj["highlighting"] = getattr(module, "highlighting", None)
79 | loaded.add(plib)
80 | except Exception:
81 | log("Could not load module %s\n%s" % (plib, str(traceback.format_exc())))
82 | raise
83 |
84 |
85 | def new_module(name):
86 | """Create a new module."""
87 |
88 | if sys.version_info < (3, 4):
89 | import imp
90 | return imp.new_module(name)
91 |
92 | import types
93 | return types.ModuleType(name)
94 |
95 |
96 | def _import_module(module_name, loaded=None):
97 | """
98 | Import the module.
99 |
100 | Import the module and track which modules have been loaded
101 | so we don't load already loaded modules.
102 | """
103 |
104 | # Pull in built-in and custom plugin directory
105 | if module_name.startswith("bh_modules."):
106 | path_name = join("Packages", "BracketHighlighter", normpath(module_name.replace('.', '/')))
107 | else:
108 | path_name = join("Packages", normpath(module_name.replace('.', '/')))
109 | path_name += ".py"
110 | if loaded is not None and module_name in loaded:
111 | module = sys.modules[module_name]
112 | else:
113 | module = new_module(module_name)
114 | sys.modules[module_name] = module
115 | exec(
116 | compile(
117 | sublime.load_resource(sublime_format_path(path_name)),
118 | module_name,
119 | 'exec'
120 | ),
121 | sys.modules[module_name].__dict__
122 | )
123 | return module
124 |
125 |
126 | def import_module(module, attribute=None):
127 | """Import module or module attribute."""
128 |
129 | mod = _import_module(module)
130 | return getattr(mod, attribute) if attribute is not None else mod
131 |
132 |
133 | class BracketPluginRunCommand(sublime_plugin.TextCommand):
134 | """Sublime run command to run BH plugins."""
135 |
136 | def run(self, edit):
137 | """Run the plugin."""
138 |
139 | try:
140 | Payload.args["edit"] = edit
141 | Payload.plugin.run(**Payload.args)
142 | Payload.status = True
143 | except Exception:
144 | print("BracketHighlighter: Plugin Run Error:\n%s" % str(traceback.format_exc()))
145 |
146 |
147 | class BracketPlugin(object):
148 | """Class for preparing and running plugins."""
149 |
150 | def __init__(self, plugin, loaded):
151 | """Load plugin module."""
152 |
153 | self.enabled = False
154 | self.args = plugin['args'] if ("args" in plugin) else {}
155 | self.plugin = None
156 | if 'command' in plugin:
157 | plib = plugin['command']
158 | try:
159 | module = _import_module(plib, loaded)
160 | self.plugin = getattr(module, 'plugin')()
161 | loaded.add(plib)
162 | self.enabled = True
163 | except Exception:
164 | print('BracketHighlighter: Load Plugin Error: %s\n%s' % (plugin['command'], traceback.format_exc()))
165 |
166 | def is_enabled(self):
167 | """Check if plugin is enabled."""
168 |
169 | return self.enabled
170 |
171 | def run_command(self, view, name, left, right, selection):
172 | """Load arguments into plugin and run."""
173 |
174 | nobracket = False
175 | refresh_match = False
176 | Payload.status = False
177 | Payload.plugin = self.plugin()
178 | setattr(Payload.plugin, "left", left)
179 | setattr(Payload.plugin, "right", right)
180 | setattr(Payload.plugin, "view", view)
181 | setattr(Payload.plugin, "selection", selection)
182 | setattr(Payload.plugin, "nobracket", False)
183 | setattr(Payload.plugin, "refresh_match", False)
184 | self.args["edit"] = None
185 | self.args["name"] = name
186 | Payload.args = self.args
187 |
188 | # Call a `TextCommand` to run the plugin so it can feed in the `Edit` object
189 | view.run_command("bracket_plugin_run")
190 |
191 | if Payload.status:
192 | left = Payload.plugin.left
193 | right = Payload.plugin.right
194 | selection = Payload.plugin.selection
195 | nobracket = Payload.plugin.nobracket
196 | refresh_match = Payload.plugin.refresh_match
197 | Payload.clear()
198 |
199 | return left, right, selection, nobracket, refresh_match
200 |
201 |
202 | class BracketPluginCommand(object):
203 | """Bracket Plugin base class."""
204 |
205 | def run(self, bracket, content, selection):
206 | """Run the plugin class."""
207 |
208 | pass
209 |
--------------------------------------------------------------------------------
/support.py:
--------------------------------------------------------------------------------
1 | """Support command."""
2 | import sublime
3 | import sublime_plugin
4 | import textwrap
5 | import webbrowser
6 | import re
7 |
8 | __version__ = "2.33.0"
9 | __pc_name__ = 'BracketHighlighter'
10 |
11 | CSS = '''
12 | div.bracket-highlighter { padding: 10px; margin: 0; }
13 | .bracket-highlighter h1, .bracket-highlighter h2, .bracket-highlighter h3,
14 | .bracket-highlighter h4, .bracket-highlighter h5, .bracket-highlighter h6 {
15 | {{'.string'|css}}
16 | }
17 | .bracket-highlighter blockquote { {{'.comment'|css}} }
18 | .bracket-highlighter a { text-decoration: none; }
19 | '''
20 |
21 | frontmatter = {
22 | "markdown_extensions": [
23 | "markdown.extensions.admonition",
24 | "markdown.extensions.attr_list",
25 | "markdown.extensions.def_list",
26 | "markdown.extensions.nl2br",
27 | # Smart quotes always have corner cases that annoy me, so don't bother with them.
28 | {"markdown.extensions.smarty": {"smart_quotes": False}},
29 | "pymdownx.betterem",
30 | {
31 | "pymdownx.magiclink": {
32 | "repo_url_shortener": True,
33 | "repo_url_shorthand": True,
34 | "user": "facelessuser",
35 | "repo": "BracketHighlighter"
36 | }
37 | },
38 | "markdown.extensions.md_in_html",
39 | "pymdownx.keys",
40 | {"pymdownx.escapeall": {"hardbreak": True, "nbsp": True}},
41 | # Sublime doesn't support superscript, so no ordinal numbers
42 | {"pymdownx.smartsymbols": {"ordinal_numbers": False}}
43 | ]
44 | }
45 |
46 |
47 | def list2string(obj):
48 | """Convert list to string."""
49 |
50 | return '.'.join([str(x) for x in obj])
51 |
52 |
53 | def format_version(module, attr, call=False):
54 | """Format the version."""
55 |
56 | try:
57 | if call:
58 | version = getattr(module, attr)()
59 | else:
60 | version = getattr(module, attr)
61 | except Exception as e:
62 | print(e)
63 | version = 'Version could not be acquired!'
64 |
65 | if not isinstance(version, str):
66 | version = list2string(version)
67 | return version
68 |
69 |
70 | def is_installed_by_package_control():
71 | """Check if installed by package control."""
72 |
73 | settings = sublime.load_settings('Package Control.sublime-settings')
74 | return str(__pc_name__ in set(settings.get('installed_packages', [])))
75 |
76 |
77 | class BracketHighlighterSupportInfoCommand(sublime_plugin.ApplicationCommand):
78 | """Support info."""
79 |
80 | def run(self):
81 | """Run command."""
82 |
83 | info = {}
84 |
85 | info["platform"] = sublime.platform()
86 | info["version"] = sublime.version()
87 | info["arch"] = sublime.arch()
88 | info["plugin_version"] = __version__
89 | info["pc_install"] = is_installed_by_package_control()
90 | try:
91 | import mdpopups
92 | info["mdpopups_version"] = format_version(mdpopups, 'version', call=True)
93 | except Exception:
94 | info["mdpopups_version"] = 'Version could not be acquired!'
95 |
96 | try:
97 | import backrefs
98 | info["backrefs_version"] = format_version(backrefs, '__version__')
99 | except Exception:
100 | info["backrefs_version"] = 'Version could not be acquired!'
101 |
102 | msg = textwrap.dedent(
103 | """\
104 | - ST ver.: %(version)s
105 | - Platform: %(platform)s
106 | - Arch: %(arch)s
107 | - Plugin ver.: %(plugin_version)s
108 | - Install via PC: %(pc_install)s
109 | - mdpopups ver.: %(mdpopups_version)s
110 | - backrefs ver.: %(backrefs_version)s
111 | """ % info
112 | )
113 |
114 | sublime.message_dialog(msg + '\nInfo has been copied to the clipboard.')
115 | sublime.set_clipboard(msg)
116 |
117 |
118 | class BracketHighlighterOpenSiteCommand(sublime_plugin.ApplicationCommand):
119 | """Open site links."""
120 |
121 | def run(self, url):
122 | """Open the URL."""
123 |
124 | webbrowser.open_new_tab(url)
125 |
126 |
127 | class BracketHighlighterDocCommand(sublime_plugin.WindowCommand):
128 | """Open doc page."""
129 |
130 | re_pkgs = re.compile(r'^Packages')
131 |
132 | def on_navigate(self, href):
133 | """Handle links."""
134 |
135 | if href.startswith('sub://Packages'):
136 | sublime.run_command('open_file', {"file": self.re_pkgs.sub('${packages}', href[6:])})
137 | else:
138 | webbrowser.open_new_tab(href)
139 |
140 | def run(self, page):
141 | """Open page."""
142 |
143 | try:
144 | import mdpopups
145 | has_phantom_support = (mdpopups.version() >= (1, 10, 0))
146 | fmatter = mdpopups.format_frontmatter(frontmatter)
147 | except Exception:
148 | fmatter = ''
149 | has_phantom_support = False
150 |
151 | if not has_phantom_support:
152 | sublime.run_command('open_file', {"file": page})
153 | else:
154 | text = sublime.load_resource(page.replace('${packages}', 'Packages'))
155 | view = self.window.new_file()
156 | view.set_name('BracketHighlighter - Quick Start')
157 | view.settings().set('gutter', False)
158 | view.settings().set('word_wrap', False)
159 | if has_phantom_support:
160 | mdpopups.add_phantom(
161 | view,
162 | 'quickstart',
163 | sublime.Region(0),
164 | fmatter + text,
165 | sublime.LAYOUT_INLINE,
166 | css=CSS,
167 | wrapper_class="bracket-highlighter",
168 | on_navigate=self.on_navigate
169 | )
170 | else:
171 | view.run_command('insert', {"characters": text})
172 | view.set_read_only(True)
173 | view.set_scratch(True)
174 |
175 |
176 | class BracketHighlighterChangesCommand(sublime_plugin.WindowCommand):
177 | """Changelog command."""
178 |
179 | def run(self):
180 | """Show the changelog in a new view."""
181 | try:
182 | import mdpopups
183 | has_phantom_support = (mdpopups.version() >= (1, 10, 0))
184 | fmatter = mdpopups.format_frontmatter(frontmatter)
185 | except Exception as e:
186 | print(e)
187 | fmatter = ''
188 | has_phantom_support = False
189 |
190 | text = sublime.load_resource('Packages/BracketHighlighter/CHANGES.md')
191 | view = self.window.new_file()
192 | view.set_name('BracketHighlighter - Changelog')
193 | view.settings().set('gutter', False)
194 | view.settings().set('word_wrap', False)
195 | if has_phantom_support:
196 | mdpopups.add_phantom(
197 | view,
198 | 'changelog',
199 | sublime.Region(0),
200 | fmatter + text,
201 | sublime.LAYOUT_INLINE,
202 | wrapper_class="bracket-highlighter",
203 | css=CSS,
204 | on_navigate=self.on_navigate
205 | )
206 | else:
207 | view.run_command('insert', {"characters": text})
208 | view.set_read_only(True)
209 | view.set_scratch(True)
210 |
211 | def on_navigate(self, href):
212 | """Open links."""
213 | webbrowser.open_new_tab(href)
214 |
--------------------------------------------------------------------------------
/docs/src/markdown/about/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing & Support
2 |
3 | ## Overview
4 |
5 | Sublime\ Versions | Description
6 | ----------------- | -----------
7 | ST2 | Supported on a separate branch, but not actively. Any further fixes or enhancements must come from the community. Issues for versions less than ST3 will not be addressed moving forward by me. Pull requests are welcome for back-porting features, enhancements, or fixes to the old branch, but the content of the pull **must** already exist on the main, actively developed branch. I will not allow an older branch to exceed the main branch in regards to functionality. |
8 | ST3 | Fully supported and actively maintained.
9 |
10 | Contribution from the community is encouraged and can be done in a variety of ways:
11 |
12 | - Bug reports.
13 | - Reviewing code.
14 | - Code patches via pull requests.
15 | - Documentation improvements via pull requests.
16 |
17 | /// warning | Bracket Rules are Supported by the Community
18 | The most common requested enhancement for BracketHighlighter is for new rules to add support for \{insert your
19 | favorite language here}. I, like you, am proficient in very specific languages. I probably don't use your favorite
20 | language or there would already be a support for it. I don't have time to learn the nuances of your language. For
21 | these reasons, support for new language brackets **requires** pull requests from the community.
22 |
23 | Though I will not personally implement rules for your favorite language, I am more than willing to offer suggestions
24 | and guidance to help those who may struggle to create rules for their specific language of interest.
25 | ///
26 |
27 | ## Become a Sponsor :octicons-heart-fill-16:{: .heart-throb}
28 |
29 | Open source projects take time and money. Help support the project by becoming a sponsor. You can add your support at
30 | any tier you feel comfortable with. No amount is too little. We also accept one time contributions via PayPal.
31 |
32 | [:octicons-mark-github-16: GitHub Sponsors](https://github.com/sponsors/facelessuser){: .md-button .md-button--primary }
33 | [:fontawesome-brands-paypal: PayPal](https://www.paypal.me/facelessuser){ .md-button}
34 |
35 | ## Bug Reports
36 |
37 | 1. Please **read the documentation** and **search the issue tracker** to try to find the answer to your question
38 | **before** posting an issue.
39 |
40 | 2. When an issue is created, a [template][template] will be shown, please fill out the appropriate sections. If the
41 | template is not followed, the issue will be marked `Invalid` and closed.
42 |
43 | 3. When creating an issue on the repository, please provide as much info as possible. The template will reiterate what
44 | is mentioned here as a reminder:
45 |
46 | - Provide environment information by running `Preferences->Package Settings->BracketHighlighter->Support Info`. The
47 | information will be copied to the clipboard; paste the info in issue.
48 | - Errors in console.
49 | - Detailed description of the problem.
50 | - Examples for reproducing the error. You can post pictures, but if specific text or code is required to reproduce
51 | the issue, please provide the text in a plain text format as well for easy copy/paste.
52 | - Provide links to 3rd party syntax highlighting package you are using if applicable.
53 |
54 | The more info provided the greater the chance someone will take the time to answer, implement, or fix the issue.
55 |
56 | 4. Be prepared to answer questions and provide additional information if required. Issues in which the creator refuses
57 | to respond to follow up questions will be marked as stale and closed.
58 |
59 | ## Reviewing Code
60 |
61 | Take part in reviewing pull requests and/or reviewing direct commits. Make suggestions to improve the code and discuss
62 | solutions to overcome weakness in the algorithm.
63 |
64 | ## Pull Requests
65 |
66 | Pull requests are welcome, and if you plan on contributing directly to the code, there are a couple of things to be
67 | mindful of.
68 |
69 | 1. Please describe the change in as much detail as possible so I can understand what is being added or modified.
70 |
71 | 2. If you are solving a bug that does not already have an issue, please describe the bug in detail and provide info on
72 | how to reproduce if applicable (this is good for me and others to reference later when verifying the issue has been
73 | resolved).
74 |
75 | 3. Please reference and link related open bugs or feature requests in this pull if applicable.
76 |
77 | 4. Make sure you've documented or updated the existing documentation if introducing a new feature or modifying the
78 | behavior of an existing feature that a user needs to be aware of. I will not accept new features or changes to
79 | existing features if you have not provided documentation describing the feature.
80 |
81 | Continuous integration tests on are run on all pull requests and commits via Travis CI. When making a pull request, the
82 | tests will automatically be run, and the request must pass to be accepted. You can (and should) run these tests before
83 | pull requesting. If it is not possible to run these tests locally, they will be run when the pull request is made, but
84 | it is strongly suggested that requesters make an effort to verify before requesting to allow for a quick, smooth merge.
85 |
86 | ### Running Validation Tests
87 |
88 | /// tip | Tip
89 | If you are running Sublime on a OSX or Linux/Unix system, you run all tests by by running the shell script (assuming
90 | you have installed your environment fulfills all requirements below):
91 |
92 | ```
93 | chmod +x run_tests.sh
94 | ./run_tests.sh
95 | ```
96 | ///
97 |
98 | There are a couple of dependencies that must be present before running the tests.
99 |
100 | 1. As ST3 is the only current, actively supported version, Python 3.3 must be used to validate the tests.
101 |
102 | 2. Unit tests are run with pytest (@pytest-dev/pytest). You can install pytest via:
103 |
104 | ```
105 | pip install pytest
106 | ```
107 |
108 | The tests should be run from the root folder of the plugin by using the following command:
109 |
110 | ```
111 | pytest .
112 | ```
113 |
114 | 3. Linting is performed on the entire project with the following modules:
115 |
116 | - @gitlab:pycqa/flake8
117 | - @gitlab:pycqa/flake8-docstrings
118 | - @ebeweber/flake8-mutable
119 | - @gforcada/flake8-builtins
120 | - @gitlab:pycqa/pep8-naming
121 |
122 | These can be installed via:
123 |
124 | ```
125 | pip install flake8
126 | pip install flake8-docstrings
127 | pip install flake8-mutable
128 | pip install flake8-builtins
129 | pip install pep8-naming
130 | ```
131 |
132 | Linting is performed with the following command:
133 |
134 | ```
135 | flake8 .
136 | ```
137 |
138 | ## Documentation Improvements
139 |
140 | A ton of time has been spent not only creating and supporting this plugin, but also spent making this documentation. If
141 | you feel it is still lacking, show your appreciation for the plugin by helping to improve the documentation. Help with
142 | documentation is always appreciated and can be done via pull requests. There shouldn't be any need to run validation
143 | tests if only updating documentation.
144 |
145 | You don't have to render the docs locally before pull requesting, but if you wish to, I currently use a combination of
146 | @mkdocs/mkdocs, the @squidfunk/mkdocs-material, and @facelessuser/pymdown-extensions to render the docs. You can
147 | preview the docs if you install these two packages. The command for previewing the docs is `mkdocs serve` from the root
148 | directory. You can then view the documents at `localhost:8000`.
149 |
--------------------------------------------------------------------------------
/docs/src/markdown/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | ## Package Control
4 |
5 | The recommended way to install BracketHighlighter is via [Package Control][package-control]. Package Control will
6 | install the correct branch on your system and keep it up to date.
7 |
8 | ---
9 |
10 | 1. Ensure Package Control is installed. Instructions are found [here][package-control-install].
11 |
12 | 2. In Sublime Text, press ++ctrl+shift+p++ (Win, Linux) or ++cmd+shift+p++ (OSX) to bring up the quick panel and start
13 | typing `Package Control: Install Package`. Select the command and it will show a list of installable plugins.
14 |
15 | 3. Start typing `BracketHighlighter`; when you see it, select it.
16 |
17 | 4. Restart to be sure everything is loaded proper.
18 |
19 | 5. Enjoy!
20 |
21 | ## Manual Installation
22 |
23 | /// warning | Warning
24 | This is not the recommended way to install BracketHighlighter for the average user. Installing this way **will
25 | not** get automatically updated.
26 |
27 | If you are forking for a pull request, you should **just** clone BH and run Package Control's `Satisfy Dependency`
28 | command to get all the dependencies.
29 | ///
30 |
31 | For those who want to install BH without package control, here are the steps. It is understood that some people, for
32 | what ever reason, will prefer manual install and may even have legitimate reasons to do so. When going this route, you
33 | will have to keep all the packages updated yourself.
34 |
35 | ---
36 |
37 | 1. Download the latest releases of the following dependencies and unpack or git clone in the `Packages` folder as shown
38 | below:
39 |
40 | - https://bitbucket.org/teddy_beer_maniac/sublime-text-dependency-markupsafe -> `markupsafe`
41 | - https://bitbucket.org/teddy_beer_maniac/sublime-text-dependency-jinja2 -> `python-jinja2`
42 | - https://github.com/packagecontrol/pygments -> `pygments`
43 | - https://github.com/facelessuser/sublime-markdown-popups -> `mdpopups`
44 | - https://github.com/facelessuser/sublime-markdown -> `python-markdown`
45 | - https://github.com/facelessuser/sublime-backrefs -> `backrefs`
46 |
47 | 2. Download and unpack, or git clone, the latest BracketHighlighter release and unpack as `BracketHighlighter`:
48 |
49 | - https://github.com/facelessuser/BracketHighlighter -> BracketHighlighter
50 |
51 | 3. Create a folder under `Packages` called `00-dependencies` and under that folder create a file called
52 | `00-dependencies.py`:
53 |
54 | Copy the following code to `00-dependencies.py` (this code was taken from Package Control):
55 |
56 | ```{.py3 .md-max-height}
57 | import sys
58 | import os
59 | from os.path import dirname
60 |
61 | if os.name == 'nt':
62 | from ctypes import windll, create_unicode_buffer
63 |
64 | import sublime
65 |
66 |
67 | if sys.version_info >= (3,):
68 | def decode(path):
69 | return path
70 |
71 | def encode(path):
72 | return path
73 |
74 | if os.path.basename(__file__) == 'sys_path.py':
75 | pc_package_path = dirname(dirname(__file__))
76 | # When loaded as a .sublime-package file, the filename ends up being
77 | # Package Control.sublime-package/Package Control.package_control.sys_path
78 | else:
79 | pc_package_path = dirname(__file__)
80 | st_version = u'3'
81 |
82 | else:
83 | def decode(path):
84 | if not isinstance(path, unicode):
85 | path = path.decode(sys.getfilesystemencoding())
86 | return path
87 |
88 | def encode(path):
89 | if isinstance(path, unicode):
90 | path = path.encode(sys.getfilesystemencoding())
91 | return path
92 |
93 | pc_package_path = decode(os.getcwd())
94 | st_version = u'2'
95 |
96 |
97 | st_dir = dirname(dirname(pc_package_path))
98 |
99 |
100 | def add(path, first=False):
101 | """
102 | Adds an entry to the beginning of sys.path, working around the fact that
103 | Python 2.6 can't import from non-ASCII paths on Windows.
104 |
105 | :param path:
106 | A unicode string of a folder, zip file or sublime-package file to
107 | add to the path
108 |
109 | :param first:
110 | If the path should be added at the beginning
111 | """
112 |
113 | if os.name == 'nt':
114 | # Work around unicode path import issue on Windows with Python 2.6
115 | buf = create_unicode_buffer(512)
116 | if windll.kernel32.GetShortPathNameW(path, buf, len(buf)):
117 | path = buf.value
118 |
119 | enc_path = encode(path)
120 |
121 | if os.path.exists(enc_path):
122 | if first:
123 | try:
124 | sys.path.remove(enc_path)
125 | except (ValueError):
126 | pass
127 | sys.path.insert(0, enc_path)
128 | elif enc_path not in sys.path:
129 | sys.path.append(enc_path)
130 |
131 |
132 | def remove(path):
133 | """
134 | Removes a path from sys.path if it is present
135 |
136 | :param path:
137 | A unicode string of a folder, zip file or sublime-package file
138 | """
139 |
140 | try:
141 | sys.path.remove(encode(path))
142 | except (ValueError):
143 | pass
144 |
145 | if os.name == 'nt':
146 | buf = create_unicode_buffer(512)
147 | if windll.kernel32.GetShortPathNameW(path, buf, len(buf)):
148 | path = buf.value
149 | try:
150 | sys.path.remove(encode(path))
151 | except (ValueError):
152 | pass
153 |
154 |
155 | def generate_dependency_paths(name):
156 | """
157 | Accepts a dependency name and generates a dict containing the three standard
158 | import paths that are valid for the current machine.
159 |
160 | :param name:
161 | A unicode string name of the dependency
162 |
163 | :return:
164 | A dict with the following keys:
165 | - 'ver'
166 | - 'plat'
167 | - 'arch'
168 | """
169 |
170 | packages_dir = os.path.join(st_dir, u'Packages')
171 | dependency_dir = os.path.join(packages_dir, name)
172 |
173 | ver = u'st%s' % st_version
174 | plat = sublime.platform()
175 | arch = sublime.arch()
176 |
177 | return {
178 | 'all': os.path.join(dependency_dir, 'all'),
179 | 'ver': os.path.join(dependency_dir, ver),
180 | 'plat': os.path.join(dependency_dir, u'%s_%s' % (ver, plat)),
181 | 'arch': os.path.join(dependency_dir, u'%s_%s_%s' % (ver, plat, arch))
182 | }
183 |
184 |
185 | def add_dependency(name, first=False):
186 | """
187 | Accepts a dependency name and automatically adds the appropriate path
188 | to sys.path, if the dependency has a path for the current platform and
189 | architecture.
190 |
191 | :param name:
192 | A unicode string name of the dependency
193 |
194 | :param first:
195 | If the path should be added to the beginning of the list
196 | """
197 |
198 | dep_paths = generate_dependency_paths(name)
199 |
200 | for path in dep_paths.values():
201 | if os.path.exists(encode(path)):
202 | add(path, first=first)
203 |
204 |
205 | add_dependency('pygments')
206 | add_dependency('backrefs')
207 | add_dependency('markupsafe')
208 | add_dependency('python-markdown')
209 | add_dependency('python-jinja2')
210 | add_dependency('mdpopups')
211 |
212 | ```
213 |
214 | 4. Restart and enjoy.
215 |
216 |
217 | ## Git Cloning
218 |
219 | 1. Quit Sublime Text.
220 |
221 | 2. Open a terminal and enter the following. For dependencies, replace the URL with the appropriate URL, and the
222 | appropriate folder to check it out to:
223 |
224 | ```
225 | cd /path/to/Sublime Text 3/Packages
226 | git clone https://github.com/facelessuser/BracketHighlighter.git BracketHighlighter
227 | ```
228 |
229 | 3. Restart Sublime Text.
230 |
--------------------------------------------------------------------------------
/Default.sublime-commands:
--------------------------------------------------------------------------------
1 | [
2 | // Debug
3 | {
4 | "caption": "BracketHighlighter: Enable Debug Mode",
5 | "command": "bh_debug",
6 | "args": {"set_value": true}
7 | },
8 | {
9 | "caption": "BracketHighlighter: Disable Debug Mode",
10 | "command": "bh_debug",
11 | "args": {"set_value": false}
12 | },
13 | {
14 | "caption": "BracketHighlighter: (Debug) Filter Rules by Key",
15 | "command": "bh_debug_rule",
16 | "args": {"filter_key": true}
17 | },
18 | {
19 | "caption": "BracketHighlighter: (Debug) Show Merged Rules",
20 | "command": "bh_debug_rule"
21 | },
22 | // Toggle Global Enable
23 | {
24 | "caption": "BracketHighlighter: Toggle Global Enable",
25 | "command": "bh_toggle_enable"
26 | },
27 | // Search to end of file for bracket
28 | {
29 | "caption": "BracketHighlighter: Match Brackets (ignore threshold)",
30 | "command": "bh_async_key",
31 | "args": {"lines" : true}
32 | },
33 | // Remove brackets
34 | {
35 | "caption": "BracketHighlighter: Remove Brackets",
36 | "command": "bh_remove_brackets"
37 | },
38 | // Go to left bracket
39 | {
40 | "caption": "BracketHighlighter: Jump to Left Bracket",
41 | "command": "bh_async_key",
42 | "args":
43 | {
44 | "no_outside_adj": null,
45 | "no_block_mode": null,
46 | "lines" : true,
47 | "plugin":
48 | {
49 | "type": ["__all__"],
50 | "command": "bh_modules.bracketselect",
51 | "args": {"select": "left"}
52 | }
53 | }
54 | },
55 | // Go to right bracket
56 | {
57 | "caption": "BracketHighlighter: Jump to Right Bracket",
58 | "command": "bh_async_key",
59 | "args":
60 | {
61 | "no_outside_adj": null,
62 | "no_block_mode": null,
63 | "lines" : true,
64 | "plugin":
65 | {
66 | "type": ["__all__"],
67 | "command": "bh_modules.bracketselect",
68 | "args": {"select": "right"}
69 | }
70 | }
71 | },
72 | // Select text between brackets
73 | {
74 | "caption": "BracketHighlighter: Select Bracket Content",
75 | "command": "bh_async_key",
76 | "args":
77 | {
78 | "no_outside_adj": null,
79 | "lines" : true,
80 | "plugin":
81 | {
82 | "type": ["__all__"],
83 | "command": "bh_modules.bracketselect"
84 | }
85 | }
86 | },
87 | // Select text including brackets
88 | {
89 | "caption": "BracketHighlighter: Select Bracket Content with Brackets",
90 | "command": "bh_async_key",
91 | "args":
92 | {
93 | "no_outside_adj": null,
94 | "lines" : true,
95 | "plugin":
96 | {
97 | "type": ["__all__"],
98 | "command": "bh_modules.bracketselect",
99 | "args": {"always_include_brackets": true}
100 | }
101 | }
102 | },
103 | // Fold contents between brackets
104 | {
105 | "caption": "BracketHighlighter: Fold Bracket Content",
106 | "command": "bh_async_key",
107 | "args":
108 | {
109 | "plugin": {
110 | "type": ["__all__"],
111 | "command" : "bh_modules.foldbracket"
112 | }
113 | }
114 | },
115 | { "caption": "-" },
116 | // Toggle between string and regex escape mode for string brackets
117 | {
118 | "caption": "BracketHighlighter: Toggle String Bracket Escape Mode",
119 | "command": "bh_toggle_string_escape_mode"
120 | },
121 | // Toggle high visibility mode
122 | {
123 | "caption": "BracketHighlighter: Toggle High Visibility Mode",
124 | "command": "bh_toggle_high_visibility"
125 | },
126 | { "caption": "-" },
127 | // Select tag name of HTML/XML tag (both opening name and closing)
128 | {
129 | "caption": "BracketHighlighter: Select Tag Name (closing and opening)",
130 | "command": "bh_async_key",
131 | "args":
132 | {
133 | "plugin":
134 | {
135 | "type": ["cfml", "html", "angle"],
136 | "command": "bh_modules.tagnameselect"
137 | }
138 | }
139 | },
140 | // Select the attribute to the right of the cursor (will wrap inside the tag)
141 | {
142 | "caption": "BracketHighlighter: Select Next Attribute (right)",
143 | "command": "bh_async_key",
144 | "args":
145 | {
146 | "plugin":
147 | {
148 | "type": ["cfml", "html", "angle"],
149 | "command": "bh_modules.tagattrselect",
150 | "args": {"direction": "right"}
151 | }
152 | }
153 | },
154 | // Select the attribute to the left of the cursor (will wrap inside the tag)
155 | {
156 | "caption": "BracketHighlighter: Select Next Attribute (left)",
157 | "command": "bh_async_key",
158 | "args":
159 | {
160 | "plugin":
161 | {
162 | "type": ["cfml", "html", "angle"],
163 | "command": "bh_modules.tagattrselect",
164 | "args": {"direction": "left"}
165 | }
166 | }
167 | },
168 | // Convert single quote string to double quoted string and vice versa
169 | // Will handle escaping or unescaping quotes within the string
170 | {
171 | "caption": "BracketHighlighter: Swap Quotes",
172 | "command": "bh_async_key",
173 | "args":
174 | {
175 | "lines" : true,
176 | "plugin":
177 | {
178 | "type": ["single_quote", "double_quote", "py_single_quote", "py_double_quote"],
179 | "command": "bh_modules.swapquotes"
180 | }
181 | }
182 | },
183 | // Swap Brackets
184 | {
185 | "caption": "BracketHighlighter: Swap Brackets",
186 | "command": "swap_brackets",
187 | "args": {"async": true}
188 | },
189 | // Surround selection with brackets from quick panel
190 | {
191 | "caption": "BracketHighlighter: Wrap Selections with Brackets",
192 | "command": "wrap_brackets"
193 | },
194 | {
195 | "caption": "BracketHighlighter: Find Matching Offscreen Bracket",
196 | "command": "bh_offscreen_popup"
197 | },
198 | {
199 | "caption": "BracketHighlighter: Settings",
200 | "command": "edit_settings",
201 | "args": {
202 | "base_file": "${packages}/BracketHighlighter/bh_core.sublime-settings",
203 | "default": "{\n$0\n}\n"
204 | }
205 | },
206 | {
207 | "caption": "BracketHighlighter: Tag Settings",
208 | "command": "edit_settings",
209 | "args": {
210 | "base_file": "${packages}/BracketHighlighter/bh_tag.sublime-settings",
211 | "default": "{\n$0\n}\n"
212 | }
213 | },
214 | {
215 | "caption": "BracketHighlighter: Wrap Settings",
216 | "command": "edit_settings",
217 | "args": {
218 | "base_file": "${packages}/BracketHighlighter/bh_wrapping.sublime-settings",
219 | "default": "{\n$0\n}\n"
220 | }
221 | },
222 | {
223 | "caption": "BracketHighlighter: Swap Settings",
224 | "command": "edit_settings",
225 | "args": {
226 | "base_file": "${packages}/BracketHighlighter/bh_swapping.sublime-settings",
227 | "default": "{\n$0\n}\n"
228 | }
229 | },
230 | {
231 | "caption": "BracketHighlighter: Documentation",
232 | "command": "bracket_highlighter_open_site",
233 | "args": {
234 | "url": "http://facelessuser.github.io/BracketHighlighter/"
235 | }
236 | },
237 | {
238 | "caption": "BracketHighlighter: Quick Start Guide",
239 | "command": "bracket_highlighter_doc",
240 | "args": {
241 | "page": "${packages}/BracketHighlighter/quickstart.md"
242 | }
243 | }
244 | ]
245 |
--------------------------------------------------------------------------------
/tests/validate_json_format.py:
--------------------------------------------------------------------------------
1 | """
2 | Validate JSON format.
3 |
4 | Licensed under MIT
5 | Copyright (c) 2012-2015 Isaac Muse
6 | """
7 | import re
8 | import codecs
9 | import json
10 |
11 | RE_LINE_PRESERVE = re.compile(r"\r?\n", re.MULTILINE)
12 | RE_COMMENT = re.compile(
13 | r'''(?x)
14 | (?P
15 | /\*[^*]*\*+(?:[^/*][^*]*\*+)*/ # multi-line comments
16 | | [ \t]*//(?:[^\r\n])* # single line comments
17 | )
18 | | (?P
19 | "(?:\\.|[^"\\])*" # double quotes
20 | | .[^/"']* # everything else
21 | )
22 | ''',
23 | re.DOTALL
24 | )
25 | RE_TRAILING_COMMA = re.compile(
26 | r'''(?x)
27 | (
28 | (?P
29 | , # trailing comma
30 | (?P[\s\r\n]*) # white space
31 | (?P\]) # bracket
32 | )
33 | | (?P
34 | , # trailing comma
35 | (?P[\s\r\n]*) # white space
36 | (?P\}) # bracket
37 | )
38 | )
39 | | (?P
40 | "(?:\\.|[^"\\])*" # double quoted string
41 | | .[^,"']* # everything else
42 | )
43 | ''',
44 | re.DOTALL
45 | )
46 | RE_LINE_INDENT_TAB = re.compile(r'^(?:(\t+)?(?:(/\*)|[^ \t\r\n])[^\r\n]*)?\r?\n$')
47 | RE_LINE_INDENT_SPACE = re.compile(r'^(?:((?: {4})+)?(?:(/\*)|[^ \t\r\n])[^\r\n]*)?\r?\n$')
48 | RE_TRAILING_SPACES = re.compile(r'^.*?[ \t]+\r?\n?$')
49 | RE_COMMENT_END = re.compile(r'\*/')
50 | PATTERN_COMMENT_INDENT_SPACE = r'^(%s *?[^\t\r\n][^\r\n]*)?\r?\n$'
51 | PATTERN_COMMENT_INDENT_TAB = r'^(%s[ \t]*[^ \t\r\n][^\r\n]*)?\r?\n$'
52 |
53 |
54 | E_MALFORMED = "E0"
55 | E_COMMENTS = "E1"
56 | E_COMMA = "E2"
57 | W_NL_START = "W1"
58 | W_NL_END = "W2"
59 | W_INDENT = "W3"
60 | W_TRAILING_SPACE = "W4"
61 | W_COMMENT_INDENT = "W5"
62 |
63 |
64 | VIOLATION_MSG = {
65 | E_MALFORMED: 'JSON content is malformed.',
66 | E_COMMENTS: 'Comments are not part of the JSON spec.',
67 | E_COMMA: 'Dangling comma found.',
68 | W_NL_START: 'Unnecessary newlines at the start of file.',
69 | W_NL_END: 'Missing a new line at the end of the file.',
70 | W_INDENT: 'Indentation Error.',
71 | W_TRAILING_SPACE: 'Trailing whitespace.',
72 | W_COMMENT_INDENT: 'Comment Indentation Error.'
73 | }
74 |
75 |
76 | class CheckJsonFormat(object):
77 | """
78 | Test JSON for format irregularities.
79 |
80 | - Trailing spaces.
81 | - Inconsistent indentation.
82 | - New lines at end of file.
83 | - Unnecessary newlines at start of file.
84 | - Trailing commas.
85 | - Malformed JSON.
86 | """
87 |
88 | def __init__(self, use_tabs=False, allow_comments=False):
89 | """Setup the settings."""
90 |
91 | self.use_tabs = use_tabs
92 | self.allow_comments = allow_comments
93 | self.fail = False
94 |
95 | def index_lines(self, text):
96 | """Index the char range of each line."""
97 |
98 | self.line_range = []
99 | count = 1
100 | last = 0
101 | for m in re.finditer('\n', text):
102 | self.line_range.append((last, m.end(0) - 1, count))
103 | last = m.end(0)
104 | count += 1
105 |
106 | def get_line(self, pt):
107 | """Get the line from char index."""
108 |
109 | line = None
110 | for r in self.line_range:
111 | if pt >= r[0] and pt <= r[1]:
112 | line = r[2]
113 | break
114 | return line
115 |
116 | def check_comments(self, text):
117 | """
118 | Check for JavaScript comments.
119 |
120 | Log them and strip them out so we can continue.
121 | """
122 |
123 | def remove_comments(group):
124 | return ''.join([x[0] for x in RE_LINE_PRESERVE.findall(group)])
125 |
126 | def evaluate(m):
127 | text = ''
128 | g = m.groupdict()
129 | if g["code"] is None:
130 | if not self.allow_comments:
131 | self.log_failure(E_COMMENTS, self.get_line(m.start(0)))
132 | text = remove_comments(g["comments"])
133 | else:
134 | text = g["code"]
135 | return text
136 |
137 | content = ''.join(map(lambda m: evaluate(m), RE_COMMENT.finditer(text)))
138 | return content
139 |
140 | def check_dangling_commas(self, text):
141 | """
142 | Check for dangling commas.
143 |
144 | Log them and strip them out so we can continue.
145 | """
146 |
147 | def check_comma(g, m, line):
148 | # ,] -> ] or ,} -> }
149 | self.log_failure(E_COMMA, line)
150 | if g["square_comma"] is not None:
151 | return g["square_ws"] + g["square_bracket"]
152 | else:
153 | return g["curly_ws"] + g["curly_bracket"]
154 |
155 | def evaluate(m):
156 | g = m.groupdict()
157 | return check_comma(g, m, self.get_line(m.start(0))) if g["code"] is None else g["code"]
158 |
159 | return ''.join(map(lambda m: evaluate(m), RE_TRAILING_COMMA.finditer(text)))
160 |
161 | def log_failure(self, code, line=None):
162 | """
163 | Log failure.
164 |
165 | Log failure code, line number (if available) and message.
166 | """
167 |
168 | if line:
169 | print("%s: Line %d - %s" % (code, line, VIOLATION_MSG[code]))
170 | else:
171 | print("%s: %s" % (code, VIOLATION_MSG[code]))
172 | self.fail = True
173 |
174 | def check_format(self, file_name):
175 | """Initiate the check."""
176 |
177 | self.fail = False
178 | comment_align = None
179 | with codecs.open(file_name, encoding='utf-8') as f:
180 | count = 1
181 | for line in f:
182 | indent_match = (RE_LINE_INDENT_TAB if self.use_tabs else RE_LINE_INDENT_SPACE).match(line)
183 | end_comment = (
184 | (comment_align is not None or (indent_match and indent_match.group(2))) and
185 | RE_COMMENT_END.search(line)
186 | )
187 | # Don't allow empty lines at file start.
188 | if count == 1 and line.strip() == '':
189 | self.log_failure(W_NL_START, count)
190 | # Line must end in new line
191 | if not line.endswith('\n'):
192 | self.log_failure(W_NL_END, count)
193 | # Trailing spaces
194 | if RE_TRAILING_SPACES.match(line):
195 | self.log_failure(W_TRAILING_SPACE, count)
196 | # Handle block comment content indentation
197 | if comment_align is not None:
198 | if comment_align.match(line) is None:
199 | self.log_failure(W_COMMENT_INDENT, count)
200 | if end_comment:
201 | comment_align = None
202 | # Handle general indentation
203 | elif indent_match is None:
204 | self.log_failure(W_INDENT, count)
205 | # Enter into block comment
206 | elif comment_align is None and indent_match.group(2):
207 | alignment = indent_match.group(1) if indent_match.group(1) is not None else ""
208 | if not end_comment:
209 | comment_align = re.compile(
210 | (PATTERN_COMMENT_INDENT_TAB if self.use_tabs else PATTERN_COMMENT_INDENT_SPACE) % alignment
211 | )
212 | count += 1
213 | f.seek(0)
214 | text = f.read()
215 |
216 | self.index_lines(text)
217 | text = self.check_comments(text)
218 | self.index_lines(text)
219 | text = self.check_dangling_commas(text)
220 | try:
221 | json.loads(text)
222 | except Exception as e:
223 | self.log_failure(E_MALFORMED)
224 | print(e)
225 | return self.fail
226 |
227 |
228 | if __name__ == "__main__":
229 | import sys
230 | cjf = CheckJsonFormat(False, True)
231 | cjf.check_format(sys.argv[1])
232 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | # BracketHighlighter
2 |
3 | ## 2.33.0
4 |
5 | - **NEW**: Release special branch for Sublime Text 4201+.
6 |
7 | ## 2.32.4
8 |
9 | - **FIX**: Fixes for Sublime beta using Python 3.13.
10 |
11 | ## 2.32.3
12 |
13 | - **FIX**: Fix example keymap for selection.
14 | - **FIX**: Use typing dependency.
15 |
16 | ## 2.32.2
17 |
18 | - **FIX**: Fix bracket highlighting in Bash due to recent changes in syntax language.
19 |
20 | ## 2.32.1
21 |
22 | - **FIX**: Remove verbose parameter from named tubple which causes issues with Python 3.8.
23 |
24 | ## 2.32.0
25 |
26 | - **NEW**: Opt in to Python 3.8.
27 | - **FIX**: Fix issues with "SINUMERIK840D" language.
28 |
29 | ## 2.31.5
30 |
31 | - **FIX**: Fix endless Ruby method case.
32 |
33 | ## 2.31.4
34 |
35 | - **FIX**: Fix PHP arrow case that could break bracket highlighting.
36 |
37 | ## 2.31.3
38 |
39 | - **FIX**: Fix some Bash cases.
40 |
41 | ## 2.31.2
42 |
43 | - **FIX**: Fix performance with content highlight bar.
44 |
45 | ## 2.31.1
46 |
47 | - **FIX**: Fix issue with CMFL bracket matching.
48 |
49 | ## 2.31
50 |
51 | - **NEW**: Add support for Ruby endless methods.
52 | - **NEW**: `validate` plugin method has been updated to accept a View, but will fallback to the legacy approach
53 | for backwards compatibility.
54 |
55 | ## 2.30.1
56 |
57 | - **FIX**: Fix PHP angle matching rule.
58 |
59 | ## 2.30.0
60 |
61 | - **NEW**: When defining key bindings `type` is now defaulted to `['__all__']` if not set.
62 | - **FIX**: Ensure Jinja2 support for works for the Jinja2 package (support existed for some older package).
63 |
64 | ## 2.29.4
65 |
66 | - **FIX**: Fix for "Ruby on Rails" embedded in HTML 4130+.
67 |
68 | ## 2.29.3
69 |
70 | - **FIX**: Fix "Ruby on Rails" for Sublime build 4130+.
71 | - **FIX**: Quotes matching for SQL string in PHP.
72 |
73 | ## 2.29.2
74 |
75 | - **FIX**: Adjustments for latest syntax changes in default JavaScript and HTML in Sublime Text. Fixes issues with
76 | JavaScript angle brackets.
77 | - **FIX**: Elixir end block detection.
78 |
79 | ## 2.29.1
80 |
81 | - **FIX**: PHP bracket matching issue.
82 | - **FIX**: Fix Lua keyword issue.
83 |
84 | ## 2.29.0
85 |
86 | - **NEW**: Enhance bracket content select feature with `extend` parameter.
87 | - **FIX**: Bash switch case highlighting on ST3.
88 | - **FIX**: Fix custom popup color handling.
89 |
90 | ## 2.28.1
91 |
92 | - **FIX**: Handle HTML attributes even when there are no spaces between them.
93 |
94 | ## 2.28.0
95 |
96 | - **NEW**: Rename `language_filter` options `whitelist` and `blacklist` to `allowlist` and `blocklist` respectively.
97 | - **NEW**: Add global option `gutter_icons` to control enabling or disabling icons.
98 |
99 | ## 2.27.10
100 |
101 | - **FIX**: Handle certain regular expression compilation failures in a more graceful way.
102 |
103 | ## 2.27.9
104 |
105 | - **FIX**: Remove old clone workaround as this issue will be fixed upstream in Sublime Text 4 builds.
106 |
107 | ## 2.27.8
108 |
109 | - **FIX**: Content align bug.
110 |
111 | ## 2.27.7
112 |
113 | - **FIX**: Update support to include OCaml comment support.
114 | - **FIX**: Fix avoiding round brackets in shell case statements.
115 | - **FIX**: Thread adjustments that allow BracketHighlighter to go completely idle when Sublime Text is idle.
116 | - **FIX**: Fix Ruby interpolated strings.
117 | - **FIX**: Fix optional tags for `option` and `optgroup`.
118 |
119 | ## 2.27.6
120 |
121 | - **FIX**: Fix issue where HTML style attribute quotes where not highlighted due to syntax definition changes.
122 | - **FIX**: Add support for `@` in HTML attributes.
123 |
124 | ## 2.27.5
125 |
126 | - **FIX**: Fix issue where bracket context code blocks in popups sometimes are recognized as Jinja2 template
127 | variables.
128 | - **FIX**: Fix internal clone view cleanup.
129 | - **FIX**: Fix bad clone reference.
130 | - **FIX**: `on_hover` should not occur if `bracket_highlighter.ignore` is set in the view.
131 |
132 | ## 2.27.4
133 |
134 | - **FIX**: Avoid targeting common `HERDOC` syntax with angle brackets. #482
135 |
136 | ## 2.27.3
137 |
138 | - **FIX**: Selecting "no threshold" search from popup quickly reverts back to unmatched.
139 | - **FIX**: Backtick string support extended to JavaScript.
140 |
141 | ## 2.27.2
142 |
143 | - **FIX**: `C#` interpolated strings !468.
144 | - **FIX**: Fix C/C++ preprocessor highlighting !474.
145 | - **FIX**: Only highlight the keyword in C/C++ preprocessors af22600cd23bd3c15a1a0f6fc54041e6d96b3dd3.
146 |
147 | ## 2.27.1
148 |
149 | - **FIX**: Fix Lua loops by avoiding `while` and `from` and just highlighting `do` #466.
150 |
151 | ## 2.27.0
152 |
153 | - **NEW**: Add option to always show the bracket popup on bracket hover #457.
154 | - **FIX**: Fix clone views not properly supported #454.
155 | - **FIX**: Improvements to Ruby conditional matching #452.
156 |
157 | ## 2.26.0
158 |
159 | - **NEW**: Added new configuration `user_bracket_styles` to allow a user to override specific rules or just part of a
160 | specific rule instead of copying all of `bracket_styles` #448.
161 | - **NEW**: Add colorization with region-ish scopes for Sublime builds 3148+ #448.
162 | - **FIX**: Ruby issue with conditionals immediately followed after return keyword #425.
163 | - **FIX**: PHP issue for arrows (`$var->prop`) #446.
164 |
165 | ## 2.25.2
166 |
167 | - **FIX**: Update tag attribute pattern.
168 | - **FIX**: Add SVG self closing tags.
169 | - **FIX**: Temporarily use `thin_underline` style to mitigate issue #443.
170 |
171 | ## 2.25.1
172 |
173 | - **FIX**: Update dependencies.
174 |
175 | ## 2.25.0
176 |
177 | - **FIX**: Quick start image links.
178 | - **FIX**: Allow Markdown related brackets to work in Markdown Extended.
179 | - **FIX**: Allow `HTML (Jinja2)` to work in HTML.
180 | - **NEW**: Add Markdown `` ` `` to swap and wrap.
181 | - **NEW**: Add commonly used commands to the command panel (documents and settings) #419.
182 |
183 | ## 2.24.2
184 |
185 | - **FIX**: Avoid things like `->` in PHP due to new Sublime default syntax changes #417.
186 | - **FIX**: Add support for Python f-strings.
187 |
188 | ## 2.24.1
189 |
190 | - **FIX**: Random regions failure.
191 | - **FIX**: Lua keyword match at beginning of line.
192 |
193 | ## 2.24.0
194 |
195 | - **NEW**: Popup/Phantom support limited to 3124+ moving forward to prepare for `mdpopups` 2.0 that will drop legacy
196 | support for old, early implementation of popups and phantoms.
197 | - **NEW**: No longer try and force dependency updates. Leave it up to Package Control (whether they do it or not).
198 | - **NEW**: CSS adjustments to popups.
199 | - **FIX**: Fix tag matching corner case #409.
200 |
201 | ## 2.23.3
202 |
203 | - **FIX**: Fix error `ImportError: No module named 'yaml'` #400.
204 |
205 | ## 2.23.2
206 |
207 | - **FIX**: Add backtick quote support for ruby and shell script syntaxes d884e8ab7aa69477c1af5d29cef24589efaf2b8e.
208 | - **FIX**: Fix console noise on global disable #397.
209 |
210 | ## 2.23.1
211 |
212 | - **FIX**: Rule position - zero is a valid position #387.
213 | - **FIX**: Protect against race condition due to Sublime bug #390.
214 |
215 | ## 2.23.0
216 |
217 | - **NEW**: Add links in menu to documentation and issues.
218 | - **NEW**: Provide new local quickstart guide from the menu.
219 | - **NEW**: Breaking change to `bh_tag.sublime-settings`. `tag_mode` is now an ordered list of dictionaries.
220 | `self_closing_patterns` and `single_tag_patterns` and replaced with `optional_tag_patterns`,
221 | `void_tag_patterns`, and `self_closing_tag_patterns`.
222 | - **NEW**: Add new `first_line` rule for determining tag mode.
223 | - **NEW**: New XML tag mode and better XHTML mode.
224 | - **NEW**: Better special tag logic which handles optional tags, void tags, and self closing tags better. #384
225 |
226 | ## 2.22.1
227 |
228 | - **FIX**: Fix changelog links
229 |
230 | ## 2.22.0
231 |
232 | - **NEW**: Manual command to show offscreen bracket popup. Can be invoked when cursor is anywhere between target
233 | bracket #378.
234 | - **NEW**: When selecting the "Match brackets without threshold" link on the unmatched bracket popup, reshow the
235 | offscreen popup.
236 | - **NEW**: Add support for "SINUMERIK840D" language #379.
237 |
238 | ## 2.21.6
239 |
240 | - **FIX**: Fix PHP conditional #366.
241 | - **FIX**: No line wrapping in code snippets in popups.
242 |
243 | ## 2.21.5
244 |
245 | - **FIX**: Fix a break caused by 2.21.4 #364.
246 | - **FIX**: Fix CSS in changelog
247 |
248 | ## 2.21.4
249 |
250 | - **FIX**: Changelog command now works for older ST3 versions.
251 |
252 | ## 2.21.3
253 |
254 | - **FIX**: Don't fail if mdpopups was not installed on old Sublime version.
255 |
256 | ## 2.21.2
257 |
258 | - **FIX**: Fix changelog typo :).
259 |
260 | ## 2.21.1
261 |
262 | - **NEW**: Message to not freak people out :).
263 |
264 | ## 2.21.0
265 |
266 | - **NEW**: Require mdpopups 1.9.0.
267 | - **NEW**: New changelog command.
268 | - **NEW**: Add support for new LaTeX syntax.
269 |
--------------------------------------------------------------------------------
/bh_popup.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 |
7 | Handles popups.
8 | """
9 | import sublime
10 | import re
11 | import textwrap
12 | import traceback
13 | from .bh_logging import log
14 |
15 | HOVER_SUPPORT = int(sublime.version()) >= 3124
16 | WRAPPER_CLASS = "bracket-highlighter"
17 |
18 | CSS = '''
19 | {%- if var.mdpopups_version >= (2, 0, 0) %}
20 | div.bracket-highlighter { padding: 0.5rem; margin: 0; }
21 | {%- else %}
22 | div.bracket-highlighter { padding: 0; margin: 0; }
23 | {%- endif %}
24 | '''
25 | MATCH_ERR = '''
26 | \x02{%%- if plugin.mdpopups_version >= (2, 0, 0) %%}\x03
27 | !!! panel-error "Matching bracket could not be found!"
28 |
29 | - There *might* be no match.
30 | - Brackets *might* be nested poorly --> `([)(])`
31 | - Matching bracket *might* be beyond the search threshold.
32 | A match done without the threshold *might* find it.
33 |
34 | [(Match brackets without threshold)](%(pt)s)
35 | \x02{%%- else %%}\x03
36 | ### Matching bracket could not be found! {: .error}
37 |
38 | - There *might* be no match.
39 | - Brackets *might* be nested poorly --> `([)(])`
40 | - Matching bracket *might* be beyond the search threshold.
41 | A match done without the threshold *might* find it.
42 | [(Match brackets without threshold)](%(pt)s)
43 | \x02{%%- endif %%}\x03
44 | '''
45 |
46 | template_options = {
47 | "block_start_string": "\x02{%",
48 | "block_end_string": "%}\x03",
49 | "variable_start_string": "\x02{{",
50 | "variable_end_string": "}}\x03",
51 | "comment_start_string": "\x02{#",
52 | "comment_end_string": "#}\x03"
53 | }
54 |
55 | if HOVER_SUPPORT:
56 | import mdpopups
57 |
58 |
59 | class BhOffscreenPopup(object):
60 | """Handle offscreen popups."""
61 |
62 | popup_view = None
63 |
64 | def on_navigate(self, href):
65 | """Navigate to code position."""
66 | if HOVER_SUPPORT:
67 | try:
68 | pt = int(href)
69 | self.popup_view.sel().clear()
70 | self.popup_view.sel().add(sublime.Region(pt))
71 | self.popup_view.show(pt)
72 | mdpopups.hide_popup(self.popup_view)
73 | except Exception:
74 | log("Problem handling popup event:\n%s" % str(traceback.format_exc()))
75 |
76 | def on_navigate_unmatched(self, href):
77 | """Handle unmatched click."""
78 | if HOVER_SUPPORT:
79 | pt = int(href)
80 | mdpopups.hide_popup(self.popup_view)
81 | self.popup_view.run_command("bh_offscreen_popup", {"point": pt, "no_threshold": True})
82 |
83 | def show_unmatched_popup(self, view, point):
84 | """Show unmatched popup."""
85 |
86 | if HOVER_SUPPORT:
87 | self.popup_view = view
88 | mdpopups.show_popup(
89 | view,
90 | textwrap.dedent(MATCH_ERR % {"pt": str(point)}),
91 | wrapper_class=WRAPPER_CLASS,
92 | flags=sublime.HIDE_ON_MOUSE_MOVE_AWAY,
93 | css=CSS,
94 | max_width=800,
95 | max_height=800,
96 | location=point,
97 | on_navigate=self.on_navigate_unmatched,
98 | template_vars={'mdpopups_version': mdpopups.version()},
99 | template_env_options=template_options
100 | )
101 |
102 | def is_bracket_visible(self, view, region):
103 | """
104 | Check if bracket is visible.
105 |
106 | Check if first point of bracket is visible or last point is visible.
107 | In short, is any part visible?
108 |
109 | ```
110 | (xa,ya)--------------------(xb,ya)
111 | | |
112 | | |
113 | | (pxa,pya){(pxb,pyb) |
114 | | |
115 | | |
116 | (xa,yb)------------------------
117 | ```
118 | """
119 |
120 | # Always report that bracket is not visible if we always want to show the popup
121 | if sublime.load_settings("bh_core.sublime-settings").get('show_bracket_popup_always', False):
122 | return False
123 |
124 | xa, ya = view.viewport_position()
125 | w, h = view.viewport_extent()
126 | xb, yb = xa + w, ya + h
127 | pxa, pya = view.text_to_layout(region[0])
128 | pxb, pyb = view.text_to_layout(region[1])
129 |
130 | return (xa <= pxa < xb and ya <= pya < yb) or (xa <= pxb < xb and ya <= pyb < yb)
131 |
132 | def get_context_line(self, view, row, col_start, col_end, tab_size):
133 | """Get line context."""
134 |
135 | line = view.line(view.text_point(row, 0))
136 | c0 = view.rowcol(line.begin())[1]
137 | c1 = view.rowcol(line.end())[1]
138 | if c0 < col_start:
139 | c0 = col_start
140 | if c0 > c1:
141 | c0 = c1
142 | diff = c1 - c0
143 | if diff > 120:
144 | c1 -= diff - 120
145 |
146 | return self.escape_code(
147 | view.substr(
148 | sublime.Region(
149 | view.text_point(row, c0),
150 | view.text_point(row, c1)
151 | )
152 | ),
153 | tab_size
154 | )
155 |
156 | def get_multiline_context(self, view, code, row, col_start, col_end, tab_size, line_context):
157 | """Get multi-line context for vertical offscreen brackets."""
158 |
159 | last_row = view.rowcol(view.size())[0]
160 |
161 | lines = [code]
162 |
163 | offset = 1
164 | while offset <= line_context:
165 | current_row = row - offset
166 | if current_row >= 0:
167 | lines.insert(0, self.get_context_line(view, current_row, col_start, col_end, tab_size))
168 | else:
169 | break
170 | offset += 1
171 | last_offset = offset
172 |
173 | offset = 1
174 | while offset <= line_context or (len(lines) != (line_context * 2) + 1):
175 | current_row = row + offset
176 | if current_row <= last_row:
177 | lines.append(self.get_context_line(view, current_row, col_start, col_end, tab_size))
178 | else:
179 | break
180 | offset += 1
181 |
182 | offset = last_offset
183 | while len(lines) < ((line_context * 2) + 1):
184 | current_row = row - offset
185 | if current_row >= 0:
186 | lines.insert(0, self.get_context_line(view, current_row, col_start, col_end, tab_size))
187 | else:
188 | break
189 | offset += 1
190 |
191 | return textwrap.dedent('\n'.join(lines)).replace('\n', '
')
192 |
193 | def escape_code(self, text, tab_size):
194 | """Format text to HTML."""
195 |
196 | encode_table = {
197 | '&': '&',
198 | '>': '>',
199 | '<': '<',
200 | '\t': ' ' * tab_size,
201 | '\n': '',
202 | ' ': ' '
203 | }
204 |
205 | return ''.join(
206 | encode_table.get(c, c) for c in text
207 | )
208 |
209 | def show_popup_between(self, view, point, region, region2, icon):
210 | """Show popup between."""
211 | if HOVER_SUPPORT:
212 | markup = ''
213 | if not self.is_bracket_visible(view, region):
214 | markup += self.get_markup(view, point, region, icon)
215 | if not self.is_bracket_visible(view, region2):
216 | markup += '\n\n'
217 | markup += self.get_markup(view, point, region2, icon)
218 | if markup:
219 | self.popup_view = view
220 | mdpopups.show_popup(
221 | view,
222 | markup,
223 | wrapper_class=WRAPPER_CLASS,
224 | css=CSS,
225 | flags=sublime.HIDE_ON_MOUSE_MOVE_AWAY,
226 | max_width=800,
227 | location=point,
228 | on_navigate=self.on_navigate,
229 | template_vars={'mdpopups_version': mdpopups.version()},
230 | template_env_options=template_options
231 | )
232 | else:
233 | self.show_unmatched_popup(view, point)
234 |
235 | def get_markup(self, view, point, region, icon):
236 | """Get markup."""
237 |
238 | settings = sublime.load_settings('bh_core.sublime-settings')
239 | tab_size = view.settings().get('tab_size', 4)
240 |
241 | # Get highlight colors
242 | color = None
243 | if icon is not None:
244 | color = mdpopups.scope2style(view, icon[1]).get('color')
245 | if color is None or bool(settings.get('use_custom_popup_bracket_emphasis', False)):
246 | bracket_em = settings.get('popup_bracket_emphasis', '#ff0000')
247 | if not bracket_em.startswith('#'):
248 | bracket_em = mdpopups.scope2style(view, bracket_em).get('color')
249 | else:
250 | bracket_em = color
251 |
252 | # Get positions of bracket extents on the line
253 | row, col = view.rowcol(region[0])
254 | col2 = view.rowcol(region[1])[1]
255 |
256 | # Calculate how far before and after bracket we can/should grab for context
257 | # Format and truncate (if necessary).
258 | context = int(settings.get('popup_char_context', 120)) - (col2 - col) - 1
259 | start = region[1] - context
260 | col_start = col2 - context
261 | overage = 0
262 | line = view.line(region[0])
263 | if start < line.begin():
264 | overage = line.begin() - start
265 | start = line.begin()
266 | col_start = 0
267 | end = region[1] + overage
268 | col_end = col2 + overage
269 | if end > line.end():
270 | end = line.end()
271 | col_end = view.rowcol(line.end())[1]
272 |
273 | # Get line of code with bracket and emphasize the bracket
274 | content = view.substr(sublime.Region(start, end))
275 | if re.match(r'#([\da-fA-F]{3}){1,2}', bracket_em):
276 | highlight_open = '' % bracket_em
277 | else:
278 | highlight_open = '' % bracket_em
279 | content = (
280 | self.escape_code(content[:col - col_start], tab_size) +
281 | highlight_open +
282 | self.escape_code(content[col - col_start: col2 - col_start], tab_size) +
283 | '' +
284 | self.escape_code(content[col2 - col_start:], tab_size)
285 | )
286 |
287 | # Get additional lines of context (if required) and format text
288 | if row != view.rowcol(point)[0]:
289 | line_context = int(int(settings.get('popup_line_context', 2)) / 2)
290 | content = self.get_multiline_context(view, content, row, col_start, col_end, tab_size, line_context)
291 | else:
292 | content = content.strip()
293 |
294 | # Put together the markup to show
295 | markup = '\n' % content
296 | markup += '\n' + '[(jump to bracket - line: %d)](%d)' % (row + 1, region[0])
297 |
298 | return markup
299 |
300 | def show_popup(self, view, point, region, icon):
301 | """Show the popup."""
302 |
303 | if HOVER_SUPPORT:
304 | if not self.is_bracket_visible(view, region):
305 | markup = self.get_markup(view, point, region, icon)
306 |
307 | self.popup_view = view
308 | mdpopups.show_popup(
309 | view,
310 | markup,
311 | wrapper_class=WRAPPER_CLASS,
312 | css=CSS,
313 | flags=sublime.HIDE_ON_MOUSE_MOVE_AWAY,
314 | max_width=800,
315 | location=point,
316 | on_navigate=self.on_navigate,
317 | template_vars={'mdpopups_version': mdpopups.version()},
318 | template_env_options=template_options
319 | )
320 |
--------------------------------------------------------------------------------
/bh_wrapping.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | import sublime
8 | import sublime_plugin
9 | from os.path import basename, splitext
10 | import re
11 |
12 |
13 | BH_TABSTOPS = re.compile(r"(\$\{BH_(SEL|TAB)(?:\:([^\}]+))?\})")
14 | TAB_REGION = "bh_plugin_wrapping_tabstop"
15 | SEL_REGION = "bh_plugin_wrapping_select"
16 | OUT_REGION = "bh_plugin_wrapping_outlier"
17 |
18 | VALID_INSERT_STYLES = (
19 | ("inline", "Inline Insert"),
20 | ("block", "Block Insert"),
21 | ("indent_block", "Indented Block Insert")
22 | )
23 |
24 |
25 | def exclude_entry(enabled, filter_type, language_list, language):
26 | """Exclude bracket wrapping entry by filter."""
27 |
28 | exclude = True
29 | if enabled:
30 | # Black list languages
31 | if filter_type == 'blocklist':
32 | exclude = False
33 | if language is not None:
34 | for item in language_list:
35 | if language == item.lower():
36 | exclude = True
37 | break
38 | # White list languages
39 | elif filter_type == 'allowlist':
40 | if language is not None:
41 | for item in language_list:
42 | if language == item.lower():
43 | exclude = False
44 | break
45 | return exclude
46 |
47 |
48 | class WrapInstance(object):
49 | """Track wrap instance globally."""
50 |
51 | obj = None
52 | value = None
53 |
54 | @classmethod
55 | def clear(cls):
56 | """Clear attributes."""
57 |
58 | cls.obj = None
59 | cls.value = None
60 |
61 |
62 | class TextInsertion(object):
63 | """Wrapper class for inserting text."""
64 |
65 | def __init__(self, view, edit):
66 | """Store view and edit objects."""
67 |
68 | self.view = view
69 | self.edit = edit
70 |
71 | def insert(self, pt, text):
72 | """Perform insertion."""
73 |
74 | return self.view.insert(self.edit, pt, text)
75 |
76 |
77 | class ExecuteWrapInstanceCommand(sublime_plugin.TextCommand):
78 | """Execute the wrap instance."""
79 |
80 | def run(self, edit):
81 | """Call wrap instance."""
82 | obj = WrapInstance.obj
83 | value = WrapInstance.value
84 | # Wrap selections with brackets
85 | style = obj._style[value]
86 | obj.insert_regions = []
87 |
88 | for sel in obj.view.sel():
89 | # Determine indentation style
90 | if style == "indent_block":
91 | obj.block(edit, sel, True)
92 | elif style == "block":
93 | obj.block(edit, sel)
94 | else:
95 | obj.inline(edit, sel)
96 |
97 | obj.select(edit)
98 |
99 |
100 | class WrapBrackets(object):
101 | """Wrap the current selection(s) with the defined wrapping options."""
102 |
103 | def __init__(self, view, setting_file, attribute):
104 | """Initialization."""
105 |
106 | self.view = view
107 | self._menu = []
108 | self._brackets = []
109 | self._insert = []
110 | self._style = []
111 | self.read_wrap_entries(setting_file, attribute)
112 |
113 | def inline(self, edit, sel):
114 | """Inline wrap."""
115 |
116 | ti = TextInsertion(self.view, edit)
117 |
118 | offset1 = ti.insert(sel.begin(), self.brackets[0])
119 | self.insert_regions.append(sublime.Region(sel.begin(), sel.begin() + offset1))
120 | offset2 = ti.insert(sel.end() + offset1, self.brackets[1])
121 | self.insert_regions.append(sublime.Region(sel.end() + offset1, sel.end() + offset1 + offset2))
122 |
123 | def block(self, edit, sel, indent=False):
124 | """Wrap brackets around selection and block off the content."""
125 |
126 | # Calculate number of lines between brackets
127 | self.calculate_lines(sel)
128 | # Calculate the current indentation of first bracket
129 | self.calculate_indentation(sel)
130 |
131 | ti = TextInsertion(self.view, edit)
132 |
133 | line_offset = 0
134 | first_end = 0
135 | second_end = 0
136 | second_start = sel.end()
137 |
138 | for b in reversed(self.brackets[1].split('\n')):
139 | second_end += ti.insert(sel.end(), "\n" + self.indent_to_col + b)
140 | num_open_lines = self.brackets[0].count('\n')
141 | for b in reversed(self.brackets[0].split('\n')):
142 | if line_offset == num_open_lines:
143 | line = b + "\n"
144 | else:
145 | line = self.indent_to_col + b + "\n"
146 | first_end += ti.insert(sel.begin(), line)
147 | line_offset += 1
148 | self.insert_regions.append(sublime.Region(sel.begin(), sel.begin() + first_end))
149 |
150 | if indent:
151 | second_start += self.indent_content(ti, line_offset)
152 | else:
153 | pt = self.view.text_point(self.first_line + line_offset, 0)
154 | second_start += ti.insert(pt, self.indent_to_col)
155 |
156 | self.insert_regions.append(sublime.Region(first_end + second_start, first_end + second_start + second_end))
157 |
158 | def indent_content(self, ti, line_offset):
159 | """Indent the block content."""
160 |
161 | first = True
162 | offset = 0
163 | for l in range(line_offset, self.total_lines + line_offset):
164 | pt = self.view.text_point(self.first_line + l, 0)
165 | if first:
166 | offset += ti.insert(pt, self.indent_to_col + "\t")
167 | first = False
168 | else:
169 | offset += ti.insert(pt, "\t")
170 | return offset
171 |
172 | def calculate_lines(self, sel):
173 | """Calculate lines between brackets."""
174 |
175 | self.first_line, self.col_position = self.view.rowcol(sel.begin())
176 | last_line = self.view.rowcol(sel.end())[0]
177 | self.total_lines = last_line - self.first_line + 1
178 |
179 | def calculate_indentation(self, sel):
180 | """Calculate how much lines should be indented."""
181 |
182 | tab_size = self.view.settings().get("tab_size", 4)
183 | tab_count = self.view.substr(sublime.Region(sel.begin() - self.col_position, sel.begin())).count('\t')
184 | spaces = self.col_position - tab_count
185 | self.indent_to_col = (
186 | "\t" * tab_count + "\t" * int(spaces / tab_size) +
187 | " " * int(spaces % tab_size if spaces >= tab_size else spaces)
188 | )
189 |
190 | def select(self, edit):
191 | """Select defined regions after wrapping."""
192 |
193 | self.view.sel().clear()
194 | self.view.sel().add_all(self.insert_regions)
195 |
196 | final_sel = []
197 | initial_sel = []
198 | for s in self.view.sel():
199 | string = self.view.substr(s)
200 | matches = [m for m in BH_TABSTOPS.finditer(string)]
201 | multi_offset = 0
202 | if matches:
203 | for m in matches:
204 | r = sublime.Region(s.begin() + multi_offset + m.start(1), s.begin() + multi_offset + m.end(1))
205 | if m.group(3):
206 | replace = m.group(3)
207 | self.view.erase(edit, r)
208 | added = self.view.insert(edit, r.begin(), replace)
209 | final_sel.append(
210 | sublime.Region(
211 | s.begin() + multi_offset + m.start(1),
212 | s.begin() + multi_offset + m.start(1) + added
213 | )
214 | )
215 | multi_offset += added - r.size()
216 | else:
217 | self.view.erase(edit, r)
218 | final_sel.append(sublime.Region(s.begin() + multi_offset + m.start(1)))
219 | multi_offset -= r.size()
220 | if m.group(2) == "SEL":
221 | initial_sel.append(final_sel[-1])
222 |
223 | if len(initial_sel) != len(final_sel):
224 | self.view.add_regions(TAB_REGION, final_sel, "", "", sublime.HIDDEN)
225 |
226 | # Re-position cursor
227 | self.view.sel().clear()
228 | if len(initial_sel):
229 | self.view.sel().add_all(initial_sel)
230 | elif len(final_sel):
231 | self.view.sel().add(final_sel[0])
232 |
233 | def read_wrap_entries(self, setting_file, attribute):
234 | """Read wrap entries from the settings file."""
235 |
236 | settings = sublime.load_settings(setting_file)
237 | syntax = self.view.settings().get('syntax')
238 | language = splitext(basename(syntax))[0].lower() if syntax is not None else "plain text"
239 | wrapping = settings.get(attribute, [])
240 | for i in wrapping:
241 | if not exclude_entry(i["enabled"], i["language_filter"], i["language_list"], language):
242 | for j in i.get("entries", []):
243 | try:
244 | menu_entry = j["name"]
245 | bracket_entry = j["brackets"]
246 | insert_style = j.get("insert_style", ["inline"])
247 | self._menu.append(menu_entry)
248 | self._brackets.append(bracket_entry)
249 | self._insert.append(insert_style)
250 | except Exception:
251 | pass
252 |
253 | def wrap_brackets(self, value):
254 | """Wrap selection(s) with defined brackets."""
255 |
256 | if value < 0:
257 | return
258 |
259 | # Use new edit object since the main run has already exited
260 | # and the old edit is more than likely closed now
261 | WrapInstance.obj = self
262 | WrapInstance.value = value
263 |
264 | self.view.run_command("execute_wrap_instance")
265 |
266 | WrapInstance.clear()
267 |
268 | def wrap_style(self, value):
269 | """Choose insert style for wrapping."""
270 |
271 | if value < 0:
272 | return
273 |
274 | style = []
275 |
276 | self.brackets = self._brackets[value]
277 | for s in VALID_INSERT_STYLES:
278 | if s[0] in self._insert[value]:
279 | self._style.append(s[0])
280 | style.append(s[1])
281 |
282 | if len(style) > 1:
283 | sublime.set_timeout(
284 | lambda:
285 | self.view.window().show_quick_panel(
286 | style,
287 | self.wrap_brackets
288 | ),
289 | 0
290 | )
291 | else:
292 | self.wrap_brackets(0)
293 |
294 |
295 | class WrapBracketsCommand(sublime_plugin.TextCommand, WrapBrackets):
296 | """Bracket wrapping command."""
297 |
298 | def run(self, edit):
299 | """Display the wrapping menu."""
300 |
301 | self._menu = []
302 | self._brackets = []
303 | self._insert = []
304 | self._style = []
305 | self.read_wrap_entries("bh_wrapping.sublime-settings", "wrapping")
306 |
307 | if len(self._menu):
308 | self.view.window().show_quick_panel(
309 | self._menu,
310 | self.wrap_style
311 | )
312 |
313 | def is_enabled(self, **kwargs):
314 | """Check if command is enabled."""
315 |
316 | settings = self.view.settings()
317 | return bool(
318 | not settings.get('bracket_highlighter.ignore', False) and
319 | (
320 | not settings.get('is_widget') or
321 | sublime.load_settings("bh_core.sublime-settings").get('search_in_widgets', False)
322 | )
323 | )
324 |
325 |
326 | class BhNextWrapSelCommand(sublime_plugin.TextCommand):
327 | """Navigate wrapping tab stop regions."""
328 |
329 | def run(self, edit):
330 | """Look for the next wrapping tab stop region."""
331 |
332 | regions = self.view.get_regions(SEL_REGION) + self.view.get_regions(OUT_REGION)
333 | if len(regions):
334 | self.view.sel().clear()
335 | self.view.sel().add_all(regions)
336 |
337 | # Clean up unneeded sections
338 | self.view.erase_regions(SEL_REGION)
339 | self.view.erase_regions(OUT_REGION)
340 |
341 |
342 | class BhWrapListener(sublime_plugin.EventListener):
343 | """Listen for wrapping tab stop tabbing."""
344 |
345 | def on_query_context(self, view, key, operator, operand, match_all):
346 | """Mark the next regions to navigate to."""
347 |
348 | accept_query = False
349 | if key == "bh_wrapping":
350 | select = []
351 | outlier = []
352 | regions = view.get_regions(TAB_REGION)
353 | tabstop = []
354 | sels = view.sel()
355 |
356 | if len(regions) == 0:
357 | return False
358 |
359 | for s in sels:
360 | count = 0
361 | found = False
362 | for r in regions[:]:
363 | if found:
364 | select.append(r)
365 | tabstop.append(r)
366 | del regions[count]
367 | break
368 | if r.begin() <= s.begin() <= r.end():
369 | del regions[count]
370 | found = True
371 | continue
372 | count += 1
373 | if not found:
374 | outlier.append(s)
375 | tabstop += regions
376 |
377 | if len(tabstop) == len(select):
378 | if len(tabstop):
379 | tabstop = []
380 | accept_query = True
381 | elif len(tabstop) != 0:
382 | accept_query = True
383 |
384 | # Mark regions to make the "next" command aware of what to do
385 | view.add_regions(SEL_REGION, select, "", "", sublime.HIDDEN)
386 | view.add_regions(OUT_REGION, outlier, "", "", sublime.HIDDEN)
387 | view.add_regions(TAB_REGION, tabstop, "", "", sublime.HIDDEN)
388 |
389 | return accept_query
390 |
--------------------------------------------------------------------------------
/bh_modules/tags.py:
--------------------------------------------------------------------------------
1 | """
2 | BracketHighlighter.
3 |
4 | Copyright (c) 2013 - 2016 Isaac Muse
5 | License: MIT
6 | """
7 | from backrefs import bre
8 | from collections import namedtuple
9 | import sublime
10 | from os.path import basename, splitext
11 |
12 | TAG_OPEN = 0
13 | TAG_CLOSE = 1
14 |
15 | last_mode = None
16 |
17 |
18 | def process_tag_pattern(pattern, variables=None):
19 | """Process the tag pattern."""
20 |
21 | if variables is None:
22 | variables = {}
23 |
24 | if isinstance(pattern, str):
25 | pattern = bre.compile_search(pattern % variables, bre.I | bre.M)
26 | return pattern
27 |
28 |
29 | class TagEntry(namedtuple('TagEntry', ['begin', 'end', 'name', 'optional', 'single'])):
30 | """Tag entry tuple."""
31 |
32 | def move(self, begin, end):
33 | """Create a new tuple from this tuple."""
34 |
35 | return self._replace(begin=begin, end=end)
36 |
37 |
38 | def compare_languge(language, lang_list):
39 | """Check if language is found."""
40 |
41 | found = False
42 | for l in lang_list:
43 | if language == l.lower():
44 | found = True
45 | break
46 | return found
47 |
48 |
49 | def get_tag_mode(view, tag_mode_config):
50 | """Get the tag mode."""
51 |
52 | default_mode = None
53 | syntax = view.settings().get('syntax')
54 | language = splitext(basename(syntax))[0].lower() if syntax is not None else "plain text"
55 | if isinstance(tag_mode_config, list):
56 | for item in tag_mode_config:
57 | if isinstance(item, dict) and compare_languge(language, item.get('syntax', [])):
58 | first_line = item.get('first_line', '')
59 | if first_line:
60 | size = view.size() - 1
61 | if size > 256:
62 | size = 256
63 | if (
64 | isinstance(first_line, str) and
65 | bre.compile_search(first_line, bre.I).match(view.substr(sublime.Region(0, size)))
66 | ):
67 | return item.get('mode', default_mode)
68 | else:
69 | return item.get('mode', default_mode)
70 | return default_mode
71 |
72 |
73 | def highlighting(view, name, style, left, right):
74 | """Highlight only the tag name."""
75 | tag_settings = sublime.load_settings("bh_tag.sublime-settings")
76 | match_style = tag_settings.get("tag_style", {}).get(last_mode, None)
77 | if match_style is not None and style == match_style:
78 | tag_name = tag_settings.get('tag_name', {}).get(last_mode, r'[\w\:\.\-]+')
79 | if left is not None:
80 | region = view.find(tag_name, left.begin)
81 | left = left.move(region.begin(), region.end())
82 | if right is not None:
83 | region = view.find(tag_name, right.begin)
84 | right = right.move(region.begin(), region.end())
85 | return left, right
86 |
87 |
88 | def post_match(view, name, style, first, second, center, bfr, threshold):
89 | """
90 | Given two brackets, determine if they contain a tag.
91 |
92 | Decide whether it is an opening or closing, and then
93 | find its respective closing or opening.
94 | """
95 |
96 | # We need to know the mode during the highlight event, so track the last mode.
97 | global last_mode
98 | left, right = first, second
99 | threshold = [0, len(bfr)] if threshold is None else threshold
100 | bh_settings = sublime.load_settings("bh_core.sublime-settings")
101 | tag_settings = sublime.load_settings("bh_tag.sublime-settings")
102 | tag_mode = get_tag_mode(view, tag_settings.get("tag_mode", []))
103 | tag_style = tag_settings.get("tag_style", {}).get(tag_mode, '?')
104 | last_mode = tag_mode
105 | outside_adj = bh_settings.get("bracket_outside_adjacent", False)
106 |
107 | bracket_style = style
108 |
109 | if first is not None and tag_mode is not None:
110 | matcher = TagMatch(view, bfr, threshold, first, second, center, outside_adj, tag_mode)
111 | left, right = matcher.match()
112 | if not matcher.no_tag:
113 | bracket_style = tag_style
114 |
115 | return left, right, bracket_style
116 |
117 |
118 | class TagSearch(object):
119 | """Searches for tags."""
120 |
121 | def __init__(
122 | self, view, bfr, window, center, pattern,
123 | match_type, mode, optional_tags, self_closing_tags, void_tags
124 | ):
125 | """Prepare tag search object."""
126 |
127 | self.start = int(window[0])
128 | self.end = int(window[1])
129 | self.optional_tags = optional_tags
130 | self.void_tags = void_tags
131 | self.self_closing_tags = self_closing_tags
132 | self.center = center
133 | self.pattern = pattern
134 | self.match_type = match_type
135 | self.mode = mode
136 | self.bfr = bfr
137 | self.prev_match = None
138 | self.return_prev = False
139 | self.done = False
140 | self.view = view
141 | settings = sublime.load_settings("bh_tag.sublime-settings")
142 | try:
143 | self.scope_exclude = settings.get("tag_scope_exclude", {}).get(mode, ['string', 'comment'])
144 | except Exception:
145 | self.scope_exclude = ['string', 'comment']
146 |
147 | def scope_check(self, pt):
148 | """Check if scope is good."""
149 |
150 | illegal_scope = False
151 | for exclude in self.scope_exclude:
152 | illegal_scope |= bool(self.view.score_selector(pt, exclude))
153 | return illegal_scope
154 |
155 | def reset_end_state(self):
156 | """Reset and end the current state."""
157 |
158 | self.done = False
159 | self.prev_match = None
160 | self.return_prev = False
161 |
162 | def remember(self):
163 | """Instruct object to return the last tag."""
164 |
165 | self.return_prev = True
166 | self.done = False
167 |
168 | def get_tags(self):
169 | """Find all the tags."""
170 |
171 | if self.done:
172 | return
173 | if self.return_prev:
174 | self.return_prev = False
175 | yield self.prev_match
176 | for m in self.pattern.finditer(self.bfr, self.start, self.end):
177 | name = m.group(1).lower()
178 | if not self.match_type:
179 | self_closing_slash = bool(m.group(2) != "")
180 | if not self_closing_slash and self.optional_tags is not None:
181 | optional = self.optional_tags.match(name) is not None
182 | else:
183 | optional = False
184 |
185 | if self_closing_slash and self.self_closing_tags is not None:
186 | self_closing = self.self_closing_tags.match(name) is not None
187 | else:
188 | self_closing = False
189 |
190 | if not optional and not self_closing and self.void_tags is not None:
191 | void = self.void_tags.match(name) is not None
192 | else:
193 | void = False
194 |
195 | else:
196 | if self.void_tags is not None and self.void_tags.match(name) is not None:
197 | continue
198 | void = False
199 | optional = False
200 | self_closing = False
201 | start = m.start(0)
202 | end = m.end(0)
203 | if not self.scope_check(start):
204 | self.prev_match = TagEntry(start, end, name, optional, void or self_closing)
205 | self.start = end
206 | yield self.prev_match
207 | self.done = True
208 |
209 |
210 | class TagMatch(object):
211 | """Find a tag match."""
212 |
213 | def __init__(self, view, bfr, threshold, first, second, center, outside_adj, mode):
214 | """Prepare tag match object."""
215 |
216 | tag_settings = sublime.load_settings('bh_tag.sublime-settings')
217 | self.view = view
218 | self.bfr = bfr
219 | self.mode = mode
220 | self.tag_open = process_tag_pattern(
221 | tag_settings.get("start_tag")[mode],
222 | {
223 | "attributes": tag_settings.get('attributes', {}).get(mode, ''),
224 | "tag_name": tag_settings.get('tag_name', {}).get(mode, '')
225 | }
226 | )
227 |
228 | self.tag_close = process_tag_pattern(
229 | tag_settings.get("end_tag")[mode]
230 | )
231 |
232 | try:
233 | self.optional_tags = bre.compile_search(tag_settings.get('optional_tag_patterns')[self.mode], bre.I)
234 | except Exception:
235 | self.optional_tags = None
236 |
237 | try:
238 | self.void_tags = bre.compile_search(tag_settings.get('void_tag_patterns')[self.mode], bre.I)
239 | except Exception:
240 | self.void_tags = None
241 |
242 | try:
243 | self.self_closing_tags = bre.compile_search(tag_settings.get('self_closing_tag_patterns')[self.mode], bre.I)
244 | except Exception:
245 | self.self_closing_tags = None
246 |
247 | tag, tag_type, tag_end = self.get_first_tag(first[0])
248 | self.left, self.right = None, None
249 | self.window = None
250 | self.no_tag = False
251 | if outside_adj:
252 | if first[0] == center:
253 | center += 1
254 | elif center == tag_end:
255 | center -= 1
256 | if tag and first[0] < center < tag_end:
257 | if tag.single:
258 | self.left = tag
259 | self.right = tag
260 | else:
261 | if tag_type == "open":
262 | self.left = tag
263 | self.window = (tag_end, len(bfr) if threshold is None else threshold[1])
264 | else:
265 | self.right = tag
266 | self.window = (0 if threshold is None else threshold[0], first[0])
267 | else:
268 | self.left = first
269 | self.right = second
270 | self.no_tag = True
271 |
272 | def get_first_tag(self, offset):
273 | """
274 | Check if tag region is an opening tag or closing tag.
275 |
276 | Return the results
277 | """
278 |
279 | tag = None
280 | tag_type = None
281 | optional = False
282 | void = False
283 | m = self.tag_open.match(self.bfr[offset:])
284 | end = None
285 | if m:
286 | name = m.group(1).lower()
287 | self_closing_slash = bool(m.group(2) != "")
288 | optional = (
289 | self.optional_tags is not None and
290 | not self_closing_slash and
291 | self.optional_tags.match(name) is not None
292 | )
293 | self_closing = (
294 | self.self_closing_tags is not None and
295 | self_closing_slash and
296 | self.self_closing_tags.match(name) is not None
297 | )
298 | void = (
299 | not optional and
300 | not self_closing and
301 | self.void_tags is not None and
302 | self.void_tags.match(name) is not None
303 | )
304 | start = m.start(0) + offset
305 | end = m.end(0) + offset
306 | tag = TagEntry(start, end, name, optional, void or self_closing)
307 | tag_type = "open"
308 | self.center = end
309 | else:
310 | m = self.tag_close.match(self.bfr[offset:])
311 | if m:
312 | name = m.group(1).lower()
313 | void = (
314 | self.void_tags is not None and
315 | self.void_tags.match(name) is not None
316 | )
317 | if not void:
318 | start = m.start(0) + offset
319 | end = m.end(0) + offset
320 | tag = TagEntry(start, end, name, optional, void)
321 | tag_type = "close"
322 | self.center = offset
323 | return tag, tag_type, end
324 |
325 | def compare_tags(self, left, right):
326 | """Check if tags share the same name."""
327 |
328 | return left.name == right.name
329 |
330 | def resolve_optional(self, stack, c):
331 | """Handle self closing tags."""
332 |
333 | found_tag = None
334 | b = stack[-1]
335 | if self.compare_tags(b, c):
336 | found_tag = b
337 | stack.pop()
338 | else:
339 | while b is not None and b.optional:
340 | stack.pop()
341 | if len(stack):
342 | b = stack[-1]
343 | if self.compare_tags(b, c):
344 | found_tag = b
345 | stack.pop()
346 | break
347 | else:
348 | b = None
349 | return found_tag
350 |
351 | def match(self):
352 | """
353 | Find the corresponding open or close.
354 |
355 | Match only if either the close or open is already found.
356 | """
357 |
358 | stack = []
359 |
360 | # No tags to search for
361 | if self.no_tag or (self.left and self.right):
362 | return self.left, self.right
363 |
364 | # Initialize tag matching objects
365 | osearch = TagSearch(
366 | self.view, self.bfr, self.window,
367 | self.center, self.tag_open,
368 | 0, self.mode,
369 | self.optional_tags,
370 | self.self_closing_tags,
371 | self.void_tags
372 | )
373 | csearch = TagSearch(
374 | self.view, self.bfr, self.window,
375 | self.center, self.tag_close,
376 | 1, self.mode,
377 | self.optional_tags,
378 | self.self_closing_tags,
379 | self.void_tags
380 | )
381 |
382 | # Searching for opening or closing tag to match
383 | match_type = TAG_OPEN if self.right else TAG_CLOSE
384 |
385 | # Match the tags
386 | for c in csearch.get_tags():
387 | if len(stack) and osearch.done:
388 | if self.resolve_optional(stack, c):
389 | continue
390 | for o in osearch.get_tags():
391 | if o.end <= c.begin:
392 | if not o.single:
393 | stack.append(o)
394 | continue
395 | else:
396 | osearch.remember()
397 | break
398 |
399 | if len(stack):
400 | if self.resolve_optional(stack, c):
401 | continue
402 | elif match_type == TAG_OPEN and not osearch.done:
403 | continue
404 | if match_type == TAG_CLOSE:
405 | if self.left is None or self.compare_tags(self.left, c):
406 | self.right = c
407 | elif self.left.optional:
408 | self.right = self.left
409 | break
410 |
411 | if match_type == TAG_OPEN:
412 | # Find the rest of the the unmatched left side open brackets
413 | # approaching the cursor if all closing brackets were matched
414 | # Select the most recent open bracket on the stack.
415 | for o in osearch.get_tags():
416 | if not o.single:
417 | stack.append(o)
418 | if len(stack):
419 | self.left = self.resolve_optional(stack, self.right)
420 | elif self.right is None and self.left is not None and self.left.optional:
421 | # Account for the opening tag that was found being a self closing
422 | self.right = self.left
423 |
424 | return self.left, self.right
425 |
--------------------------------------------------------------------------------