From 32def5a146e6f0d33c49071a716ea756cc502fb9 Mon Sep 17 00:00:00 2001 From: Zibi Braniecki Date: Mon, 9 Sep 2019 11:32:58 -0700 Subject: [PATCH 01/21] Refactor the inner logic of the crate to cleaner, faster and zero-copy code --- .travis.yml | 30 - CHANGELOG.md | 15 - Cargo.toml | 27 +- LICENSE-APACHE | 201 ------- LICENSE-MIT | 19 - README.md | 92 --- benches/simple.rs | 89 ++- examples/expected_type.rs | 40 -- examples/footer.rs | 37 -- examples/format.rs | 47 +- examples/multislice.rs | 34 -- src/display_list/annotation.rs | 12 + src/display_list/from_snippet.rs | 403 ------------- src/display_list/line.rs | 83 +++ src/display_list/list.rs | 104 ++++ src/display_list/mod.rs | 127 +--- src/display_list/structs.rs | 253 -------- src/formatter/mod.rs | 367 ------------ src/formatter/style.rs | 98 ---- src/lib.rs | 57 +- src/slice.rs | 33 ++ src/snippet.rs | 81 --- src/stylesheets/color.rs | 43 -- src/stylesheets/mod.rs | 11 - src/stylesheets/no_color.rs | 21 - tests/diff/mod.rs | 43 -- tests/dl_from_snippet.rs | 256 -------- .../no-color/multiline_annotation.txt | 14 - .../no-color/multiline_annotation.yaml | 38 -- .../no-color/multiline_annotation2.txt | 9 - .../no-color/multiline_annotation2.yaml | 16 - .../no-color/multiple_annotations.txt | 14 - .../no-color/multiple_annotations.yaml | 23 - tests/fixtures/no-color/simple.txt | 9 - tests/fixtures/no-color/simple.yaml | 17 - tests/fixtures_test.rs | 57 -- tests/formatter.rs | 551 ------------------ tests/snippet/mod.rs | 103 ---- 38 files changed, 295 insertions(+), 3179 deletions(-) delete mode 100644 .travis.yml delete mode 100644 CHANGELOG.md delete mode 100644 LICENSE-APACHE delete mode 100644 LICENSE-MIT delete mode 100644 README.md delete mode 100644 examples/expected_type.rs delete mode 100644 examples/footer.rs delete mode 100644 examples/multislice.rs create mode 100644 src/display_list/annotation.rs delete mode 100644 src/display_list/from_snippet.rs create mode 100644 src/display_list/line.rs create mode 100644 src/display_list/list.rs delete mode 100644 src/display_list/structs.rs delete mode 100644 src/formatter/mod.rs delete mode 100644 src/formatter/style.rs create mode 100644 src/slice.rs delete mode 100644 src/snippet.rs delete mode 100644 src/stylesheets/color.rs delete mode 100644 src/stylesheets/mod.rs delete mode 100644 src/stylesheets/no_color.rs delete mode 100644 tests/diff/mod.rs delete mode 100644 tests/dl_from_snippet.rs delete mode 100644 tests/fixtures/no-color/multiline_annotation.txt delete mode 100644 tests/fixtures/no-color/multiline_annotation.yaml delete mode 100644 tests/fixtures/no-color/multiline_annotation2.txt delete mode 100644 tests/fixtures/no-color/multiline_annotation2.yaml delete mode 100644 tests/fixtures/no-color/multiple_annotations.txt delete mode 100644 tests/fixtures/no-color/multiple_annotations.yaml delete mode 100644 tests/fixtures/no-color/simple.txt delete mode 100644 tests/fixtures/no-color/simple.yaml delete mode 100644 tests/fixtures_test.rs delete mode 100644 tests/formatter.rs delete mode 100644 tests/snippet/mod.rs diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e186cc2..0000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: rust -sudo: required -dist: trusty -addons: - apt: - packages: - - libssl-dev -cache: cargo -rust: - - stable - - beta - - nightly -matrix: - allow_failures: - - rust: nightly - -before_cache: | - if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f - fi - -script: -- cargo clean -- cargo build -- cargo test - -after_success: | - if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then - cargo tarpaulin --ciserver travis-ci --coveralls $TRAVIS_JOB_ID - fi diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index a125e8f..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,15 +0,0 @@ -# Changelog - -## Unreleased - - - … - -## annotate-snippets 0.6.1 (July 23, 2019) - - - Fix too many anonymized line numbers (#5) - -## annotate-snippets 0.6.0 (June 26, 2019) - - - Add an option to anonymize line numbers (#3) - - Transition the crate to rust-lang org. - - Update the syntax to Rust 2018 idioms. (#4) diff --git a/Cargo.toml b/Cargo.toml index 7980bc9..8f93fa3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,35 +1,16 @@ [package] name = "annotate-snippets" -version = "0.6.1" +version = "0.1.0" +authors = ["Zibi Braniecki "] edition = "2018" -authors = ["Zibi Braniecki "] -description = "Library for building code annotations" -license = "Apache-2.0/MIT" -repository = "https://github.com/rust-lang/annotate-snippets-rs" -readme = "README.md" -keywords = ["code", "analysis", "ascii", "errors", "debug"] -[badges] -travis-ci = { repository = "rust-lang/annotate-snippets-rs", branch = "master" } -coveralls = { repository = "rust-lang/annotate-snippets-rs", branch = "master", service = "github" } - -maintenance = { status = "actively-developed" } +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ansi_term = { version = "^0.12", optional = true } [dev-dependencies] -glob = "^0.3" -serde_yaml = "^0.8" -serde = { version = "^1.0", features = ["derive"] } -difference = "^2.0" -ansi_term = "^0.12" criterion = "0.3" [[bench]] name = "simple" -harness = false - -[features] -default = [] -color = ["ansi_term"] +harness = false \ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE deleted file mode 100644 index 261eeb9..0000000 --- a/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT deleted file mode 100644 index 5655fa3..0000000 --- a/LICENSE-MIT +++ /dev/null @@ -1,19 +0,0 @@ -Copyright 2017 Mozilla - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index 26a54a6..0000000 --- a/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# annotate-snippets - -`annotate-snippets` is a Rust library for annotation of programming code slices. - -[![crates.io](https://meritbadge.herokuapp.com/annotate-snippets)](https://crates.io/crates/annotate-snippets) -[![Build Status](https://travis-ci.com/rust-lang/annotate-snippets-rs.svg?branch=master)](https://travis-ci.com/rust-lang/annotate-snippets-rs) -[![Coverage Status](https://coveralls.io/repos/github/rust-lang/annotate-snippets-rs/badge.svg?branch=master)](https://coveralls.io/github/rust-lang/annotate-snippets-rs?branch=master) - -The library helps visualize meta information annotating source code slices. -It takes a data structure called `Snippet` on the input and produces a `String` -which may look like this: - -```text -error[E0308]: mismatched types - --> src/format.rs:52:1 - | -51 | ) -> Option { - | -------------- expected `Option` because of return type -52 | / for ann in annotations { -53 | | match (ann.range.0, ann.range.1) { -54 | | (None, None) => continue, -55 | | (Some(start), Some(end)) if start > end_index => continue, -... | -71 | | } -72 | | } - | |_____^ expected enum `std::option::Option`, found () -``` - -[Documentation][] - -[Documentation]: https://docs.rs/annotate-snippets/ - -Usage ------ - -```rust -use annotate_snippets::{ - display_list::DisplayList, - formatter::DisplayListFormatter, - snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, -}; - -fn main() { - let snippet = Snippet { - title: Some(Annotation { - label: Some("expected type, found `22`".to_string()), - id: None, - annotation_type: AnnotationType::Error, - }), - footer: vec![], - slices: vec![ - Slice { - source: r#" -This is an example -content of the slice -which will be annotated -with the list of annotations below. - "#.to_string(), - line_start: 26, - origin: Some("examples/example.txt".to_string()), - fold: false, - annotations: vec![ - SourceAnnotation { - label: "Example error annotation".to_string(), - annotation_type: AnnotationType::Error, - range: (13, 18), - }, - SourceAnnotation { - label: "and here's a warning".to_string(), - annotation_type: AnnotationType::Warning, - range: (34, 50), - }, - ], - }, - ], - }; - - let dl = DisplayList::from(snippet); - let dlf = DisplayListFormatter::new(true, false); - dlf.format(&dl); -} -``` - -Local Development ------------------ - - cargo build - cargo test - -When submitting a PR please use [`cargo fmt`][] (nightly). - -[`cargo fmt`]: https://github.com/rust-lang/rustfmt diff --git a/benches/simple.rs b/benches/simple.rs index 9633260..a4a1eef 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -4,63 +4,48 @@ extern crate criterion; use criterion::black_box; use criterion::Criterion; -use annotate_snippets::display_list::DisplayList; -use annotate_snippets::formatter::DisplayListFormatter; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; +use std::fmt::Write; -fn create_snippet() { - let snippet = Snippet { - slices: vec![Slice { - source: r#") -> Option { - for ann in annotations { - match (ann.range.0, ann.range.1) { - (None, None) => continue, - (Some(start), Some(end)) if start > end_index => continue, - (Some(start), Some(end)) if start >= start_index => { - let label = if let Some(ref label) = ann.label { - format!(" {}", label) - } else { - String::from("") - }; +use annotate_snippets::slice::{AnnotationType, SourceAnnotation}; +use annotate_snippets::{DisplayList, Slice}; + +const SOURCE: &'static str = r#") -> Option { +for ann in annotations { + match (ann.range.0, ann.range.1) { + (None, None) => continue, + (Some(start), Some(end)) if start > end_index => continue, + (Some(start), Some(end)) if start >= start_index => { + let label = if let Some(ref label) = ann.label { + format!(" {}", label) + } else { + String::from("") + }; - return Some(format!( - "{}{}{}", - " ".repeat(start - start_index), - "^".repeat(end - start), - label - )); - } - _ => continue, + return Some(format!( + "{}{}{}", + " ".repeat(start - start_index), + "^".repeat(end - start), + label + )); } - }"# - .to_string(), - line_start: 51, - origin: Some("src/format.rs".to_string()), - fold: false, - annotations: vec![ - SourceAnnotation { - label: "expected `Option` because of return type".to_string(), - annotation_type: AnnotationType::Warning, - range: (5, 19), - }, - SourceAnnotation { - label: "expected enum `std::option::Option`".to_string(), - annotation_type: AnnotationType::Error, - range: (23, 745), - }, - ], + _ => continue, + } +}"#; + +fn create_snippet() { + let slice = Slice { + source: SOURCE, + line_start: Some(51), + origin: Some("src/format.rs"), + annotations: vec![SourceAnnotation { + label: "expected `Option` because of return type", + annotation_type: AnnotationType::Warning, + range: (5, 19), }], - title: Some(Annotation { - label: Some("mismatched types".to_string()), - id: Some("E0308".to_string()), - annotation_type: AnnotationType::Error, - }), - footer: vec![], }; - - let dl = DisplayList::from(snippet); - let dlf = DisplayListFormatter::new(true, false); - let _result = dlf.format(&dl); + let dl: DisplayList = (&slice).into(); + let mut result = String::new(); + write!(result, "{}", dl).unwrap(); } pub fn criterion_benchmark(c: &mut Criterion) { diff --git a/examples/expected_type.rs b/examples/expected_type.rs deleted file mode 100644 index 20cda66..0000000 --- a/examples/expected_type.rs +++ /dev/null @@ -1,40 +0,0 @@ -use annotate_snippets::display_list::DisplayList; -use annotate_snippets::formatter::DisplayListFormatter; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; - -fn main() { - let snippet = Snippet { - title: Some(Annotation { - label: Some("expected type, found `22`".to_string()), - id: None, - annotation_type: AnnotationType::Error, - }), - footer: vec![], - slices: vec![Slice { - source: r#" annotations: vec![SourceAnnotation { - label: "expected struct `annotate_snippets::snippet::Slice`, found reference" - .to_string(), - range: <22, 25>,"# - .to_string(), - line_start: 26, - origin: Some("examples/footer.rs".to_string()), - fold: true, - annotations: vec![ - SourceAnnotation { - label: "".to_string(), - annotation_type: AnnotationType::Error, - range: (208, 210), - }, - SourceAnnotation { - label: "while parsing this struct".to_string(), - annotation_type: AnnotationType::Info, - range: (34, 50), - }, - ], - }], - }; - - let dl = DisplayList::from(snippet); - let dlf = DisplayListFormatter::new(true, false); - println!("{}", dlf.format(&dl)); -} diff --git a/examples/footer.rs b/examples/footer.rs deleted file mode 100644 index 1b03910..0000000 --- a/examples/footer.rs +++ /dev/null @@ -1,37 +0,0 @@ -use annotate_snippets::display_list::DisplayList; -use annotate_snippets::formatter::DisplayListFormatter; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; - -fn main() { - let snippet = Snippet { - title: Some(Annotation { - label: Some("mismatched types".to_string()), - id: Some("E0308".to_string()), - annotation_type: AnnotationType::Error, - }), - footer: vec![Annotation { - label: Some( - "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`" - .to_string(), - ), - id: None, - annotation_type: AnnotationType::Note, - }], - slices: vec![Slice { - source: " slices: vec![\"A\",".to_string(), - line_start: 13, - origin: Some("src/multislice.rs".to_string()), - fold: false, - annotations: vec![SourceAnnotation { - label: "expected struct `annotate_snippets::snippet::Slice`, found reference" - .to_string(), - range: (21, 24), - annotation_type: AnnotationType::Error, - }], - }], - }; - - let dl = DisplayList::from(snippet); - let dlf = DisplayListFormatter::new(true, false); - println!("{}", dlf.format(&dl)); -} diff --git a/examples/format.rs b/examples/format.rs index 7e41802..2965dc4 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,11 +1,8 @@ -use annotate_snippets::display_list::DisplayList; -use annotate_snippets::formatter::DisplayListFormatter; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; +use annotate_snippets::slice::{AnnotationType, SourceAnnotation}; +use annotate_snippets::Slice; fn main() { - let snippet = Snippet { - slices: vec![Slice { - source: r#") -> Option { + let source = r#") -> Option { for ann in annotations { match (ann.range.0, ann.range.1) { (None, None) => continue, @@ -26,33 +23,17 @@ fn main() { } _ => continue, } - }"# - .to_string(), - line_start: 51, - origin: Some("src/format.rs".to_string()), - fold: false, - annotations: vec![ - SourceAnnotation { - label: "expected `Option` because of return type".to_string(), - annotation_type: AnnotationType::Warning, - range: (5, 19), - }, - SourceAnnotation { - label: "expected enum `std::option::Option`".to_string(), - annotation_type: AnnotationType::Error, - range: (23, 745), - }, - ], + }"#; + + let slice = Slice { + source, + line_start: Some(51), + origin: Some("src/format.rs"), + annotations: vec![SourceAnnotation { + label: "expected `Option` because of return type", + annotation_type: AnnotationType::Warning, + range: (5, 19), }], - title: Some(Annotation { - label: Some("mismatched types".to_string()), - id: Some("E0308".to_string()), - annotation_type: AnnotationType::Error, - }), - footer: vec![], }; - - let dl = DisplayList::from(snippet); - let dlf = DisplayListFormatter::new(true, false); - println!("{}", dlf.format(&dl)); + println!("{}", slice); } diff --git a/examples/multislice.rs b/examples/multislice.rs deleted file mode 100644 index af1cb15..0000000 --- a/examples/multislice.rs +++ /dev/null @@ -1,34 +0,0 @@ -use annotate_snippets::display_list::DisplayList; -use annotate_snippets::formatter::DisplayListFormatter; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet}; - -fn main() { - let snippet = Snippet { - title: Some(Annotation { - label: Some("mismatched types".to_string()), - id: None, - annotation_type: AnnotationType::Error, - }), - footer: vec![], - slices: vec![ - Slice { - source: "Foo".to_string(), - line_start: 51, - origin: Some("src/format.rs".to_string()), - fold: false, - annotations: vec![], - }, - Slice { - source: "Faa".to_string(), - line_start: 129, - origin: Some("src/display.rs".to_string()), - fold: false, - annotations: vec![], - }, - ], - }; - - let dl = DisplayList::from(snippet); - let dlf = DisplayListFormatter::new(true, false); - println!("{}", dlf.format(&dl)); -} diff --git a/src/display_list/annotation.rs b/src/display_list/annotation.rs new file mode 100644 index 0000000..a61d1a1 --- /dev/null +++ b/src/display_list/annotation.rs @@ -0,0 +1,12 @@ +use std::fmt; + +#[derive(Debug, Clone)] +pub struct Annotation<'d> { + pub label: &'d str, +} + +impl<'d> fmt::Display for Annotation<'d> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.label) + } +} diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs deleted file mode 100644 index e3d79ed..0000000 --- a/src/display_list/from_snippet.rs +++ /dev/null @@ -1,403 +0,0 @@ -//! Trait for converting `Snippet` to `DisplayList`. -use super::*; -use crate::snippet; - -fn format_label(label: Option<&str>, style: Option) -> Vec { - let mut result = vec![]; - if let Some(label) = label { - let elements: Vec<&str> = label.split("__").collect(); - for (idx, element) in elements.iter().enumerate() { - let element_style = match style { - Some(s) => s, - None => { - if idx % 2 == 0 { - DisplayTextStyle::Regular - } else { - DisplayTextStyle::Emphasis - } - } - }; - result.push(DisplayTextFragment { - content: element.to_string(), - style: element_style, - }); - } - } - result -} - -fn format_title(annotation: &snippet::Annotation) -> DisplayLine { - let label = annotation.label.clone().unwrap_or_default(); - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::from(annotation.annotation_type), - id: annotation.id.clone(), - label: format_label(Some(&label), Some(DisplayTextStyle::Emphasis)), - }, - source_aligned: false, - continuation: false, - }) -} - -fn format_annotation(annotation: &snippet::Annotation) -> Vec { - let mut result = vec![]; - let label = annotation.label.clone().unwrap_or_default(); - for (i, line) in label.lines().enumerate() { - result.push(DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::from(annotation.annotation_type), - id: None, - label: format_label(Some(line), None), - }, - source_aligned: true, - continuation: i != 0, - })); - } - result -} - -fn format_slice(slice: &snippet::Slice, is_first: bool, has_footer: bool) -> Vec { - let mut body = format_body(slice, has_footer); - let mut result = vec![]; - - let header = format_header(slice, &body, is_first); - if let Some(header) = header { - result.push(header); - } - result.append(&mut body); - result -} - -fn format_header( - slice: &snippet::Slice, - body: &[DisplayLine], - is_first: bool, -) -> Option { - let main_annotation = slice.annotations.get(0); - - let display_header = if is_first { - DisplayHeaderType::Initial - } else { - DisplayHeaderType::Continuation - }; - - if let Some(annotation) = main_annotation { - let mut col = 1; - let mut row = slice.line_start; - - for item in body.iter() { - if let DisplayLine::Source { - line: DisplaySourceLine::Content { range, .. }, - .. - } = item - { - if annotation.range.0 >= range.0 && annotation.range.0 <= range.1 { - col = annotation.range.0 - range.0; - break; - } - row += 1; - } - } - if let Some(ref path) = slice.origin { - return Some(DisplayLine::Raw(DisplayRawLine::Origin { - path: path.to_string(), - pos: Some((row, col)), - header_type: display_header, - })); - } - } - if let Some(ref path) = slice.origin { - return Some(DisplayLine::Raw(DisplayRawLine::Origin { - path: path.to_string(), - pos: None, - header_type: display_header, - })); - } - None -} - -fn fold_body(body: &[DisplayLine]) -> Vec { - let mut new_body = vec![]; - - let mut no_annotation_lines_counter = 0; - let mut idx = 0; - - while idx < body.len() { - match body[idx] { - DisplayLine::Source { - line: DisplaySourceLine::Annotation { .. }, - ref inline_marks, - .. - } => { - if no_annotation_lines_counter > 2 { - let fold_start = idx - no_annotation_lines_counter; - let fold_end = idx; - let pre_len = if no_annotation_lines_counter > 8 { - 4 - } else { - 0 - }; - let post_len = if no_annotation_lines_counter > 8 { - 2 - } else { - 1 - }; - for item in body.iter().take(fold_start + pre_len).skip(fold_start) { - new_body.push(item.clone()); - } - new_body.push(DisplayLine::Fold { - inline_marks: inline_marks.clone(), - }); - for item in body.iter().take(fold_end).skip(fold_end - post_len) { - new_body.push(item.clone()); - } - } else { - let start = idx - no_annotation_lines_counter; - for item in body.iter().take(idx).skip(start) { - new_body.push(item.clone()); - } - } - no_annotation_lines_counter = 0; - } - DisplayLine::Source { .. } => { - no_annotation_lines_counter += 1; - idx += 1; - continue; - } - _ => { - no_annotation_lines_counter += 1; - } - } - new_body.push(body[idx].clone()); - idx += 1; - } - - new_body -} - -fn format_body(slice: &snippet::Slice, has_footer: bool) -> Vec { - let mut body = vec![]; - - let mut current_line = slice.line_start; - let mut current_index = 0; - let mut line_index_ranges = vec![]; - - for line in slice.source.lines() { - let line_length = line.chars().count() + 1; - let line_range = (current_index, current_index + line_length); - body.push(DisplayLine::Source { - lineno: Some(current_line), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: line.to_string(), - range: line_range, - }, - }); - line_index_ranges.push(line_range); - current_line += 1; - current_index += line_length + 1; - } - - let mut annotation_line_count = 0; - let mut annotations = slice.annotations.clone(); - for idx in 0..body.len() { - let (line_start, line_end) = line_index_ranges[idx]; - // It would be nice to use filter_drain here once it's stable. - annotations = annotations - .into_iter() - .filter(|annotation| { - let body_idx = idx + annotation_line_count; - let annotation_type = match annotation.annotation_type { - snippet::AnnotationType::Error => DisplayAnnotationType::None, - snippet::AnnotationType::Warning => DisplayAnnotationType::None, - _ => DisplayAnnotationType::from(annotation.annotation_type), - }; - match annotation.range { - (start, _) if start > line_end => true, - (start, end) if start >= line_start && end <= line_end + 1 => { - let range = (start - line_start, end - line_start); - body.insert( - body_idx + 1, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type, - id: None, - label: format_label(Some(&annotation.label), None), - }, - range, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - ); - annotation_line_count += 1; - false - } - (start, end) if start >= line_start && start <= line_end && end > line_end => { - if start - line_start == 0 { - if let DisplayLine::Source { - ref mut inline_marks, - .. - } = body[body_idx] - { - inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationStart, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - }); - } - } else { - let range = (start - line_start, start - line_start + 1); - body.insert( - body_idx + 1, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![], - }, - range, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - annotation_part: DisplayAnnotationPart::MultilineStart, - }, - }, - ); - annotation_line_count += 1; - } - true - } - (start, end) if start < line_start && end > line_end => { - if let DisplayLine::Source { - ref mut inline_marks, - .. - } = body[body_idx] - { - inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - }); - } - true - } - (start, end) if start < line_start && end >= line_start && end <= line_end => { - if let DisplayLine::Source { - ref mut inline_marks, - .. - } = body[body_idx] - { - inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - }); - } - let range = (end - line_start, end - line_start + 1); - body.insert( - body_idx + 1, - DisplayLine::Source { - lineno: None, - inline_marks: vec![DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - }], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type, - id: None, - label: format_label(Some(&annotation.label), None), - }, - range, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - annotation_part: DisplayAnnotationPart::MultilineEnd, - }, - }, - ); - annotation_line_count += 1; - false - } - _ => true, - } - }) - .collect(); - } - - if slice.fold { - body = fold_body(&body); - } - - body.insert( - 0, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - ); - if has_footer { - body.push(DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }); - } else if let Some(DisplayLine::Source { .. }) = body.last() { - body.push(DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }); - } - body -} - -impl From for DisplayList { - fn from(snippet: snippet::Snippet) -> Self { - let mut body = vec![]; - if let Some(annotation) = snippet.title { - body.push(format_title(&annotation)); - } - - for (idx, slice) in snippet.slices.iter().enumerate() { - body.append(&mut format_slice( - &slice, - idx == 0, - !snippet.footer.is_empty(), - )); - } - - for annotation in snippet.footer { - body.append(&mut format_annotation(&annotation)); - } - - Self { body } - } -} - -impl From for DisplayAnnotationType { - fn from(at: snippet::AnnotationType) -> Self { - match at { - snippet::AnnotationType::Error => DisplayAnnotationType::Error, - snippet::AnnotationType::Warning => DisplayAnnotationType::Warning, - snippet::AnnotationType::Info => DisplayAnnotationType::Info, - snippet::AnnotationType::Note => DisplayAnnotationType::Note, - snippet::AnnotationType::Help => DisplayAnnotationType::Help, - } - } -} diff --git a/src/display_list/line.rs b/src/display_list/line.rs new file mode 100644 index 0000000..f1140f3 --- /dev/null +++ b/src/display_list/line.rs @@ -0,0 +1,83 @@ +use std::fmt; +use std::fmt::Write; + +use super::annotation::Annotation; + +#[derive(Debug, Clone)] +pub enum DisplayLine<'d> { + Source { + lineno: Option, + line: DisplaySourceLine<'d>, + }, + Raw(DisplayRawLine<'d>), +} + +impl<'d> DisplayLine<'d> { + pub fn fmt(&self, f: &mut fmt::Formatter<'_>, lineno_max: Option) -> fmt::Result { + let lineno_max = lineno_max.unwrap_or(1); + match self { + Self::Source { lineno, line } => { + if let Some(lineno) = lineno { + write!(f, "{:>1$}", lineno, lineno_max)?; + writeln!(f, " | {}", line) + } else { + write!(f, "{:>1$}", "", lineno_max)?; + writeln!(f, " | {}", line) + } + } + Self::Raw(dl) => dl.fmt(f, lineno_max), + } + } +} + +#[derive(Debug, Clone)] +pub enum DisplaySourceLine<'d> { + Content { + text: &'d str, + }, + Annotation { + annotation: Annotation<'d>, + range: (usize, usize), + }, + Empty, +} + +impl<'d> fmt::Display for DisplaySourceLine<'d> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Content { text } => f.write_str(text), + Self::Annotation { + annotation, + range: (start, end), + } => { + write!(f, "{:>1$}", "", start)?; + write!(f, "{:->1$} ", "", end - start)?; + annotation.fmt(f) + } + Self::Empty => Ok(()), + } + } +} + +#[derive(Debug, Clone)] +pub enum DisplayRawLine<'d> { + Origin { + path: &'d str, + pos: (Option, Option), + }, +} + +impl<'d> DisplayRawLine<'d> { + fn fmt(&self, f: &mut fmt::Formatter<'_>, lineno_max: usize) -> fmt::Result { + match self { + Self::Origin { path, pos } => { + write!(f, "{:>1$}", "", lineno_max)?; + write!(f, "--> {}", path)?; + if let Some(line) = pos.0 { + write!(f, ":{}", line)?; + } + f.write_char('\n') + } + } + } +} diff --git a/src/display_list/list.rs b/src/display_list/list.rs new file mode 100644 index 0000000..d1c08b3 --- /dev/null +++ b/src/display_list/list.rs @@ -0,0 +1,104 @@ +use super::annotation::Annotation; +use super::line::{DisplayLine, DisplayRawLine, DisplaySourceLine}; +use crate::slice::Slice; +use std::fmt; + +#[derive(Debug, Clone)] +pub struct DisplayList<'d> { + pub body: Vec>, +} + +fn get_header_pos(slice: &Slice) -> (Option, Option) { + let line = slice.line_start; + (line, None) +} + +impl<'d> From<&Slice<'d>> for DisplayList<'d> { + fn from(slice: &Slice<'d>) -> Self { + let mut body = vec![]; + + if let Some(path) = slice.origin { + body.push(DisplayLine::Raw(DisplayRawLine::Origin { + path, + pos: get_header_pos(slice), + })); + } + + body.push(DisplayLine::Source { + lineno: None, + line: DisplaySourceLine::Empty, + }); + + let mut annotations = slice.annotations.iter(); + + let mut current_annotation = annotations.next(); + let mut line_start_pos = 0; + + let mut i = slice.line_start.unwrap_or(1); + for line in slice.source.lines() { + let line_length = line.chars().count(); + body.push(DisplayLine::Source { + lineno: Some(i), + line: DisplaySourceLine::Content { text: line }, + }); + if let Some(annotation) = current_annotation { + if annotation.range.0 >= line_start_pos + && annotation.range.1 <= line_start_pos + line_length + { + body.push(DisplayLine::Source { + lineno: None, + line: DisplaySourceLine::Annotation { + annotation: Annotation { + label: annotation.label, + }, + range: ( + annotation.range.0 - line_start_pos, + annotation.range.1 - line_start_pos, + ), + }, + }); + current_annotation = annotations.next(); + } + } + line_start_pos += line_length; + i += 1; + } + + body.push(DisplayLine::Source { + lineno: None, + line: DisplaySourceLine::Empty, + }); + + DisplayList { body } + } +} + +fn digits(n: &usize) -> usize { + let mut n = n.clone(); + let mut sum = 0; + while n != 0 { + n = n / 10; + sum += 1; + } + sum +} + +impl<'d> fmt::Display for DisplayList<'d> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let lineno_max = self.body.iter().rev().find_map(|line| { + if let DisplayLine::Source { + lineno: Some(lineno), + .. + } = line + { + Some(digits(lineno)) + } else { + None + } + }); + for line in &self.body { + line.fmt(f, lineno_max)? + } + Ok(()) + } +} diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs index 5e0b393..7dfef99 100644 --- a/src/display_list/mod.rs +++ b/src/display_list/mod.rs @@ -1,124 +1,5 @@ -//! display_list module stores the output model for the snippet. -//! -//! `DisplayList` is a central structure in the crate, which contains -//! the structured list of lines to be displayed. -//! -//! It is made of two types of lines: `Source` and `Raw`. All `Source` lines -//! are structured using four columns: -//! -//! ```text -//! /------------ (1) Line number column. -//! | /--------- (2) Line number column delimiter. -//! | | /------- (3) Inline marks column. -//! | | | /--- (4) Content column with the source and annotations for slices. -//! | | | | -//! ============================================================================= -//! error[E0308]: mismatched types -//! --> src/format.rs:51:5 -//! | -//! 151 | / fn test() -> String { -//! 152 | | return "test"; -//! 153 | | } -//! | |___^ error: expected `String`, for `&str`. -//! | -//! ``` -//! -//! The first two lines of the example above are `Raw` lines, while the rest -//! are `Source` lines. -//! -//! `DisplayList` does not store column alignment information, and those are -//! only calculated by the `DisplayListFormatter` using information such as -//! styling. -//! -//! The above snippet has been built out of the following structure: -//! -//! ``` -//! use annotate_snippets::display_list::*; -//! -//! let dl = DisplayList { -//! body: vec![ -//! DisplayLine::Raw(DisplayRawLine::Annotation { -//! annotation: Annotation { -//! annotation_type: DisplayAnnotationType::Error, -//! id: Some("E0308".to_string()), -//! label: vec![ -//! DisplayTextFragment { -//! content: "mismatched types".to_string(), -//! style: DisplayTextStyle::Regular, -//! } -//! ] -//! }, -//! source_aligned: false, -//! continuation: false, -//! }), -//! DisplayLine::Raw(DisplayRawLine::Origin { -//! path: "src/format.rs".to_string(), -//! pos: Some((51, 5)), -//! header_type: DisplayHeaderType::Initial, -//! }), -//! DisplayLine::Source { -//! lineno: Some(151), -//! inline_marks: vec![ -//! DisplayMark { -//! mark_type: DisplayMarkType::AnnotationStart, -//! annotation_type: DisplayAnnotationType::Error, -//! } -//! ], -//! line: DisplaySourceLine::Content { -//! text: " fn test() -> String {".to_string(), -//! range: (0, 24) -//! } -//! }, -//! DisplayLine::Source { -//! lineno: Some(152), -//! inline_marks: vec![ -//! DisplayMark { -//! mark_type: DisplayMarkType::AnnotationThrough, -//! annotation_type: DisplayAnnotationType::Error, -//! } -//! ], -//! line: DisplaySourceLine::Content { -//! text: " return \"test\";".to_string(), -//! range: (25, 46) -//! } -//! }, -//! DisplayLine::Source { -//! lineno: Some(153), -//! inline_marks: vec![ -//! DisplayMark { -//! mark_type: DisplayMarkType::AnnotationThrough, -//! annotation_type: DisplayAnnotationType::Error, -//! } -//! ], -//! line: DisplaySourceLine::Content { -//! text: " }".to_string(), -//! range: (47, 51) -//! } -//! }, -//! DisplayLine::Source { -//! lineno: None, -//! inline_marks: vec![], -//! line: DisplaySourceLine::Annotation { -//! annotation: Annotation { -//! annotation_type: DisplayAnnotationType::Error, -//! id: None, -//! label: vec![ -//! DisplayTextFragment { -//! content: "expected `String`, for `&str`.".to_string(), -//! style: DisplayTextStyle::Regular, -//! } -//! ] -//! }, -//! range: (3, 4), -//! annotation_type: DisplayAnnotationType::Error, -//! annotation_part: DisplayAnnotationPart::MultilineEnd, -//! } -//! -//! } -//! ] -//! }; -//! ``` -mod from_snippet; -mod structs; +mod annotation; +mod line; +mod list; -pub use self::structs::*; +pub use list::DisplayList; diff --git a/src/display_list/structs.rs b/src/display_list/structs.rs deleted file mode 100644 index 0d3e0bc..0000000 --- a/src/display_list/structs.rs +++ /dev/null @@ -1,253 +0,0 @@ -/// List of lines to be displayed. -#[derive(Debug, Clone, PartialEq)] -pub struct DisplayList { - pub body: Vec, -} - -impl From> for DisplayList { - fn from(body: Vec) -> Self { - Self { body } - } -} - -/// Inline annotation which can be used in either Raw or Source line. -#[derive(Debug, Clone, PartialEq)] -pub struct Annotation { - pub annotation_type: DisplayAnnotationType, - pub id: Option, - pub label: Vec, -} - -/// A single line used in `DisplayList`. -#[derive(Debug, Clone, PartialEq)] -pub enum DisplayLine { - /// A line with `lineno` portion of the slice. - Source { - lineno: Option, - inline_marks: Vec, - line: DisplaySourceLine, - }, - - /// A line indicating a folded part of the slice. - Fold { inline_marks: Vec }, - - /// A line which is displayed outside of slices. - Raw(DisplayRawLine), -} - -/// A source line. -#[derive(Debug, Clone, PartialEq)] -pub enum DisplaySourceLine { - /// A line with the content of the Slice. - Content { - text: String, - range: (usize, usize), // meta information for annotation placement. - }, - - /// An annotation line which is displayed in context of the slice. - Annotation { - annotation: Annotation, - range: (usize, usize), - annotation_type: DisplayAnnotationType, - annotation_part: DisplayAnnotationPart, - }, - - /// An empty source line. - Empty, -} - -/// Raw line - a line which does not have the `lineno` part and is not considered -/// a part of the snippet. -#[derive(Debug, Clone, PartialEq)] -pub enum DisplayRawLine { - /// A line which provides information about the location of the given - /// slice in the project structure. - Origin { - path: String, - pos: Option<(usize, usize)>, - header_type: DisplayHeaderType, - }, - - /// An annotation line which is not part of any snippet. - Annotation { - annotation: Annotation, - - /// If set to `true`, the annotation will be aligned to the - /// lineno delimiter of the snippet. - source_aligned: bool, - /// If set to `true`, only the label of the `Annotation` will be - /// displayed. It allows for a multiline annotation to be aligned - /// without displaing the meta information (`type` and `id`) to be - /// displayed on each line. - continuation: bool, - }, -} - -/// An inline text fragment which any label is composed of. -#[derive(Debug, Clone, PartialEq)] -pub struct DisplayTextFragment { - pub content: String, - pub style: DisplayTextStyle, -} - -/// A style for the `DisplayTextFragment` which can be visually formatted. -/// -/// This information may be used to emphasis parts of the label. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum DisplayTextStyle { - Regular, - Emphasis, -} - -/// An indicator of what part of the annotation a given `Annotation` is. -#[derive(Debug, Clone, PartialEq)] -pub enum DisplayAnnotationPart { - /// A standalone, single-line annotation. - Standalone, - /// A continuation of a multi-line label of an annotation. - LabelContinuation, - /// A consequitive annotation in case multiple annotations annotate a single line. - Consequitive, - /// A line starting a multiline annotation. - MultilineStart, - /// A line ending a multiline annotation. - MultilineEnd, -} - -/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`. -#[derive(Debug, Clone, PartialEq)] -pub struct DisplayMark { - pub mark_type: DisplayMarkType, - pub annotation_type: DisplayAnnotationType, -} - -/// A type of the `DisplayMark`. -#[derive(Debug, Clone, PartialEq)] -pub enum DisplayMarkType { - /// A mark indicating a multiline annotation going through the current line. - /// - /// Example: - /// ``` - /// use annotate_snippets::display_list::*; - /// use annotate_snippets::formatter::DisplayListFormatter; - /// - /// let dlf = DisplayListFormatter::new(false, false); // Don't use colors - /// - /// let dl = DisplayList { - /// body: vec![ - /// DisplayLine::Source { - /// lineno: Some(51), - /// inline_marks: vec![ - /// DisplayMark { - /// mark_type: DisplayMarkType::AnnotationThrough, - /// annotation_type: DisplayAnnotationType::Error, - /// } - /// ], - /// line: DisplaySourceLine::Content { - /// text: "Example".to_string(), - /// range: (0, 7), - /// } - /// } - /// ] - /// }; - /// assert_eq!(dlf.format(&dl), "51 | | Example"); - /// ``` - AnnotationThrough, - - /// A mark indicating a multiline annotation starting on the given line. - /// - /// Example: - /// ``` - /// use annotate_snippets::display_list::*; - /// use annotate_snippets::formatter::DisplayListFormatter; - /// - /// let dlf = DisplayListFormatter::new(false, false); // Don't use colors - /// - /// let dl = DisplayList { - /// body: vec![ - /// DisplayLine::Source { - /// lineno: Some(51), - /// inline_marks: vec![ - /// DisplayMark { - /// mark_type: DisplayMarkType::AnnotationStart, - /// annotation_type: DisplayAnnotationType::Error, - /// } - /// ], - /// line: DisplaySourceLine::Content { - /// text: "Example".to_string(), - /// range: (0, 7), - /// } - /// } - /// ] - /// }; - /// assert_eq!(dlf.format(&dl), "51 | / Example"); - /// ``` - AnnotationStart, -} - -/// A type of the `Annotation` which may impact the sigils, style or text displayed. -/// -/// There are several ways in which the `DisplayListFormatter` uses this information -/// when formatting the `DisplayList`: -/// -/// * An annotation may display the name of the type like `error` or `info`. -/// * An underline for `Error` may be `^^^` while for `Warning` it coule be `---`. -/// * `ColorStylesheet` may use different colors for different annotations. -#[derive(Debug, Clone, PartialEq)] -pub enum DisplayAnnotationType { - None, - Error, - Warning, - Info, - Note, - Help, -} - -/// Information whether the header is the initial one or a consequitive one -/// for multi-slice cases. -#[derive(Debug, Clone, PartialEq)] -pub enum DisplayHeaderType { - /// Initial header is the first header in the snippet. - /// - /// Example: - /// ``` - /// use annotate_snippets::display_list::*; - /// use annotate_snippets::formatter::DisplayListFormatter; - /// - /// let dlf = DisplayListFormatter::new(false, false); // Don't use colors - /// - /// let dl = DisplayList { - /// body: vec![ - /// DisplayLine::Raw(DisplayRawLine::Origin { - /// path: "file1.rs".to_string(), - /// pos: Some((51, 5)), - /// header_type: DisplayHeaderType::Initial, - /// }) - /// ] - /// }; - /// assert_eq!(dlf.format(&dl), "--> file1.rs:51:5"); - /// ``` - Initial, - - /// Continuation marks all headers of following slices in the snippet. - /// - /// Example: - /// ``` - /// use annotate_snippets::display_list::*; - /// use annotate_snippets::formatter::DisplayListFormatter; - /// - /// let dlf = DisplayListFormatter::new(false, false); // Don't use colors - /// - /// let dl = DisplayList { - /// body: vec![ - /// DisplayLine::Raw(DisplayRawLine::Origin { - /// path: "file1.rs".to_string(), - /// pos: Some((51, 5)), - /// header_type: DisplayHeaderType::Continuation, - /// }) - /// ] - /// }; - /// assert_eq!(dlf.format(&dl), "::: file1.rs:51:5"); - /// ``` - Continuation, -} diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs deleted file mode 100644 index b10a5e6..0000000 --- a/src/formatter/mod.rs +++ /dev/null @@ -1,367 +0,0 @@ -//! DisplayListFormatter is a module handling the formatting of a -//! `DisplayList` into a formatted string. -//! -//! Besides formatting into a string it also uses a `style::Stylesheet` to -//! provide additional styling like colors and emphasis to the text. - -pub mod style; - -use self::style::{Style, StyleClass, Stylesheet}; -use crate::display_list::*; -use std::cmp; - -#[cfg(feature = "ansi_term")] -use crate::stylesheets::color::AnsiTermStylesheet; -use crate::stylesheets::no_color::NoColorStylesheet; - -fn repeat_char(c: char, n: usize) -> String { - let mut s = String::with_capacity(c.len_utf8()); - s.push(c); - s.repeat(n) -} - -/// DisplayListFormatter' constructor accepts two arguments: -/// -/// * `color` allows the formatter to optionally apply colors and emphasis -/// using the `ansi_term` crate. -/// * `anonymized_line_numbers` will replace line numbers in the left column with the text `LL`. -/// -/// Example: -/// -/// ``` -/// use annotate_snippets::formatter::DisplayListFormatter; -/// use annotate_snippets::display_list::{DisplayList, DisplayLine, DisplaySourceLine}; -/// -/// let dlf = DisplayListFormatter::new(false, false); // Don't use colors, Don't anonymize line numbers -/// -/// let dl = DisplayList { -/// body: vec![ -/// DisplayLine::Source { -/// lineno: Some(192), -/// inline_marks: vec![], -/// line: DisplaySourceLine::Content { -/// text: "Example line of text".into(), -/// range: (0, 21) -/// } -/// } -/// ] -/// }; -/// assert_eq!(dlf.format(&dl), "192 | Example line of text"); -/// ``` -pub struct DisplayListFormatter { - stylesheet: Box, - anonymized_line_numbers: bool, -} - -impl DisplayListFormatter { - const ANONYMIZED_LINE_NUM: &'static str = "LL"; - - /// Constructor for the struct. - /// - /// The argument `color` selects the stylesheet depending on the user preferences and - /// `ansi_term` crate availability. - /// - /// The argument `anonymized_line_numbers` will replace line numbers in the left column with - /// the text `LL`. This can be useful to enable when running UI tests, such as in the Rust - /// test suite. - pub fn new(color: bool, anonymized_line_numbers: bool) -> Self { - if color { - Self { - #[cfg(feature = "ansi_term")] - stylesheet: Box::new(AnsiTermStylesheet {}), - #[cfg(not(feature = "ansi_term"))] - stylesheet: Box::new(NoColorStylesheet {}), - anonymized_line_numbers, - } - } else { - Self { - stylesheet: Box::new(NoColorStylesheet {}), - anonymized_line_numbers, - } - } - } - - /// Formats a `DisplayList` into a String. - pub fn format(&self, dl: &DisplayList) -> String { - let lineno_width = dl.body.iter().fold(0, |max, line| match line { - DisplayLine::Source { - lineno: Some(lineno), - .. - } => { - if self.anonymized_line_numbers { - Self::ANONYMIZED_LINE_NUM.len() - } else { - cmp::max(lineno.to_string().len(), max) - } - } - _ => max, - }); - let inline_marks_width = dl.body.iter().fold(0, |max, line| match line { - DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max), - _ => max, - }); - - dl.body - .iter() - .map(|line| self.format_line(line, lineno_width, inline_marks_width)) - .collect::>() - .join("\n") - } - - fn format_annotation_type(&self, annotation_type: &DisplayAnnotationType) -> &'static str { - match annotation_type { - DisplayAnnotationType::Error => "error", - DisplayAnnotationType::Warning => "warning", - DisplayAnnotationType::Info => "info", - DisplayAnnotationType::Note => "note", - DisplayAnnotationType::Help => "help", - DisplayAnnotationType::None => "", - } - } - - fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box { - self.stylesheet.get_style(match annotation_type { - DisplayAnnotationType::Error => StyleClass::Error, - DisplayAnnotationType::Warning => StyleClass::Warning, - DisplayAnnotationType::Info => StyleClass::Info, - DisplayAnnotationType::Note => StyleClass::Note, - DisplayAnnotationType::Help => StyleClass::Help, - DisplayAnnotationType::None => StyleClass::None, - }) - } - - fn format_label(&self, label: &[DisplayTextFragment]) -> String { - let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis); - label - .iter() - .map(|fragment| match fragment.style { - DisplayTextStyle::Regular => fragment.content.clone(), - DisplayTextStyle::Emphasis => emphasis_style.paint(&fragment.content), - }) - .collect::>() - .join("") - } - - fn format_annotation( - &self, - annotation: &Annotation, - continuation: bool, - in_source: bool, - ) -> String { - let color = self.get_annotation_style(&annotation.annotation_type); - let formatted_type = if let Some(ref id) = annotation.id { - format!( - "{}[{}]", - self.format_annotation_type(&annotation.annotation_type), - id - ) - } else { - self.format_annotation_type(&annotation.annotation_type) - .to_string() - }; - let label = self.format_label(&annotation.label); - - let label_part = if label.is_empty() { - "".to_string() - } else if in_source { - color.paint(&format!(": {}", self.format_label(&annotation.label))) - } else { - format!(": {}", self.format_label(&annotation.label)) - }; - if continuation { - let indent = formatted_type.len() + 2; - return format!("{}{}", repeat_char(' ', indent), label); - } - if !formatted_type.is_empty() { - format!("{}{}", color.paint(&formatted_type), label_part) - } else { - label - } - } - - fn format_source_line(&self, line: &DisplaySourceLine) -> Option { - match line { - DisplaySourceLine::Empty => None, - DisplaySourceLine::Content { text, .. } => Some(format!(" {}", text)), - DisplaySourceLine::Annotation { - range, - annotation, - annotation_type, - annotation_part, - } => { - let indent_char = match annotation_part { - DisplayAnnotationPart::Standalone => ' ', - DisplayAnnotationPart::LabelContinuation => ' ', - DisplayAnnotationPart::Consequitive => ' ', - DisplayAnnotationPart::MultilineStart => '_', - DisplayAnnotationPart::MultilineEnd => '_', - }; - let mark = match annotation_type { - DisplayAnnotationType::Error => '^', - DisplayAnnotationType::Warning => '-', - DisplayAnnotationType::Info => '-', - DisplayAnnotationType::Note => '-', - DisplayAnnotationType::Help => '-', - DisplayAnnotationType::None => ' ', - }; - let color = self.get_annotation_style(annotation_type); - let indent_length = match annotation_part { - DisplayAnnotationPart::LabelContinuation => range.1, - DisplayAnnotationPart::Consequitive => range.1, - _ => range.0, - }; - let indent = color.paint(&repeat_char(indent_char, indent_length + 1)); - let marks = color.paint(&repeat_char(mark, range.1 - indent_length)); - let annotation = self.format_annotation( - annotation, - annotation_part == &DisplayAnnotationPart::LabelContinuation, - true, - ); - if annotation.is_empty() { - return Some(format!("{}{}", indent, marks)); - } - Some(format!("{}{} {}", indent, marks, color.paint(&annotation))) - } - } - } - - fn format_lineno(&self, lineno: Option, lineno_width: usize) -> String { - match lineno { - Some(n) => format!("{:>width$}", n, width = lineno_width), - None => repeat_char(' ', lineno_width), - } - } - - fn format_raw_line(&self, line: &DisplayRawLine, lineno_width: usize) -> String { - match line { - DisplayRawLine::Origin { - path, - pos, - header_type, - } => { - let header_sigil = match header_type { - DisplayHeaderType::Initial => "-->", - DisplayHeaderType::Continuation => ":::", - }; - let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); - - if let Some((col, row)) = pos { - format!( - "{}{} {}:{}:{}", - repeat_char(' ', lineno_width), - lineno_color.paint(header_sigil), - path, - col, - row - ) - } else { - format!( - "{}{} {}", - repeat_char(' ', lineno_width), - lineno_color.paint(header_sigil), - path - ) - } - } - DisplayRawLine::Annotation { - annotation, - source_aligned, - continuation, - } => { - if *source_aligned { - if *continuation { - format!( - "{}{}", - repeat_char(' ', lineno_width + 3), - self.format_annotation(annotation, *continuation, false) - ) - } else { - let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); - format!( - "{} {} {}", - repeat_char(' ', lineno_width), - lineno_color.paint("="), - self.format_annotation(annotation, *continuation, false) - ) - } - } else { - self.format_annotation(annotation, *continuation, false) - } - } - } - } - - fn format_line( - &self, - dl: &DisplayLine, - lineno_width: usize, - inline_marks_width: usize, - ) -> String { - match dl { - DisplayLine::Source { - lineno, - inline_marks, - line, - } => { - let lineno = if self.anonymized_line_numbers && lineno.is_some() { - Self::ANONYMIZED_LINE_NUM.to_string() - } else { - self.format_lineno(*lineno, lineno_width) - }; - let marks = self.format_inline_marks(inline_marks, inline_marks_width); - let lf = self.format_source_line(line); - let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); - - let mut prefix = lineno_color.paint(&format!("{} |", lineno)); - - match lf { - Some(lf) => { - if !marks.is_empty() { - prefix.push_str(&format!(" {}", marks)); - } - format!("{}{}", prefix, lf) - } - None => { - if !marks.trim().is_empty() { - prefix.push_str(&format!(" {}", marks)); - } - prefix - } - } - } - DisplayLine::Fold { inline_marks } => { - let marks = self.format_inline_marks(inline_marks, inline_marks_width); - let indent = lineno_width; - if marks.trim().is_empty() { - String::from("...") - } else { - format!("...{}{}", repeat_char(' ', indent), marks) - } - } - DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width), - } - } - - fn format_inline_marks( - &self, - inline_marks: &[DisplayMark], - inline_marks_width: usize, - ) -> String { - format!( - "{}{}", - " ".repeat(inline_marks_width - inline_marks.len()), - inline_marks - .iter() - .map(|mark| { - let sigil = match mark.mark_type { - DisplayMarkType::AnnotationThrough => "|", - DisplayMarkType::AnnotationStart => "/", - }; - let color = self.get_annotation_style(&mark.annotation_type); - color.paint(sigil) - }) - .collect::>() - .join(""), - ) - } -} diff --git a/src/formatter/style.rs b/src/formatter/style.rs deleted file mode 100644 index c1b9046..0000000 --- a/src/formatter/style.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Set of structures required to implement a stylesheet for -//! [DisplayListFormatter](super::DisplayListFormatter). -//! -//! In order to provide additional styling information for the -//! formatter, a structs can implement `Stylesheet` and `Style` -//! traits. -//! -//! Example: -//! -//! ``` -//! use annotate_snippets::formatter::style::{Stylesheet, StyleClass, Style}; -//! -//! struct HTMLStyle { -//! prefix: String, -//! postfix: String, -//! }; -//! -//! impl HTMLStyle { -//! fn new(prefix: &str, postfix: &str) -> Self { -//! HTMLStyle { -//! prefix: prefix.into(), -//! postfix: postfix.into() -//! } -//! } -//! }; -//! -//! impl Style for HTMLStyle { -//! fn paint(&self, text: &str) -> String { -//! format!("{}{}{}", self.prefix, text, self.postfix) -//! } -//! -//! fn bold(&self) -> Box
")
+    }
+
+    fn fmt_footer(&self, w: &mut impl std::io::Write) -> std::io::Result<()> {
+        write!(w, "
") + } } impl RendererTrait for Renderer { From 231cf08fdf31c7ba6cc664aff40cf482e8dc0031 Mon Sep 17 00:00:00 2001 From: Zibi Braniecki Date: Mon, 28 Oct 2019 18:29:39 -0700 Subject: [PATCH 20/21] Finish HTML POC --- src/renderers/ascii_default/mod.rs | 4 +-- src/renderers/html/mod.rs | 54 +++++++++++++++--------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/renderers/ascii_default/mod.rs b/src/renderers/ascii_default/mod.rs index 83095fd..980df58 100644 --- a/src/renderers/ascii_default/mod.rs +++ b/src/renderers/ascii_default/mod.rs @@ -126,8 +126,8 @@ impl Renderer { let styles = [StyleType::Emphasis, style]; let indent = if range.start == 0 { 0 } else { range.start + 1 }; write!(w, "{:>width$}", "", width = indent)?; + let horizontal_mark = MarkKind::get(MarkKind::Horizontal); if range.start == 0 { - let horizontal_mark = MarkKind::get(MarkKind::Horizontal); S::fmt( w, format_args!( @@ -191,7 +191,7 @@ impl Renderer { } S::fmt( w, - format_args!(": {}\n", annotation.label), + format_args!(": {}\n", annotation.label), &[StyleType::Emphasis], ) } diff --git a/src/renderers/html/mod.rs b/src/renderers/html/mod.rs index 19f7a2f..85d1d72 100644 --- a/src/renderers/html/mod.rs +++ b/src/renderers/html/mod.rs @@ -133,13 +133,15 @@ impl Renderer { write!(w, r#" {}"#, text) } DisplaySourceLine::Annotation { annotation, range } => { + let desc = self.get_annotation_type_style(&annotation.annotation_type); let indent = if range.start == 0 { 0 } else { range.start + 1 }; write!(w, "{:>width$}", "", width = indent)?; let horizontal_mark = MarkKind::get(MarkKind::Horizontal); if range.start == 0 { write!( w, - "{}{} {}", + r#"{}{} {}"#, + desc, repeat(horizontal_mark) .take(range.len()) .collect::(), @@ -149,7 +151,8 @@ impl Renderer { } else { write!( w, - "{} {}", + r#"{} {}"#, + desc, repeat(horizontal_mark) .take(range.len()) .collect::(), @@ -171,33 +174,25 @@ impl Renderer { match line { DisplayRawLine::Origin { path, pos } => { write!(w, "{:>width$}", "", width = lineno_max)?; - //S::fmt( - //w, - //format_args!( - //"{}{}>", - //MarkKind::get(MarkKind::Horizontal), - //MarkKind::get(MarkKind::Horizontal), - //), - //&[StyleType::Emphasis, StyleType::LineNo], - //)?; - //write!(w, " {}", path)?; - //if let Some(line) = pos.0 { - //write!(w, ":{}", line)?; - //} + write!( + w, + r#"{}{}>"#, + MarkKind::get(MarkKind::Horizontal), + MarkKind::get(MarkKind::Horizontal), + )?; + write!(w, " {}", path)?; + if let Some(line) = pos.0 { + write!(w, ":{}", line)?; + } writeln!(w) } DisplayRawLine::Annotation { annotation, .. } => { let desc = self.get_annotation_type_style(&annotation.annotation_type); - //let s = [StyleType::Emphasis, style]; - //S::fmt(w, desc, &s)?; - //if let Some(id) = annotation.id { - //S::fmt(w, format_args!("[{}]", id), &s)?; - //} - //S::fmt( - //w, - //format_args!(": {}\n", annotation.label), - //&[StyleType::Emphasis], - //) + write!(w, r#"{}"#, desc, desc)?; + if let Some(id) = annotation.id { + write!(w, "[{}]", id)?; + } + write!(w, ": {}\n", annotation.label)?; Ok(()) } } @@ -219,20 +214,25 @@ impl Renderer { w: &mut impl std::io::Write, display_mark: &DisplayMark, ) -> std::io::Result<()> { + let desc = self.get_annotation_type_style(&display_mark.annotation_type); let ch = match display_mark.mark_type { DisplayMarkType::AnnotationStart => MarkKind::get(MarkKind::DownRight), DisplayMarkType::AnnotationEnd => MarkKind::get(MarkKind::UpRight), DisplayMarkType::AnnotationThrough => MarkKind::get(MarkKind::Vertical), }; - write!(w, "{}", ch)?; + write!(w, r#"{}"#, desc, ch)?; Ok(()) } fn fmt_header(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { writeln!(w, "
")
     }
 

From e1c14289e43c80939642699b424f32977d215d59 Mon Sep 17 00:00:00 2001
From: Zibi Braniecki 
Date: Tue, 29 Oct 2019 13:32:22 -0700
Subject: [PATCH 21/21] Add InlineAnnotation

---
 examples/format.rs                 | 20 +++++++++++++-
 src/annotation.rs                  |  6 ++++
 src/display_list/line.rs           | 11 ++++++--
 src/display_list/list.rs           | 44 +++++++++++++++++++++++++++---
 src/lib.rs                         |  2 +-
 src/renderers/ascii_default/mod.rs | 18 +++++++++++-
 src/slice.rs                       |  3 +-
 7 files changed, 94 insertions(+), 10 deletions(-)

diff --git a/examples/format.rs b/examples/format.rs
index c59bb09..1bff405 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -1,5 +1,5 @@
 use annotate_snippets::DisplayList;
-use annotate_snippets::{Annotation, AnnotationType, SourceAnnotation};
+use annotate_snippets::{Annotation, AnnotationType, InlineAnnotation, SourceAnnotation};
 use annotate_snippets::{Slice, Snippet};
 
 use annotate_snippets::renderers::get_renderer;
@@ -52,6 +52,24 @@ fn main() {
                     range: 23..725,
                 },
             ],
+            inline_annotations: &[
+                InlineAnnotation {
+                    annotation_type: AnnotationType::Warning,
+                    range: 5..19,
+                },
+                InlineAnnotation {
+                    annotation_type: AnnotationType::Error,
+                    range: 49..50,
+                },
+                InlineAnnotation {
+                    annotation_type: AnnotationType::Error,
+                    range: 724..725,
+                },
+                InlineAnnotation {
+                    annotation_type: AnnotationType::Help,
+                    range: 421..427,
+                },
+            ],
         }],
     };
     let dl = DisplayList::from(&snippet);
diff --git a/src/annotation.rs b/src/annotation.rs
index b1ac52d..a8cd26a 100644
--- a/src/annotation.rs
+++ b/src/annotation.rs
@@ -23,3 +23,9 @@ pub struct SourceAnnotation<'s> {
     pub label: &'s str,
     pub annotation_type: AnnotationType,
 }
+
+#[derive(Debug, Clone)]
+pub struct InlineAnnotation {
+    pub range: Range,
+    pub annotation_type: AnnotationType,
+}
diff --git a/src/display_list/line.rs b/src/display_list/line.rs
index f0774b7..6219413 100644
--- a/src/display_list/line.rs
+++ b/src/display_list/line.rs
@@ -13,10 +13,17 @@ pub enum DisplayLine<'d> {
 }
 
 #[derive(Debug, Clone)]
-pub enum DisplaySourceLine<'d> {
-    Content {
+pub enum DisplayContentElement<'d> {
+    Text(&'d str),
+    AnnotatedText {
         text: &'d str,
+        annotation_type: AnnotationType,
     },
+}
+
+#[derive(Debug, Clone)]
+pub enum DisplaySourceLine<'d> {
+    Content(Vec>),
     Annotation {
         annotation: Annotation<'d>,
         range: Range,
diff --git a/src/display_list/list.rs b/src/display_list/list.rs
index 3b18248..e945fc0 100644
--- a/src/display_list/list.rs
+++ b/src/display_list/list.rs
@@ -1,6 +1,9 @@
 use super::annotation::Annotation;
-use super::line::{DisplayLine, DisplayMark, DisplayMarkType, DisplayRawLine, DisplaySourceLine};
-use crate::{Slice, Snippet, SourceAnnotation};
+use super::line::{
+    DisplayContentElement, DisplayLine, DisplayMark, DisplayMarkType, DisplayRawLine,
+    DisplaySourceLine,
+};
+use crate::{InlineAnnotation, Slice, Snippet, SourceAnnotation};
 
 #[derive(Debug, Clone)]
 pub struct DisplayList<'d> {
@@ -55,8 +58,9 @@ impl<'d> From<&Slice<'d>> for DisplayList<'d> {
         });
 
         let mut annotations: Vec<&SourceAnnotation> = slice.annotations.iter().collect();
+        let mut inline_annotations: Vec<&InlineAnnotation> =
+            slice.inline_annotations.iter().collect();
 
-        // let mut current_annotation = annotations.next();
         let mut line_start_pos = 0;
 
         let mut i = slice.line_start.unwrap_or(1);
@@ -64,6 +68,7 @@ impl<'d> From<&Slice<'d>> for DisplayList<'d> {
             let line_range = line_start_pos..(line_start_pos + line.chars().count() + 1);
 
             let mut current_annotations = vec![];
+            let mut current_inline_annotations = vec![];
             let mut inline_marks = vec![];
 
             annotations.retain(|ann| {
@@ -100,10 +105,41 @@ impl<'d> From<&Slice<'d>> for DisplayList<'d> {
                 }
             });
 
+            inline_annotations.retain(|ann| {
+                if line_range.contains(&ann.range.start) && line_range.contains(&ann.range.end) {
+                    // Annotation in this line
+                    current_inline_annotations.push(*ann);
+                    false
+                } else {
+                    true
+                }
+            });
+
+            let mut frag = vec![];
+
+            let mut ptr = 0;
+            for ann in current_inline_annotations {
+                if ptr < ann.range.start {
+                    frag.push(DisplayContentElement::Text(
+                        &line[ptr..ann.range.start - line_range.start],
+                    ));
+                }
+                frag.push(DisplayContentElement::AnnotatedText {
+                    text: &line
+                        [(ann.range.start - line_range.start)..(ann.range.end - line_range.start)],
+                    annotation_type: ann.annotation_type.clone(),
+                });
+                ptr = ann.range.end - line_range.start;
+            }
+
+            if ptr < line_range.end {
+                frag.push(DisplayContentElement::Text(&line[ptr..]));
+            }
+
             body.push(DisplayLine::Source {
                 lineno: Some(i),
                 inline_marks,
-                line: DisplaySourceLine::Content { text: line },
+                line: DisplaySourceLine::Content(frag),
             });
             for ann in current_annotations {
                 let start = if ann.range.start >= line_start_pos {
diff --git a/src/lib.rs b/src/lib.rs
index 9848c43..1838677 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,7 +4,7 @@ pub mod renderers;
 pub mod slice;
 pub mod snippet;
 
-pub use annotation::{Annotation, AnnotationType, SourceAnnotation};
+pub use annotation::{Annotation, AnnotationType, InlineAnnotation, SourceAnnotation};
 pub use display_list::DisplayList;
 pub use slice::Slice;
 pub use snippet::Snippet;
diff --git a/src/renderers/ascii_default/mod.rs b/src/renderers/ascii_default/mod.rs
index 980df58..201af5f 100644
--- a/src/renderers/ascii_default/mod.rs
+++ b/src/renderers/ascii_default/mod.rs
@@ -10,6 +10,7 @@ use crate::renderers::ascii_default::styles::plain::Style;
 
 use super::Renderer as RendererTrait;
 use crate::annotation::AnnotationType;
+use crate::display_list::line::DisplayContentElement;
 use crate::display_list::line::DisplayLine;
 use crate::display_list::line::DisplayMark;
 use crate::display_list::line::DisplayMarkType;
@@ -120,7 +121,22 @@ impl Renderer {
         line: &DisplaySourceLine,
     ) -> std::io::Result<()> {
         match line {
-            DisplaySourceLine::Content { text } => write!(w, " {}", text),
+            DisplaySourceLine::Content(frag) => {
+                write!(w, " ")?;
+                for elem in frag {
+                    match elem {
+                        DisplayContentElement::Text(t) => write!(w, "{}", t)?,
+                        DisplayContentElement::AnnotatedText {
+                            text,
+                            annotation_type,
+                        } => {
+                            let (_, style) = self.get_annotation_type_style(&annotation_type);
+                            S::fmt(w, text, &[StyleType::Emphasis, style])?
+                        }
+                    }
+                }
+                Ok(())
+            }
             DisplaySourceLine::Annotation { annotation, range } => {
                 let (_, style) = self.get_annotation_type_style(&annotation.annotation_type);
                 let styles = [StyleType::Emphasis, style];
diff --git a/src/slice.rs b/src/slice.rs
index 23a58d0..88f270e 100644
--- a/src/slice.rs
+++ b/src/slice.rs
@@ -1,4 +1,4 @@
-use crate::annotation::SourceAnnotation;
+use crate::annotation::{InlineAnnotation, SourceAnnotation};
 
 #[derive(Debug, Clone, Default)]
 pub struct Slice<'s> {
@@ -6,4 +6,5 @@ pub struct Slice<'s> {
     pub line_start: Option,
     pub origin: Option<&'s str>,
     pub annotations: &'s [SourceAnnotation<'s>],
+    pub inline_annotations: &'s [InlineAnnotation],
 }