├── assets ├── .ignore ├── .gitattributes ├── themes.bin ├── screenshot.jpg ├── syntaxes.bin ├── theme_preview.rs ├── JavaDoc.sublime-syntax.patch ├── create.sh ├── syntaxes │ ├── Pikalang.sublime-syntax │ ├── show-nonprintable.sublime-syntax │ ├── INI.sublime-syntax │ ├── Cabal.sublime-syntax │ ├── CSV.sublime-syntax │ ├── Robot.sublime-syntax │ ├── VimL.sublime-syntax │ ├── FSharp.sublime-syntax │ ├── Dart.sublime-syntax │ ├── Kotlin.sublime-syntax │ ├── Swift.sublime-syntax │ ├── Nix.sublime-syntax │ ├── PowerShell.sublime-syntax │ └── HTML (Twig).sublime-syntax └── README.md ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── src ├── dirs.rs ├── syntax_mapping.rs ├── terminal.rs ├── style.rs ├── preprocessor.rs ├── decorations.rs ├── output.rs ├── inputfile.rs ├── lib.rs ├── assets.rs ├── line_range.rs ├── builder.rs └── printer.rs ├── fixtures └── fib.rs ├── examples ├── load-theme.rs ├── stderr.rs ├── dynamic-config.rs └── load-syntax.rs ├── LICENSE_MIT ├── Cargo.toml ├── README.md └── LICENSE_APACHE /assets/.ignore: -------------------------------------------------------------------------------- 1 | syntaxes/* 2 | themes/* 3 | -------------------------------------------------------------------------------- /assets/.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-vendored 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: mre 2 | patreon: hellorust 3 | -------------------------------------------------------------------------------- /assets/themes.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mre/prettyprint/HEAD/assets/themes.bin -------------------------------------------------------------------------------- /assets/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mre/prettyprint/HEAD/assets/screenshot.jpg -------------------------------------------------------------------------------- /assets/syntaxes.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mre/prettyprint/HEAD/assets/syntaxes.bin -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Highlighting Assets 2 | **/*.bin 3 | 4 | # Rust 5 | /target 6 | **/*.rs.bk 7 | Cargo.lock 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /assets/theme_preview.rs: -------------------------------------------------------------------------------- 1 | // Output the square of a number. 2 | fn print_square(num: f64) { 3 | let result = f64::powf(num, 2.0); 4 | println!("The square of {:.2} is {:.2}.", num, result); 5 | } 6 | -------------------------------------------------------------------------------- /src/dirs.rs: -------------------------------------------------------------------------------- 1 | use crate::directories::ProjectDirs; 2 | 3 | lazy_static! { 4 | pub static ref PROJECT_DIRS: ProjectDirs = 5 | ProjectDirs::from("", "", crate_name!()).expect("Could not get home directory"); 6 | } 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: derive_builder 11 | versions: 12 | - 0.10.0 13 | -------------------------------------------------------------------------------- /fixtures/fib.rs: -------------------------------------------------------------------------------- 1 | /// Fibonacci implementation in Rust 2 | /// ``` 3 | /// assert_eq!(fib(0), 1); 4 | /// assert_eq!(fib(1), 1); 5 | /// assert_eq!(fib(9), 55); 6 | /// ``` 7 | pub fn fib(n: usize) -> usize { 8 | match n { 9 | 0 | 1 => 1, 10 | _ => fib(n - 1) + fib(n - 2), 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/load-theme.rs: -------------------------------------------------------------------------------- 1 | //! Run 2 | //! ``` 3 | //! cargo run --example load-theme 4 | //! ``` 5 | 6 | use prettyprint::{PrettyPrintError, PrettyPrinter}; 7 | 8 | fn main() -> Result<(), PrettyPrintError> { 9 | let theme = include_bytes!("../assets/themes.bin"); 10 | 11 | let print = PrettyPrinter::default() 12 | .language("rust") 13 | .load_theme(theme.to_vec()) 14 | .build()?; 15 | 16 | print.string(include_str!("../fixtures/fib.rs")) 17 | } 18 | -------------------------------------------------------------------------------- /examples/stderr.rs: -------------------------------------------------------------------------------- 1 | //! Run 2 | //! ``` 3 | //! cargo run --example stderr 2> error.log 4 | //! ``` 5 | //! then the `error.log` file should contain `ERROR: unexpected` with some gibberish 6 | 7 | use prettyprint::{PagingMode, PrettyPrintError, PrettyPrinter}; 8 | 9 | fn main() -> Result<(), PrettyPrintError> { 10 | let epprint = PrettyPrinter::default() 11 | .paging_mode(PagingMode::Error) 12 | // Comment ☝️ to make `error.log` empty 13 | .build() 14 | .unwrap(); 15 | 16 | epprint.string("ERROR: unexpected") 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | # Runs every Thursday at 20:23 GMT to avoid bit rot 10 | - cron: "20 23 * * 4" 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: 18 | - ubuntu-latest 19 | - macos-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Build 24 | run: cargo build --verbose 25 | - name: Run tests 26 | run: cargo test --verbose 27 | -------------------------------------------------------------------------------- /assets/JavaDoc.sublime-syntax.patch: -------------------------------------------------------------------------------- 1 | diff --git syntaxes/Packages/Java/JavaDoc.sublime-syntax syntaxes/Packages/Java/JavaDoc.sublime-syntax 2 | index 422a6a9..40a741e 100644 3 | --- syntaxes/Packages/Java/JavaDoc.sublime-syntax 4 | +++ syntaxes/Packages/Java/JavaDoc.sublime-syntax 5 | @@ -13,7 +13,7 @@ variables: 6 | contexts: 7 | prototype: 8 | # https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#leadingasterisks 9 | - - match: ^\s*(\*)\s*(?!\s*@) 10 | + - match: ^\s*(\*)(?!/)\s*(?!\s*@) 11 | captures: 12 | 1: punctuation.definition.comment.javadoc 13 | 14 | -------------------------------------------------------------------------------- /assets/create.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ASSET_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | # Always remove the local cache to avoid any confusion 6 | prettyprint cache --clear 7 | 8 | # TODO: Remove this (and the reverse part below) when 9 | # https://github.com/trishume/syntect/issues/222 has been fixed 10 | JAVADOC_FILE="${ASSET_DIR}/syntaxes/Packages/Java/JavaDoc.sublime-syntax" 11 | JAVADOC_PATCH="${ASSET_DIR}/JavaDoc.sublime-syntax.patch" 12 | patch "$JAVADOC_FILE" "$JAVADOC_PATCH" 13 | 14 | prettyprint cache --init --blank --source="$ASSET_DIR" --target="$ASSET_DIR" 15 | 16 | patch -R "$JAVADOC_FILE" "$JAVADOC_PATCH" 17 | -------------------------------------------------------------------------------- /examples/dynamic-config.rs: -------------------------------------------------------------------------------- 1 | //! Run 2 | //! ``` 3 | //! cargo run --example dynamic-config 4 | //! ``` 5 | 6 | use prettyprint::{PrettyPrintError, PrettyPrinter}; 7 | 8 | fn main() -> Result<(), PrettyPrintError> { 9 | let print = PrettyPrinter::default() 10 | .language("rust") 11 | .grid(false) 12 | .line_numbers(false) 13 | .build() 14 | .unwrap(); 15 | 16 | print.string("fn main() {")?; 17 | 18 | for x in 0..9 { 19 | let printer = print.configure().grid(true).build().unwrap(); 20 | printer.string(format!("let x = {};", x))?; 21 | } 22 | 23 | print.string("}") 24 | } 25 | -------------------------------------------------------------------------------- /assets/syntaxes/Pikalang.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: Pikalang 5 | file_extensions: [pokeball] # https://github.com/groteworld/pikalang#file-extention 6 | scope: source.pikalang 7 | contexts: 8 | main: # https://github.com/groteworld/pikalang#syntax 9 | - match: \spi\s 10 | scope: markup.underline.pikalang 11 | - match: \ska\s 12 | scope: markup.underline.pikalang 13 | - match: \spika\s 14 | scope: keyword.control.name 15 | - match: \schu\s 16 | scope: keyword.control.name 17 | - match: \spipi\s 18 | scope: keyword.operator.pikalang 19 | - match: \spichu\s 20 | scope: keyword.operator.pikalang 21 | - match: \spikapi\s 22 | scope: support.function.name 23 | - match: \spikachu\s 24 | scope: support.function.name 25 | - match: "#.*$" 26 | scope: comment.pikalang 27 | 28 | -------------------------------------------------------------------------------- /assets/syntaxes/show-nonprintable.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: Highlight non-printables 5 | file_extensions: 6 | - show-nonprintable 7 | scope: whitespace 8 | contexts: 9 | main: 10 | - match: "•" 11 | scope: support.function.show-nonprintable.space 12 | - match: "├─*┤" 13 | scope: constant.character.escape.show-nonprintable.tab 14 | - match: "↹" 15 | scope: constant.character.escape.show-nonprintable.tab 16 | - match: "␊" 17 | scope: keyword.operator.show-nonprintable.line-feed 18 | - match: "␍" 19 | scope: string.show-nonprintable.carriage-return 20 | - match: "␀" 21 | scope: entity.other.attribute-name.show-nonprintable.null 22 | - match: "␇" 23 | scope: entity.other.attribute-name.show-nonprintable.bell 24 | - match: "␛" 25 | scope: entity.other.attribute-name.show-nonprintable.escape 26 | - match: "␈" 27 | scope: entity.other.attribute-name.show-nonprintable.backspace 28 | -------------------------------------------------------------------------------- /examples/load-syntax.rs: -------------------------------------------------------------------------------- 1 | //! Run 2 | //! ``` 3 | //! cargo run --example load-syntax 4 | //! ``` 5 | 6 | use prettyprint::{PrettyPrintError, PrettyPrinter}; 7 | 8 | const LANGUAGE: &str = "pikalang"; // https://github.com/groteworld/pikalang 9 | const CODE: &str = " 10 | pi pi pi pi pi pi pi pi pi pi pika pipi pi pi pi pi pi pi pi pipi pi pi pi 11 | pi pi pi pi pi pi pi pipi pi pi pi pipi pi pichu pichu pichu pichu ka chu 12 | pipi pi pi pikachu pipi pi pikachu pi pi pi pi pi pi pi pikachu pikachu pi 13 | pi pi pikachu pipi pi pi pikachu pichu pichu pi pi pi pi pi pi pi pi pi pi 14 | pi pi pi pi pi pikachu pipi pikachu pi pi pi pikachu ka ka ka ka ka ka 15 | pikachu ka ka ka ka ka ka ka ka pikachu pipi pi pikachu pipi pikachu"; 16 | 17 | fn main() -> Result<(), PrettyPrintError> { 18 | let syntax = include_bytes!("../assets/syntaxes.bin"); 19 | 20 | let print = PrettyPrinter::default() 21 | .language(LANGUAGE) 22 | .load_syntax(syntax.to_vec()) 23 | .build()?; 24 | 25 | print.string(CODE) 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE_MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 bat-developers (https://github.com/sharkdp/bat), Matthias Endler . 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/syntax_mapping.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct SyntaxMapping(HashMap); 6 | 7 | impl Default for SyntaxMapping { 8 | fn default() -> Self { 9 | SyntaxMapping(HashMap::new()) 10 | } 11 | } 12 | 13 | impl SyntaxMapping { 14 | pub fn new() -> SyntaxMapping { 15 | SyntaxMapping(HashMap::new()) 16 | } 17 | 18 | pub fn insert(&mut self, from: String, to: String) -> Option { 19 | self.0.insert(from, to) 20 | } 21 | 22 | pub fn replace<'a>(&self, input: &'a str) -> Cow<'a, str> { 23 | let mut out = Cow::from(input); 24 | if let Some(value) = self.0.get(input) { 25 | out = Cow::from(value.clone()) 26 | } 27 | out 28 | } 29 | } 30 | 31 | #[test] 32 | fn basic() { 33 | let mut map = SyntaxMapping::new(); 34 | map.insert("Cargo.lock".into(), "toml".into()); 35 | map.insert(".ignore".into(), ".gitignore".into()); 36 | 37 | assert_eq!("toml", map.replace("Cargo.lock")); 38 | assert_eq!("other.lock", map.replace("other.lock")); 39 | 40 | assert_eq!(".gitignore", map.replace(".ignore")); 41 | } 42 | -------------------------------------------------------------------------------- /src/terminal.rs: -------------------------------------------------------------------------------- 1 | extern crate ansi_colours; 2 | 3 | use ansi_term::Colour::{Fixed, RGB}; 4 | use ansi_term::{self, Style}; 5 | use syntect::highlighting::{self, FontStyle}; 6 | 7 | pub fn to_ansi_color(color: highlighting::Color, true_color: bool) -> ansi_term::Colour { 8 | if true_color { 9 | RGB(color.r, color.g, color.b) 10 | } else { 11 | Fixed(ansi_colours::ansi256_from_rgb((color.r, color.g, color.b))) 12 | } 13 | } 14 | 15 | pub fn as_terminal_escaped( 16 | style: highlighting::Style, 17 | text: &str, 18 | true_color: bool, 19 | colored: bool, 20 | italics: bool, 21 | ) -> String { 22 | let style = if !colored { 23 | Style::default() 24 | } else { 25 | let color = to_ansi_color(style.foreground, true_color); 26 | 27 | if style.font_style.contains(FontStyle::BOLD) { 28 | color.bold() 29 | } else if style.font_style.contains(FontStyle::UNDERLINE) { 30 | color.underline() 31 | } else if italics && style.font_style.contains(FontStyle::ITALIC) { 32 | color.italic() 33 | } else { 34 | color.normal() 35 | } 36 | }; 37 | 38 | style.paint(text).to_string() 39 | } 40 | -------------------------------------------------------------------------------- /assets/syntaxes/INI.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: INI 5 | file_extensions: 6 | - ini 7 | - INI 8 | - inf 9 | - INF 10 | - reg 11 | - REG 12 | - lng 13 | - cfg 14 | - CFG 15 | - desktop 16 | - url 17 | - URL 18 | - .editorconfig 19 | - .hgrc 20 | - hgrc 21 | scope: source.ini 22 | contexts: 23 | main: 24 | - match: ^\s*(;|#).*$\n? 25 | scope: comment.line.semicolon.ini 26 | captures: 27 | 1: punctuation.definition.comment.ini 28 | - match: '^\s*(\[)(.*?)(\])' 29 | scope: meta.tag.section.ini 30 | captures: 31 | 1: punctuation.definition.section.ini 32 | 2: entity.section.ini 33 | 3: punctuation.definition.section.ini 34 | - match: '^(\s*(["'']?)(.+?)(\2)\s*(=))?\s*((["'']?)(.*?)(\7))\s*(;.*)?$\n?' 35 | scope: meta.declaration.ini 36 | captures: 37 | 1: meta.property.ini 38 | 2: punctuation.definition.quote.ini 39 | 3: keyword.name.ini 40 | 4: punctuation.definition.quote.ini 41 | 5: punctuation.definition.equals.ini 42 | 6: meta.value.ini 43 | 7: punctuation.definition.quote.ini 44 | 8: string.name.value.ini 45 | 9: punctuation.definition.quote.ini 46 | 10: comment.declarationline.semicolon.ini 47 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prettyprint" 3 | license = "Apache-2.0/MIT" 4 | readme = "README.md" 5 | repository = "https://github.com/mre/prettyprint" 6 | version = "0.8.1" 7 | edition = "2018" 8 | authors = [ 9 | "Matthias Endler ", 10 | ] 11 | categories = [ 12 | "command-line-interface", 13 | "parsing", 14 | ] 15 | description = "Print beautifully formatted files and strings to your terminal" 16 | documentation = "https://github.com/mre/prettyprint/blob/master/README.md" 17 | homepage = "https://github.com/mre/prettyprint" 18 | keywords = [ 19 | "syntax", 20 | "highlighting", 21 | "highlighter", 22 | "colouring", 23 | "prettyprint" 24 | ] 25 | 26 | [dependencies] 27 | ansi_colours = "1.0.1" 28 | error-chain = "0.12.2" 29 | ansi_term = "0.12.1" 30 | clap = "4.0.2" 31 | console = "0.15.0" 32 | content_inspector = "0.2.4" 33 | directories = "5.0.0" 34 | encoding = "0.2.33" 35 | shell-words = "1.0.0" 36 | lazy_static = "1.4.0" 37 | atty = "0.2.14" 38 | derive_builder = "0.12.0" 39 | 40 | [dependencies.syntect] 41 | version = "5.0.0" 42 | default-features = false 43 | features = ["parsing", "dump-load"] 44 | 45 | [features] 46 | default = ["regex-onig"] 47 | regex-onig = ["syntect/regex-onig"] # Use the "oniguruma" regex engine 48 | regex-fancy = ["syntect/regex-fancy"] # Use the pure rust "fancy-regex" engine 49 | -------------------------------------------------------------------------------- /assets/syntaxes/Cabal.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: Cabal 5 | file_extensions: 6 | - cabal 7 | scope: source.cabal 8 | contexts: 9 | main: 10 | - match: ^(\s*)(exposed-modules):$ 11 | captures: 12 | 2: constant.other.cabal 13 | push: 14 | - meta_scope: exposed.modules.cabal 15 | - match: ^(?!\1\s) 16 | pop: true 17 | - include: module_name 18 | - match: ^(\s*)(build-depends):$ 19 | captures: 20 | 2: constant.other.cabal 21 | push: 22 | - meta_scope: exposed.modules.cabal 23 | - match: ^(?!\1\s) 24 | pop: true 25 | - match: "([<>=]+)|([&|]+)" 26 | scope: keyword.operator.haskell 27 | - match: ((\d+|\*)\.)*(\d+|\*) 28 | scope: constant.numeric.haskell 29 | - match: '([\w\-]+)' 30 | scope: support.function.haskell 31 | - match: '^\s*([a-zA-Z_-]+)(:)\s+' 32 | scope: entity.cabal 33 | captures: 34 | 1: constant.other.cabal 35 | 2: punctuation.entity.cabal 36 | - match: '^(?i)(executable|library|test-suite|benchmark|flag|source-repository)\s+([^\s,]+)\s*$' 37 | scope: entity.cabal 38 | captures: 39 | 1: keyword.entity.cabal 40 | 2: string.cabal 41 | - match: ^(?i)library\s*$ 42 | scope: keyword.entity.cabal 43 | - match: "--.*$" 44 | scope: comment.cabal 45 | module_name: 46 | - match: '([A-Z][A-Za-z_''0-9]*)(\.[A-Z][A-Za-z_''0-9]*)*' 47 | scope: storage.module.haskell 48 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | ## Syntax Highlighting in prettyprint 2 | 3 | `prettyprint` uses the [syntect](https://github.com/trishume/syntect) library to highlight source 4 | code. As a basis, syntect uses [Sublime Text](https://www.sublimetext.com/) syntax definitions 5 | in the `.sublime-syntax` format. 6 | 7 | In order to add new syntaxes to `prettyprint`, follow these steps: 8 | 9 | 1. Find a Sublime Text syntax for the given language, preferably in a separate Git repository 10 | which can be included as a submodule (under `assets/syntaxes`). 11 | 12 | 2. If the Sublime Text syntax is only available as a `.tmLanguage` file, open the file in 13 | Sublime Text and convert it to a `.sublime-syntax` file via *Tools* -> *Developer* -> 14 | *New Syntax from XXX.tmLanguage...*. Save the new file in the `assets/syntaxes` folder. 15 | 16 | 3. Run the `create.sh` script. It calls `prettyprint cache --init` to parse all available 17 | `.sublime-syntax` files and serialize them to a `syntaxes.bin` file (in this folder). 18 | 19 | 4. Re-compile `prettyprint`. At compilation time, the `syntaxes.bin` file will be stored inside the 20 | `prettyprint` binary. 21 | 22 | ### Troubleshooting 23 | 24 | Make sure that the local cache does not interfere with the internally stored syntaxes and 25 | themes (`prettyprint cache --clear`). 26 | 27 | ### Manual modifications 28 | 29 | The following files have been manually modified after converting from a `.tmLanguage` file: 30 | 31 | * `Dart.sublime-syntax` => removed `#regex.dart` include. 32 | * `INI.sublime-syntax` => added `.hgrc`, `hgrc`, and `desktop` file types. 33 | -------------------------------------------------------------------------------- /assets/syntaxes/CSV.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: Advanced CSV 5 | file_extensions: 6 | - csv 7 | - tsv 8 | scope: text.advanced_csv 9 | contexts: 10 | main: 11 | - match: (\") 12 | captures: 13 | 1: string.quoted.double.advanced_csv 14 | push: 15 | - meta_scope: meta.quoted.advanced_csv 16 | - match: (\") 17 | captures: 18 | 1: string.quoted.double.advanced_csv 19 | pop: true 20 | - include: main 21 | - match: '(\[([+-]?\d*)(\:)?([+-]?\d*)(\,)?([+-]?\d*)(\:)?([+-]?\d*)\])?\s*([<>v^])?\s*(=)' 22 | captures: 23 | 1: keyword.operator.advanced_csv 24 | 2: constant.numeric.formula.advanced_csv 25 | 4: constant.numeric.formula.advanced_csv 26 | 6: constant.numeric.formula.advanced_csv 27 | 8: constant.numeric.formula.advanced_csv 28 | 9: keyword.operator.advanced_csv 29 | 10: keyword.operator.advanced_csv 30 | push: 31 | - meta_scope: meta.range.advanced_csv 32 | - match: (?=(\")|$) 33 | pop: true 34 | - include: scope:source.python 35 | - match: '(?<=^|,|\s|\")([0-9.eE+-]+)(?=$|,|\s|\")' 36 | scope: meta.number.advanced_csv 37 | captures: 38 | 1: constant.numeric.advanced_csv 39 | - match: '(?<=^|,|\s|\")([^, \t\"]+)(?=$|,|\s|\")' 40 | scope: meta.nonnumber.advanced_csv 41 | captures: 42 | 1: storage.type.advanced_csv 43 | - match: (\,) 44 | scope: meta.delimiter.advanced_csv 45 | captures: 46 | 1: keyword.operator.advanced_csv 47 | -------------------------------------------------------------------------------- /src/style.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::str::FromStr; 3 | 4 | use crate::errors::*; 5 | 6 | #[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] 7 | pub enum OutputComponent { 8 | Auto, 9 | Changes, 10 | Grid, 11 | Header, 12 | Numbers, 13 | Full, 14 | Plain, 15 | } 16 | 17 | #[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] 18 | pub enum OutputWrap { 19 | Character, 20 | None, 21 | } 22 | 23 | impl Default for OutputWrap { 24 | fn default() -> Self { 25 | OutputWrap::None 26 | } 27 | } 28 | 29 | impl FromStr for OutputComponent { 30 | type Err = Error; 31 | 32 | fn from_str(s: &str) -> Result { 33 | match s { 34 | "auto" => Ok(OutputComponent::Auto), 35 | "changes" => Ok(OutputComponent::Changes), 36 | "grid" => Ok(OutputComponent::Grid), 37 | "header" => Ok(OutputComponent::Header), 38 | "numbers" => Ok(OutputComponent::Numbers), 39 | "full" => Ok(OutputComponent::Full), 40 | "plain" => Ok(OutputComponent::Plain), 41 | _ => Err(format!("Unknown style '{}'", s).into()), 42 | } 43 | } 44 | } 45 | 46 | #[derive(Debug, Clone)] 47 | pub struct OutputComponents(pub HashSet); 48 | 49 | impl Default for OutputComponents { 50 | fn default() -> Self { 51 | let mut set = HashSet::new(); 52 | set.insert(OutputComponent::Auto); 53 | OutputComponents(set) 54 | } 55 | } 56 | 57 | impl OutputComponents { 58 | pub fn grid(&self) -> bool { 59 | self.0.contains(&OutputComponent::Grid) 60 | } 61 | 62 | pub fn header(&self) -> bool { 63 | self.0.contains(&OutputComponent::Header) 64 | } 65 | 66 | pub fn numbers(&self) -> bool { 67 | self.0.contains(&OutputComponent::Numbers) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /assets/syntaxes/Robot.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: Robot Framework syntax highlighting. 5 | file_extensions: 6 | - robot 7 | scope: source.robot 8 | contexts: 9 | main: 10 | - match: '\$\{(\d+|\d+\.\d*|0[bB][01]+|0[oO][0-7]+|0[xX][0-9a-fA-F]+)\}' 11 | comment: "Robot Framework numbers, like ${1} or ${12.3}" 12 | scope: constant.numeric.robot 13 | - match: '(?i)(%\{[\w|\s]+\})' 14 | comment: "Robot Framework environment variable, like %{USER}" 15 | scope: variable.parameter.robot 16 | - match: '((? String { 5 | let mut buffer = String::with_capacity(line.len() * 2); 6 | 7 | for chunk in AnsiCodeIterator::new(line) { 8 | match chunk { 9 | (text, true) => buffer.push_str(text), 10 | (mut text, false) => { 11 | while let Some(index) = text.find('\t') { 12 | // Add previous text. 13 | if index > 0 { 14 | *cursor += index; 15 | buffer.push_str(&text[0..index]); 16 | } 17 | 18 | // Add tab. 19 | let spaces = width - (*cursor % width); 20 | *cursor += spaces; 21 | buffer.push_str(&*" ".repeat(spaces)); 22 | 23 | // Next. 24 | text = &text[index + 1..text.len()]; 25 | } 26 | 27 | *cursor += text.len(); 28 | buffer.push_str(text); 29 | } 30 | } 31 | } 32 | 33 | buffer 34 | } 35 | 36 | pub fn replace_nonprintable(input: &str, tab_width: usize) -> String { 37 | let mut output = String::new(); 38 | 39 | let tab_width = if tab_width == 0 { 4 } else { tab_width }; 40 | 41 | for chr in input.chars() { 42 | match chr { 43 | // space 44 | ' ' => output.push('•'), 45 | // tab 46 | '\t' => { 47 | if tab_width == 1 { 48 | output.push('↹'); 49 | } else { 50 | output.push('├'); 51 | output.push_str(&"─".repeat(tab_width - 2)); 52 | output.push('┤'); 53 | } 54 | } 55 | // line feed 56 | '\x0A' => output.push('␊'), 57 | // carriage return 58 | '\x0D' => output.push('␍'), 59 | // null 60 | '\x00' => output.push('␀'), 61 | // bell 62 | '\x07' => output.push('␇'), 63 | // backspace 64 | '\x08' => output.push('␈'), 65 | // escape 66 | '\x1B' => output.push('␛'), 67 | // anything else 68 | _ => output.push(chr), 69 | } 70 | } 71 | 72 | output 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # prettyprint 2 | 3 | [![Documentation](https://docs.rs/prettyprint/badge.svg)](https://docs.rs/prettyprint/) 4 | ![CI](https://github.com/mre/prettyprint/workflows/CI/badge.svg) 5 | 6 | Syntax highlighting library with batteries included. 7 | 8 | > :warning: For new projects, you might want to use [`bat`](https://github.com/sharkdp/bat/) instead, 9 | > which can be used as a [library now](https://github.com/sharkdp/bat/pull/936). 10 | > `prettyprint` is in maintenance mode, which means it will 11 | > not see any major updates anymore, but pull requests will still be reviewed and dependencies will 12 | > be kept up-to-date for the time being. You might want to consider migrating away eventually, though. 13 | 14 | ## Quick start 15 | 16 | ![Screenshot](./assets/screenshot.jpg) 17 | 18 | The above output was created with the following code: 19 | 20 | ```rust 21 | let printer = PrettyPrinter::default() 22 | .language("rust") 23 | .build()?; 24 | 25 | printer.file("fixtures/fib.rs")?; 26 | ``` 27 | 28 | Note that `prettyprint` is a [builder](https://github.com/rust-unofficial/patterns/blob/master/patterns/builder.md) and can be customized. For example, if you don't like the grid or the header, you can disable those: 29 | 30 | ```rust 31 | let printer = PrettyPrinter::default() 32 | .header(false) 33 | .grid(false) 34 | .language("ruby") 35 | .build()?; 36 | 37 | let example = r#" 38 | def fib(n) 39 | return 1 if n <= 1 40 | fib(n-1) + fib(n-2) 41 | end 42 | "#; 43 | printer.string_with_header(example, "fib.rb")?; 44 | ``` 45 | 46 | "What!? It can also print strings, Matthias? That's insane." 47 | It's true. You're welcome. 48 | 49 | ## Installation 50 | 51 | Add this to your `Cargo.toml`: 52 | 53 | ```TOML 54 | prettyprint = "*" 55 | ``` 56 | 57 | ## But why? 58 | 59 | [`syntect`](https://github.com/trishume/syntect/) is a great package for highlighting text. 60 | When writing a command-line tool that prints text however, you might be looking for some additional functionality. 61 | 62 | * Line numbers 63 | * More built-in color-themes 64 | * Automatic pagination 65 | * Proper terminal handling 66 | * Showing non-printable characters 67 | * File headers 68 | * Windows support 69 | 70 | `prettyprint` offers all of this in one crate (thanks to [bat](https://github.com/sharkdp/bat/)). 71 | 72 | ## Known limitations 73 | 74 | * Doesn't run on `no-std` targets. I don't plan to support those. 75 | 76 | ## Credits 77 | 78 | `prettyprint` is simply a fork of [`bat`](https://github.com/sharkdp/bat/), with some functionality stripped out and bundled up as a library. 79 | I built it, because [I needed it](https://github.com/sharkdp/bat/issues/423) for [cargo-inspect](https://github.com/mre/cargo-inspect/). 80 | All credits go to the original authors. 81 | -------------------------------------------------------------------------------- /src/decorations.rs: -------------------------------------------------------------------------------- 1 | use ansi_term::Style; 2 | 3 | use crate::printer::{Colors, InteractivePrinter}; 4 | 5 | #[derive(Clone)] 6 | pub struct DecorationText { 7 | pub width: usize, 8 | pub text: String, 9 | } 10 | 11 | pub trait Decoration { 12 | fn generate( 13 | &self, 14 | line_number: usize, 15 | continuation: bool, 16 | printer: &InteractivePrinter, 17 | ) -> DecorationText; 18 | fn width(&self) -> usize; 19 | } 20 | 21 | pub struct LineNumberDecoration { 22 | color: Style, 23 | cached_wrap: DecorationText, 24 | cached_wrap_invalid_at: usize, 25 | } 26 | 27 | impl LineNumberDecoration { 28 | pub fn new(colors: &Colors) -> Self { 29 | LineNumberDecoration { 30 | color: colors.line_number, 31 | cached_wrap_invalid_at: 10000, 32 | cached_wrap: DecorationText { 33 | text: colors.line_number.paint(" ".repeat(4)).to_string(), 34 | width: 4, 35 | }, 36 | } 37 | } 38 | } 39 | 40 | impl Decoration for LineNumberDecoration { 41 | fn generate( 42 | &self, 43 | line_number: usize, 44 | continuation: bool, 45 | _printer: &InteractivePrinter, 46 | ) -> DecorationText { 47 | if continuation { 48 | if line_number > self.cached_wrap_invalid_at { 49 | let new_width = self.cached_wrap.width + 1; 50 | return DecorationText { 51 | text: self.color.paint(" ".repeat(new_width)).to_string(), 52 | width: new_width, 53 | }; 54 | } 55 | 56 | self.cached_wrap.clone() 57 | } else { 58 | let plain: String = format!("{:4}", line_number); 59 | DecorationText { 60 | width: plain.len(), 61 | text: self.color.paint(plain).to_string(), 62 | } 63 | } 64 | } 65 | 66 | fn width(&self) -> usize { 67 | 4 68 | } 69 | } 70 | 71 | pub struct GridBorderDecoration { 72 | cached: DecorationText, 73 | } 74 | 75 | impl GridBorderDecoration { 76 | pub fn new(colors: &Colors) -> Self { 77 | GridBorderDecoration { 78 | cached: DecorationText { 79 | text: colors.grid.paint("│").to_string(), 80 | width: 1, 81 | }, 82 | } 83 | } 84 | } 85 | 86 | impl Decoration for GridBorderDecoration { 87 | fn generate( 88 | &self, 89 | _line_number: usize, 90 | _continuation: bool, 91 | _printer: &InteractivePrinter, 92 | ) -> DecorationText { 93 | self.cached.clone() 94 | } 95 | 96 | fn width(&self) -> usize { 97 | self.cached.width 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /assets/syntaxes/VimL.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: VimL 5 | file_extensions: 6 | - vim 7 | - .vimrc 8 | scope: source.viml 9 | contexts: 10 | main: 11 | - include: comment 12 | - include: string_quoted_double 13 | - include: string_quoted_single 14 | - include: string_regex 15 | - include: inline_comment 16 | - include: number_int 17 | - include: number_hex 18 | - include: keyword 19 | - include: support_function 20 | - include: support_variable 21 | - include: support_type 22 | - include: function_params 23 | - include: function_definition 24 | - include: function_call 25 | - include: function 26 | - include: variable 27 | - include: expr 28 | comment: 29 | - match: ^\s*".*$ 30 | scope: comment.line.quotes.viml 31 | captures: 32 | 1: punctuation.definition.comment.vim 33 | expr: 34 | - match: (\|\||&&|==(\?|#)?|(!|>|<)=(#|\?)?|(=|!)~(#|\?)?|(>|<)(#|\?)is|isnot|\.|\*|\\|%) 35 | scope: storage.function.viml 36 | function: 37 | - match: \b(fu(n|nction)?|end(f|fu|fun|function)?)\b 38 | scope: storage.function.viml 39 | function_call: 40 | - match: '(([sgbwtl]|)?:?[0-9a-zA-Z_#]+)(?=\()' 41 | scope: support.function.any-method 42 | function_definition: 43 | - match: '^\s*(function)\s*!?\s+(?=(s:)?[0-9a-zA-Z_#]+\s*\()' 44 | captures: 45 | 1: storage.function.viml 46 | push: 47 | - meta_scope: meta.function.viml 48 | - match: (\() 49 | captures: 50 | 1: punctuation.definition.parameters.begin.viml 51 | pop: true 52 | - match: "(s:)?[0-9a-zA-Z_#]+" 53 | scope: entity.name.function.viml 54 | function_params: 55 | - match: '-\w+=' 56 | scope: support.type.viml 57 | inline_comment: 58 | - match: '(?!\$)(")(?!\{).*$\n?' 59 | scope: comment.line.quotes.viml 60 | captures: 61 | 1: punctuation.definition.comment.vim 62 | keyword: 63 | - match: \b(if|while|for|try|return|throw|end(if|for|while|try)?|au(g|group)|else(if|)?|do|in|catch|finally|:)\b 64 | scope: keyword.control.viml 65 | number_hex: 66 | - match: "0x[0-9a-f]+" 67 | scope: constant.numeric.hex 68 | number_int: 69 | - match: '-?\d+' 70 | scope: constant.numeric.integer 71 | string_quoted_double: 72 | - match: '"(\\\\|\\"|\n[^\S\n]*\\|[^\n"])*"' 73 | scope: string.quoted.double.viml 74 | string_quoted_single: 75 | - match: '''(''''|\n[^\S\n]*\\|[^\n''])*''' 76 | scope: string.quoted.single.viml 77 | string_regex: 78 | - match: '/(\\\\|\\/|\n[^\S\n]*\\|[^\n/])*/' 79 | scope: string.regexp.viml 80 | support_function: 81 | - match: \b(set(local|global)?|let|command|filetype|colorscheme|\w*map|\w*a(b|brev)?|syn|exe(c|cute)?|ec(ho|)?|au(tocmd|)?)\b 82 | scope: support.function.viml 83 | support_type: 84 | - match: <.*?> 85 | scope: support.type.viml 86 | support_variable: 87 | - match: '\b(am(enu|)?|(hl|inc)?search|[Bb]uf([Nn]ew[Ff]ile|[Rr]ead)?|[Ff]ile[Tt]ype)\b' 88 | scope: support.variable.viml 89 | variable: 90 | - match: '([sSgGbBwWlLaAvV]:|@|$|&(?!&))\w*' 91 | scope: variable.other.viml 92 | -------------------------------------------------------------------------------- /src/output.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi::OsString; 3 | use std::io::{self, Write}; 4 | use std::path::PathBuf; 5 | use std::process::{Child, Command, Stdio}; 6 | 7 | use shell_words; 8 | 9 | use crate::builder::PagingMode; 10 | use crate::errors::*; 11 | 12 | pub enum OutputType { 13 | Pager(Child), 14 | Stdout(io::Stdout), 15 | Stderr(io::Stderr), 16 | } 17 | 18 | impl OutputType { 19 | pub fn from_mode(mode: PagingMode, pager: Option) -> Result { 20 | use self::PagingMode::*; 21 | Ok(match mode { 22 | Always => OutputType::try_pager(false, pager)?, 23 | QuitIfOneScreen => OutputType::try_pager(true, pager)?, 24 | Never => OutputType::stdout(), 25 | Error => OutputType::stderr(), 26 | }) 27 | } 28 | 29 | /// Try to launch the pager. Fall back to stdout in case of errors. 30 | fn try_pager(quit_if_one_screen: bool, pager_from_config: Option) -> Result { 31 | let pager_from_env = env::var("PRETTYPRINT_PAGER").or_else(|_| env::var("PAGER")); 32 | 33 | let pager = pager_from_config 34 | .map(|p| p.to_string()) 35 | .or(pager_from_env.ok()) 36 | .unwrap_or(String::from("less")); 37 | 38 | let pagerflags = shell_words::split(&pager) 39 | .chain_err(|| "Could not parse (PRETTYPRINT_)PAGER environment variable.")?; 40 | 41 | match pagerflags.split_first() { 42 | Some((pager_name, mut args)) => { 43 | let mut pager_path = PathBuf::from(pager_name); 44 | 45 | if pager_path.file_stem() == Some(&OsString::from("prettyprint")) { 46 | pager_path = PathBuf::from("less"); 47 | args = &[]; 48 | } 49 | 50 | let is_less = pager_path.file_stem() == Some(&OsString::from("less")); 51 | 52 | let mut process = if is_less { 53 | let mut p = Command::new(&pager_path); 54 | if args.is_empty() { 55 | p.args(vec!["--RAW-CONTROL-CHARS", "--no-init"]); 56 | if quit_if_one_screen { 57 | p.arg("--quit-if-one-screen"); 58 | } 59 | } 60 | p.env("LESSCHARSET", "UTF-8"); 61 | p 62 | } else { 63 | Command::new(&pager_path) 64 | }; 65 | 66 | Ok(process 67 | .args(args) 68 | .stdin(Stdio::piped()) 69 | .spawn() 70 | .map(OutputType::Pager) 71 | .unwrap_or_else(|_| OutputType::stdout())) 72 | } 73 | None => Ok(OutputType::stdout()), 74 | } 75 | } 76 | 77 | fn stdout() -> Self { 78 | OutputType::Stdout(io::stdout()) 79 | } 80 | 81 | fn stderr() -> Self { 82 | OutputType::Stderr(io::stderr()) 83 | } 84 | 85 | pub fn handle(&mut self) -> Result<&mut dyn Write> { 86 | Ok(match *self { 87 | OutputType::Pager(ref mut command) => command 88 | .stdin 89 | .as_mut() 90 | .chain_err(|| "Could not open stdin for pager")?, 91 | OutputType::Stdout(ref mut handle) => handle, 92 | OutputType::Stderr(ref mut handle) => handle, 93 | }) 94 | } 95 | } 96 | 97 | impl Drop for OutputType { 98 | fn drop(&mut self) { 99 | if let OutputType::Pager(ref mut command) = *self { 100 | let _ = command.wait(); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/inputfile.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{self, BufRead, BufReader}; 3 | 4 | use content_inspector::{self, ContentType}; 5 | 6 | use crate::errors::*; 7 | 8 | pub struct InputFileReader<'a> { 9 | inner: Box, 10 | pub first_line: Vec, 11 | pub content_type: ContentType, 12 | } 13 | 14 | impl<'a> InputFileReader<'a> { 15 | fn new(mut reader: R) -> InputFileReader<'a> { 16 | let mut first_line = vec![]; 17 | reader.read_until(b'\n', &mut first_line).ok(); 18 | 19 | let content_type = content_inspector::inspect(&first_line[..]); 20 | 21 | if content_type == ContentType::UTF_16LE { 22 | reader.read_until(0x00, &mut first_line).ok(); 23 | } 24 | 25 | InputFileReader { 26 | inner: Box::new(reader), 27 | first_line, 28 | content_type, 29 | } 30 | } 31 | 32 | pub fn read_line(&mut self, buf: &mut Vec) -> io::Result { 33 | if self.first_line.is_empty() { 34 | let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?; 35 | 36 | if self.content_type == ContentType::UTF_16LE { 37 | self.inner.read_until(0x00, buf).ok(); 38 | } 39 | 40 | Ok(res) 41 | } else { 42 | buf.append(&mut self.first_line); 43 | Ok(true) 44 | } 45 | } 46 | } 47 | 48 | #[derive(Debug, Clone, PartialEq)] 49 | pub enum InputFile { 50 | StdIn, 51 | Ordinary(String), 52 | String(String), 53 | } 54 | 55 | impl InputFile { 56 | pub fn get_reader(&self) -> Result { 57 | match self { 58 | InputFile::Ordinary(filename) => { 59 | let file = File::open(filename)?; 60 | 61 | if file.metadata()?.is_dir() { 62 | return Err(format!("'{}' is a directory.", filename).into()); 63 | } 64 | 65 | Ok(InputFileReader::new(BufReader::new(file))) 66 | } 67 | InputFile::String(s) => Ok(InputFileReader::new(s.as_bytes())), 68 | _ => unimplemented!(), // Used to be InputFile::Stdin 69 | } 70 | } 71 | } 72 | 73 | #[test] 74 | fn basic() { 75 | let content = b"#!/bin/bash\necho hello"; 76 | let mut reader = InputFileReader::new(&content[..]); 77 | 78 | assert_eq!(b"#!/bin/bash\n", &reader.first_line[..]); 79 | 80 | let mut buffer = vec![]; 81 | 82 | let res = reader.read_line(&mut buffer); 83 | assert!(res.is_ok()); 84 | assert_eq!(true, res.unwrap()); 85 | assert_eq!(b"#!/bin/bash\n", &buffer[..]); 86 | 87 | buffer.clear(); 88 | 89 | let res = reader.read_line(&mut buffer); 90 | assert!(res.is_ok()); 91 | assert_eq!(true, res.unwrap()); 92 | assert_eq!(b"echo hello", &buffer[..]); 93 | 94 | buffer.clear(); 95 | 96 | let res = reader.read_line(&mut buffer); 97 | assert!(res.is_ok()); 98 | assert_eq!(false, res.unwrap()); 99 | assert!(buffer.is_empty()); 100 | } 101 | 102 | #[test] 103 | fn utf16le() { 104 | let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00"; 105 | let mut reader = InputFileReader::new(&content[..]); 106 | 107 | assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &reader.first_line[..]); 108 | 109 | let mut buffer = vec![]; 110 | 111 | let res = reader.read_line(&mut buffer); 112 | assert!(res.is_ok()); 113 | assert_eq!(true, res.unwrap()); 114 | assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &buffer[..]); 115 | 116 | buffer.clear(); 117 | 118 | let res = reader.read_line(&mut buffer); 119 | assert!(res.is_ok()); 120 | assert_eq!(true, res.unwrap()); 121 | assert_eq!(b"\x64\x00", &buffer[..]); 122 | 123 | buffer.clear(); 124 | 125 | let res = reader.read_line(&mut buffer); 126 | assert!(res.is_ok()); 127 | assert_eq!(false, res.unwrap()); 128 | assert!(buffer.is_empty()); 129 | } 130 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // `error_chain!` can recurse deeply 2 | #![recursion_limit = "1024"] 3 | 4 | #[macro_use] 5 | extern crate derive_builder; 6 | 7 | #[macro_use] 8 | extern crate error_chain; 9 | 10 | #[macro_use] 11 | extern crate clap; 12 | 13 | #[macro_use] 14 | extern crate lazy_static; 15 | 16 | extern crate ansi_term; 17 | extern crate atty; 18 | extern crate console; 19 | extern crate content_inspector; 20 | extern crate directories; 21 | extern crate encoding; 22 | extern crate shell_words; 23 | extern crate syntect; 24 | 25 | mod assets; 26 | mod builder; 27 | mod decorations; 28 | mod dirs; 29 | mod inputfile; 30 | mod line_range; 31 | mod output; 32 | mod preprocessor; 33 | mod printer; 34 | mod style; 35 | mod syntax_mapping; 36 | mod terminal; 37 | 38 | pub use crate::builder::{PagingMode, PrettyPrint, PrettyPrinter}; 39 | 40 | #[allow(deprecated)] // remove it after error-chain/issues/254 resolved 🤗 41 | mod errors { 42 | error_chain! { 43 | foreign_links { 44 | Clap(::clap::Error); 45 | Io(::std::io::Error); 46 | SyntectError(::syntect::LoadingError); 47 | ParseIntError(::std::num::ParseIntError); 48 | } 49 | } 50 | } 51 | 52 | pub use errors::Error as PrettyPrintError; 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use super::*; 57 | 58 | /// Pretty prints its own code 59 | #[test] 60 | fn it_works() { 61 | let printer = PrettyPrinter::default().build().unwrap(); 62 | printer.file("fixtures/fib.rs").unwrap(); 63 | } 64 | 65 | /// Pretty prints its own code with some more formatting shenanigans 66 | #[test] 67 | fn it_works_with_output_opts() { 68 | let printer = PrettyPrinter::default() 69 | .line_numbers(true) 70 | .header(true) 71 | .grid(true) 72 | .paging_mode(PagingMode::Never) 73 | .language("ruby") 74 | .build() 75 | .unwrap(); 76 | 77 | let example = r#" 78 | def fib(n) 79 | return 1 if n <= 1 80 | fib(n-1) + fib(n-2) 81 | end 82 | "#; 83 | printer.string_with_header(example, "example.rb").unwrap(); 84 | } 85 | 86 | #[test] 87 | fn it_works_inside_loop() { 88 | let printer = PrettyPrinter::default() 89 | .language("markdown") 90 | .build() 91 | .unwrap(); 92 | for i in 0..7 { 93 | printer.string(format!("## Heading {}", i)).unwrap(); 94 | } 95 | } 96 | 97 | #[test] 98 | fn it_works_inside_closure() { 99 | let printer = PrettyPrinter::default() 100 | .language("markdown") 101 | .build() 102 | .unwrap(); 103 | let print_heading = |string| printer.string(format!("## {}", string)).expect("Printed"); 104 | print_heading("Thankyou for making a crate version of `bat` 🥺"); 105 | } 106 | 107 | #[test] 108 | fn it_can_print_multiple_times() { 109 | let printer = PrettyPrinter::default().language("rust").build().unwrap(); 110 | printer.string("").unwrap(); 111 | 112 | printer.string("let example = Ok(());").unwrap(); 113 | printer 114 | .string_with_header("let example = Ok(());", "example.rs") 115 | .unwrap(); 116 | printer.file("fixtures/fib.rs").unwrap(); 117 | } 118 | 119 | #[test] 120 | fn it_can_load_syntaxset() { 121 | let buffer: Vec = include_bytes!("../assets/syntaxes.bin").to_vec(); 122 | let printer = PrettyPrinter::default() 123 | .language("rust") 124 | .load_syntax(buffer) 125 | .build() 126 | .unwrap(); 127 | 128 | printer.file("fixtures/fib.rs").unwrap(); 129 | } 130 | 131 | #[test] 132 | fn it_can_load_themeset() { 133 | let buffer = include_bytes!("../assets/themes.bin").to_vec(); 134 | let printer = PrettyPrinter::default() 135 | .language("rust") 136 | .load_theme(buffer) 137 | .build() 138 | .unwrap(); 139 | 140 | printer.file("fixtures/fib.rs").unwrap(); 141 | } 142 | 143 | /// Show available syntax highlighting themes 144 | #[test] 145 | fn show_themes() { 146 | let printer = PrettyPrinter::default().build().unwrap(); 147 | assert!(printer.get_themes().len() > 0); 148 | println!("{:?}", printer.get_themes().keys()); 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/assets.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::BufReader; 3 | use std::path::{Path, PathBuf}; 4 | 5 | use syntect::dumps::{from_binary, from_reader}; 6 | use syntect::highlighting::{Theme, ThemeSet}; 7 | use syntect::parsing::{SyntaxReference, SyntaxSet}; 8 | 9 | use crate::dirs::PROJECT_DIRS; 10 | use crate::errors::*; 11 | use crate::inputfile::{InputFile, InputFileReader}; 12 | use crate::syntax_mapping::SyntaxMapping; 13 | 14 | pub const PRETTYPRINT_THEME_DEFAULT: &str = "Monokai Extended"; 15 | 16 | pub struct HighlightingAssets { 17 | pub syntax_set: SyntaxSet, 18 | pub theme_set: ThemeSet, 19 | } 20 | 21 | impl HighlightingAssets { 22 | pub fn new() -> Self { 23 | Self::from_cache().unwrap_or_else(|_| Self::from_binary()) 24 | } 25 | 26 | fn from_cache() -> Result { 27 | let theme_set_path = theme_set_path(); 28 | let syntax_set_file = File::open(&syntax_set_path()).chain_err(|| { 29 | format!( 30 | "Could not load cached syntax set '{}'", 31 | syntax_set_path().to_string_lossy() 32 | ) 33 | })?; 34 | let syntax_set: SyntaxSet = from_reader(BufReader::new(syntax_set_file)) 35 | .chain_err(|| "Could not parse cached syntax set")?; 36 | 37 | let theme_set_file = File::open(&theme_set_path).chain_err(|| { 38 | format!( 39 | "Could not load cached theme set '{}'", 40 | theme_set_path.to_string_lossy() 41 | ) 42 | })?; 43 | let theme_set: ThemeSet = from_reader(BufReader::new(theme_set_file)) 44 | .chain_err(|| "Could not parse cached theme set")?; 45 | 46 | Ok(HighlightingAssets { 47 | syntax_set, 48 | theme_set, 49 | }) 50 | } 51 | 52 | pub(crate) fn get_integrated_syntaxset() -> SyntaxSet { 53 | from_binary(include_bytes!("../assets/syntaxes.bin")) 54 | } 55 | 56 | pub(crate) fn get_integrated_themeset() -> ThemeSet { 57 | from_binary(include_bytes!("../assets/themes.bin")) 58 | } 59 | 60 | fn from_binary() -> Self { 61 | let syntax_set = Self::get_integrated_syntaxset(); 62 | let theme_set = Self::get_integrated_themeset(); 63 | 64 | HighlightingAssets { 65 | syntax_set, 66 | theme_set, 67 | } 68 | } 69 | 70 | pub fn get_theme(&self, theme: &str) -> &Theme { 71 | match self.theme_set.themes.get(theme) { 72 | Some(theme) => theme, 73 | None => { 74 | use ansi_term::Colour::Yellow; 75 | eprintln!( 76 | "{}: Unknown theme '{}', using default.", 77 | Yellow.paint("[prettyprint warning]"), 78 | theme 79 | ); 80 | &self.theme_set.themes[PRETTYPRINT_THEME_DEFAULT] 81 | } 82 | } 83 | } 84 | 85 | pub fn get_syntax( 86 | &self, 87 | language: Option, 88 | filename: &InputFile, 89 | reader: &mut InputFileReader, 90 | mapping: &SyntaxMapping, 91 | ) -> &SyntaxReference { 92 | let syntax = match (language, filename) { 93 | (Some(language), _) => self.syntax_set.find_syntax_by_token(&language), 94 | (None, InputFile::Ordinary(filename)) => { 95 | let path = Path::new(&filename); 96 | let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or(""); 97 | let extension = path.extension().and_then(|x| x.to_str()).unwrap_or(""); 98 | 99 | let file_name = mapping.replace(file_name); 100 | let extension = mapping.replace(extension); 101 | 102 | let ext_syntax = self 103 | .syntax_set 104 | .find_syntax_by_extension(&file_name) 105 | .or_else(|| self.syntax_set.find_syntax_by_extension(&extension)); 106 | let line_syntax = if ext_syntax.is_none() { 107 | String::from_utf8(reader.first_line.clone()) 108 | .ok() 109 | .and_then(|l| self.syntax_set.find_syntax_by_first_line(&l)) 110 | } else { 111 | None 112 | }; 113 | let syntax = ext_syntax.or(line_syntax); 114 | syntax 115 | } 116 | (None, InputFile::StdIn) => String::from_utf8(reader.first_line.clone()) 117 | .ok() 118 | .and_then(|l| self.syntax_set.find_syntax_by_first_line(&l)), 119 | (None, InputFile::String(_)) => String::from_utf8(reader.first_line.clone()) 120 | .ok() 121 | .and_then(|l| self.syntax_set.find_syntax_by_first_line(&l)), 122 | }; 123 | 124 | syntax.unwrap_or_else(|| self.syntax_set.find_syntax_plain_text()) 125 | } 126 | } 127 | 128 | fn theme_set_path() -> PathBuf { 129 | PROJECT_DIRS.cache_dir().join("themes.bin") 130 | } 131 | 132 | fn syntax_set_path() -> PathBuf { 133 | PROJECT_DIRS.cache_dir().join("syntaxes.bin") 134 | } 135 | -------------------------------------------------------------------------------- /src/line_range.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::*; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct LineRange { 5 | pub lower: usize, 6 | pub upper: usize, 7 | } 8 | 9 | impl Default for LineRange { 10 | fn default() -> Self { 11 | LineRange { 12 | lower: usize::min_value(), 13 | upper: usize::max_value(), 14 | } 15 | } 16 | } 17 | 18 | impl LineRange { 19 | pub fn from(range_raw: &str) -> Result { 20 | LineRange::parse_range(range_raw) 21 | } 22 | 23 | pub fn new() -> LineRange { 24 | LineRange::default() 25 | } 26 | 27 | pub fn parse_range(range_raw: &str) -> Result { 28 | let mut new_range = LineRange::new(); 29 | 30 | if range_raw.bytes().nth(0).ok_or("Empty line range")? == b':' { 31 | new_range.upper = range_raw[1..].parse()?; 32 | return Ok(new_range); 33 | } else if range_raw.bytes().last().ok_or("Empty line range")? == b':' { 34 | new_range.lower = range_raw[..range_raw.len() - 1].parse()?; 35 | return Ok(new_range); 36 | } 37 | 38 | let line_numbers: Vec<&str> = range_raw.split(':').collect(); 39 | if line_numbers.len() == 2 { 40 | new_range.lower = line_numbers[0].parse()?; 41 | new_range.upper = line_numbers[1].parse()?; 42 | return Ok(new_range); 43 | } 44 | 45 | Err("expected single ':' character".into()) 46 | } 47 | 48 | pub fn is_inside(&self, line: usize) -> bool { 49 | line >= self.lower && line <= self.upper 50 | } 51 | } 52 | 53 | #[test] 54 | fn test_parse_full() { 55 | let range = LineRange::from("40:50").expect("Shouldn't fail on test!"); 56 | assert_eq!(40, range.lower); 57 | assert_eq!(50, range.upper); 58 | } 59 | 60 | #[test] 61 | fn test_parse_partial_min() { 62 | let range = LineRange::from(":50").expect("Shouldn't fail on test!"); 63 | assert_eq!(usize::min_value(), range.lower); 64 | assert_eq!(50, range.upper); 65 | } 66 | 67 | #[test] 68 | fn test_parse_partial_max() { 69 | let range = LineRange::from("40:").expect("Shouldn't fail on test!"); 70 | assert_eq!(40, range.lower); 71 | assert_eq!(usize::max_value(), range.upper); 72 | } 73 | 74 | #[test] 75 | fn test_parse_fail() { 76 | let range = LineRange::from("40:50:80"); 77 | assert!(range.is_err()); 78 | let range = LineRange::from("40::80"); 79 | assert!(range.is_err()); 80 | let range = LineRange::from(":40:"); 81 | assert!(range.is_err()); 82 | let range = LineRange::from("40"); 83 | assert!(range.is_err()); 84 | } 85 | 86 | #[derive(Copy, Clone, Debug, PartialEq)] 87 | pub enum RangeCheckResult { 88 | // Within one of the given ranges 89 | InRange, 90 | 91 | // Before the first range or within two ranges 92 | OutsideRange, 93 | 94 | // Line number is outside of all ranges and larger than the last range. 95 | AfterLastRange, 96 | } 97 | 98 | #[derive(Debug, Clone)] 99 | pub struct LineRanges { 100 | ranges: Vec, 101 | largest_upper_bound: usize, 102 | } 103 | 104 | impl Default for LineRanges { 105 | fn default() -> Self { 106 | LineRanges { 107 | ranges: vec![LineRange::default()], 108 | largest_upper_bound: usize::max_value(), 109 | } 110 | } 111 | } 112 | 113 | impl LineRanges { 114 | pub fn from(ranges: Vec) -> LineRanges { 115 | let largest_upper_bound = ranges 116 | .iter() 117 | .map(|r| r.upper) 118 | .max() 119 | .unwrap_or(usize::max_value()); 120 | LineRanges { 121 | ranges, 122 | largest_upper_bound, 123 | } 124 | } 125 | 126 | pub fn check(&self, line: usize) -> RangeCheckResult { 127 | if self.ranges.is_empty() { 128 | RangeCheckResult::InRange 129 | } else { 130 | if self.ranges.iter().any(|r| r.is_inside(line)) { 131 | RangeCheckResult::InRange 132 | } else { 133 | if line < self.largest_upper_bound { 134 | RangeCheckResult::OutsideRange 135 | } else { 136 | RangeCheckResult::AfterLastRange 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | #[cfg(test)] 144 | fn ranges(rs: &[&str]) -> LineRanges { 145 | LineRanges::from(rs.iter().map(|r| LineRange::from(r).unwrap()).collect()) 146 | } 147 | 148 | #[test] 149 | fn test_ranges_simple() { 150 | let ranges = ranges(&["3:8"]); 151 | 152 | assert_eq!(RangeCheckResult::OutsideRange, ranges.check(2)); 153 | assert_eq!(RangeCheckResult::InRange, ranges.check(5)); 154 | assert_eq!(RangeCheckResult::AfterLastRange, ranges.check(9)); 155 | } 156 | 157 | #[test] 158 | fn test_ranges_advanced() { 159 | let ranges = ranges(&["3:8", "11:20", "25:30"]); 160 | 161 | assert_eq!(RangeCheckResult::OutsideRange, ranges.check(2)); 162 | assert_eq!(RangeCheckResult::InRange, ranges.check(5)); 163 | assert_eq!(RangeCheckResult::OutsideRange, ranges.check(9)); 164 | assert_eq!(RangeCheckResult::InRange, ranges.check(11)); 165 | assert_eq!(RangeCheckResult::OutsideRange, ranges.check(22)); 166 | assert_eq!(RangeCheckResult::InRange, ranges.check(28)); 167 | assert_eq!(RangeCheckResult::AfterLastRange, ranges.check(31)); 168 | } 169 | 170 | #[test] 171 | fn test_ranges_open_low() { 172 | let ranges = ranges(&["3:8", ":5"]); 173 | 174 | assert_eq!(RangeCheckResult::InRange, ranges.check(1)); 175 | assert_eq!(RangeCheckResult::InRange, ranges.check(3)); 176 | assert_eq!(RangeCheckResult::InRange, ranges.check(7)); 177 | assert_eq!(RangeCheckResult::AfterLastRange, ranges.check(9)); 178 | } 179 | 180 | #[test] 181 | fn test_ranges_open_high() { 182 | let ranges = ranges(&["3:", "2:5"]); 183 | 184 | assert_eq!(RangeCheckResult::OutsideRange, ranges.check(1)); 185 | assert_eq!(RangeCheckResult::InRange, ranges.check(3)); 186 | assert_eq!(RangeCheckResult::InRange, ranges.check(5)); 187 | assert_eq!(RangeCheckResult::InRange, ranges.check(9)); 188 | } 189 | 190 | #[test] 191 | fn test_ranges_empty() { 192 | let ranges = ranges(&[]); 193 | 194 | assert_eq!(RangeCheckResult::InRange, ranges.check(1)); 195 | } 196 | -------------------------------------------------------------------------------- /assets/syntaxes/FSharp.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: "F#" 5 | file_extensions: 6 | - fs 7 | - fsi 8 | - fsx 9 | scope: source.fsharp 10 | contexts: 11 | main: 12 | - include: comments 13 | - include: constants 14 | - include: structure 15 | - include: attributes 16 | - include: strings 17 | - include: definition 18 | - include: method_calls 19 | - include: modules 20 | - include: anonymous_functions 21 | - include: keywords 22 | anonymous_functions: 23 | - match: \b(fun)\b 24 | captures: 25 | 1: keyword.other.function-definition.fsharp 26 | push: 27 | - meta_scope: meta.function.anonymous 28 | - match: (->) 29 | captures: 30 | 1: keyword.other.fsharp 31 | pop: true 32 | - include: variables 33 | attributes: 34 | - match: '\[\<' 35 | push: 36 | - meta_scope: support.function.attribute.fsharp 37 | - match: '\>\]' 38 | pop: true 39 | - include: main 40 | characters: 41 | - match: (') 42 | captures: 43 | 1: punctuation.definition.string.begin.fsharp 44 | push: 45 | - meta_scope: string.quoted.single.fsharp 46 | - match: (') 47 | captures: 48 | 1: punctuation.definition.string.end.fsharp 49 | pop: true 50 | - match: '\\$[ \t]*' 51 | scope: punctuation.separator.string.ignore-eol.fsharp 52 | - match: '\\([\\""ntbr]|u[a-fA-F0-9]{4}|u[a-fA-F0-9]{8})' 53 | scope: constant.character.string.escape.fsharp 54 | - match: '\\(?![\\''ntbr]|u[a-fA-F0-9]{4}|u[a-fA-F0-9]{8}).' 55 | scope: invalid.illeagal.character.string.fsharp 56 | comments: 57 | - match: \(\*\*?(\*)\) 58 | scope: comment.block.fsharp 59 | captures: 60 | 1: comment.block.empty.fsharp 61 | - match: '\(\*[^\)]' 62 | push: 63 | - meta_scope: comment.block.fsharp 64 | - match: \*\) 65 | pop: true 66 | - include: comments 67 | - match: //.*$ 68 | scope: comment.line.double-slash.fsharp 69 | constants: 70 | - match: \(\) 71 | scope: constant.language.unit.fsharp 72 | - match: '\b-?[0-9][0-9_]*((\.([0-9][0-9_]*([eE][+-]??[0-9][0-9_]*)?)?)|([eE][+-]??[0-9][0-9_]*))' 73 | scope: constant.numeric.floating-point.fsharp 74 | - match: '\b(-?((0(x|X)[0-9a-fA-F][0-9a-fA-F_]*)|(0(o|O)[0-7][0-7_]*)|(0(b|B)[01][01_]*)|([0-9][0-9_]*)))' 75 | scope: constant.numeric.integer.nativeint.fsharp 76 | definition: 77 | - match: '\b(val|val|let|and|member|override|use)\s?(rec|inline|mutable)?(\s+\(?([a-zA-Z.\|_][a-zA-Z0-9.|_]*)\)?\w*)\b' 78 | captures: 79 | 1: keyword.other.binding.fsharp 80 | 2: keyword.other.function-recursive.fsharp 81 | 3: variable.other.binding.fsharp 82 | push: 83 | - meta_scope: meta.binding.fsharp 84 | - match: "=|$" 85 | captures: 86 | 1: punctuation.separator.function.type-constraint.fsharp 87 | pop: true 88 | - include: variables 89 | - match: \b(let)\s+((\(\))|(_))\s+= 90 | scope: meta.expression.fsharp 91 | captures: 92 | 1: keyword.other.binding.fsharp 93 | 2: keyword.other.function-recursive.fsharp 94 | 4: constant.language.unit.fsharp 95 | 5: constant.language.ignored.fsharp 96 | keywords: 97 | - match: ^\s*#\s*(light|if|else|endif|r|I|load|time|help|quit)\b 98 | push: 99 | - meta_scope: meta.preprocessor.fsharp 100 | - match: (\s|$) 101 | pop: true 102 | - match: \b(new|in|as|if|then|else|elif|for|begin|end|match|with|type|inherit|true|false|null|do)\b 103 | scope: keyword.other.fsharp 104 | - match: '(\|>|\|?>|\->|\<\-|:>|:|\[|\]|\;|\||_)' 105 | scope: entity.name 106 | method_calls: 107 | - match: '(? Self { 32 | PagingMode::Always 33 | } 34 | } 35 | 36 | /// The main pretty print object. 37 | /// 38 | /// This gets created through a builder. 39 | #[derive(Default, Builder, Debug)] 40 | #[builder(name = "PrettyPrinter", setter(into))] 41 | pub struct PrettyPrint { 42 | // This is a hack, because we can not use skip right now 43 | // See https://github.com/colin-kiegel/rust-derive-builder/issues/110 44 | /// Language for syntax highlighting 45 | #[builder(default = "\"unknown\".to_string()")] 46 | language: String, 47 | 48 | /// Whether or not to show/replace non-printable 49 | /// characters like space, tab and newline. 50 | #[builder(default = "false")] 51 | show_nonprintable: bool, 52 | 53 | /// The character width of the terminal 54 | #[builder(default = "Term::stdout().size().1 as usize")] 55 | term_width: usize, 56 | 57 | /// The width of tab characters. 58 | /// Currently, a value of 0 will cause tabs to be 59 | /// passed through without expanding them. 60 | #[builder(default = "0")] 61 | tab_width: usize, 62 | 63 | /// Whether or not to simply loop through all input (`cat` mode) 64 | #[builder(default = "false")] 65 | loop_through: bool, 66 | 67 | /// Whether or not the output should be colorized 68 | #[builder(default = "true")] 69 | colored_output: bool, 70 | 71 | /// Whether or not the output terminal supports true color 72 | #[builder(default = "is_truecolor_terminal()")] 73 | true_color: bool, 74 | 75 | /// Print grid 76 | #[builder(default = "true")] 77 | grid: bool, 78 | 79 | /// Print header with output file name 80 | #[builder(default = "true")] 81 | header: bool, 82 | 83 | /// Print line numbers 84 | #[builder(default = "true")] 85 | line_numbers: bool, 86 | 87 | /// Text wrapping mode 88 | #[builder(default = "OutputWrap::None")] 89 | output_wrap: OutputWrap, 90 | 91 | /// Pager or STDOUT 92 | #[builder(default = "PagingMode::QuitIfOneScreen")] 93 | paging_mode: PagingMode, 94 | 95 | /// Specifies the lines that should be printed 96 | #[builder(default)] 97 | line_ranges: LineRanges, 98 | 99 | /// The syntax highlighting theme 100 | #[builder(default = "String::from(PRETTYPRINT_THEME_DEFAULT)")] 101 | theme: String, 102 | 103 | /// File extension/name mappings 104 | #[builder(default)] 105 | syntax_mapping: SyntaxMapping, 106 | 107 | /// Load custom syntax-highlighter 108 | #[builder(default = "None")] 109 | load_syntax: Option>, 110 | 111 | /// Load custom theme library 112 | #[builder(default = "None")] 113 | load_theme: Option>, 114 | 115 | /// Command to start the pager 116 | #[builder(default = "None")] 117 | pager: Option, 118 | 119 | /// Whether to print some characters using italics 120 | #[builder(default = "false")] 121 | use_italic_text: bool, 122 | } 123 | 124 | impl From<&PrettyPrint> for PrettyPrinter { 125 | fn from(printer: &PrettyPrint) -> Self { 126 | PrettyPrinter::default() 127 | .language(printer.language.clone()) 128 | .show_nonprintable(printer.show_nonprintable) 129 | .term_width(printer.term_width) 130 | .tab_width(printer.tab_width) 131 | .loop_through(printer.loop_through) 132 | .colored_output(printer.colored_output) 133 | .true_color(printer.true_color) 134 | .grid(printer.grid) 135 | .header(printer.header) 136 | .line_numbers(printer.line_numbers) 137 | .output_wrap(printer.output_wrap) 138 | .paging_mode(printer.paging_mode) 139 | .line_ranges(printer.line_ranges.clone()) 140 | .theme(printer.theme.clone()) 141 | .syntax_mapping(printer.syntax_mapping.clone()) 142 | .pager(printer.pager.clone()) 143 | .use_italic_text(printer.use_italic_text) 144 | .clone() // As expected, a lot of clone() 😂 145 | } 146 | } 147 | 148 | impl PrettyPrint { 149 | /// Dynamically configure printer 150 | pub fn configure(&self) -> PrettyPrinter { 151 | self.into() 152 | } 153 | 154 | /// Prints a file. 155 | pub fn file>(&self, filename: T) -> Result<()> { 156 | let file_string = filename.into(); 157 | let input = if file_string == "-" { 158 | InputFile::StdIn 159 | } else { 160 | InputFile::Ordinary(file_string) 161 | }; 162 | 163 | self.run_controller(input, None) 164 | } 165 | 166 | /// Prints a string. 167 | pub fn string>(&self, input: T) -> Result<()> { 168 | self.run_controller(InputFile::String(input.into()), None) 169 | } 170 | 171 | /// Prints a string with a specific header. 172 | pub fn string_with_header>(&self, input: T, header: T) -> Result<()> { 173 | self.run_controller(InputFile::String(input.into()), Some(header.into())) 174 | } 175 | 176 | /// List all available themes for syntax highlighting 177 | pub fn get_themes(&self) -> BTreeMap { 178 | let assets = self.get_assets(); 179 | assets.theme_set.themes 180 | } 181 | 182 | fn run_controller( 183 | &self, 184 | input_file: InputFile, 185 | header_overwrite: Option, 186 | ) -> Result<()> { 187 | #[cfg(windows)] 188 | let _ = ansi_term::enable_ansi_support(); 189 | // let interactive_output = atty::is(Stream::Stdout); 190 | 191 | let assets = self.get_assets(); 192 | let mut reader = input_file.get_reader()?; 193 | 194 | let lang_opt = match self.language.as_ref() { 195 | "unknown" => None, 196 | s => Some(s.to_string()), 197 | }; 198 | 199 | // This is faaaar from ideal, I know. 200 | let mut printer = InteractivePrinter::new( 201 | &assets, 202 | &input_file, 203 | &mut reader, 204 | self.get_output_components(), 205 | self.theme.clone(), 206 | self.colored_output, 207 | self.true_color, 208 | self.term_width, 209 | lang_opt, 210 | self.syntax_mapping.clone(), 211 | self.tab_width, 212 | self.show_nonprintable, 213 | self.output_wrap, 214 | self.use_italic_text, 215 | ); 216 | 217 | let mut output_type = OutputType::from_mode(self.paging_mode, self.pager.clone())?; 218 | let writer = output_type.handle()?; 219 | 220 | self.print_file(reader, &mut printer, writer, &input_file, header_overwrite)?; 221 | Ok(()) 222 | } 223 | 224 | fn get_assets(&self) -> HighlightingAssets { 225 | let syntax_set = self.load_syntax.as_ref().map(|b| from_binary(b.as_slice())); 226 | let theme_set = self.load_theme.as_ref().map(|b| from_binary(b.as_slice())); 227 | 228 | match (syntax_set, theme_set) { 229 | (Some(syntax_set), Some(theme_set)) => HighlightingAssets { 230 | syntax_set, 231 | theme_set, 232 | }, 233 | (Some(syntax_set), None) => HighlightingAssets { 234 | syntax_set, 235 | theme_set: HighlightingAssets::get_integrated_themeset(), 236 | }, 237 | (None, Some(theme_set)) => HighlightingAssets { 238 | syntax_set: HighlightingAssets::get_integrated_syntaxset(), 239 | theme_set, 240 | }, 241 | (None, None) => HighlightingAssets::new(), 242 | } 243 | } 244 | 245 | fn get_output_components(&self) -> OutputComponents { 246 | let mut components = HashSet::new(); 247 | if self.grid { 248 | components.insert(OutputComponent::Grid); 249 | } 250 | if self.header { 251 | components.insert(OutputComponent::Header); 252 | } 253 | if self.line_numbers { 254 | components.insert(OutputComponent::Numbers); 255 | } 256 | OutputComponents(components) 257 | } 258 | 259 | fn print_file<'a, P: Printer>( 260 | &self, 261 | reader: InputFileReader, 262 | printer: &mut P, 263 | writer: &mut dyn Write, 264 | input_file: &InputFile, 265 | header_overwrite: Option, 266 | ) -> Result<()> { 267 | printer.print_header(writer, &input_file, header_overwrite)?; 268 | self.print_file_ranges(printer, writer, reader, &self.line_ranges)?; 269 | printer.print_footer(writer)?; 270 | 271 | Ok(()) 272 | } 273 | 274 | fn print_file_ranges<'a, P: Printer>( 275 | &self, 276 | printer: &mut P, 277 | writer: &mut dyn Write, 278 | mut reader: InputFileReader, 279 | line_ranges: &LineRanges, 280 | ) -> Result<()> { 281 | let mut line_buffer = Vec::new(); 282 | let mut line_number: usize = 1; 283 | 284 | while reader.read_line(&mut line_buffer)? { 285 | match line_ranges.check(line_number) { 286 | RangeCheckResult::OutsideRange => { 287 | // Call the printer in case we need to call the syntax highlighter 288 | // for this line. However, set `out_of_range` to `true`. 289 | printer.print_line(true, writer, line_number, &line_buffer)?; 290 | } 291 | RangeCheckResult::InRange => { 292 | printer.print_line(false, writer, line_number, &line_buffer)?; 293 | } 294 | RangeCheckResult::AfterLastRange => { 295 | break; 296 | } 297 | } 298 | 299 | line_number += 1; 300 | line_buffer.clear(); 301 | } 302 | Ok(()) 303 | } 304 | } 305 | 306 | fn is_truecolor_terminal() -> bool { 307 | env::var("COLORTERM") 308 | .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit") 309 | .unwrap_or(false) 310 | } 311 | -------------------------------------------------------------------------------- /LICENSE_APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /assets/syntaxes/Dart.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: Dart 5 | file_extensions: 6 | - dart 7 | scope: source.dart 8 | contexts: 9 | main: 10 | - match: ^(#!.*)$ 11 | scope: meta.preprocessor.script.dart 12 | - match: ^\s*\b(library|import|export|part of|part)\b 13 | captures: 14 | 0: keyword.other.import.dart 15 | push: 16 | - meta_scope: meta.declaration.dart 17 | - match: ; 18 | captures: 19 | 0: punctuation.terminator.dart 20 | pop: true 21 | - include: strings 22 | - match: \b(as|show|hide|deferred)\b 23 | scope: keyword.other.import.dart 24 | - include: comments 25 | - include: constants-and-special-vars 26 | - include: annotations 27 | - include: decl-typedef 28 | - include: decl-class 29 | - include: decl-enum 30 | - include: decl-function 31 | - include: keywords 32 | - include: strings 33 | annotations: 34 | - match: '^(?:\s*)((@)([a-zA-Z0-9_]+))' 35 | captures: 36 | 1: annotation.dart 37 | 2: entity.name.function.annotation.dart 38 | 3: support.type.dart 39 | comments: 40 | - match: /\*\*/ 41 | scope: comment.block.empty.dart 42 | captures: 43 | 0: punctuation.definition.comment.dart 44 | - include: comments-inline 45 | comments-inline: 46 | - match: /\* 47 | push: 48 | - meta_scope: comment.block.dart 49 | - match: \*/ 50 | pop: true 51 | - include: scope:text.dart-doccomments 52 | - match: (///) 53 | captures: 54 | 1: marker.dart 55 | push: 56 | - meta_scope: comment.line.triple-slash.dart 57 | - match: $ 58 | pop: true 59 | - include: scope:text.dart-doccomments 60 | - match: (//) 61 | captures: 62 | 1: marker.dart 63 | push: 64 | - meta_scope: comment.line.double-slash.dart 65 | - match: $ 66 | pop: true 67 | - include: scope:text.dart-doccomments 68 | constants-and-special-vars: 69 | - match: \b(true|false|null)\b 70 | scope: constant.language.dart 71 | - match: \b(this|super)\b 72 | scope: variable.language.dart 73 | - match: '\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)\b' 74 | scope: constant.numeric.dart 75 | decl-class: 76 | - match: \bclass\b 77 | captures: 78 | 0: keyword.control.new.dart 79 | push: 80 | - meta_scope: meta.declaration.class.dart 81 | - match: "(?={)" 82 | pop: true 83 | - include: keywords 84 | - match: "[A-Za-z_][A-Za-z0-9_]*" 85 | scope: class.name.dart 86 | decl-enum: 87 | - match: \benum\b 88 | captures: 89 | 0: keyword.declaration.dart 90 | push: 91 | - meta_scope: meta.declaration.enum.dart 92 | - match: "(?={)" 93 | pop: true 94 | - include: keywords 95 | - match: "[A-Za-z_][A-Za-z0-9_]*" 96 | scope: enum.name.dart 97 | decl-function: 98 | - match: ^\s*(?:\b(void|bool|num|int|double|dynamic|var|String|List|Map)\b)\s+(get)\s+(\w+)\s+(?==>) 99 | comment: A getter with a primitive return type. 100 | scope: meta.declaration.function.dart 101 | captures: 102 | 1: storage.type.primitive.dart 103 | 2: keyword.declaration.dart 104 | 3: function.name.dart 105 | - match: ^\s*(?:\b(\w+)\b\s+)?(get)\s+(\w+)\s+(?==>) 106 | comment: A getter with a user-defined return type or no return type. 107 | scope: meta.declaration.function.dart 108 | captures: 109 | 1: type.user-defined.dart 110 | 2: keyword.declaration.dart 111 | 3: function.name.dart 112 | - match: ^\s*(set)\s+(\w+)(?=\() 113 | comment: A setter. 114 | captures: 115 | 1: keyword.declaration.dart 116 | 2: function.name.dart 117 | push: 118 | - meta_scope: meta.declaration.function.dart 119 | - match: \) 120 | pop: true 121 | - include: comments-inline 122 | - include: decl-function-parameter 123 | - include: strings 124 | - include: keywords 125 | - match: ^\s*(?:\b(void|bool|num|int|double|dynamic|var|String|List|Map)\b)\s+(\w+)(?=\() 126 | comment: A function with a primitive return type. 127 | captures: 128 | 1: storage.type.primitive.dart 129 | 2: function.name.dart 130 | push: 131 | - meta_scope: meta.declaration.function.dart 132 | - match: \) 133 | pop: true 134 | - include: comments-inline 135 | - include: decl-function-parameter 136 | - include: strings 137 | - include: keywords 138 | - match: ^\s*(?:\b(return)\b)\s+(\w+)(?=\() 139 | comment: A function invocation after 'return' 140 | captures: 141 | 1: keyword.control.dart 142 | 2: function.name.dart 143 | push: 144 | - meta_scope: meta.invocation.function.dart 145 | - match: \) 146 | pop: true 147 | - include: comments-inline 148 | - include: decl-function-parameter 149 | - include: strings 150 | - include: keywords 151 | - match: ^\s*\b(new)\b\s+(\w+)(?=\() 152 | comment: A class instantiation after 'new' 153 | captures: 154 | 1: keyword.declaration.dart 155 | 2: function.name.dart 156 | push: 157 | - meta_scope: meta.invocation.function.dart 158 | - match: \) 159 | pop: true 160 | - include: comments-inline 161 | - include: decl-function-parameter 162 | - include: strings 163 | - include: keywords 164 | decl-function-parameter: 165 | - include: constants-and-special-vars 166 | - match: (?:\b(void|bool|num|int|double|dynamic|var|String|List|Map)\b)\s+(\w+)(?=\() 167 | comment: A function with a primitive return type. 168 | captures: 169 | 1: storage.type.primitive.dart 170 | 2: function.name.dart 171 | push: 172 | - meta_scope: meta.parameter.function.dart 173 | - match: \) 174 | pop: true 175 | - include: decl-function-parameter 176 | - include: strings 177 | - include: keywords 178 | - match: \b(new)\b\s+(\w+)(?=\() 179 | comment: A class instantiation after 'new' 180 | captures: 181 | 1: keyword.declaration.dart 182 | 2: function.name.dart 183 | push: 184 | - meta_scope: meta.invocation.function.dart 185 | - match: \) 186 | pop: true 187 | - include: decl-function-parameter 188 | - include: strings 189 | - include: keywords 190 | - match: (?:\b(\w+)\b)\s+(\w+)(?=\() 191 | comment: A function with a user-defined return type. 192 | captures: 193 | 1: type.user-defined.dart 194 | 2: function.name.dart 195 | push: 196 | - meta_scope: meta.parameter.function.dart 197 | - match: \) 198 | pop: true 199 | - include: decl-function-parameter 200 | - include: strings 201 | - include: keywords 202 | - match: (\w+)(?=\() 203 | comment: A function with no return type. 204 | captures: 205 | 1: function.name.dart 206 | push: 207 | - meta_scope: meta.parameter.function.dart 208 | - match: \) 209 | pop: true 210 | - include: decl-function-parameter 211 | - include: strings 212 | - include: keywords 213 | decl-typedef: 214 | - match: typedef 215 | captures: 216 | 0: keyword.control.new.dart 217 | push: 218 | - meta_scope: meta.declaration.typedef.dart 219 | - match: ; 220 | captures: 221 | 0: punctuation.terminator.dart 222 | pop: true 223 | - match: '(?:\b(void|bool|num|int|double|dynamic|var|String|List|Map)\b|([a-zA-Z_][a-zA-Z0-9_]*))\s+([a-zA-Z_][a-zA-Z0-9_]+)' 224 | captures: 225 | 1: storage.type.primitive.dart 226 | 2: typedef.return.dart 227 | 3: typedef.name.dart 228 | - match: \( 229 | push: 230 | - meta_scope: typedef.params.dart 231 | - match: \) 232 | pop: true 233 | - include: keywords 234 | keywords: 235 | - match: \bassert\b 236 | scope: keyword.control.assert.dart 237 | - match: \bas\b 238 | scope: keyword.cast.dart 239 | - match: \b(try|catch|finally|throw|on|rethrow)\b 240 | scope: keyword.control.catch-exception.dart 241 | - match: \s+\?\s+|\s+:\s+ 242 | scope: keyword.control.ternary.dart 243 | - match: \b(break|case|continue|default|do|else|for|if|in|return|switch|while)\b 244 | scope: keyword.control.dart 245 | - match: \b(async\*|async|await\*|await|yield)\b 246 | scope: keyword.control.async.dart 247 | - match: \b(new)\b 248 | scope: keyword.control.new.dart 249 | - match: \b(abstract|extends|external|factory|implements|with|interface|get|native|operator|set|typedef)\b 250 | scope: keyword.declaration.dart 251 | - match: \b(is\!?)\b 252 | scope: keyword.operator.dart 253 | - match: (<<|>>>?|~|\^|\||&) 254 | scope: keyword.operator.bitwise.dart 255 | - match: ((&|\^|\||<<|>>>?)=) 256 | scope: keyword.operator.assignment.bitwise.dart 257 | - match: (===?|!==?|<=?|>=?) 258 | scope: keyword.operator.comparison.dart 259 | - match: '(([+*/%-]|\~)=)' 260 | scope: keyword.operator.assignment.arithmetic.dart 261 | - match: (=) 262 | scope: keyword.operator.assignment.dart 263 | - match: (\-\-|\+\+) 264 | scope: keyword.operator.increment-decrement.dart 265 | - match: (\-|\+|\*|\/|\~\/|%) 266 | scope: keyword.operator.arithmetic.dart 267 | - match: (!|&&|\|\|) 268 | scope: keyword.operator.logical.dart 269 | - match: ; 270 | scope: punctuation.terminator.dart 271 | - match: \b(static|final|const)\b 272 | scope: storage.modifier.dart 273 | - match: \b(?:void|bool|num|int|double|dynamic|var|String|List|Map)\b 274 | scope: storage.type.primitive.dart 275 | regexp: 276 | - match: '\\[^''"]' 277 | scope: constant.character.escaped.regex.dart 278 | - match: \( 279 | push: 280 | - meta_content_scope: meta.capture.regex.dart 281 | - match: \) 282 | pop: true 283 | - match: \?(:|=|!) 284 | scope: ignore.capture.regex.dart 285 | - match: \*|\+|\?|\.|\| 286 | scope: keyword.other.regex.dart 287 | - match: \^|\$ 288 | scope: keyword.other.regex.dart 289 | - match: \. 290 | scope: constant.other.regex.dart 291 | - match: '\[(\^)?' 292 | captures: 293 | 1: keyword.other.negation.regex.dart 294 | push: 295 | - meta_scope: constant.character.range.regex.dart 296 | - match: '\]' 297 | pop: true 298 | - match: '\\[^"'']' 299 | scope: constant.character.escaped.regex.dart 300 | - match: '\{(?:\d+)?,(?:\d+)?\}' 301 | scope: keyword.other.regex.dart 302 | string-interp: 303 | - match: '(\$)(\{)' 304 | captures: 305 | 1: keyword.other.dart 306 | 2: keyword.other.dart 307 | push: 308 | - meta_scope: interpolation.dart 309 | - meta_content_scope: source.dart 310 | - match: '(\})' 311 | captures: 312 | 1: keyword.other.dart 313 | pop: true 314 | - include: main 315 | - match: (\$)(\w+) 316 | captures: 317 | 1: keyword.other.dart 318 | 2: variable.parameter.dart 319 | - match: \\. 320 | scope: constant.character.escape.dart 321 | strings: 322 | - match: (?" 37 | pop: true 38 | - include: generics 39 | - match: \( 40 | push: 41 | - match: \) 42 | pop: true 43 | - include: parameters 44 | - match: (:) 45 | captures: 46 | 1: keyword.operator.declaration.kotlin 47 | push: 48 | - match: "(?={|$)" 49 | pop: true 50 | - match: \w+ 51 | scope: entity.other.inherited-class.kotlin 52 | - match: \( 53 | push: 54 | - match: \) 55 | pop: true 56 | - include: expressions 57 | - match: '\{' 58 | push: 59 | - match: '\}' 60 | pop: true 61 | - include: statements 62 | comments: 63 | - match: /\* 64 | captures: 65 | 0: punctuation.definition.comment.kotlin 66 | push: 67 | - meta_scope: comment.block.kotlin 68 | - match: \*/ 69 | captures: 70 | 0: punctuation.definition.comment.kotlin 71 | pop: true 72 | - match: \s*((//).*$\n?) 73 | captures: 74 | 1: comment.line.double-slash.kotlin 75 | 2: punctuation.definition.comment.kotlin 76 | constants: 77 | - match: \b(true|false|null|this|super)\b 78 | scope: constant.language.kotlin 79 | - match: '\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)([LlFf])?\b' 80 | scope: constant.numeric.kotlin 81 | - match: '\b([A-Z][A-Z0-9_]+)\b' 82 | scope: constant.other.kotlin 83 | expressions: 84 | - match: \( 85 | push: 86 | - match: \) 87 | pop: true 88 | - include: expressions 89 | - include: types 90 | - include: strings 91 | - include: constants 92 | - include: comments 93 | - include: keywords 94 | functions: 95 | - match: (?=\s*\b(?:fun)\b) 96 | push: 97 | - match: '(?=$|\})' 98 | pop: true 99 | - match: \b(fun)\b 100 | captures: 101 | 1: keyword.other.kotlin 102 | push: 103 | - match: (?=\() 104 | pop: true 105 | - match: < 106 | push: 107 | - match: ">" 108 | pop: true 109 | - include: generics 110 | - match: '([\.<\?>\w]+\.)?(\w+)' 111 | captures: 112 | 2: entity.name.function.kotlin 113 | - match: \( 114 | push: 115 | - match: \) 116 | pop: true 117 | - include: parameters 118 | - match: (:) 119 | captures: 120 | 1: keyword.operator.declaration.kotlin 121 | push: 122 | - match: "(?={|=|$)" 123 | pop: true 124 | - include: types 125 | - match: '\{' 126 | push: 127 | - match: '(?=\})' 128 | pop: true 129 | - include: statements 130 | - match: (=) 131 | captures: 132 | 1: keyword.operator.assignment.kotlin 133 | push: 134 | - match: (?=$) 135 | pop: true 136 | - include: expressions 137 | generics: 138 | - match: (:) 139 | captures: 140 | 1: keyword.operator.declaration.kotlin 141 | push: 142 | - match: (?=,|>) 143 | pop: true 144 | - include: types 145 | - include: keywords 146 | - match: \w+ 147 | scope: storage.type.generic.kotlin 148 | getters-and-setters: 149 | - match: \b(get)\b\s*\(\s*\) 150 | captures: 151 | 1: entity.name.function.kotlin 152 | push: 153 | - match: '\}|(?=\bset\b)|$' 154 | pop: true 155 | - match: (=) 156 | captures: 157 | 1: keyword.operator.assignment.kotlin 158 | push: 159 | - match: (?=$|\bset\b) 160 | pop: true 161 | - include: expressions 162 | - match: '\{' 163 | push: 164 | - match: '\}' 165 | pop: true 166 | - include: expressions 167 | - match: \b(set)\b\s*(?=\() 168 | captures: 169 | 1: entity.name.function.kotlin 170 | push: 171 | - match: '\}|(?=\bget\b)|$' 172 | pop: true 173 | - match: \( 174 | push: 175 | - match: \) 176 | pop: true 177 | - include: parameters 178 | - match: (=) 179 | captures: 180 | 1: keyword.operator.assignment.kotlin 181 | push: 182 | - match: (?=$|\bset\b) 183 | pop: true 184 | - include: expressions 185 | - match: '\{' 186 | push: 187 | - match: '\}' 188 | pop: true 189 | - include: expressions 190 | imports: 191 | - match: '^\s*(import)\s+[^ $]+\s+(as)?' 192 | captures: 193 | 1: keyword.other.kotlin 194 | 2: keyword.other.kotlin 195 | keywords: 196 | - match: \b(var|val|public|private|protected|abstract|final|sealed|enum|open|attribute|annotation|override|inline|vararg|in|out|internal|data|tailrec|operator|infix|const|yield|typealias|typeof|reified|suspend)\b 197 | scope: storage.modifier.kotlin 198 | - match: \b(try|catch|finally|throw)\b 199 | scope: keyword.control.catch-exception.kotlin 200 | - match: \b(if|else|while|for|do|return|when|where|break|continue)\b 201 | scope: keyword.control.kotlin 202 | - match: \b(in|is|!in|!is|as|as\?|assert)\b 203 | scope: keyword.operator.kotlin 204 | - match: (==|!=|===|!==|<=|>=|<|>) 205 | scope: keyword.operator.comparison.kotlin 206 | - match: (=) 207 | scope: keyword.operator.assignment.kotlin 208 | - match: (::) 209 | scope: keyword.operator.kotlin 210 | - match: (:) 211 | scope: keyword.operator.declaration.kotlin 212 | - match: \b(by)\b 213 | scope: keyword.other.by.kotlin 214 | - match: (\?\.) 215 | scope: keyword.operator.safenav.kotlin 216 | - match: (\.) 217 | scope: keyword.operator.dot.kotlin 218 | - match: (\?:) 219 | scope: keyword.operator.elvis.kotlin 220 | - match: (\-\-|\+\+) 221 | scope: keyword.operator.increment-decrement.kotlin 222 | - match: (\+=|\-=|\*=|\/=) 223 | scope: keyword.operator.arithmetic.assign.kotlin 224 | - match: (\.\.) 225 | scope: keyword.operator.range.kotlin 226 | - match: (\-|\+|\*|\/|%) 227 | scope: keyword.operator.arithmetic.kotlin 228 | - match: (!|&&|\|\|) 229 | scope: keyword.operator.logical.kotlin 230 | - match: (;) 231 | scope: punctuation.terminator.kotlin 232 | namespaces: 233 | - match: \b(namespace)\b 234 | scope: keyword.other.kotlin 235 | - match: '\{' 236 | push: 237 | - match: '\}' 238 | pop: true 239 | - include: statements 240 | parameters: 241 | - match: (:) 242 | captures: 243 | 1: keyword.operator.declaration.kotlin 244 | push: 245 | - match: (?=,|\)|=) 246 | pop: true 247 | - include: types 248 | - match: (=) 249 | captures: 250 | 1: keyword.operator.declaration.kotlin 251 | push: 252 | - match: (?=,|\)) 253 | pop: true 254 | - include: expressions 255 | - include: keywords 256 | - match: \w+ 257 | scope: variable.parameter.function.kotlin 258 | statements: 259 | - include: namespaces 260 | - include: typedefs 261 | - include: classes 262 | - include: functions 263 | - include: variables 264 | - include: getters-and-setters 265 | - include: expressions 266 | strings: 267 | - match: '"""' 268 | captures: 269 | 0: punctuation.definition.string.begin.kotlin 270 | push: 271 | - meta_scope: string.quoted.third.kotlin 272 | - match: '"""' 273 | captures: 274 | 0: punctuation.definition.string.end.kotlin 275 | pop: true 276 | - match: '(\$\w+|\$\{[^\}]+\})' 277 | scope: variable.parameter.template.kotlin 278 | - match: \\. 279 | scope: constant.character.escape.kotlin 280 | - match: '"' 281 | captures: 282 | 0: punctuation.definition.string.begin.kotlin 283 | push: 284 | - meta_scope: string.quoted.double.kotlin 285 | - match: '"' 286 | captures: 287 | 0: punctuation.definition.string.end.kotlin 288 | pop: true 289 | - match: '(\$\w+|\$\{[^\}]+\})' 290 | scope: variable.parameter.template.kotlin 291 | - match: \\. 292 | scope: constant.character.escape.kotlin 293 | - match: "'" 294 | captures: 295 | 0: punctuation.definition.string.begin.kotlin 296 | push: 297 | - meta_scope: string.quoted.single.kotlin 298 | - match: "'" 299 | captures: 300 | 0: punctuation.definition.string.end.kotlin 301 | pop: true 302 | - match: \\. 303 | scope: constant.character.escape.kotlin 304 | - match: "`" 305 | captures: 306 | 0: punctuation.definition.string.begin.kotlin 307 | push: 308 | - meta_scope: string.quoted.single.kotlin 309 | - match: "`" 310 | captures: 311 | 0: punctuation.definition.string.end.kotlin 312 | pop: true 313 | typedefs: 314 | - match: (?=\s*(?:type)) 315 | push: 316 | - match: (?=$) 317 | pop: true 318 | - match: \b(type)\b 319 | scope: keyword.other.kotlin 320 | - match: < 321 | push: 322 | - match: ">" 323 | pop: true 324 | - include: generics 325 | - include: expressions 326 | types: 327 | - match: \b(Nothing|Any|Unit|String|CharSequence|Int|Boolean|Char|Long|Double|Float|Short|Byte|dynamic)\b 328 | scope: storage.type.buildin.kotlin 329 | - match: \b(IntArray|BooleanArray|CharArray|LongArray|DoubleArray|FloatArray|ShortArray|ByteArray)\b 330 | scope: storage.type.buildin.array.kotlin 331 | - match: \b(Array|Collection|List|Map|Set|MutableList|MutableMap|MutableSet|Sequence)<\b 332 | captures: 333 | 1: storage.type.buildin.collection.kotlin 334 | push: 335 | - match: ">" 336 | pop: true 337 | - include: types 338 | - include: keywords 339 | - match: \w+< 340 | push: 341 | - match: ">" 342 | pop: true 343 | - include: types 344 | - include: keywords 345 | - match: '\{' 346 | push: 347 | - match: '\}' 348 | pop: true 349 | - include: statements 350 | - match: \( 351 | push: 352 | - match: \) 353 | pop: true 354 | - include: types 355 | - match: (->) 356 | scope: keyword.operator.declaration.kotlin 357 | variables: 358 | - match: (?=\s*\b(?:var|val)\b) 359 | push: 360 | - match: (?=:|=|(\b(by)\b)|$) 361 | pop: true 362 | - match: \b(var|val)\b 363 | captures: 364 | 1: keyword.other.kotlin 365 | push: 366 | - match: (?=:|=|(\b(by)\b)|$) 367 | pop: true 368 | - match: < 369 | push: 370 | - match: ">" 371 | pop: true 372 | - include: generics 373 | - match: '([\.<\?>\w]+\.)?(\w+)' 374 | captures: 375 | 2: entity.name.variable.kotlin 376 | - match: (:) 377 | captures: 378 | 1: keyword.operator.declaration.kotlin 379 | push: 380 | - match: (?==|$) 381 | pop: true 382 | - include: types 383 | - include: getters-and-setters 384 | - match: \b(by)\b 385 | captures: 386 | 1: keyword.other.kotlin 387 | push: 388 | - match: (?=$) 389 | pop: true 390 | - include: expressions 391 | - match: (=) 392 | captures: 393 | 1: keyword.operator.assignment.kotlin 394 | push: 395 | - match: (?=$) 396 | pop: true 397 | - include: expressions 398 | - include: getters-and-setters 399 | -------------------------------------------------------------------------------- /assets/syntaxes/Swift.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: Swift 5 | file_extensions: 6 | - swift 7 | first_line_match: ^#!/.*\bswift 8 | scope: source.swift 9 | contexts: 10 | main: 11 | - include: shebang-line 12 | - include: comment 13 | - include: attribute 14 | - include: literal 15 | - include: operator 16 | - include: declaration 17 | - include: storage-type 18 | - include: keyword 19 | - include: type 20 | - include: boolean 21 | comment: 22 | - include: documentation-comment 23 | - include: block-comment 24 | - include: in-line-comment 25 | access-level-modifier: 26 | - match: \b(open|public|internal|fileprivate|private)\b(?:\(set\))? 27 | comment: access-level-modifier 28 | scope: keyword.other.access-level-modifier.swift 29 | arithmetic-operator: 30 | - match: '(?&|\^~.])(\+|\-|\*|\/)(?![/=\-+!*%<>&|\^~.])' 31 | scope: keyword.operator.arithmetic.swift 32 | array-type: 33 | - match: \b(Array)(<) 34 | captures: 35 | 1: support.type.array.swift 36 | 2: punctuation.array.begin.swift 37 | push: 38 | - meta_scope: meta.array.swift 39 | - match: (>) 40 | captures: 41 | 1: punctuation.array.end.swift 42 | pop: true 43 | - include: main 44 | assignment-operator: 45 | - match: '(?&|\^~.])(\+|\-|\*|\/|%|<<|>>|&|\^|\||&&|\|\|)?=(?![/=\-+!*%<>&|\^~.])' 46 | scope: keyword.operator.assignment.swift 47 | attribute: 48 | - match: '((@)(\B\$[0-9]+|\b[\w^\d][\w\d]*\b|\B`[\w^\d][\w\d]*`\B))(\()' 49 | captures: 50 | 1: storage.modifier.attribute.swift 51 | 2: punctuation.definition.attribute.swift 52 | 3: punctuation.definition.attribute-arguments.begin.swift 53 | push: 54 | - meta_content_scope: meta.attribute.arguments.swift 55 | - match: \) 56 | captures: 57 | 0: punctuation.definition.attribute-arguments.end.swift 58 | pop: true 59 | - include: main 60 | - match: '((@)(\B\$[0-9]+|\b[\w^\d][\w\d]*\b|\B`[\w^\d][\w\d]*`\B))' 61 | captures: 62 | 1: storage.modifier.attribute.swift 63 | 2: punctuation.definition.attribute.swift 64 | bitwise-operator: 65 | - match: '(?&|\^~.])(&|\||\^|<<|>>)(?![/=\-+!*%<>&|\^~.])' 66 | scope: keyword.operator.bitwise.swift 67 | block-comment: 68 | - match: /\* 69 | comment: Block comment 70 | captures: 71 | 0: punctuation.definition.comment.block.begin.swift 72 | push: 73 | - meta_scope: comment.block.swift 74 | - match: \*/ 75 | captures: 76 | 0: punctuation.definition.comment.block.end.swift 77 | pop: true 78 | boolean: 79 | - match: \b(true|false)\b 80 | scope: keyword.constant.boolean.swift 81 | branch-statement-keyword: 82 | - include: if-statement-keyword 83 | - include: switch-statement-keyword 84 | catch-statement-keyword: 85 | - match: \b(catch|do)\b 86 | comment: catch-statement 87 | scope: kewyord.control.catch.swift 88 | code-block: 89 | - match: '(\{)' 90 | comment: code-block 91 | captures: 92 | 1: punctuation.definition.code-block.begin.swift 93 | push: 94 | - match: '(\})' 95 | captures: 96 | 1: punctuation.definition.code-block.end.swift 97 | pop: true 98 | - include: main 99 | collection-type: 100 | - include: array-type 101 | - include: dictionary-type 102 | - match: \b(Array|Dictionary)\b 103 | scope: support.type.swift 104 | comparative-operator: 105 | - match: '(?&|\^~.])((=|!)==?|(<|>)=?|~=)(?![/=\-+!*%<>&|\^~.])' 106 | scope: keyword.operator.comparative.swift 107 | control-transfer-statement-keyword: 108 | - match: \b(continue|break|fallthrough|return)\b 109 | comment: control-transfer-statement 110 | scope: keyword.control.transfer.swift 111 | custom-operator: 112 | - match: '(?<=[\s(\[{,;:])([/=\-+!*%<>&|\^~.]++)(?![\s)\]},;:])' 113 | scope: keyword.operator.custom.prefix.unary.swift 114 | - match: '(?&|\^~.]++)(?![\s)\]},;:\.])' 115 | scope: keyword.operator.custom.postfix.unary.swift 116 | - match: '(?<=[\s(\[{,;:])([/=\-+!*%<>&|\^~.]++)(?=[\s)\]},;:])' 117 | scope: keyword.operator.custom.binary.swift 118 | declaration: 119 | - include: import-declaration 120 | - include: function-declaration 121 | declaration-modifier: 122 | - match: \b(class|convenience|dynamic|final|lazy|(non)?mutating|optional|override|required|static|unowned((un)?safe)?|weak)\b 123 | comment: declaration-modifier 124 | scope: keyword.other.declaration-modifier.swift 125 | dictionary-type: 126 | - match: \b(Dictionary)(<) 127 | captures: 128 | 1: support.type.dictionary.swift 129 | 2: punctuation.dictionary.begin.swift 130 | push: 131 | - meta_scope: meta.dictionary.swift 132 | - match: (>) 133 | captures: 134 | 1: punctuation.dictionary.end.swift 135 | pop: true 136 | - include: main 137 | documentation-comment: 138 | - match: /\*\* 139 | comment: Documentation comment 140 | captures: 141 | 0: punctuation.definition.comment.block.documentation.begin.swift 142 | push: 143 | - meta_scope: comment.block.documentation.swift 144 | - match: \*/ 145 | captures: 146 | 0: punctuation.definition.comment.block.documentation.end.swift 147 | pop: true 148 | floating-point-literal: 149 | - match: '\b([0-9][0-9_]*)(\.([0-9][0-9_]*))?([eE][+\-]?([0-9][0-9_]*))?\b' 150 | comment: floating-point-literal -> (decimal-literal)(decimal-fraction)?(decimal-exponent)? 151 | - match: '\b(0x\h[\h_]*)(\.(0x\h[\h_]*))?([pP][+\-]?(0x\h[\h_]*))\b' 152 | comment: floating-point-literal -> (hexadecimal-literal)(hexadecimal-fraction)?(hexadecimal-exponent) 153 | function-body: 154 | - include: code-block 155 | function-declaration: 156 | - match: '\b(func)\s+(\B\$[0-9]+|\b[\w^\d][\w\d]*\b|\B`[\w^\d][\w\d]*`\B|[/=\-+!*%<>&|\^~.]+)\s*(?=\(|<)' 157 | comment: function-declaration 158 | captures: 159 | 1: storage.type.function.swift 160 | 2: entity.type.function.swift 161 | push: 162 | - meta_scope: meta.function-declaration.swift 163 | - match: '(?<=\})' 164 | pop: true 165 | - include: generic-parameter-clause 166 | - include: parameter-clause 167 | - include: function-result 168 | - include: function-body 169 | function-result: 170 | - match: '(?&|\^~.])(\->)(?![/=\-+!*%<>&|\^~.])\s*' 171 | comment: function-result 172 | captures: 173 | 1: keyword.operator.function-result.swift 174 | push: 175 | - meta_scope: meta.function-result.swift 176 | - match: '\s*(?=\{)' 177 | pop: true 178 | - include: type 179 | generic-parameter-clause: 180 | - match: (<) 181 | comment: generic-parameter-clause 182 | captures: 183 | 1: punctuation.definition.generic-parameter-clause.begin.swift 184 | push: 185 | - meta_scope: meta.generic-parameter-clause.swift 186 | - match: (>) 187 | captures: 188 | 1: punctuation.definition.generic-parameter-clause.end.swift 189 | pop: true 190 | - include: main 191 | identifier: 192 | - match: '(\B\$[0-9]+|\b[\w^\d][\w\d]*\b|\B`[\w^\d][\w\d]*`\B)' 193 | comment: identifier 194 | scope: meta.identifier.swift 195 | if-statement-keyword: 196 | - match: \b(if|else)\b 197 | comment: if-statement 198 | scope: keyword.control.if.swift 199 | import-declaration: 200 | - match: '\b(import)\s+(?:(typealias|struct|class|enum|protocol|var|func)\s+)?((?:\B\$[0-9]+|\b[\w^\d][\w\d]*\b|\B`[\w^\d][\w\d]*`\B|[/=\-+!*%<>&|\^~.]+)(?:\.(?:\B\$[0-9]+|\b[\w^\d][\w\d]*\b|\B`[\w^\d][\w\d]*`\B|[/=\-+!*%<>&|\^~.]+))*)' 201 | comment: import-declaration 202 | scope: meta.import.swift 203 | captures: 204 | 1: keyword.other.import.swift 205 | 2: storage.modifier.swift 206 | 3: support.type.module.import.swift 207 | in-line-comment: 208 | - match: (//).* 209 | comment: In-line comment 210 | scope: comment.line.double-slash.swift 211 | captures: 212 | 1: punctuation.definition.comment.line.double-slash.swift 213 | increment-decrement-operator: 214 | - match: '(?&|\^~.])(\+\+|\-\-)(?![/=\-+!*%<>&|\^~.])' 215 | scope: keyword.operator.increment-or-decrement.swift 216 | integer-literal: 217 | - match: '(\B\-|\b)(0b[01][01_]*)\b' 218 | comment: binary-literal 219 | scope: constant.numeric.integer.binary.swift 220 | - match: '(\B\-|\b)(0o[0-7][0-7_]*)\b' 221 | comment: octal-literal 222 | scope: constant.numeric.integer.octal.swift 223 | - match: '(\B\-|\b)([0-9][0-9_]*)\b' 224 | comment: decimal-literal 225 | scope: constant.numeric.integer.decimal.swift 226 | - match: '(\B\-|\b)(0x\h[\h_]*)\b' 227 | comment: hexadecimal-literal 228 | scope: constant.numeric.integer.hexadecimal.swift 229 | integer-type: 230 | - match: \bU?Int(8|16|32|64)?\b 231 | comment: Int types 232 | scope: support.type.swift 233 | keyword: 234 | - include: branch-statement-keyword 235 | - include: control-transfer-statement-keyword 236 | - include: loop-statement-keyword 237 | - include: catch-statement-keyword 238 | - include: operator-declaration-modifier 239 | - include: declaration-modifier 240 | - include: access-level-modifier 241 | - match: \b(class|deinit|enum|extension|func|import|init|let|protocol|static|struct|subscript|typealias|var|throws|rethrows)\b 242 | comment: declaration keyword 243 | scope: keyword.declaration.swift 244 | - match: \b(break|case|continue|default|do|else|fallthrough|if|in|for|return|switch|where|while|repeat|catch|guard|defer|try|throw)\b 245 | comment: statement keyword 246 | scope: keyword.statement.swift 247 | - match: \b(as|dynamicType|is|new|super|self|Self|Type)\b 248 | comment: expression and type keyword 249 | scope: keyword.other.statement.swift 250 | - match: \b(associativity|didSet|get|infix|inout|left|mutating|none|nonmutating|operator|override|postfix|precedence|prefix|right|set|unowned((un)?safe)?|weak|willSet)\b 251 | comment: other keyword 252 | scope: keyword.other.swift 253 | literal: 254 | - include: integer-literal 255 | - include: floating-point-literal 256 | - include: nil-literal 257 | - include: string-literal 258 | - include: special-literal 259 | logical-operator: 260 | - match: '(?&|\^~.])(!|&&|\|\|)(?![/=\-+!*%<>&|\^~.])' 261 | scope: keyword.operator.logical.swift 262 | loop-statement-keyword: 263 | - match: \b(while|repeat|for|in)\b 264 | comment: loop-statement 265 | scope: keyword.control.loop.swift 266 | nil-literal: 267 | - match: \bnil\b 268 | comment: nil-literal 269 | scope: constant.nil.swift 270 | operator: 271 | - include: comparative-operator 272 | - include: assignment-operator 273 | - include: logical-operator 274 | - include: remainder-operator 275 | - include: increment-decrement-operator 276 | - include: overflow-operator 277 | - include: range-operator 278 | - include: bitwise-operator 279 | - include: arithmetic-operator 280 | - include: ternary-operator 281 | - include: type-casting-operator 282 | - include: custom-operator 283 | operator-declaration-modifier: 284 | - match: \b(operator|prefix|infix|postfix)\b 285 | comment: operator-declaration 286 | scope: keyword.other.operator.swift 287 | optional-type: 288 | - match: \b(Optional)(<) 289 | scope: meta.optional.swift 290 | overflow-operator: 291 | - match: '(?&|\^~.])\&(\+|\-|\*|\/|%)(?![/=\-+!*%<>&|\^~.])' 292 | scope: keyword.operator.overflow.swift 293 | parameter-clause: 294 | - match: (\() 295 | comment: parameter-clause 296 | captures: 297 | 1: punctuation.definition.function-arguments.begin.swift 298 | push: 299 | - meta_scope: meta.parameter-clause.swift 300 | - match: (\)) 301 | captures: 302 | 1: punctuation.definition.function-arguments.end.swift 303 | pop: true 304 | - include: main 305 | primitive-type: 306 | - match: \b(Int|Float|Double|String|Bool|Character|Void)\b 307 | comment: Primitive types 308 | scope: support.type.swift 309 | protocol-composition-type: 310 | - match: \b(protocol)(<) 311 | scope: meta.protocol.swift 312 | range-operator: 313 | - match: '(?&|\^~.])\.\.(?:\.)?(?![/=\-+!*%<>&|\^~.])' 314 | scope: keyword.operator.range.swift 315 | remainder-operator: 316 | - match: '(?&|\^~.])\%(?![/=\-+!*%<>&|\^~.])' 317 | scope: keyword.operator.remainder.swift 318 | shebang-line: 319 | - match: ^(#!).*$ 320 | comment: Shebang line 321 | scope: comment.line.shebang.swift 322 | captures: 323 | 1: punctuation.definition.comment.line.shebang.swift 324 | special-literal: 325 | - match: \b__(FILE|LINE|COLUMN|FUNCTION)__\b 326 | scope: keyword.other.literal.swift 327 | storage-type: 328 | - match: \b(var|func|let|class|enum|struct|protocol|extension|typealias)\b 329 | scope: storage.type.swift 330 | string-literal: 331 | - match: \" 332 | captures: 333 | 0: string.quoted.double.swift 334 | push: 335 | - meta_scope: meta.literal.string.swift 336 | - match: \" 337 | captures: 338 | 0: string.quoted.double.swift 339 | pop: true 340 | - match: '\\([0tnr\"\''\\]|x\h{2}|u\h{4}|U\h{8})' 341 | scope: constant.character.escape.swift 342 | - match: (\\\() 343 | captures: 344 | 1: support.punctuation.expression.begin.swift 345 | push: 346 | - meta_content_scope: meta.expression.swift 347 | - match: (\)) 348 | captures: 349 | 1: support.punctuation.expression.end.swift 350 | pop: true 351 | - include: scope:source.swift 352 | - match: (\"|\\) 353 | scope: invalid.illegal.swift 354 | - match: (.) 355 | scope: string.quoted.double.swift 356 | switch-statement-keyword: 357 | - match: \b(switch|case|default|where)\b 358 | comment: switch-statement 359 | scope: keyword.control.switch.swift 360 | ternary-operator: 361 | - match: '(?<=[\s(\[{,;:])(\?|:)(?=[\s)\]},;:])' 362 | scope: keyword.operator.ternary.swift 363 | type: 364 | - include: primitive-type 365 | - include: integer-type 366 | - include: collection-type 367 | - include: optional-type 368 | - include: protocol-composition-type 369 | type-casting-operator: 370 | - match: \b(is\b|as(\?\B|\b)) 371 | scope: keyword.operator.type-casting.swift 372 | -------------------------------------------------------------------------------- /src/printer.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::vec::Vec; 3 | 4 | use ansi_term::Colour::Fixed; 5 | use ansi_term::Style; 6 | 7 | use console::AnsiCodeIterator; 8 | 9 | use syntect::easy::HighlightLines; 10 | use syntect::highlighting::Theme; 11 | use syntect::parsing::SyntaxSet; 12 | 13 | use content_inspector::ContentType; 14 | 15 | use encoding::all::{UTF_16BE, UTF_16LE}; 16 | use encoding::{DecoderTrap, Encoding}; 17 | 18 | use crate::assets::HighlightingAssets; 19 | use crate::decorations::{Decoration, GridBorderDecoration, LineNumberDecoration}; 20 | use crate::errors::*; 21 | use crate::inputfile::{InputFile, InputFileReader}; 22 | use crate::preprocessor::{expand_tabs, replace_nonprintable}; 23 | use crate::style::OutputComponents; 24 | use crate::style::OutputWrap; 25 | use crate::syntax_mapping::SyntaxMapping; 26 | use crate::terminal::{as_terminal_escaped, to_ansi_color}; 27 | 28 | pub trait Printer { 29 | fn print_header( 30 | &mut self, 31 | handle: &mut dyn Write, 32 | file: &InputFile, 33 | header_overwrite: Option, 34 | ) -> Result<()>; 35 | fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()>; 36 | fn print_line( 37 | &mut self, 38 | out_of_range: bool, 39 | handle: &mut dyn Write, 40 | line_number: usize, 41 | line_buffer: &[u8], 42 | ) -> Result<()>; 43 | } 44 | 45 | pub struct InteractivePrinter<'a> { 46 | colors: Colors, 47 | decorations: Vec>, 48 | panel_width: usize, 49 | ansi_prefix_sgr: String, 50 | content_type: ContentType, 51 | highlighter: Option>, 52 | syntax_set: &'a SyntaxSet, 53 | output_components: OutputComponents, 54 | colored_output: bool, 55 | true_color: bool, 56 | term_width: usize, 57 | tab_width: usize, 58 | show_nonprintable: bool, 59 | output_wrap: OutputWrap, 60 | use_italic_text: bool, 61 | } 62 | 63 | impl<'a> InteractivePrinter<'a> { 64 | pub fn new( 65 | assets: &'a HighlightingAssets, 66 | file: &InputFile, 67 | reader: &mut InputFileReader, 68 | output_components: OutputComponents, 69 | theme: String, 70 | colored_output: bool, 71 | true_color: bool, 72 | term_width: usize, 73 | language: Option, 74 | syntax_mapping: SyntaxMapping, 75 | tab_width: usize, 76 | show_nonprintable: bool, 77 | output_wrap: OutputWrap, 78 | use_italic_text: bool, 79 | ) -> Self { 80 | let theme = assets.get_theme(&theme); 81 | 82 | let colors = if colored_output { 83 | Colors::colored(theme, true_color) 84 | } else { 85 | Colors::plain() 86 | }; 87 | 88 | // Create decorations. 89 | let mut decorations: Vec> = Vec::new(); 90 | 91 | if output_components.numbers() { 92 | decorations.push(Box::new(LineNumberDecoration::new(&colors))); 93 | } 94 | 95 | let mut panel_width: usize = 96 | decorations.len() + decorations.iter().fold(0, |a, x| a + x.width()); 97 | 98 | // The grid border decoration isn't added until after the panel_width calculation, since the 99 | // print_horizontal_line, print_header, and print_footer functions all assume the panel 100 | // width is without the grid border. 101 | if output_components.grid() && !decorations.is_empty() { 102 | decorations.push(Box::new(GridBorderDecoration::new(&colors))); 103 | } 104 | 105 | // Disable the panel if the terminal is too small (i.e. can't fit 5 characters with the 106 | // panel showing). 107 | if term_width < (decorations.len() + decorations.iter().fold(0, |a, x| a + x.width())) + 5 { 108 | decorations.clear(); 109 | panel_width = 0; 110 | } 111 | 112 | let highlighter = if reader.content_type.is_binary() { 113 | None 114 | } else { 115 | // Determine the type of syntax for highlighting 116 | let syntax = assets.get_syntax(language, file, reader, &syntax_mapping); 117 | Some(HighlightLines::new(syntax, theme)) 118 | }; 119 | 120 | InteractivePrinter { 121 | panel_width, 122 | colors, 123 | decorations, 124 | content_type: reader.content_type, 125 | ansi_prefix_sgr: String::new(), 126 | highlighter, 127 | syntax_set: &assets.syntax_set, 128 | output_components, 129 | colored_output, 130 | true_color, 131 | term_width, 132 | tab_width, 133 | show_nonprintable, 134 | output_wrap, 135 | use_italic_text, 136 | } 137 | } 138 | 139 | fn print_horizontal_line(&mut self, handle: &mut dyn Write, grid_char: char) -> Result<()> { 140 | if self.panel_width == 0 { 141 | writeln!( 142 | handle, 143 | "{}", 144 | self.colors.grid.paint("─".repeat(self.term_width)) 145 | )?; 146 | } else { 147 | let hline = "─".repeat(self.term_width - (self.panel_width + 1)); 148 | let hline = format!("{}{}{}", "─".repeat(self.panel_width), grid_char, hline); 149 | writeln!(handle, "{}", self.colors.grid.paint(hline))?; 150 | } 151 | 152 | Ok(()) 153 | } 154 | 155 | fn preprocess(&self, text: &str, cursor: &mut usize) -> String { 156 | if self.tab_width > 0 { 157 | expand_tabs(text, self.tab_width, cursor) 158 | } else { 159 | text.to_string() 160 | } 161 | } 162 | } 163 | 164 | impl<'a> Printer for InteractivePrinter<'a> { 165 | fn print_header( 166 | &mut self, 167 | handle: &mut dyn Write, 168 | file: &InputFile, 169 | header_overwrite: Option, 170 | ) -> Result<()> { 171 | if !self.output_components.header() { 172 | return Ok(()); 173 | } 174 | 175 | if self.output_components.grid() { 176 | self.print_horizontal_line(handle, '┬')?; 177 | 178 | write!( 179 | handle, 180 | "{}{}", 181 | " ".repeat(self.panel_width), 182 | self.colors 183 | .grid 184 | .paint(if self.panel_width > 0 { "│ " } else { "" }), 185 | )?; 186 | } else { 187 | write!(handle, "{}", " ".repeat(self.panel_width))?; 188 | }; 189 | 190 | let (prefix, name): (&str, String) = match header_overwrite { 191 | Some(overwrite) => ("", overwrite), 192 | None => match file { 193 | InputFile::Ordinary(filename) => ("File: ", filename.to_string()), 194 | InputFile::String(_) => ("", "".to_string()), 195 | // _ => ("", &"STDIN".to_string()), 196 | _ => unimplemented!(), 197 | }, 198 | }; 199 | 200 | let mode = match self.content_type { 201 | ContentType::BINARY => " ", 202 | ContentType::UTF_16LE => " ", 203 | ContentType::UTF_16BE => " ", 204 | _ => "", 205 | }; 206 | 207 | writeln!( 208 | handle, 209 | "{}{}{}", 210 | prefix, 211 | self.colors.filename.paint(name), 212 | mode 213 | )?; 214 | 215 | if self.output_components.grid() { 216 | if self.content_type.is_text() { 217 | self.print_horizontal_line(handle, '┼')?; 218 | } else { 219 | self.print_horizontal_line(handle, '┴')?; 220 | } 221 | } 222 | 223 | Ok(()) 224 | } 225 | 226 | fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()> { 227 | if self.output_components.grid() && self.content_type.is_text() { 228 | self.print_horizontal_line(handle, '┴') 229 | } else { 230 | Ok(()) 231 | } 232 | } 233 | 234 | fn print_line( 235 | &mut self, 236 | out_of_range: bool, 237 | handle: &mut dyn Write, 238 | line_number: usize, 239 | line_buffer: &[u8], 240 | ) -> Result<()> { 241 | let mut line = match self.content_type { 242 | ContentType::BINARY => { 243 | return Ok(()); 244 | } 245 | ContentType::UTF_16LE => UTF_16LE 246 | .decode(&line_buffer, DecoderTrap::Strict) 247 | .unwrap_or("Invalid UTF-16LE".into()), 248 | ContentType::UTF_16BE => UTF_16BE 249 | .decode(&line_buffer, DecoderTrap::Strict) 250 | .unwrap_or("Invalid UTF-16BE".into()), 251 | _ => String::from_utf8_lossy(&line_buffer).to_string(), 252 | }; 253 | 254 | if self.show_nonprintable { 255 | line = replace_nonprintable(&mut line, self.tab_width); 256 | } 257 | 258 | let regions = { 259 | let highlighter = match self.highlighter { 260 | Some(ref mut highlighter) => highlighter, 261 | _ => { 262 | return Ok(()); 263 | } 264 | }; 265 | highlighter.highlight(line.as_ref(), self.syntax_set) 266 | }; 267 | 268 | if out_of_range { 269 | return Ok(()); 270 | } 271 | 272 | let mut cursor: usize = 0; 273 | let mut cursor_max: usize = self.term_width; 274 | let mut cursor_total: usize = 0; 275 | let mut panel_wrap: Option = None; 276 | 277 | // Line decorations. 278 | if self.panel_width > 0 { 279 | let decorations = self 280 | .decorations 281 | .iter() 282 | .map(|ref d| d.generate(line_number, false, self)) 283 | .collect::>(); 284 | 285 | for deco in decorations { 286 | write!(handle, "{} ", deco.text)?; 287 | cursor_max -= deco.width + 1; 288 | } 289 | } 290 | 291 | // Line contents. 292 | if self.output_wrap == OutputWrap::None { 293 | let true_color = self.true_color; 294 | let colored_output = self.colored_output; 295 | let italics = self.use_italic_text; 296 | 297 | for &(style, region) in regions.iter() { 298 | let text = &*self.preprocess(region, &mut cursor_total); 299 | write!( 300 | handle, 301 | "{}", 302 | as_terminal_escaped(style, &*text, true_color, colored_output, italics,) 303 | )?; 304 | } 305 | 306 | if line.bytes().next_back() != Some(b'\n') { 307 | write!(handle, "\n")?; 308 | } 309 | } else { 310 | for &(style, region) in regions.iter() { 311 | let ansi_iterator = AnsiCodeIterator::new(region); 312 | let mut ansi_prefix: String = String::new(); 313 | for chunk in ansi_iterator { 314 | match chunk { 315 | // ANSI escape passthrough. 316 | (text, true) => { 317 | if text.chars().last().map_or(false, |c| c == 'm') { 318 | ansi_prefix.push_str(text); 319 | if text == "\x1B[0m" { 320 | self.ansi_prefix_sgr = "\x1B[0m".to_owned(); 321 | } else { 322 | self.ansi_prefix_sgr.push_str(text); 323 | } 324 | } else { 325 | ansi_prefix.push_str(text); 326 | } 327 | } 328 | 329 | // Regular text. 330 | (text, false) => { 331 | let text = self.preprocess( 332 | text.trim_end_matches(|c| c == '\r' || c == '\n'), 333 | &mut cursor_total, 334 | ); 335 | 336 | let mut chars = text.chars(); 337 | let mut remaining = text.chars().count(); 338 | 339 | while remaining > 0 { 340 | let available = cursor_max - cursor; 341 | 342 | // It fits. 343 | if remaining <= available { 344 | let text = chars.by_ref().take(remaining).collect::(); 345 | cursor += remaining; 346 | 347 | write!( 348 | handle, 349 | "{}", 350 | as_terminal_escaped( 351 | style, 352 | &*format!( 353 | "{}{}{}", 354 | self.ansi_prefix_sgr, ansi_prefix, text 355 | ), 356 | self.true_color, 357 | self.colored_output, 358 | self.use_italic_text 359 | ) 360 | )?; 361 | break; 362 | } 363 | 364 | // Generate wrap padding if not already generated. 365 | if panel_wrap.is_none() { 366 | panel_wrap = if self.panel_width > 0 { 367 | Some(format!( 368 | "{} ", 369 | self.decorations 370 | .iter() 371 | .map(|ref d| d 372 | .generate(line_number, true, self) 373 | .text) 374 | .collect::>() 375 | .join(" ") 376 | )) 377 | } else { 378 | Some("".to_string()) 379 | } 380 | } 381 | 382 | // It wraps. 383 | let text = chars.by_ref().take(available).collect::(); 384 | cursor = 0; 385 | remaining -= available; 386 | 387 | write!( 388 | handle, 389 | "{}\n{}", 390 | as_terminal_escaped( 391 | style, 392 | &*format!( 393 | "{}{}{}", 394 | self.ansi_prefix_sgr, ansi_prefix, text 395 | ), 396 | self.true_color, 397 | self.colored_output, 398 | self.use_italic_text 399 | ), 400 | panel_wrap.clone().unwrap() 401 | )?; 402 | } 403 | 404 | // Clear the ANSI prefix buffer. 405 | ansi_prefix.clear(); 406 | } 407 | } 408 | } 409 | } 410 | 411 | write!(handle, "\n")?; 412 | } 413 | 414 | Ok(()) 415 | } 416 | } 417 | 418 | const DEFAULT_GUTTER_COLOR: u8 = 238; 419 | 420 | #[derive(Default)] 421 | pub struct Colors { 422 | pub grid: Style, 423 | pub filename: Style, 424 | pub line_number: Style, 425 | } 426 | 427 | impl Colors { 428 | fn plain() -> Self { 429 | Colors::default() 430 | } 431 | 432 | fn colored(theme: &Theme, true_color: bool) -> Self { 433 | let gutter_color = theme 434 | .settings 435 | .gutter_foreground 436 | .map(|c| to_ansi_color(c, true_color)) 437 | .unwrap_or(Fixed(DEFAULT_GUTTER_COLOR)); 438 | 439 | Colors { 440 | grid: gutter_color.normal(), 441 | filename: Style::new().bold(), 442 | line_number: gutter_color.normal(), 443 | } 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /assets/syntaxes/Nix.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: Nix 5 | file_extensions: 6 | - nix 7 | scope: source.nix 8 | contexts: 9 | main: 10 | - include: expression 11 | comment: 12 | - match: '/\*([^*]|\*[^\/])*' 13 | push: 14 | - meta_scope: comment.block.nix 15 | - match: \*\/ 16 | pop: true 17 | - include: comment-remark 18 | - match: '\#' 19 | push: 20 | - meta_scope: comment.line.number-sign.nix 21 | - match: $ 22 | pop: true 23 | - include: comment-remark 24 | attribute-bind: 25 | - include: attribute-name 26 | - include: attribute-bind-from-equals 27 | attribute-bind-from-equals: 28 | - match: \= 29 | captures: 30 | 0: keyword.operator.bind.nix 31 | push: 32 | - match: \; 33 | captures: 34 | 0: punctuation.terminator.bind.nix 35 | pop: true 36 | - include: expression 37 | attribute-inherit: 38 | - match: \binherit\b 39 | captures: 40 | 0: keyword.other.inherit.nix 41 | push: 42 | - match: \; 43 | captures: 44 | 0: punctuation.terminator.inherit.nix 45 | pop: true 46 | - match: \( 47 | captures: 48 | 0: punctuation.section.function.arguments.nix 49 | push: 50 | - match: (?=\;) 51 | pop: true 52 | - match: \) 53 | captures: 54 | 0: punctuation.section.function.arguments.nix 55 | push: 56 | - match: (?=\;) 57 | pop: true 58 | - include: bad-reserved 59 | - include: attribute-name-single 60 | - include: others 61 | - include: expression 62 | - match: '(?=[a-zA-Z\_])' 63 | push: 64 | - match: (?=\;) 65 | pop: true 66 | - include: bad-reserved 67 | - include: attribute-name-single 68 | - include: others 69 | - include: others 70 | attribute-name: 71 | - match: '\b[a-zA-Z\_][a-zA-Z0-9\_\''\-]*' 72 | scope: entity.other.attribute-name.multipart.nix 73 | - match: \. 74 | - include: string-quoted 75 | - include: interpolation 76 | attribute-name-single: 77 | - match: '\b[a-zA-Z\_][a-zA-Z0-9\_\''\-]*' 78 | scope: entity.other.attribute-name.single.nix 79 | attrset-contents: 80 | - include: attribute-inherit 81 | - include: bad-reserved 82 | - include: attribute-bind 83 | - include: others 84 | attrset-definition: 85 | - match: '(?=\{)' 86 | push: 87 | - match: '(?=([\])};,]|\b(else|then)\b))' 88 | pop: true 89 | - match: '(\{)' 90 | captures: 91 | 0: punctuation.definition.attrset.nix 92 | push: 93 | - match: '(\})' 94 | captures: 95 | 0: punctuation.definition.attrset.nix 96 | pop: true 97 | - include: attrset-contents 98 | - match: '(?<=\})' 99 | push: 100 | - match: '(?=([\])};,]|\b(else|then)\b))' 101 | pop: true 102 | - include: expression-cont 103 | attrset-definition-brace-opened: 104 | - match: '(?<=\})' 105 | push: 106 | - match: '(?=([\])};,]|\b(else|then)\b))' 107 | pop: true 108 | - include: expression-cont 109 | - match: (?=.?) 110 | push: 111 | - match: '\}' 112 | captures: 113 | 0: punctuation.definition.attrset.nix 114 | pop: true 115 | - include: attrset-contents 116 | attrset-for-sure: 117 | - match: (?=\brec\b) 118 | push: 119 | - match: '(?=([\])};,]|\b(else|then)\b))' 120 | pop: true 121 | - match: \brec\b 122 | captures: 123 | 0: keyword.other.nix 124 | push: 125 | - match: '(?=\{)' 126 | pop: true 127 | - include: others 128 | - include: attrset-definition 129 | - include: others 130 | - match: '(?=\{\s*(\}|[^,?]*(=|;)))' 131 | push: 132 | - match: '(?=([\])};,]|\b(else|then)\b))' 133 | pop: true 134 | - include: attrset-definition 135 | - include: others 136 | attrset-or-function: 137 | - match: '\{' 138 | captures: 139 | 0: punctuation.definition.attrset-or-function.nix 140 | push: 141 | - match: '(?=([\])};]|\b(else|then)\b))' 142 | pop: true 143 | - match: '(?=(\s*\}|\"|\binherit\b|\b[a-zA-Z\_][a-zA-Z0-9\_\''\-]*(\s*\.|\s*=[^=])))' 144 | push: 145 | - match: '(?=([\])};,]|\b(else|then)\b))' 146 | pop: true 147 | - include: attrset-definition-brace-opened 148 | - match: '(?=(\.\.\.|\b[a-zA-Z\_][a-zA-Z0-9\_\''\-]*\s*[,?]))' 149 | push: 150 | - match: '(?=([\])};,]|\b(else|then)\b))' 151 | pop: true 152 | - include: function-definition-brace-opened 153 | - include: bad-reserved 154 | - match: '\b[a-zA-Z\_][a-zA-Z0-9\_\''\-]*' 155 | captures: 156 | 0: variable.parameter.function.maybe.nix 157 | push: 158 | - match: '(?=([\])};]|\b(else|then)\b))' 159 | pop: true 160 | - match: (?=\.) 161 | push: 162 | - match: '(?=([\])};,]|\b(else|then)\b))' 163 | pop: true 164 | - include: attrset-definition-brace-opened 165 | - match: \s*(\,) 166 | captures: 167 | 1: keyword.operator.nix 168 | push: 169 | - match: '(?=([\])};,]|\b(else|then)\b))' 170 | pop: true 171 | - include: function-definition-brace-opened 172 | - match: (?=\=) 173 | push: 174 | - match: '(?=([\])};,]|\b(else|then)\b))' 175 | pop: true 176 | - include: attribute-bind-from-equals 177 | - include: attrset-definition-brace-opened 178 | - match: (?=\?) 179 | push: 180 | - match: '(?=([\])};,]|\b(else|then)\b))' 181 | pop: true 182 | - include: function-parameter-default 183 | - match: \, 184 | captures: 185 | 0: keyword.operator.nix 186 | push: 187 | - match: '(?=([\])};,]|\b(else|then)\b))' 188 | pop: true 189 | - include: function-definition-brace-opened 190 | - include: others 191 | - include: others 192 | bad-reserved: 193 | - match: '(?\=|\>|&&|\|\||-\>|//|\?|\+\+|-|\*|/(?=([^*]|$))|\+)' 250 | scope: keyword.operator.nix 251 | - include: constants 252 | - include: bad-reserved 253 | - include: parameter-name 254 | - include: others 255 | function-body: 256 | - match: '(@\s*([a-zA-Z\_][a-zA-Z0-9\_\''\-]*)\s*)?(\:)' 257 | push: 258 | - match: '(?=([\])};,]|\b(else|then)\b))' 259 | pop: true 260 | - include: expression 261 | function-body-from-colon: 262 | - match: (\:) 263 | captures: 264 | 0: punctuation.definition.function.nix 265 | push: 266 | - match: '(?=([\])};,]|\b(else|then)\b))' 267 | pop: true 268 | - include: expression 269 | function-contents: 270 | - include: bad-reserved 271 | - include: function-parameter 272 | - include: others 273 | function-definition: 274 | - match: (?=.?) 275 | push: 276 | - match: '(?=([\])};,]|\b(else|then)\b))' 277 | pop: true 278 | - include: function-body-from-colon 279 | - match: (?=.?) 280 | push: 281 | - match: (?=\:) 282 | pop: true 283 | - match: '(\b[a-zA-Z\_][a-zA-Z0-9\_\''\-]*)' 284 | captures: 285 | 0: variable.parameter.function.4.nix 286 | push: 287 | - match: (?=\:) 288 | pop: true 289 | - match: \@ 290 | push: 291 | - match: (?=\:) 292 | pop: true 293 | - include: function-header-until-colon-no-arg 294 | - include: others 295 | - include: others 296 | - match: '(?=\{)' 297 | push: 298 | - match: (?=\:) 299 | pop: true 300 | - include: function-header-until-colon-with-arg 301 | - include: others 302 | function-definition-brace-opened: 303 | - match: (?=.?) 304 | push: 305 | - match: '(?=([\])};,]|\b(else|then)\b))' 306 | pop: true 307 | - include: function-body-from-colon 308 | - match: (?=.?) 309 | push: 310 | - match: (?=\:) 311 | pop: true 312 | - include: function-header-close-brace-with-arg 313 | - match: (?=.?) 314 | push: 315 | - match: '(?=\})' 316 | pop: true 317 | - include: function-contents 318 | - include: others 319 | function-for-sure: 320 | - match: '(?=(\b[a-zA-Z\_][a-zA-Z0-9\_\''\-]*\s*[:@]|\{[^}]*\}\s*:|\{[^#}"''/=]*[,\?]))' 321 | push: 322 | - match: '(?=([\])};,]|\b(else|then)\b))' 323 | pop: true 324 | - include: function-definition 325 | function-header-close-brace-no-arg: 326 | - match: '\}' 327 | captures: 328 | 0: punctuation.definition.entity.function.nix 329 | push: 330 | - match: (?=\:) 331 | pop: true 332 | - include: others 333 | function-header-close-brace-with-arg: 334 | - match: '\}' 335 | captures: 336 | 0: punctuation.definition.entity.function.nix 337 | push: 338 | - match: (?=\:) 339 | pop: true 340 | - include: function-header-terminal-arg 341 | - include: others 342 | function-header-open-brace: 343 | - match: '\{' 344 | captures: 345 | 0: punctuation.definition.entity.function.2.nix 346 | push: 347 | - match: '(?=\})' 348 | pop: true 349 | - include: function-contents 350 | function-header-terminal-arg: 351 | - match: (?=@) 352 | push: 353 | - match: (?=\:) 354 | pop: true 355 | - match: \@ 356 | push: 357 | - match: (?=\:) 358 | pop: true 359 | - match: '(\b[a-zA-Z\_][a-zA-Z0-9\_\''\-]*)' 360 | push: 361 | - meta_scope: variable.parameter.function.3.nix 362 | - match: (?=\:) 363 | pop: true 364 | - include: others 365 | - include: others 366 | function-header-until-colon-no-arg: 367 | - match: '(?=\{)' 368 | push: 369 | - match: (?=\:) 370 | pop: true 371 | - include: function-header-open-brace 372 | - include: function-header-close-brace-no-arg 373 | function-header-until-colon-with-arg: 374 | - match: '(?=\{)' 375 | push: 376 | - match: (?=\:) 377 | pop: true 378 | - include: function-header-open-brace 379 | - include: function-header-close-brace-with-arg 380 | function-parameter: 381 | - match: (\.\.\.) 382 | push: 383 | - meta_scope: keyword.operator.nix 384 | - match: '(,|(?=\}))' 385 | pop: true 386 | - include: others 387 | - match: '\b[a-zA-Z\_][a-zA-Z0-9\_\''\-]*' 388 | captures: 389 | 0: variable.parameter.function.1.nix 390 | push: 391 | - match: '(,|(?=\}))' 392 | captures: 393 | 0: keyword.operator.nix 394 | pop: true 395 | - include: whitespace 396 | - include: comment 397 | - include: function-parameter-default 398 | - include: expression 399 | - include: others 400 | function-parameter-default: 401 | - match: \? 402 | captures: 403 | 0: keyword.operator.nix 404 | push: 405 | - match: "(?=[,}])" 406 | pop: true 407 | - include: expression 408 | if: 409 | - match: (?=\bif\b) 410 | push: 411 | - match: '(?=([\])};,]|\b(else|then)\b))' 412 | pop: true 413 | - match: \bif\b 414 | captures: 415 | 0: keyword.other.nix 416 | push: 417 | - match: \bth(?=en\b) 418 | captures: 419 | 0: keyword.other.nix 420 | pop: true 421 | - include: expression 422 | - match: (?<=th)en\b 423 | captures: 424 | 0: keyword.other.nix 425 | push: 426 | - match: \bel(?=se\b) 427 | captures: 428 | 0: keyword.other.nix 429 | pop: true 430 | - include: expression 431 | - match: (?<=el)se\b 432 | captures: 433 | 0: keyword.other.nix 434 | push: 435 | - match: '(?=([\])};,]|\b(else|then)\b))' 436 | captures: 437 | 0: keyword.other.nix 438 | pop: true 439 | - include: expression 440 | illegal: 441 | - match: . 442 | scope: invalid.illegal 443 | interpolation: 444 | - match: '\$\{' 445 | captures: 446 | 0: punctuation.section.embedded.begin.nix 447 | push: 448 | - meta_scope: markup.italic 449 | - match: '\}' 450 | captures: 451 | 0: punctuation.section.embedded.end.nix 452 | pop: true 453 | - include: expression 454 | let: 455 | - match: (?=\blet\b) 456 | push: 457 | - match: '(?=([\])};,]|\b(else|then)\b))' 458 | pop: true 459 | - match: \blet\b 460 | captures: 461 | 0: keyword.other.nix 462 | push: 463 | - match: '(?=([\])};,]|\b(in|else|then)\b))' 464 | pop: true 465 | - match: '(?=\{)' 466 | push: 467 | - match: '(?=([\])};,]|\b(else|then)\b))' 468 | pop: true 469 | - match: '\{' 470 | push: 471 | - match: '\}' 472 | pop: true 473 | - include: attrset-contents 474 | - match: '(^|(?<=\}))' 475 | push: 476 | - match: '(?=([\])};,]|\b(else|then)\b))' 477 | pop: true 478 | - include: expression-cont 479 | - include: others 480 | - include: attrset-contents 481 | - include: others 482 | - match: \bin\b 483 | captures: 484 | 0: keyword.other.nix 485 | push: 486 | - match: '(?=([\])};,]|\b(else|then)\b))' 487 | pop: true 488 | - include: expression 489 | list: 490 | - match: '\[' 491 | captures: 492 | 0: punctuation.definition.list.nix 493 | push: 494 | - match: '\]' 495 | captures: 496 | 0: punctuation.definition.list.nix 497 | pop: true 498 | - include: expression 499 | list-and-cont: 500 | - match: '(?=\[)' 501 | push: 502 | - match: '(?=([\])};,]|\b(else|then)\b))' 503 | pop: true 504 | - include: list 505 | - include: expression-cont 506 | operator-unary: 507 | - match: (!|-) 508 | scope: keyword.operator.unary.nix 509 | others: 510 | - include: whitespace 511 | - include: comment 512 | - include: illegal 513 | parameter-name: 514 | - match: '\b[a-zA-Z\_][a-zA-Z0-9\_\''\-]*' 515 | captures: 516 | 0: variable.parameter.name.nix 517 | parameter-name-and-cont: 518 | - match: '\b[a-zA-Z\_][a-zA-Z0-9\_\''\-]*' 519 | captures: 520 | 0: variable.parameter.name.nix 521 | push: 522 | - match: '(?=([\])};,]|\b(else|then)\b))' 523 | pop: true 524 | - include: expression-cont 525 | parens: 526 | - match: \( 527 | captures: 528 | 0: punctuation.definition.expression.nix 529 | push: 530 | - match: \) 531 | captures: 532 | 0: punctuation.definition.expression.nix 533 | pop: true 534 | - include: expression 535 | parens-and-cont: 536 | - match: (?=\() 537 | push: 538 | - match: '(?=([\])};,]|\b(else|then)\b))' 539 | pop: true 540 | - include: parens 541 | - include: expression-cont 542 | string: 543 | - match: (?=\'\') 544 | push: 545 | - match: '(?=([\])};,]|\b(else|then)\b))' 546 | pop: true 547 | - match: \'\' 548 | captures: 549 | 0: punctuation.definition.string.other.start.nix 550 | push: 551 | - meta_scope: string.quoted.other.nix 552 | - match: \'\'(?!\$|\'|\\.) 553 | captures: 554 | 0: punctuation.definition.string.other.end.nix 555 | pop: true 556 | - match: \'\'(\$|\'|\\.) 557 | scope: constant.character.escape.nix 558 | - include: interpolation 559 | - include: expression-cont 560 | - match: (?=\") 561 | push: 562 | - match: '(?=([\])};,]|\b(else|then)\b))' 563 | pop: true 564 | - include: string-quoted 565 | - include: expression-cont 566 | - match: '([a-zA-Z0-9\.\_\-\+]*(\/[a-zA-Z0-9\.\_\-\+]+)+)' 567 | captures: 568 | 0: string.unquoted.path.nix 569 | push: 570 | - match: '(?=([\])};,]|\b(else|then)\b))' 571 | pop: true 572 | - include: expression-cont 573 | - match: '(\<[a-zA-Z0-9\.\_\-\+]+(\/[a-zA-Z0-9\.\_\-\+]+)*\>)' 574 | captures: 575 | 0: string.unquoted.spath.nix 576 | push: 577 | - match: '(?=([\])};,]|\b(else|then)\b))' 578 | pop: true 579 | - include: expression-cont 580 | - match: '([a-zA-Z][a-zA-Z0-9\+\-\.]*\:[a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\'']+)' 581 | captures: 582 | 0: string.unquoted.url.nix 583 | push: 584 | - match: '(?=([\])};,]|\b(else|then)\b))' 585 | pop: true 586 | - include: expression-cont 587 | string-quoted: 588 | - match: \" 589 | captures: 590 | 0: punctuation.definition.string.double.start.nix 591 | push: 592 | - meta_scope: string.quoted.double.nix 593 | - match: \" 594 | captures: 595 | 0: punctuation.definition.string.double.end.nix 596 | pop: true 597 | - match: \\. 598 | scope: constant.character.escape.nix 599 | - include: interpolation 600 | whitespace: 601 | - match: \s+ 602 | with-assert: 603 | - match: '(?" 18 | captures: 19 | 0: punctuation.definition.comment.block.end.powershell 20 | pop: true 21 | - include: commentEmbeddedDocs 22 | - match: '[2-6]>&1|>>|>|<<|<|>|>\||[1-6]>|[1-6]>>' 23 | scope: keyword.operator.redirection.powershell 24 | - include: commands 25 | - include: commentLine 26 | - include: variable 27 | - include: interpolatedStringContent 28 | - include: function 29 | - include: attribute 30 | - include: UsingDirective 31 | - include: type 32 | - include: hashtable 33 | - include: doubleQuotedString 34 | - include: scriptblock 35 | - include: doubleQuotedStringEscapes 36 | - match: (?]*>)' 12 | captures: 13 | 1: punctuation.definition.tag.html 14 | 2: entity.name.tag.html 15 | push: 16 | - meta_scope: meta.tag.any.html 17 | - match: (>(<)/)(\2)(>) 18 | captures: 19 | 1: punctuation.definition.tag.html 20 | 2: meta.scope.between-tag-pair.html 21 | 3: entity.name.tag.html 22 | 4: punctuation.definition.tag.html 23 | pop: true 24 | - include: tag-stuff 25 | - match: (<\?)(xml) 26 | captures: 27 | 1: punctuation.definition.tag.html 28 | 2: entity.name.tag.xml.html 29 | push: 30 | - meta_scope: meta.tag.preprocessor.xml.html 31 | - match: (\?>) 32 | captures: 33 | 1: punctuation.definition.tag.html 34 | 2: entity.name.tag.xml.html 35 | pop: true 36 | - include: tag-generic-attribute 37 | - include: string-double-quoted 38 | - include: string-single-quoted 39 | - match: