├── .python-version ├── tests ├── __init__.py ├── test_json.py └── validate_json_format.py ├── .gitignore ├── docs ├── src │ ├── markdown │ │ ├── _snippets │ │ │ ├── refs.md │ │ │ ├── abbr.md │ │ │ └── links.md │ │ ├── images │ │ │ └── Example.png │ │ ├── index.md │ │ ├── about │ │ │ ├── license.md │ │ │ └── contributing.md │ │ ├── installation.md │ │ └── usage.md │ ├── requirements.txt │ └── dictionary │ │ └── en-custom.txt └── theme │ └── announce.html ├── messages.json ├── dependencies.json ├── messages ├── install.md └── rencent.md ├── Default.sublime-commands ├── raw_line_edit.sublime-settings ├── tox.ini ├── .github ├── labels.yml └── workflows │ ├── deploy.yml │ └── build.yml ├── Default.sublime-keymap ├── README.md ├── CHANGES.md ├── Main.sublime-menu ├── .pyspelling.yml ├── mkdocs.yml ├── support.py └── raw_line_edit.py /.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit Tests.""" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | site/* 2 | *.pyc 3 | build/* 4 | .tox/* 5 | -------------------------------------------------------------------------------- /docs/src/markdown/_snippets/refs.md: -------------------------------------------------------------------------------- 1 | --8<-- 2 | links.md 3 | abbr.md 4 | --8<-- 5 | -------------------------------------------------------------------------------- /docs/src/markdown/_snippets/abbr.md: -------------------------------------------------------------------------------- 1 | *[ST2]: Sublime Text 2 2 | *[ST3]: Sublime Text 3 3 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "install": "messages/install.md", 3 | "2.1.0": "messages/recent.md" 4 | } 5 | -------------------------------------------------------------------------------- /dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "*": { 3 | ">=4201": [ 4 | "mdpopups" 5 | ] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docs/src/markdown/images/Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/RawLineEdit/HEAD/docs/src/markdown/images/Example.png -------------------------------------------------------------------------------- /docs/src/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs_pymdownx_material_extras>=1.2.2 2 | mkdocs-git-revision-date-localized-plugin 3 | mkdocs-minify-plugin 4 | pyspelling 5 | -------------------------------------------------------------------------------- /messages/install.md: -------------------------------------------------------------------------------- 1 | # RawLineEdit 2 | 3 | Welcome to RawLineEdit! Check out the documentation: 4 | `Preferences->Package Settings->RawLineEdit->Documentation`. 5 | -------------------------------------------------------------------------------- /messages/rencent.md: -------------------------------------------------------------------------------- 1 | # RawLineEdit 2 | 3 | New release! 4 | 5 | Please see `Preferences->Package Settings->RawLineEdit->Changelog` 6 | for more info about the release. 7 | 8 | ## 2.1.0 9 | 10 | - **NEW**: Updates for Python 3.13 on ST 4201+. 11 | -------------------------------------------------------------------------------- /docs/src/dictionary/en-custom.txt: -------------------------------------------------------------------------------- 1 | Changelog 2 | Cmd 3 | Ctrl 4 | JSON 5 | MERCHANTABILITY 6 | MkDocs 7 | NONINFRINGEMENT 8 | PyMdown 9 | RawLineEdit 10 | SubNotify 11 | Twemoji 12 | changelog 13 | glyphs 14 | installable 15 | keybinding 16 | macOS 17 | requesters 18 | sublicense 19 | tmTheme 20 | -------------------------------------------------------------------------------- /docs/src/markdown/index.md: -------------------------------------------------------------------------------- 1 | # RawLineEdit 2 | 3 | ## Overview 4 | 5 | ![Example](images/Example.png) 6 | 7 | View and edit line endings in Sublime Text. RawLineEdit displays line endings very clearly and allows changing the line 8 | endings per line (something sublime text doesn't allow out of the box). 9 | -------------------------------------------------------------------------------- /docs/src/markdown/_snippets/links.md: -------------------------------------------------------------------------------- 1 | [mkdocs]: http://www.mkdocs.org 2 | [mkdocs-material]: https://github.com/squidfunk/mkdocs-material 3 | [package-control]: https://packagecontrol.io/ 4 | [package-control-install]: https://packagecontrol.io/installation 5 | [pymdown-extensions]: https://github.com/facelessuser/pymdown-extensions 6 | [subnotify]: https://github.com/facelessuser/SubNotify 7 | -------------------------------------------------------------------------------- /docs/theme/announce.html: -------------------------------------------------------------------------------- 1 | 2 | {% set icon = "material/alert-decagram" %} 3 | {% include ".icons/" ~ icon ~ ".svg" %} 4 | 5 | Sponsorship 6 | is now available! 7 | 8 | {% set icon = "octicons/heart-fill-16" %} 9 | {% include ".icons/" ~ icon ~ ".svg" %} 10 | 11 | -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | ////////////////////////////////// 3 | // Raw Line Edit 4 | ////////////////////////////////// 5 | { 6 | "caption": "Raw Line Edit: Toggle Line Edit Mode", 7 | "command": "toggle_raw_line_edit" 8 | }, 9 | { 10 | "caption": "Raw Line Edit: View Line Endings", 11 | "command": "popup_raw_line_edit" 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /raw_line_edit.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // Use subnotify if available 3 | "use_sub_notify": true, 4 | 5 | // Operate on sublime unsaved view buffer 6 | // Instead of reading the file from disk, 7 | // The file will be read directly from the buffer 8 | // In these cases the line endings will be normalized, 9 | // but you can edit them and save them back to disk. 10 | // Not sure how useful this is. 11 | "operate_on_unsaved_buffers": false 12 | } 13 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | skipsdist=True 3 | envlist = 4 | py39,py310,py311,py312,py313,py314,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 | "{envpython}" -m 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 31 | max-line-length=120 32 | exclude=site/*.py,.tox/* 33 | -------------------------------------------------------------------------------- /.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))', '.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: settings'] 23 | patterns: ['*.sublime-@(keymap|menu|settings|commands)'] 24 | 25 | # Label management 26 | 27 | labels: 28 | - name: 'C: settings' 29 | renamed: settings 30 | color: subcategory 31 | description: Related to Sublime settings. 32 | -------------------------------------------------------------------------------- /Default.sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | ////////////////////////////////// 3 | // Raw Line Edit 4 | ////////////////////////////////// 5 | { 6 | "keys": ["enter"], 7 | "command": "raw_line_insert", 8 | "context": 9 | [ 10 | { 11 | "key": "raw_line_edit" 12 | } 13 | ], 14 | "args": { "style": "Unix" } 15 | }, 16 | { 17 | "keys": ["shift+enter"], 18 | "command": "raw_line_insert", 19 | "context": 20 | [ 21 | { 22 | "key": "raw_line_edit" 23 | } 24 | ], 25 | "args": { "style": "Windows" } 26 | }, 27 | { 28 | "keys": ["ctrl+enter"], 29 | "command": "raw_line_insert", 30 | "context": 31 | [ 32 | { 33 | "key": "raw_line_edit" 34 | } 35 | ], 36 | "args": { "style": "MacOS" } 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Donate via PayPal][donate-image]][donate-link] 2 | [![Package Control Downloads][pc-image]][pc-link] 3 | ![License][license-image] 4 | # RawLineEdit 5 | 6 | ![Example](docs/src/markdown/images/Example.png) 7 | 8 | View and edit line endings in Sublime Text. RawLineEdit displays line endings very clearly and allows changing the line 9 | endings per line (something sublime text doesn't allow out of the box). 10 | 11 | # Documentation 12 | 13 | http://facelessuser.github.io/RawLineEdit/ 14 | 15 | # License 16 | 17 | Raw Line Edit is released under the MIT license. 18 | 19 | [pc-image]: https://img.shields.io/packagecontrol/dt/RawLineEdit.svg?logo=sublime%20text&logoColor=cccccc 20 | [pc-link]: https://packagecontrol.io/packages/RawLineEdit 21 | [license-image]: https://img.shields.io/badge/license-MIT-blue.svg 22 | [donate-image]: https://img.shields.io/badge/Donate-PayPal-3fabd1?logo=paypal 23 | [donate-link]: https://www.paypal.me/facelessuser 24 | -------------------------------------------------------------------------------- /.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 build 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/RawLineEdit.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 | -------------------------------------------------------------------------------- /docs/src/markdown/about/license.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | Raw Line Edit is 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 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # RawLineEdit 2 | 3 | ## 2.1.0 4 | 5 | - **NEW**: Updates for Python 3.13 on ST 4201+. 6 | 7 | ## 2.0.3 8 | 9 | - **FIX**: Update dependencies to remove unnecessary ones and add `typing`. 10 | 11 | ## 2.0.2 12 | 13 | - **FIX**: Fix issue when viewing a file over and over old phantoms persist. 14 | 15 | ## 2.0.1 16 | 17 | - **FIX**: Fix installation message referring to non-existent quick start guide. 18 | 19 | ## 2.0.0 20 | 21 | - **NEW**: When view is unsaved, allow the ability to save, continue without saving, and canceling when using popup 22 | output. 23 | - **NEW**: Raw Line views will now use phantoms to show line endings. 24 | - **NEW**: Raw Line views will always use the same syntax as the original view. 25 | - **NEW**: Raw Line views, due to change in implementation (phantoms), can now properly handle toggling between macOS 26 | 9 `\r`, Windows `\r\n`, and Linux/Unix `\n` line endings. 27 | - **NEW**: Remove `view_only` setting as you can now call either the edit command or the view command as desired. 28 | 29 | ## 1.2.1 30 | 31 | - **FIX**: When view is unsaved, allow the ability to save, continue without saving, and canceling. 32 | 33 | ## 1.2.0 34 | 35 | - **FIX**: Update dependencies. 36 | 37 | ## 1.1.0 38 | 39 | - **NEW**: Migrate to tag releases. 40 | 41 | ## 1.0.0 42 | 43 | - **NEW**: Initial release. 44 | -------------------------------------------------------------------------------- /docs/src/markdown/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## Package Control 4 | 5 | The recommended way to install RawLineEdit is via [Package Control][package-control]. Package Control will install the 6 | correct branch on your system and keep it up to date. 7 | 8 | 1. Ensure Package Control is installed. Instructions are found [here][package-control-install]. 9 | 10 | 2. In Sublime Text, press ++ctrl+shift+p++ (Win, Linux) or ++cmd+shift+p++ (macOS) to bring up the quick panel and 11 | start typing `Package Control: Install Package`. Select the command and it will show a list of installable plugins. 12 | 13 | 3. Start typing `RawLineEdit`; when you see it, select it. 14 | 15 | 4. Restart to be sure everything is loaded proper. 16 | 17 | 5. Enjoy! 18 | 19 | ## Git Cloning 20 | 21 | /// warning | Warning 22 | This is not the recommended way to install RawLineEdit for the casual user as it requires the user to know which 23 | branch to install, know how to use git, and **will not** get automatically updated. 24 | 25 | If you are forking for a pull request, this is the way to go, just replace the official repository with the link for 26 | your fork. 27 | /// 28 | 29 | 1. Quit Sublime Text. 30 | 31 | 2. Open a terminal: 32 | 33 | ``` 34 | cd /path/to/Sublime Text 3/Packages 35 | git clone https://github.com/facelessuser/RawLineEdit.git RawLineEdit 36 | ``` 37 | 38 | 3. Restart Sublime Text. 39 | 40 | --8<-- "refs.md" 41 | -------------------------------------------------------------------------------- /.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 build 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 build 50 | - name: Lint 51 | run: | 52 | python -m tox 53 | 54 | documents: 55 | 56 | env: 57 | TOXENV: documents 58 | 59 | runs-on: ubuntu-latest 60 | 61 | steps: 62 | - uses: actions/checkout@v4 63 | - name: Set up Python 64 | uses: actions/setup-python@v4 65 | with: 66 | python-version: 3.11 67 | - name: Install dependencies 68 | run: | 69 | python -m pip install --upgrade pip setuptools tox build 70 | - name: Install Aspell 71 | run: | 72 | sudo apt-get install aspell aspell-en 73 | - name: Build documents 74 | run: | 75 | python -m tox 76 | -------------------------------------------------------------------------------- /docs/src/markdown/usage.md: -------------------------------------------------------------------------------- 1 | # User Guide 2 | 3 | ## General Usage 4 | 5 | Toggle the current view to a "RawLineEdit" view via the command palette command `Raw Line Edit: Toggle Line Edit Mode`. 6 | To simply view the raw line endings in a output panel, call the command `Raw Line Edit: View Line Endings`. 7 | 8 | Using the ++enter++ key you can change a line ending to Windows style, to Linux/Unix style with ++shift+enter++, or even 9 | macOS 9 with ++ctrl+enter++. Select multiple lines to change more than one line. 10 | 11 | ## Settings 12 | 13 | RawLineEdit has a few settings that can tweak the behavior and look of the plugin. 14 | 15 | ### `operate_on_unsaved_buffers` 16 | 17 | Allows RawLineEdit to operate on views that haven't been saved to disk. 18 | 19 | ```js 20 | // Operate on sublime unsaved view buffer 21 | // Instead of reading the file from disk, 22 | // The file will be read directly from the buffer 23 | // In these cases the line endings will be normalized, 24 | // but you can edit them and save them back to disk. 25 | // Not sure how useful this is. 26 | "operate_on_unsaved_buffers": false 27 | ``` 28 | 29 | ### `use_sub_notify` 30 | 31 | Enables sending messages through the [SubNotify][subnotify] plugin. 32 | 33 | ```javascript 34 | // Use subnotify if available 35 | "use_sub_notify": true, 36 | ``` 37 | 38 | ## Create Key Bindings 39 | 40 | To enable raw line edit/view mode via a keybinding you can bind the following commands: 41 | 42 | - `toggle_raw_line_edit`: a command for create a view where you can view and modify line endings. 43 | - `popup_raw_line_edit`: creates an output panel with a read only view of the line endings. 44 | 45 | --8<-- "refs.md" 46 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Preferences", 4 | "mnemonic": "n", 5 | "id": "preferences", 6 | "children": 7 | [ 8 | { 9 | "caption": "Package Settings", 10 | "mnemonic": "P", 11 | "id": "package-settings", 12 | "children": 13 | [ 14 | { 15 | "caption": "RawLineEdit", 16 | "children": 17 | [ 18 | { 19 | "command": "edit_settings", "args": { 20 | "base_file": "${packages}/RawLineEdit/raw_line_edit.sublime-settings", 21 | "default": "{\n$0\n}\n" 22 | }, 23 | "caption": "Settings" 24 | }, 25 | { "caption": "-" }, 26 | { 27 | "caption": "Changelog", 28 | "command": "raw_line_edit_changes" 29 | }, 30 | { 31 | "caption": "Documentation", 32 | "command": "export_html_open_site", 33 | "args": { 34 | "url": "http://facelessuser.github.io/RawLineEdit/" 35 | } 36 | }, 37 | { "caption": "-" }, 38 | { 39 | "caption": "Support Info", 40 | "command": "raw_line_edit_support_info" 41 | }, 42 | { 43 | "caption": "Issues", 44 | "command": "raw_line_edit_open_site", 45 | "args": { 46 | "url": "https://github.com/facelessuser/RawLineEdit/issues" 47 | } 48 | }, 49 | { "caption": "-" } 50 | ] 51 | } 52 | ] 53 | } 54 | ] 55 | } 56 | ] 57 | -------------------------------------------------------------------------------- /.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|tests/**/*.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 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: RawLineEdit Documentation 2 | site_url: https://facelessuser.github.io/RawLineEdit 3 | repo_url: https://github.com/facelessuser/RawLineEdit 4 | edit_uri: tree/master/docs/src/markdown 5 | site_description: View and edit raw lines in Sublime Text. 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 | - search.share 26 | - search.highlight 27 | - search.suggest 28 | pymdownx: 29 | sponsor: "https://github.com/sponsors/facelessuser" 30 | 31 | nav: 32 | - Home: 33 | - RawLineEdit: index.md 34 | - Installation: installation.md 35 | - User Guide: usage.md 36 | - About: 37 | - Contributing & Support: about/contributing.md 38 | - Changelog: https://github.com/facelessuser/RawLineEdit/blob/master/CHANGES.md 39 | - License: about/license.md 40 | 41 | markdown_extensions: 42 | - markdown.extensions.toc: 43 | slugify: !!python/name:pymdownx.slugs.uslugify 44 | permalink: "" 45 | - markdown.extensions.smarty: 46 | smart_quotes: false 47 | - pymdownx.betterem: 48 | - markdown.extensions.attr_list: 49 | - markdown.extensions.tables: 50 | - markdown.extensions.abbr: 51 | - markdown.extensions.footnotes: 52 | - markdown.extensions.md_in_html: 53 | - pymdownx.superfences: 54 | - pymdownx.highlight: 55 | extend_pygments_lang: 56 | - name: pycon3 57 | lang: pycon 58 | options: 59 | python3: true 60 | - pymdownx.inlinehilite: 61 | - pymdownx.magiclink: 62 | repo_url_shortener: true 63 | repo_url_shorthand: true 64 | social_url_shorthand: true 65 | user: facelessuser 66 | repo: RawLineEdit 67 | - pymdownx.tilde: 68 | - pymdownx.caret: 69 | - pymdownx.smartsymbols: 70 | - pymdownx.emoji: 71 | emoji_index: !!python/name:material.extensions.emoji.twemoji 72 | emoji_generator: !!python/name:material.extensions.emoji.to_svg 73 | - pymdownx.escapeall: 74 | hardbreak: true 75 | nbsp: true 76 | - pymdownx.tasklist: 77 | custom_checkbox: true 78 | - pymdownx.arithmatex: 79 | - pymdownx.mark: 80 | - pymdownx.striphtml: 81 | - pymdownx.snippets: 82 | base_path: docs/src/markdown/_snippets 83 | - pymdownx.keys: 84 | separator: "\uff0b" 85 | - pymdownx.saneheaders: 86 | - pymdownx.blocks.admonition: 87 | types: 88 | - new 89 | - settings 90 | - note 91 | - abstract 92 | - info 93 | - tip 94 | - success 95 | - question 96 | - warning 97 | - failure 98 | - danger 99 | - bug 100 | - example 101 | - quote 102 | - pymdownx.blocks.details: 103 | types: 104 | - name: details-new 105 | class: new 106 | - name: details-settings 107 | class: settings 108 | - name: details-note 109 | class: note 110 | - name: details-abstract 111 | class: abstract 112 | - name: details-info 113 | class: info 114 | - name: details-tip 115 | class: tip 116 | - name: details-success 117 | class: success 118 | - name: details-question 119 | class: question 120 | - name: details-warning 121 | class: warning 122 | - name: details-failure 123 | class: failure 124 | - name: details-danger 125 | class: danger 126 | - name: details-bug 127 | class: bug 128 | - name: details-example 129 | class: example 130 | - name: details-quote 131 | class: quote 132 | - pymdownx.blocks.html: 133 | - pymdownx.blocks.definition: 134 | - pymdownx.blocks.tab: 135 | alternate_style: True 136 | 137 | extra: 138 | social: 139 | - icon: fontawesome/brands/github 140 | link: https://github.com/facelessuser 141 | 142 | plugins: 143 | - search 144 | - git-revision-date-localized 145 | - mkdocs_pymdownx_material_extras 146 | - minify: 147 | minify_html: true 148 | -------------------------------------------------------------------------------- /docs/src/markdown/about/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing & Support 2 | 3 | ## Overview 4 | 5 | Sublime Versions | Description 6 | ---------------- | ----------- 7 | ST3 | Fully supported and actively maintained. 8 | 9 | Contribution from the community is encouraged and can be done in a variety of ways: 10 | 11 | - Become a sponsor. 12 | - Bug reports. 13 | - Reviewing code. 14 | - Code patches via pull requests. 15 | - Documentation improvements via pull requests. 16 | 17 | ## Bug Reports 18 | 19 | 1. Please **read the documentation** and **search the issue tracker** to try to find the answer to your question 20 | **before** posting an issue. 21 | 22 | 2. When creating an issue on the repository, please provide as much info as possible: 23 | 24 | - Provide environment information by running `Preferences->Package Settings->RawLineEdit->Support Info`. The 25 | information will be copied to the clipboard; paste the info in issue. 26 | - Errors in console. 27 | - Detailed description of the problem. 28 | - Examples for reproducing the error. You can post pictures, but if specific text or code is required to 29 | reproduce the issue, please provide the text in a plain text format for easy copy/paste. 30 | 31 | The more info provided, the greater the chance someone will take the time to answer, implement, or fix the issue. 32 | 33 | 3. Be prepared to answer questions and provide additional information if required. Issues in which the creator refuses 34 | to respond to follow up questions will be marked as stale and closed. 35 | 36 | ## Become a Sponsor :octicons-heart-fill-16:{: .heart-throb} 37 | 38 | Open source projects take time and money. Help support the project by becoming a sponsor. You can add your support at 39 | any tier you feel comfortable with. No amount is too little. We also accept one time contributions via PayPal. 40 | 41 | [:octicons-mark-github-16: GitHub Sponsors](https://github.com/sponsors/facelessuser){: .md-button .md-button--primary } 42 | [:fontawesome-brands-paypal: PayPal](https://www.paypal.me/facelessuser){ .md-button} 43 | 44 | ## Reviewing Code 45 | 46 | Take part in reviewing pull requests and/or reviewing direct commits. Make suggestions to improve the code and discuss 47 | solutions to overcome weakness in the algorithm. 48 | 49 | ## Pull Requests 50 | 51 | Pull requests are welcome, and if you plan on contributing directly to the code, there are a couple of things to be 52 | mindful of. 53 | 54 | Continuous integration tests on are run on all pull requests and commits via Travis CI. When making a pull request, the 55 | tests will automatically be run, and the request must pass to be accepted. You can (and should) run these tests before 56 | pull requesting. If it is not possible to run these tests locally, they will be run when the pull request is made, but 57 | it is strongly suggested that requesters make an effort to verify before requesting to allow for a quick, smooth merge. 58 | 59 | Feel free to use a virtual environment if you are concerned about installing any of the Python packages. 60 | 61 | ### Running Validation Tests 62 | 63 | /// tip | Tip 64 | If you are running Sublime on a macOS or Linux/Unix system, you run all tests by by running the shell script 65 | (assuming you have installed your environment fulfills all requirements below): 66 | 67 | ``` 68 | chmod +x run_tests.sh 69 | ./run_tests.sh 70 | ``` 71 | /// 72 | 73 | There are a couple of dependencies that must be present before running the tests. 74 | 75 | 1. As ST3 is the only current, actively supported version, Python 3.3 must be used to validate the tests. 76 | 77 | 2. Unit tests are run with `pytest`. You can install `pytest` via: 78 | 79 | ``` 80 | pip install pytest 81 | ``` 82 | 83 | The tests should be run from the root folder of the plugin by using the following command: 84 | 85 | ``` 86 | py.test . 87 | ``` 88 | 89 | 3. Linting is performed on the entire project with `flake8` with the plugins listed below: 90 | 91 | ``` 92 | pip install flake8 93 | pip install flake8-docstrings 94 | pip install flake8-mutable 95 | pip install flake8-builtins 96 | pip install pep8-naming 97 | ``` 98 | 99 | Linting is performed with the following command: 100 | 101 | ``` 102 | flake8 . 103 | ``` 104 | 105 | ## Documentation Improvements 106 | 107 | A ton of time has been spent not only creating and supporting this plugin, but also spent making this documentation. If 108 | you feel it is still lacking, show your appreciation for the plugin by helping to improve the documentation. Help with 109 | documentation is always appreciated and can be done via pull requests. There shouldn't be any need to run validation 110 | tests if only updating documentation. 111 | 112 | You don't have to render the docs locally before pull requesting, but if you wish to, I currently use a combination of 113 | [MkDocs][mkdocs], the [Material theme][mkdocs-material], and [PyMdown Extensions][pymdown-extensions] to render the 114 | docs. You can preview the docs if you install these two packages. The command for previewing the docs is 115 | `mkdocs serve` from the root directory. You can then view the documents at `localhost:8000`. 116 | 117 | --8<-- "refs.md" 118 | -------------------------------------------------------------------------------- /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.1.0" 9 | __pc_name__ = 'RawLineEdit' 10 | 11 | CSS = ''' 12 | div.raw-line-edit { padding: 10px; margin: 0; } 13 | .raw-line-edit h1, .raw-line-edit h2, .raw-line-edit h3, 14 | .raw-line-edit h4, .raw-line-edit h5, .raw-line-edit h6 { 15 | {{'.string'|css}} 16 | } 17 | .raw-line-edit blockquote { {{'.comment'|css}} } 18 | .raw-line-edit a { text-decoration: none; } 19 | ''' 20 | 21 | 22 | def list2string(obj): 23 | """Convert list to string.""" 24 | 25 | return '.'.join([str(x) for x in obj]) 26 | 27 | 28 | def format_version(module, attr, call=False): 29 | """Format the version.""" 30 | 31 | try: 32 | if call: 33 | version = getattr(module, attr)() 34 | else: 35 | version = getattr(module, attr) 36 | except Exception as e: 37 | print(e) 38 | version = 'Version could not be acquired!' 39 | 40 | if not isinstance(version, str): 41 | version = list2string(version) 42 | return version 43 | 44 | 45 | def is_installed_by_package_control(): 46 | """Check if installed by package control.""" 47 | 48 | settings = sublime.load_settings('Package Control.sublime-settings') 49 | return str(__pc_name__ in set(settings.get('installed_packages', []))) 50 | 51 | 52 | class RawLineEditSupportInfoCommand(sublime_plugin.ApplicationCommand): 53 | """Support info.""" 54 | 55 | def run(self): 56 | """Run command.""" 57 | 58 | info = {} 59 | 60 | info["platform"] = sublime.platform() 61 | info["version"] = sublime.version() 62 | info["arch"] = sublime.arch() 63 | info["plugin_version"] = __version__ 64 | info["pc_install"] = is_installed_by_package_control() 65 | try: 66 | import mdpopups 67 | info["mdpopups_version"] = format_version(mdpopups, 'version', call=True) 68 | except Exception: 69 | info["mdpopups_version"] = 'Version could not be acquired!' 70 | 71 | msg = textwrap.dedent( 72 | """\ 73 | - ST ver.: %(version)s 74 | - Platform: %(platform)s 75 | - Arch: %(arch)s 76 | - Plugin ver.: %(plugin_version)s 77 | - Install via PC: %(pc_install)s 78 | - mdpopups ver.: %(mdpopups_version)s 79 | """ % info 80 | ) 81 | 82 | sublime.message_dialog(msg + '\nInfo has been copied to the clipboard.') 83 | sublime.set_clipboard(msg) 84 | 85 | 86 | class RawLineEditOpenSiteCommand(sublime_plugin.ApplicationCommand): 87 | """Open site links.""" 88 | 89 | def run(self, url): 90 | """Open the URL.""" 91 | 92 | webbrowser.open_new_tab(url) 93 | 94 | 95 | class RawLineEditDocCommand(sublime_plugin.WindowCommand): 96 | """Open doc page.""" 97 | 98 | re_pkgs = re.compile(r'^Packages') 99 | 100 | def on_navigate(self, href): 101 | """Handle links.""" 102 | 103 | if href.startswith('sub://Packages'): 104 | sublime.run_command('open_file', {"file": self.re_pkgs.sub('${packages}', href[6:])}) 105 | else: 106 | webbrowser.open_new_tab(href) 107 | 108 | def run(self, page): 109 | """Open page.""" 110 | 111 | try: 112 | import mdpopups 113 | has_phantom_support = (mdpopups.version() >= (1, 10, 0)) and (int(sublime.version()) >= 3124) 114 | except Exception: 115 | has_phantom_support = False 116 | 117 | if not has_phantom_support: 118 | sublime.run_command('open_file', {"file": page}) 119 | else: 120 | text = sublime.load_resource(page.replace('${packages}', 'Packages')) 121 | view = self.window.new_file() 122 | view.set_name('RawLineEdit - Quick Start') 123 | view.settings().set('gutter', False) 124 | view.settings().set('word_wrap', False) 125 | if has_phantom_support: 126 | mdpopups.add_phantom( 127 | view, 128 | 'quickstart', 129 | sublime.Region(0), 130 | text, 131 | sublime.LAYOUT_INLINE, 132 | css=CSS, 133 | wrapper_class="raw-line-edit", 134 | on_navigate=self.on_navigate 135 | ) 136 | else: 137 | view.run_command('insert', {"characters": text}) 138 | view.set_read_only(True) 139 | view.set_scratch(True) 140 | 141 | 142 | class RawLineEditChangesCommand(sublime_plugin.WindowCommand): 143 | """Changelog command.""" 144 | 145 | def run(self): 146 | """Show the changelog in a new view.""" 147 | try: 148 | import mdpopups 149 | has_phantom_support = (mdpopups.version() >= (1, 10, 0)) and (int(sublime.version()) >= 3124) 150 | except Exception: 151 | has_phantom_support = False 152 | 153 | text = sublime.load_resource('Packages/RawLineEdit/CHANGES.md') 154 | view = self.window.new_file() 155 | view.set_name('RawLineEdit - Changelog') 156 | view.settings().set('gutter', False) 157 | view.settings().set('word_wrap', False) 158 | if has_phantom_support: 159 | mdpopups.add_phantom( 160 | view, 161 | 'changelog', 162 | sublime.Region(0), 163 | text, 164 | sublime.LAYOUT_INLINE, 165 | wrapper_class="raw-line-edit", 166 | css=CSS, 167 | on_navigate=self.on_navigate 168 | ) 169 | else: 170 | view.run_command('insert', {"characters": text}) 171 | view.set_read_only(True) 172 | view.set_scratch(True) 173 | 174 | def on_navigate(self, href): 175 | """Open links.""" 176 | webbrowser.open_new_tab(href) 177 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /raw_line_edit.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Raw Line Edit. 4 | 5 | Licensed under MIT 6 | Copyright (c) 2013 - 2016 Isaac Muse 7 | """ 8 | from __future__ import unicode_literals 9 | import sublime 10 | import sublime_plugin 11 | import codecs 12 | import re 13 | from os.path import exists 14 | try: 15 | from SubNotify.sub_notify import SubNotifyIsReadyCommand as Notify 16 | except Exception: 17 | 18 | class Notify(object): 19 | """Fallback notify class.""" 20 | 21 | @classmethod 22 | def is_ready(cls): 23 | """Disable SubNotify by returning False.""" 24 | 25 | return False 26 | 27 | CSS = """ 28 | 46 | """ 47 | 48 | RE_NEW_LINE = re.compile(r'(?:\r\n|(?!\r\n)[\n\r])') 49 | 50 | 51 | def process_lines(text): 52 | """Count line ending types and return buffer with only new lines.""" 53 | 54 | crlf = [] 55 | lf = [] 56 | cr = [] 57 | line = {'value': -1} 58 | 59 | def repl(m): 60 | """Replace.""" 61 | 62 | line['value'] += 1 63 | end = m.group(0) 64 | if end == '\r\n': 65 | crlf.append(line['value']) 66 | elif end == '\n': 67 | lf.append(line['value']) 68 | else: 69 | cr.append((line['value'])) 70 | return '\n' 71 | 72 | text = RE_NEW_LINE.sub(repl, text) 73 | return text, lf, cr, crlf 74 | 75 | 76 | def strip_buffer_glyphs(view): 77 | """Strip all glyphs from buffer to load back into view.""" 78 | 79 | line = -1 80 | more = True 81 | last_value = -1 82 | lines = [] 83 | mappings = {'crlf': '\r\n', 'cr': '\r', 'lf': '\n'} 84 | while more: 85 | line += 1 86 | value = view.text_point(line, 0) 87 | region = None 88 | for line_type in ('crlf', 'cr', 'lf'): 89 | regions = view.get_regions('rle_line_%d_%s' % (line, line_type)) 90 | if regions: 91 | view.erase_regions('rle_line_%d_%s' % (line, line_type)) 92 | region = regions[0] 93 | break 94 | if region is not None: 95 | view.erase_phantoms('rle_line_%d' % line) 96 | lines.append(view.substr(view.line(region)) + mappings[line_type]) 97 | if value == last_value: 98 | more = False 99 | last_value = value 100 | return ''.join(lines) 101 | 102 | 103 | def convert_buffers(): 104 | """Operate on unsaved buffers.""" 105 | 106 | return bool(sublime.load_settings("raw_line_edit.sublime-settings").get("operate_on_unsaved_buffers", False)) 107 | 108 | 109 | def get_encoding(view): 110 | """Get the file encoding.""" 111 | 112 | encoding = view.encoding() 113 | mapping = [ 114 | ("with BOM", ""), 115 | ("Windows", "cp"), 116 | ("-", "_"), 117 | (" ", "") 118 | ] 119 | encoding = view.encoding() 120 | m = re.match(r'.+\((.*)\)', encoding) 121 | if m is not None: 122 | encoding = m.group(1) 123 | 124 | for item in mapping: 125 | encoding = encoding.replace(item[0], item[1]) 126 | 127 | return "utf_8" if encoding in ["Undefined", "Hexidecimal"] else encoding 128 | 129 | 130 | def notify(msg): 131 | """Notify message.""" 132 | 133 | settings = sublime.load_settings("raw_line_edit.sublime-settings") 134 | if settings.get("use_sub_notify", False) and Notify.is_ready(): 135 | sublime.run_command("sub_notify", {"title": "RawLineEdit", "msg": msg}) 136 | else: 137 | sublime.status_message(msg) 138 | 139 | 140 | def error(msg): 141 | """Error message.""" 142 | 143 | settings = sublime.load_settings("raw_line_edit.sublime-settings") 144 | if settings.get("use_sub_notify", False) and Notify.is_ready(): 145 | sublime.run_command("sub_notify", {"title": "RawLineEdit", "msg": msg, "level": "error"}) 146 | else: 147 | sublime.error_message("RawLineEdit:\n%s" % msg) 148 | 149 | 150 | class RawLineTextBuffer(object): 151 | """Text buffer.""" 152 | 153 | bfr = None 154 | syntax = None 155 | endings = None 156 | view = None 157 | 158 | @classmethod 159 | def set_buffer(cls, view): 160 | """Read buffer from view and strip new line glyphs.""" 161 | 162 | cls.bfr = strip_buffer_glyphs(view) 163 | 164 | @classmethod 165 | def clear_buffer(cls): 166 | """Clear the buffer object.""" 167 | 168 | cls.bfr = None 169 | cls.view = None 170 | 171 | @classmethod 172 | def check_loading(cls, view): 173 | """Check if file is done loading yet before applying buffer.""" 174 | 175 | cls.view = view 176 | sublime.set_timeout(cls.poll_loading, 300) 177 | 178 | @classmethod 179 | def poll_loading(cls): 180 | """Check if file is done loading, and if so, update view with buffer.""" 181 | 182 | if cls.view.is_loading(): 183 | sublime.set_timeout(cls.poll_loading, 300) 184 | else: 185 | cls.view.run_command("write_raw_line_text") 186 | 187 | 188 | class WriteRawLineTextCommand(sublime_plugin.TextCommand): 189 | """Write buffer to view.""" 190 | 191 | def run(self, edit): 192 | """Write the unsaved buffer to the view.""" 193 | 194 | if RawLineTextBuffer.bfr is None: 195 | return 196 | self.view.replace(edit, sublime.Region(0, self.view.size()), RawLineTextBuffer.bfr) 197 | RawLineTextBuffer.clear_buffer() 198 | 199 | 200 | class ToggleRawLineEditCommand(sublime_plugin.TextCommand): 201 | """Toggle raw line edit mode.""" 202 | 203 | def disable_rle(self, edit): 204 | """Disable raw line ending mode.""" 205 | 206 | # Save raw line ending changes 207 | if self.view.is_dirty(): 208 | if sublime.ok_cancel_dialog("Raw Line Edit:\nFile has unsaved changes. Save?", "Save"): 209 | self.view.run_command("save") 210 | 211 | # Get the settings 212 | settings = self.view.settings() 213 | file_name = settings.get("RawLineEditFilename") 214 | syntax = settings.get("RawLineEditSyntax") 215 | buffer_endings = settings.get("RawLineBuffer", None) 216 | 217 | # Strip the buffer of glyphs and prepare to write 218 | # the stripped buffer back to the view 219 | if buffer_endings is not None: 220 | RawLineTextBuffer.set_buffer(self.view) 221 | 222 | # Open temp view if only one view is open, 223 | # so not to close the window when we remove the view. 224 | window = self.view.window() 225 | temp = None 226 | if len(window.views()) <= 1: 227 | temp = window.new_file() 228 | 229 | # Close raw line view 230 | window.focus_view(self.view) 231 | window.run_command("close_file") 232 | 233 | # Open the file on disk 234 | new_view = window.open_file(file_name) 235 | 236 | # Close temp view if needed 237 | if temp is not None: 238 | window.focus_view(temp) 239 | window.run_command("close_file") 240 | 241 | # Set view settings 242 | window.focus_view(new_view) 243 | new_view.set_syntax_file(syntax) 244 | 245 | # Reapply unsaved buffer if needed 246 | if buffer_endings is not None: 247 | new_view.set_line_endings(buffer_endings) 248 | RawLineTextBuffer.check_loading(new_view) 249 | 250 | def enable_rle(self, edit, file_name): 251 | """Enable raw line ending mode.""" 252 | 253 | if self.view.is_dirty(): 254 | if convert_buffers(): 255 | msg = ( 256 | "File has unsaved changes. If you choose to 'continue' without a 'save', " 257 | "the view buffer will be parsed as the source.\n\nSave?" 258 | ) 259 | else: 260 | msg = ( 261 | "File has unsaved changes. If you choose to 'continue' without a 'save', " 262 | "changes will be discarded and the file will be parsed from disk.\n\nSave?" 263 | ) 264 | value = sublime.yes_no_cancel_dialog(msg, "Save", "Continue") 265 | if value == sublime.DIALOG_YES: 266 | # Save the file 267 | self.view.run_command("save") 268 | elif value == sublime.DIALOG_NO: 269 | # Convert the unsaved buffer 270 | if convert_buffers(): 271 | self.enable_buffer_rle(edit, file_name) 272 | return 273 | else: 274 | if file_name is None: 275 | error("File must exist on disk!") 276 | return 277 | else: 278 | notify("Changes discarded.") 279 | else: 280 | return 281 | 282 | if file_name is None or not exists(file_name): 283 | if convert_buffers(): 284 | self.enable_buffer_rle(edit) 285 | else: 286 | error("File must exist on disk!") 287 | return 288 | 289 | # Convert the file on disk to a raw line view 290 | encoding = get_encoding(self.view) 291 | try: 292 | self.show_rle(edit, file_name, encoding) 293 | except Exception: 294 | self.show_rle(edit, file_name, "utf-8") 295 | 296 | def show_rle(self, edit, file_name, encoding): 297 | """ 298 | Read the file from disk converting actual lines to glyphs. 299 | 300 | Present the info in raw line view. 301 | """ 302 | with codecs.open(file_name, "r", encoding) as f: 303 | text, lf, cr, crlf = process_lines(f.read()) 304 | self.view.replace(edit, sublime.Region(0, self.view.size()), text) 305 | self.view.set_line_endings("Unix") 306 | settings = self.view.settings() 307 | settings.set("RawLineEdit", True) 308 | settings.set("RawLineEditSyntax", settings.get('syntax')) 309 | settings.set("RawLineEditFilename", file_name) 310 | self.view.assign_syntax(settings.get('syntax')) 311 | self.view.set_scratch(True) 312 | self.view.set_read_only(True) 313 | 314 | self.update_phantoms(crlf, cr, lf) 315 | 316 | def read_buffer(self): 317 | """Read the unsaved buffer and replace with new line glyphs.""" 318 | 319 | endings = { 320 | "Windows": "\r\n", 321 | "Unix": "\n", 322 | "CR": "\r" 323 | } 324 | line_ending = endings[self.view.line_endings()] 325 | bfr = [] 326 | for line in self.view.split_by_newlines(sublime.Region(0, self.view.size())): 327 | bfr.append(self.view.substr(line) + line_ending) 328 | return "".join(bfr) 329 | 330 | def enable_buffer_rle(self, edit, file_name=None): 331 | """Enable the raw line mode on an unsaved buffer.""" 332 | 333 | if self.view.is_read_only(): 334 | self.view.set_read_only(False) 335 | settings = self.view.settings() 336 | self.view.settings().set("RawLineBuffer", self.view.line_endings()) 337 | text, lf, cr, crlf = process_lines(self.read_buffer()) 338 | self.view.replace(edit, sublime.Region(0, self.view.size()), text) 339 | self.view.set_line_endings("Unix") 340 | settings.set("RawLineEdit", True) 341 | settings.set("RawLineEditSyntax", self.view.settings().get('syntax')) 342 | if file_name is not None: 343 | settings.set("RawLineEditFilename", file_name) 344 | self.view.set_scratch(True) 345 | self.view.set_read_only(True) 346 | self.update_phantoms(crlf, cr, lf) 347 | 348 | def disable_buffer_rle(self, edit): 349 | """Disable the raw line mode on an unsaved buffer.""" 350 | 351 | if self.view.is_dirty(): 352 | if sublime.ok_cancel_dialog("Raw Line Edit:\nFile has unsaved changes. Save?", "Save"): 353 | self.view.run_command("save") 354 | self.disable_rle(edit) 355 | return 356 | win = self.view.window() 357 | new_view = win.new_file() 358 | settings = self.view.settings() 359 | syntax = settings.get("RawLineEditSyntax") 360 | line_endings = settings.get("RawLineBuffer") 361 | bfr = strip_buffer_glyphs(self.view) 362 | new_view.replace(edit, sublime.Region(0, self.view.size()), bfr) 363 | new_view.set_line_endings(line_endings) 364 | new_view.set_syntax_file(syntax) 365 | win.focus_view(self.view) 366 | win.run_command("close_file") 367 | win.focus_view(new_view) 368 | 369 | def update_phantoms(self, crlf, cr, lf): 370 | """Update phantoms.""" 371 | 372 | for line in crlf: 373 | pt = self.view.text_point(line + 1, 0) - 1 374 | region = sublime.Region(pt) 375 | self.view.add_phantom( 376 | 'rle_line_%d' % line, 377 | region, 378 | '%s¤¬' % CSS, 379 | sublime.LAYOUT_INLINE 380 | ) 381 | self.view.add_regions('rle_line_%d_crlf' % line, [region], '', '', sublime.HIDDEN) 382 | for line in cr: 383 | pt = self.view.text_point(line + 1, 0) - 1 384 | region = sublime.Region(pt) 385 | self.view.add_phantom( 386 | 'rle_line_%d' % line, 387 | region, 388 | '%s¤' % CSS, 389 | sublime.LAYOUT_INLINE 390 | ) 391 | self.view.add_regions('rle_line_%d_cr' % line, [region], '', '', sublime.HIDDEN) 392 | for line in lf: 393 | pt = self.view.text_point(line + 1, 0) - 1 394 | region = sublime.Region(pt) 395 | self.view.add_phantom( 396 | 'rle_line_%d' % line, 397 | region, 398 | '%s¬' % CSS, 399 | sublime.LAYOUT_INLINE 400 | ) 401 | self.view.add_regions('rle_line_%d_lf' % line, [region], '', '', sublime.HIDDEN) 402 | 403 | def run(self, edit): 404 | """Toggle the raw line mode.""" 405 | 406 | file_name = self.view.file_name() 407 | settings = self.view.settings() 408 | 409 | if (file_name is None or not exists(file_name)) and settings.get("RawLineEdit", False): 410 | self.disable_buffer_rle(edit) 411 | elif settings.get("RawLineEdit", False): 412 | self.disable_rle(edit) 413 | elif not settings.get("RawLineEdit", False): 414 | self.enable_rle(edit, file_name) 415 | 416 | 417 | class PopupRawLineEditCommand(sublime_plugin.TextCommand): 418 | """Popup command.""" 419 | 420 | def popup_rle(self, file_name): 421 | """Popup raw line edit view.""" 422 | 423 | if self.view.is_dirty(): 424 | if convert_buffers(): 425 | msg = ( 426 | "File has unsaved changes. If you choose to 'continue' without a 'save', " 427 | "the view buffer will be parsed as the source.\n\nSave?" 428 | ) 429 | else: 430 | msg = ( 431 | "File has unsaved changes. If you choose to 'continue' without a 'save', " 432 | "changes will be discarded and the file will be parsed from disk.\n\nSave?" 433 | ) 434 | value = sublime.yes_no_cancel_dialog(msg, "Save", "Discard Changes", "Cancel") 435 | if value == sublime.DIALOG_YES: 436 | self.view.run_command("save") 437 | elif value == sublime.DIALOG_NO: 438 | # Convert the unsaved buffer 439 | if convert_buffers(): 440 | self.enable_buffer_rle(file_name) 441 | return 442 | else: 443 | if file_name is None: 444 | error("File must exist on disk!") 445 | return 446 | else: 447 | notify("Changes discarded.") 448 | else: 449 | return 450 | 451 | if file_name is None or not exists(file_name): 452 | if convert_buffers(): 453 | self.enable_buffer_rle() 454 | else: 455 | error("File must exist on disk!") 456 | return 457 | 458 | encoding = get_encoding(self.view) 459 | try: 460 | self.show_rle(file_name, encoding) 461 | except Exception: 462 | self.show_rle(file_name, "utf-8") 463 | 464 | def read_buffer(self): 465 | """Read the unsaved buffer and replace with new line glyphs.""" 466 | 467 | endings = { 468 | "Windows": "\r\n", 469 | "Unix": "\n", 470 | "CR": "\r" 471 | } 472 | line_ending = endings[self.view.line_endings()] 473 | bfr = [] 474 | for line in self.view.split_by_newlines(sublime.Region(0, self.view.size())): 475 | bfr.append(self.view.substr(line) + line_ending) 476 | return "".join(bfr) 477 | 478 | def get_output_panel(self): 479 | """Get output panel.""" 480 | 481 | win = self.view.window() 482 | view = win.find_output_panel('raw_line_edit_view') 483 | if view is not None: 484 | win.destroy_output_panel('raw_line_edit_view') 485 | return win.get_output_panel('raw_line_edit_view') 486 | 487 | def enable_buffer_rle(self, file_name=None): 488 | """Enable the raw line mode on an unsaved buffer.""" 489 | 490 | view = self.get_output_panel() 491 | view.set_line_endings("Unix") 492 | view.set_read_only(False) 493 | 494 | RawLinesEditReplaceCommand.region = sublime.Region(0, view.size()) 495 | RawLinesEditReplaceCommand.text, lf, cr, crlf = process_lines(self.read_buffer()) 496 | view.run_command("raw_lines_edit_replace") 497 | view.sel().clear() 498 | settings = view.settings() 499 | view.assign_syntax(self.view.settings().get('syntax')) 500 | settings.set("RawLineEdit", True) 501 | settings.set("RawLineEditSyntax", self.view.settings().get('syntax')) 502 | settings.set("RawLineEditPopup", True) 503 | settings.set("RawLineBuffer", self.view.line_endings()) 504 | if file_name is not None: 505 | settings.set("RawLineEditFilename", file_name) 506 | view.set_scratch(True) 507 | view.set_read_only(True) 508 | 509 | self.update_phantoms(view, crlf, cr, lf) 510 | self.view.window().run_command("show_panel", {"panel": "output.raw_line_edit_view"}) 511 | 512 | def show_rle(self, file_name, encoding): 513 | """Show the raw line view popup.""" 514 | 515 | try: 516 | view = self.get_output_panel() 517 | view.set_line_endings("Unix") 518 | with codecs.open(file_name, "r", encoding) as f: 519 | view.set_read_only(False) 520 | RawLinesEditReplaceCommand.region = sublime.Region(0, view.size()) 521 | RawLinesEditReplaceCommand.text, lf, cr, crlf = process_lines(f.read()) 522 | view.run_command("raw_lines_edit_replace") 523 | view.sel().clear() 524 | view.assign_syntax(self.view.settings().get('syntax')) 525 | view.settings().set("RawLineEditSyntax", self.view.settings().get('syntax')) 526 | view.settings().set("RawLineEdit", True) 527 | view.settings().set("RawLineEditFilename", file_name) 528 | view.settings().set("RawLineEditPopup", True) 529 | view.set_scratch(True) 530 | view.set_read_only(True) 531 | 532 | self.update_phantoms(view, crlf, cr, lf) 533 | self.view.window().run_command("show_panel", {"panel": "output.raw_line_edit_view"}) 534 | except Exception: 535 | self.view.window().run_command("hide_panel", {"panel": "output.raw_line_edit_view"}) 536 | raise 537 | 538 | def update_phantoms(self, view, crlf, cr, lf): 539 | """Update phantoms.""" 540 | 541 | for line in crlf: 542 | pt = view.text_point(line + 1, 0) - 1 543 | region = sublime.Region(pt) 544 | view.add_phantom( 545 | 'rle_line_%d' % line, 546 | region, 547 | '%s¤¬' % CSS, 548 | sublime.LAYOUT_INLINE 549 | ) 550 | view.add_regions('rle_line_%d_crlf' % line, [region], '', '', sublime.HIDDEN) 551 | for line in cr: 552 | pt = view.text_point(line + 1, 0) - 1 553 | region = sublime.Region(pt) 554 | view.add_phantom( 555 | 'rle_line_%d' % line, 556 | region, 557 | '%s¤' % CSS, 558 | sublime.LAYOUT_INLINE 559 | ) 560 | view.add_regions('rle_line_%d_cr' % line, [region], '', '', sublime.HIDDEN) 561 | for line in lf: 562 | pt = view.text_point(line + 1, 0) - 1 563 | region = sublime.Region(pt) 564 | view.add_phantom( 565 | 'rle_line_%d' % line, 566 | region, 567 | '%s¬' % CSS, 568 | sublime.LAYOUT_INLINE 569 | ) 570 | view.add_regions('rle_line_%d_lf' % line, [region], '', '', sublime.HIDDEN) 571 | 572 | def run(self, edit): 573 | """Popup panel with raw line view.""" 574 | 575 | file_name = self.view.file_name() 576 | settings = self.view.settings() 577 | if (not settings.get("RawLineEdit", False)) and not settings.get('RawLineEditPopup', False): 578 | self.popup_rle(file_name) 579 | 580 | 581 | class RawLineInsertCommand(sublime_plugin.TextCommand): 582 | """Insert text in view.""" 583 | 584 | def run(self, edit, style="Unix"): 585 | """Insert text.""" 586 | 587 | for s in reversed(self.view.sel()): 588 | line_regions = self.view.lines(s) 589 | for region in line_regions: 590 | row = self.view.rowcol(region.begin())[0] 591 | if row >= 0: 592 | r = None 593 | for line_type in ('crlf', 'cr', 'lf'): 594 | temp = self.view.get_regions('rle_line_%d_%s' % (row, line_type)) 595 | if temp: 596 | r = temp[0] 597 | break 598 | if r is not None: 599 | self.view.erase_regions('rle_line_%d_%s' % (row, line_type)) 600 | self.view.erase_phantoms('rle_line_%d' % row) 601 | 602 | pt = self.view.text_point(row + 1, 0) - 1 603 | r = sublime.Region(pt) 604 | if style == "Unix": 605 | temp = '%s¬' 606 | line_type = 'lf' 607 | elif style == "Windows": 608 | temp = '%s¤¬' 609 | line_type = 'crlf' 610 | else: 611 | temp = '%s¤' 612 | line_type = 'cr' 613 | self.view.add_phantom( 614 | 'rle_line_%d' % row, 615 | r, 616 | temp % CSS, 617 | sublime.LAYOUT_INLINE 618 | ) 619 | self.view.add_regions('rle_line_%d_%s' % (row, line_type), [r], '', '', sublime.HIDDEN) 620 | 621 | 622 | class RawLinesEditReplaceCommand(sublime_plugin.TextCommand): 623 | """Replace text in view.""" 624 | 625 | text = None 626 | region = None 627 | 628 | def run(self, edit): 629 | """Replace text.""" 630 | 631 | cls = RawLinesEditReplaceCommand 632 | if cls.text is not None and cls.region is not None: 633 | self.view.replace(edit, cls.region, cls.text) 634 | cls.text = None 635 | cls.region = None 636 | 637 | 638 | class RawLineEditListener(sublime_plugin.EventListener): 639 | """RawLineEdit Listener.""" 640 | 641 | def on_pre_save(self, view): 642 | """Convert raw line mode back to normal mode before save.""" 643 | 644 | if view.settings().get("RawLineEdit", False) and not view.settings().get('RawLineEditPopup', False): 645 | RawLinesEditReplaceCommand.region = sublime.Region(0, view.size()) 646 | RawLinesEditReplaceCommand.text = strip_buffer_glyphs(view) 647 | view.set_read_only(False) 648 | view.run_command("raw_lines_edit_replace") 649 | view.set_read_only(True) 650 | 651 | def on_post_save(self, view): 652 | """Convert view back to raw line mode after save.""" 653 | 654 | if view.settings().get("RawLineEdit", False) and not view.settings().get('RawLineEditPopup', False): 655 | file_name = view.file_name() 656 | if file_name is not None: 657 | view.settings().set("RawLineEditFilename", file_name) 658 | if view.settings().set("RawLineBuffer", None) is not None: 659 | view.settings().erase("RawLineBuffer") 660 | 661 | view.set_read_only(False) 662 | RawLinesEditReplaceCommand.region = sublime.Region(0, view.size()) 663 | RawLinesEditReplaceCommand.text, lf, cr, crlf = process_lines( 664 | view.substr(RawLinesEditReplaceCommand.region) 665 | ) 666 | view.run_command("raw_lines_edit_replace") 667 | 668 | for line in crlf: 669 | pt = view.text_point(line + 1, 0) - 1 670 | region = sublime.Region(pt) 671 | view.add_phantom( 672 | 'rle_line_%d' % line, 673 | region, 674 | '%s¤¬' % CSS, 675 | sublime.LAYOUT_INLINE 676 | ) 677 | view.add_regions('rle_line_%d_crlf' % line, [region], '', '', sublime.HIDDEN) 678 | for line in cr: 679 | pt = view.text_point(line + 1, 0) - 1 680 | region = sublime.Region(pt) 681 | view.add_phantom( 682 | 'rle_line_%d' % line, 683 | region, 684 | '%s¤' % CSS, 685 | sublime.LAYOUT_INLINE 686 | ) 687 | view.add_regions('rle_line_%d_cr' % line, [region], '', '', sublime.HIDDEN) 688 | for line in lf: 689 | pt = view.text_point(line + 1, 0) - 1 690 | region = sublime.Region(pt) 691 | view.add_phantom( 692 | 'rle_line_%d' % line, 693 | region, 694 | '%s¬' % CSS, 695 | sublime.LAYOUT_INLINE 696 | ) 697 | view.add_regions('rle_line_%d_lf' % line, [region], '', '', sublime.HIDDEN) 698 | 699 | view.set_scratch(True) 700 | view.set_read_only(True) 701 | 702 | def on_query_context(self, view, key, operator, operand, match_all): 703 | """Handle raw line mode shortcuts.""" 704 | 705 | settings = view.settings() 706 | return ( 707 | settings.get("RawLineEdit", False) and key.startswith("raw_line_edit") and 708 | not settings.get('RawLineEditPopup', False) 709 | ) 710 | --------------------------------------------------------------------------------