diff --git a/Cargo.lock b/Cargo.lock
index abe375c..869c7fb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -73,6 +73,12 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
[[package]]
name = "atomic"
version = "0.6.1"
@@ -1170,15 +1176,32 @@ dependencies = [
]
[[package]]
-name = "pretty-print"
-version = "0.1.9"
+name = "pretty"
+version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5ad516586f2191e7ce412b9b164e61ee6403638aa70e98672b978c1f448e63f"
+checksum = "0d22152487193190344590e4f30e219cf3fe140d9e7a3fdb683d82aa2c5f4156"
+dependencies = [
+ "arrayvec",
+ "typed-arena",
+ "unicode-width",
+]
+
+[[package]]
+name = "pretty-print"
+version = "0.1.8"
dependencies = [
"color-ansi",
"unicode-segmentation",
]
+[[package]]
+name = "pretty-test"
+version = "0.0.0"
+dependencies = [
+ "pretty",
+ "pretty-print",
+]
+
[[package]]
name = "prettyplease"
version = "0.2.37"
@@ -1898,6 +1921,12 @@ dependencies = [
"ratatui-widgets",
]
+[[package]]
+name = "typed-arena"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
+
[[package]]
name = "typenum"
version = "1.19.0"
diff --git a/Cargo.toml b/Cargo.toml
index fe5329e..fbf57f7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,8 +8,8 @@ name = "lv"
path = "./src/main.rs"
[workspace]
-members = [".", "./logparse/"]
-default-members = [".", "./logparse/"]
+members = [".", "./logparse/", "./pretty-print/projects/*"]
+default-members = [".", "./logparse/", "./pretty-print/projects/pretty-print"]
[dependencies]
clap = {version="4.5", features=["derive", "string"]}
@@ -26,4 +26,4 @@ regex = "1"
crossterm = "*"
dumpster = "2.1"
logparse = {path = "./logparse/", version="0.2.0"}
-pretty-print = "0.1"
+pretty-print = {path = "./pretty-print/projects/pretty-print"}
diff --git a/pretty-print/.editorconfig b/pretty-print/.editorconfig
new file mode 100644
index 0000000..d8612ca
--- /dev/null
+++ b/pretty-print/.editorconfig
@@ -0,0 +1,11 @@
+[*]
+charset = utf-8
+
+
+[*.pest]
+indent_style = space
+indent_size = 4
+
+[*.toml]
+indent_style = space
+indent_size = 4
\ No newline at end of file
diff --git a/pretty-print/.github/workflows/rust.yml b/pretty-print/.github/workflows/rust.yml
new file mode 100644
index 0000000..568af06
--- /dev/null
+++ b/pretty-print/.github/workflows/rust.yml
@@ -0,0 +1,27 @@
+name: Rust
+
+on:
+ push:
+ branches: [ master, dev ]
+ pull_request:
+ branches: [ master, dev ]
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ steps:
+ - run: git config --global core.autocrlf false
+ - uses: actions/checkout@v2
+ - name: Rust Nightly
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: nightly
+ override: true
+ components: rustfmt, clippy
+ - name: Build
+ run: cargo build --release
+# - name: Tests
+# run: cargo test --release
diff --git a/pretty-print/.gitignore b/pretty-print/.gitignore
new file mode 100644
index 0000000..080460e
--- /dev/null
+++ b/pretty-print/.gitignore
@@ -0,0 +1,14 @@
+# OS
+.DS_Store/
+thumbs.db
+time-travel.*
+
+# IDE
+.vscode/
+.vs/
+.idea/
+*.iml
+
+# Rust
+target/
+Cargo.lock
diff --git a/pretty-print/.run/Test All.run.xml b/pretty-print/.run/Test All.run.xml
new file mode 100644
index 0000000..facdcbf
--- /dev/null
+++ b/pretty-print/.run/Test All.run.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pretty-print/License.md b/pretty-print/License.md
new file mode 100644
index 0000000..0afe08d
--- /dev/null
+++ b/pretty-print/License.md
@@ -0,0 +1,356 @@
+Mozilla Public License Version 2.0
+==================================
+
+### 1. Definitions
+
+**1.1. “Contributor”**
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+**1.2. “Contributor Version”**
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+**1.3. “Contribution”**
+ means Covered Software of a particular Contributor.
+
+**1.4. “Covered Software”**
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+**1.5. “Incompatible With Secondary Licenses”**
+ means
+
+* **(a)** that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+* **(b)** that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+**1.6. “Executable Form”**
+ means any form of the work other than Source Code Form.
+
+**1.7. “Larger Work”**
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+**1.8. “License”**
+ means this document.
+
+**1.9. “Licensable”**
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+**1.10. “Modifications”**
+ means any of the following:
+
+* **(a)** any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+* **(b)** any new file in Source Code Form that contains any Covered
+ Software.
+
+**1.11. “Patent Claims” of a Contributor**
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+**1.12. “Secondary License”**
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+**1.13. “Source Code Form”**
+ means the form of the work preferred for making modifications.
+
+**1.14. “You” (or “Your”)**
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, “You” includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, “control” means **(a)** the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or **(b)** ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+
+### 2. License Grants and Conditions
+
+#### 2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+* **(a)** under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+* **(b)** under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+#### 2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+#### 2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+* **(a)** for any code that a Contributor has removed from Covered Software;
+ or
+* **(b)** for infringements caused by: **(i)** Your and any other third party's
+ modifications of Covered Software, or **(ii)** the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+* **(c)** under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+#### 2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+#### 2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+#### 2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+#### 2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+
+### 3. Responsibilities
+
+#### 3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+#### 3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+* **(a)** such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+* **(b)** You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+#### 3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+#### 3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+#### 3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+
+### 4. Inability to Comply Due to Statute or Regulation
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: **(a)** comply with
+the terms of this License to the maximum extent possible; and **(b)**
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+
+### 5. Termination
+
+**5.1.** The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated **(a)** provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and **(b)** on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+**5.2.** If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+**5.3.** In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+
+### 6. Disclaimer of Warranty
+
+> Covered Software is provided under this License on an “as is”
+> basis, without warranty of any kind, either expressed, implied, or
+> statutory, including, without limitation, warranties that the
+> Covered Software is free of defects, merchantable, fit for a
+> particular purpose or non-infringing. The entire risk as to the
+> quality and performance of the Covered Software is with You.
+> Should any Covered Software prove defective in any respect, You
+> (not any Contributor) assume the cost of any necessary servicing,
+> repair, or correction. This disclaimer of warranty constitutes an
+> essential part of this License. No use of any Covered Software is
+> authorized under this License except under this disclaimer.
+
+### 7. Limitation of Liability
+
+> Under no circumstances and under no legal theory, whether tort
+> (including negligence), contract, or otherwise, shall any
+> Contributor, or anyone who distributes Covered Software as
+> permitted above, be liable to You for any direct, indirect,
+> special, incidental, or consequential damages of any character
+> including, without limitation, damages for lost profits, loss of
+> goodwill, work stoppage, computer failure or malfunction, or any
+> and all other commercial damages or losses, even if such party
+> shall have been informed of the possibility of such damages. This
+> limitation of liability shall not apply to liability for death or
+> personal injury resulting from such party's negligence to the
+> extent applicable law prohibits such limitation. Some
+> jurisdictions do not allow the exclusion or limitation of
+> incidental or consequential damages, so this exclusion and
+> limitation may not apply to You.
+
+
+### 8. Litigation
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+
+### 9. Miscellaneous
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+
+### 10. Versions of the License
+
+#### 10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+#### 10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+#### 10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+#### 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+## Exhibit A - Source Code Form License Notice
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+## Exhibit B - “Incompatible With Secondary Licenses” Notice
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
+
diff --git a/pretty-print/Readme.md b/pretty-print/Readme.md
new file mode 100644
index 0000000..ed8ede1
--- /dev/null
+++ b/pretty-print/Readme.md
@@ -0,0 +1,68 @@
+# Pretty Printer
+
+This crate defines a
+[Wadler-style](http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf)
+pretty-printing API.
+
+Start with the static functions of [PrettyTree](enum.Doc.html).
+
+## Quick start
+
+Let's pretty-print simple sexps! We want to pretty print sexps like
+
+```lisp
+(1 2 3)
+```
+or, if the line would be too long, like
+
+```lisp
+((1)
+ (2 3)
+ (4 5 6))
+```
+
+A _simple symbolic expression_ consists of a numeric _atom_ or a nested ordered _list_ of
+symbolic expression children.
+
+
+
+```rust
+use pretty_print::*;
+use SExp::*;
+enum SExp {
+ Atom(u32),
+ List(Vec),
+}
+
+impl SExp {
+ /// Return a pretty printed format of self.
+ pub fn to_doc(&self) -> PrettyTree {
+ match self {
+ Atom(x) => PrettyTree::text(x.to_string()),
+ List(xs) => PrettyTree::text("(")
+ .append(PrettyTree::join(xs.into_iter().map(|x| x.to_doc()), PrettyTree::line_or_space()).nest(1).group())
+ .append(PrettyTree::text(")")),
+ }
+ }
+ /// Return a pretty printed format of self.
+ pub fn to_pretty(&self, width: usize) -> String {
+ let mut w = Vec::new();
+ self.to_doc().render(width, &mut w).unwrap();
+ String::from_utf8(w).unwrap()
+ }
+}
+
+fn main() {
+ let atom = Atom(5);
+ assert_eq!("5", atom.to_pretty(10));
+ let list = List(vec![Atom(1), Atom(2), Atom(3)]);
+ assert_eq!("(1 2 3)", list.to_pretty(10));
+ assert_eq!(
+ "\
+(1
+ 2
+ 3)",
+ list.to_pretty(5)
+ );
+}
+```
\ No newline at end of file
diff --git a/pretty-print/package.json b/pretty-print/package.json
new file mode 100644
index 0000000..3dad9e7
--- /dev/null
+++ b/pretty-print/package.json
@@ -0,0 +1,11 @@
+{
+ "private": true,
+ "scripts": {
+ "doc": "cargo doc --no-deps --all-features",
+ "test": "cargo test --release",
+ "fmt": "cargo fmt --all",
+ "p": "git push && git push --tags --prune",
+ "reset": "git reset Head~ --soft",
+ "u": "cargo upgrade --workspace"
+ }
+}
diff --git a/pretty-print/projects/pretty-print/Cargo.toml b/pretty-print/projects/pretty-print/Cargo.toml
new file mode 100644
index 0000000..3cfa4ca
--- /dev/null
+++ b/pretty-print/projects/pretty-print/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "pretty-print"
+version = "0.1.8"
+authors = ["Aster <192607617@qq.com>"]
+description = "pretty print tree"
+repository = "https://github.com/oovm/pretty-print"
+documentation = "https://docs.rs/pretty-print"
+readme = "Readme.md"
+license = "MPL-2.0"
+edition = "2021"
+
+[dependencies]
+unicode-segmentation = "1.10.1"
+
+[dependencies.color-ansi]
+version = "0.1.0"
+#default-features = false
+#path = 'C:\Users\Dell\CLionProjects\color-rs\projects\color-ansi'
+
+[dev-dependencies]
+
+
+[features]
+default = ["std"]
+std = []
\ No newline at end of file
diff --git a/pretty-print/projects/pretty-print/package.json b/pretty-print/projects/pretty-print/package.json
new file mode 100644
index 0000000..381d4be
--- /dev/null
+++ b/pretty-print/projects/pretty-print/package.json
@@ -0,0 +1,6 @@
+{
+ "private": true,
+ "scripts": {
+ "p": "cargo publish --allow-dirty"
+ }
+}
diff --git a/pretty-print/projects/pretty-print/readme.md b/pretty-print/projects/pretty-print/readme.md
new file mode 100644
index 0000000..271b962
--- /dev/null
+++ b/pretty-print/projects/pretty-print/readme.md
@@ -0,0 +1,24 @@
+This crate defines a
+[Wadler-style](http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf)
+pretty-printing API.
+
+Start with the static functions of [Doc](enum.Doc.html).
+
+## Quick start
+
+Let's pretty-print simple sexps! We want to pretty print sexps like
+
+```lisp
+(1 2 3)
+```
+or, if the line would be too long, like
+
+```lisp
+((1)
+ (2 3)
+ (4 5 6))
+```
+
+A _simple symbolic expression_ consists of a numeric _atom_ or a nested ordered _list_ of
+symbolic expression children.
+
diff --git a/pretty-print/projects/pretty-print/src/helpers/affixes.rs b/pretty-print/projects/pretty-print/src/helpers/affixes.rs
new file mode 100644
index 0000000..de618b4
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/helpers/affixes.rs
@@ -0,0 +1,193 @@
+//! Document formatting of "helpers" such as where some number of prefixes and suffixes would
+//! ideally be layed out onto a single line instead of breaking them up into multiple lines. See
+//! `BlockDoc` for an example
+
+use crate::{docs, DocumentTree, DocumentTree};
+
+pub struct Affixes<'doc, D, A>
+{
+ prefix: DocumentTree<'doc, D, A>,
+ suffix: DocumentTree<'doc, D, A>,
+ nest: bool,
+}
+
+impl<'a, D> Clone for Affixes<'a, D>
+{
+ fn clone(&self) -> Self {
+ Affixes {
+ prefix: self.prefix.clone(),
+ suffix: self.suffix.clone(),
+ nest: self.nest,
+ }
+ }
+}
+
+impl<'doc, D, A> Affixes<'doc, D, A>
+{
+ pub fn new(prefix: DocumentTree<'doc, D, A>, suffix: DocumentTree<'doc, D, A>) -> Self {
+ Affixes {
+ prefix,
+ suffix,
+ nest: false,
+ }
+ }
+
+ pub fn nest(mut self) -> Self {
+ self.nest = true;
+ self
+ }
+}
+
+/// Formats a set of `prefix` and `suffix` documents around a `body`
+///
+/// The following document split into the prefixes [\x y ->, \z ->, {], suffixes [nil, nil, }] and
+/// body [result: x + y - z] will try to be formatted
+///
+/// ```gluon
+/// \x y -> \z -> { result: x + y - z }
+/// ```
+///
+/// ```gluon
+/// \x y -> \z -> {
+/// result: x + y - z
+/// }
+/// ```
+///
+/// ```gluon
+/// \x y -> \z ->
+/// {
+/// result: x + y - z
+/// }
+/// ```
+///
+/// ```gluon
+/// \x y ->
+/// \z ->
+/// {
+/// result: x + y - z
+/// }
+/// ```
+pub struct BlockDoc<'doc, D, A>
+ where
+ D: DocAllocator<'doc, A>,
+{
+ pub affixes: Vec>,
+ pub body: DocumentTree<'doc, D, A>,
+}
+
+impl<'doc, D, A> BlockDoc<'doc, D, A>
+ where
+ D: DocAllocator<'doc, A>,
+ D::Doc: Clone,
+ A: Clone,
+{
+ pub fn format(self, nest: isize) -> DocumentTree<'doc, D, A> {
+ let arena = self.body.0;
+
+ let fail_on_multi_line = arena.fail().flat_alt(arena.nil());
+
+ (1..self.affixes.len() + 1)
+ .rev()
+ .map(|split| {
+ let (before, after) = self.affixes.split_at(split);
+ let last = before.len() == 1;
+ docs![
+ arena,
+ docs![
+ arena,
+ arena.concat(before.iter().map(|affixes| affixes.prefix.clone())),
+ if last {
+ arena.nil()
+ } else {
+ fail_on_multi_line.clone()
+ }
+ ]
+ .group(),
+ docs![
+ arena,
+ after.iter().rev().cloned().fold(
+ docs![
+ arena,
+ self.body.clone(),
+ // If there is no prefix then we must not allow the body to laid out on multiple
+ // lines without nesting
+ if !last
+ && before
+ .iter()
+ .all(|affixes| matches!(&*affixes.prefix.1, DocumentTree::Nil))
+ {
+ fail_on_multi_line.clone()
+ } else {
+ arena.nil()
+ },
+ ]
+ .nest(nest)
+ .append(
+ arena.concat(after.iter().map(|affixes| affixes.suffix.clone()))
+ ),
+ |acc, affixes| {
+ let mut doc = affixes.prefix.append(acc);
+ if affixes.nest {
+ doc = doc.nest(nest);
+ }
+ doc.group()
+ },
+ ),
+ arena.concat(before.iter().map(|affixes| affixes.suffix.clone())),
+ ]
+ .group(),
+ ]
+ })
+ .fold(None::>, |acc, doc| {
+ Some(match acc {
+ None => doc,
+ Some(acc) => acc.union(doc),
+ })
+ })
+ .unwrap_or(self.body)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::Arena;
+
+ #[test]
+ fn format_block() {
+ let arena = &Arena::<()>::new();
+ let mk_doc = || BlockDoc {
+ affixes: vec![
+ Affixes::new(docs![arena, "\\x y ->"], arena.nil()).nest(),
+ Affixes::new(docs![arena, arena.line(), "\\z ->"], arena.nil()).nest(),
+ Affixes::new(
+ docs![arena, arena.line(), "{"],
+ docs![arena, arena.line(), "}"],
+ )
+ .nest(),
+ ],
+ body: docs![arena, arena.line(), "result"],
+ };
+ expect_test::expect![[r#"\x y -> \z -> { result }"#]]
+ .assert_eq(&mk_doc().format(4).1.pretty(40).to_string());
+ expect_test::expect![[r#"
+\x y -> \z -> {
+ result
+}"#]]
+ .assert_eq(&mk_doc().format(4).1.pretty(15).to_string());
+ expect_test::expect![[r#"
+\x y -> \z ->
+ {
+ result
+ }"#]]
+ .assert_eq(&mk_doc().format(4).1.pretty(14).to_string());
+ expect_test::expect![[r#"
+\x y ->
+ \z ->
+ {
+ result
+ }"#]]
+ .assert_eq(&mk_doc().format(4).1.pretty(12).to_string());
+ }
+}
diff --git a/pretty-print/projects/pretty-print/src/helpers/hard_block.rs b/pretty-print/projects/pretty-print/src/helpers/hard_block.rs
new file mode 100644
index 0000000..a005f98
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/helpers/hard_block.rs
@@ -0,0 +1,66 @@
+use super::*;
+
+/// A soft block is a block that is not required to be on a new line.
+///
+/// ```vk
+/// {a, b, c}
+///
+/// {
+/// a,
+/// b,
+/// }
+/// ```
+#[derive(Clone, Debug)]
+pub struct HardBlock {
+ /// The indentation of the soft block
+ pub indent: usize,
+ /// The left hand side of the soft block
+ pub lhs: &'static str,
+ /// The right hand side of the soft block
+ pub rhs: &'static str,
+ /// The joint node of the soft block
+ pub joint: PrettyTree,
+}
+
+impl HardBlock {
+ /// Build a new soft block
+ pub fn new(lhs: &'static str, rhs: &'static str) -> Self {
+ Self { lhs, rhs, indent: 4, joint: PrettyTree::line_or_space() }
+ }
+ /// Build a new soft block with the parentheses syntax
+ pub fn parentheses() -> Self {
+ Self::new("(", ")")
+ }
+ /// Build a new soft block with the brackets syntax
+ pub fn brackets() -> Self {
+ Self::new("[", "]")
+ }
+ /// Build a new soft block with the curly braces syntax
+ pub fn curly_braces() -> Self {
+ Self::new("{", "}")
+ }
+ /// Set the joint node of the soft block
+ pub fn with_joint(self, joint: PrettyTree) -> Self {
+ Self { joint, ..self }
+ }
+}
+
+impl HardBlock {
+ /// Join a slice of pretty printables with the soft block
+ pub fn join_slice(&self, slice: &[T], theme: &PrettyProvider) -> PrettyTree {
+ let mut outer = PrettySequence::new(5);
+ outer += self.lhs;
+ outer += PrettyTree::Hardline;
+ let mut inner = PrettySequence::new(slice.len() * 2);
+ for (idx, term) in slice.iter().enumerate() {
+ if idx != 0 {
+ inner += self.joint.clone();
+ }
+ inner += term.pretty(theme);
+ }
+ outer += inner.indent(self.indent);
+ outer += PrettyTree::Hardline;
+ outer += self.rhs;
+ outer.into()
+ }
+}
diff --git a/pretty-print/projects/pretty-print/src/helpers/k_and_r_bracket.rs b/pretty-print/projects/pretty-print/src/helpers/k_and_r_bracket.rs
new file mode 100644
index 0000000..8bf51fa
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/helpers/k_and_r_bracket.rs
@@ -0,0 +1,61 @@
+use super::*;
+use crate::PrettyBuilder;
+
+/// `K & R` style brackets
+///
+/// ```vk
+/// a {}
+/// ```
+///
+/// ```vk
+/// a {
+///
+/// }
+/// ```
+#[derive(Copy, Clone, Debug)]
+pub struct KAndRBracket {
+ /// Whether to add a space after the keyword
+ pub head_space: bool,
+ /// The left bracket
+ pub bracket_l: &'static str,
+ /// The right bracket
+ pub bracket_r: &'static str,
+}
+
+impl KAndRBracket {
+ /// Build a bracketed block
+ pub fn curly_braces() -> Self {
+ Self { head_space: true, bracket_l: "{", bracket_r: "}" }
+ }
+ /// Build a bracketed block
+ pub fn build<'a, I>(
+ &self,
+ items: &[I],
+ allocator: &'a PrettyProvider,
+ inline_join: PrettyTree,
+ block_join: PrettyTree,
+ ) -> PrettyTree
+ where
+ I: PrettyPrint,
+ {
+ let mut output = PrettySequence::new(5);
+ if self.head_space {
+ output.push(" ");
+ }
+ output.push(self.bracket_l);
+ // inline
+ let mut inline = PrettySequence::new(3);
+ inline.push(" ");
+ inline.push(allocator.join_slice(items, inline_join));
+ inline.push(" ");
+ // block
+ let mut block = PrettySequence::new(3);
+ block.push(PrettyTree::Hardline);
+ block.push(allocator.join_slice(items, block_join).indent(4));
+ block.push(PrettyTree::Hardline);
+ //
+ output.push(block.flat_alt(inline));
+ output.push(self.bracket_r);
+ output.into()
+ }
+}
diff --git a/pretty-print/projects/pretty-print/src/helpers/mod.rs b/pretty-print/projects/pretty-print/src/helpers/mod.rs
new file mode 100644
index 0000000..bc9c6fe
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/helpers/mod.rs
@@ -0,0 +1,13 @@
+#![doc = include_str!("readme.md")]
+
+use crate::{PrettyBuilder, PrettyPrint, PrettyProvider, PrettyTree};
+use alloc::vec::Vec;
+use core::ops::AddAssign;
+
+mod hard_block;
+mod k_and_r_bracket;
+mod sequence;
+mod soft_block;
+// mod affixes;
+
+pub use self::{hard_block::HardBlock, k_and_r_bracket::KAndRBracket, sequence::PrettySequence, soft_block::SoftBlock};
diff --git a/pretty-print/projects/pretty-print/src/helpers/readme.md b/pretty-print/projects/pretty-print/src/helpers/readme.md
new file mode 100644
index 0000000..e69de29
diff --git a/pretty-print/projects/pretty-print/src/helpers/sequence.rs b/pretty-print/projects/pretty-print/src/helpers/sequence.rs
new file mode 100644
index 0000000..246eae0
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/helpers/sequence.rs
@@ -0,0 +1,60 @@
+use super::*;
+
+/// The document sequence type.
+#[derive(Clone, Debug, Default)]
+pub struct PrettySequence {
+ items: Vec,
+}
+
+impl PrettySequence {
+ /// Create a new sequence with the given capacity.
+ pub fn new(capacity: usize) -> Self {
+ Self { items: Vec::with_capacity(capacity) }
+ }
+ /// Create a new sequence with the given capacity.
+ pub fn push(&mut self, item: T)
+ where
+ T: Into,
+ {
+ self.items.push(item.into());
+ }
+ /// Create a new sequence with the given capacity.
+ pub fn extend(&mut self, items: I)
+ where
+ I: IntoIterator- ,
+ T: Into,
+ {
+ self.items.extend(items.into_iter().map(|x| x.into()));
+ }
+}
+
+impl PrettyBuilder for PrettySequence {
+ fn flat_alt(self, flat: E) -> PrettyTree
+ where
+ E: Into,
+ {
+ PrettyTree::from(self).flat_alt(flat)
+ }
+ fn indent(self, indent: usize) -> PrettyTree {
+ PrettyTree::from(self).indent(indent)
+ }
+
+ fn nest(self, offset: isize) -> PrettyTree {
+ PrettyTree::from(self).nest(offset)
+ }
+}
+
+impl AddAssign for PrettySequence
+where
+ T: Into,
+{
+ fn add_assign(&mut self, rhs: T) {
+ self.push(rhs);
+ }
+}
+
+impl From for PrettyTree {
+ fn from(value: PrettySequence) -> Self {
+ Self::concat(value.items)
+ }
+}
diff --git a/pretty-print/projects/pretty-print/src/helpers/soft_block.rs b/pretty-print/projects/pretty-print/src/helpers/soft_block.rs
new file mode 100644
index 0000000..d53f716
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/helpers/soft_block.rs
@@ -0,0 +1,72 @@
+use super::*;
+
+/// A soft block is a block that is not required to be on a new line.
+///
+/// ```vk
+/// {a, b, c}
+///
+/// {
+/// a,
+/// b,
+/// }
+/// ```
+#[derive(Clone, Debug)]
+pub struct SoftBlock {
+ /// The indentation of the soft block
+ pub indent: usize,
+ /// The left hand side of the soft block
+ pub lhs: &'static str,
+ /// The right hand side of the soft block
+ pub rhs: &'static str,
+ /// The joint node of the soft block
+ pub joint: PrettyTree,
+ /// The tail node of the soft block
+ pub tail: PrettyTree,
+}
+
+impl SoftBlock {
+ /// Build a new soft block
+ pub fn new(lhs: &'static str, rhs: &'static str) -> Self {
+ Self { lhs, rhs, indent: 4, joint: PrettyTree::line_or_space(), tail: PrettyTree::Nil }
+ }
+ /// Build a new soft block with the tuple syntax
+ pub fn tuple() -> Self {
+ Self::new("(", ")")
+ }
+ /// Build a new soft block with the parentheses syntax
+ pub fn parentheses() -> Self {
+ Self::new("(", ")")
+ }
+ /// Build a new soft block with the brackets syntax
+ pub fn brackets() -> Self {
+ Self::new("[", "]")
+ }
+ /// Build a new soft block with the curly braces syntax
+ pub fn curly_braces() -> Self {
+ Self::new("{", "}")
+ }
+ /// Set the joint node of the soft block
+ pub fn with_joint(self, joint: PrettyTree) -> Self {
+ Self { joint, ..self }
+ }
+}
+
+impl SoftBlock {
+ /// Join a slice of pretty printables with the soft block
+ pub fn join_slice(&self, slice: &[T], theme: &PrettyProvider) -> PrettyTree {
+ let mut outer = PrettySequence::new(5);
+ outer += self.lhs;
+ outer += PrettyTree::line_or_space();
+ let mut inner = PrettySequence::new(slice.len() * 2);
+ for (idx, term) in slice.iter().enumerate() {
+ if idx != 0 {
+ inner += self.joint.clone();
+ }
+ inner += term.pretty(theme);
+ }
+ outer += inner.indent(self.indent);
+ outer += PrettyTree::line_or_space();
+ outer += self.rhs;
+ outer.into()
+ }
+}
diff --git a/pretty-print/projects/pretty-print/src/lib.rs b/pretty-print/projects/pretty-print/src/lib.rs
new file mode 100644
index 0000000..616dc58
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/lib.rs
@@ -0,0 +1,51 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+#![deny(missing_debug_implementations, missing_copy_implementations)]
+#![warn(missing_docs, rustdoc::missing_crate_level_docs)]
+#![doc = include_str!("../readme.md")]
+#![doc(html_logo_url = "https://raw.githubusercontent.com/oovm/shape-rs/dev/projects/images/Trapezohedron.svg")]
+#![doc(html_favicon_url = "https://raw.githubusercontent.com/oovm/shape-rs/dev/projects/images/Trapezohedron.svg")]
+
+extern crate alloc;
+extern crate core;
+
+pub mod helpers;
+mod providers;
+mod render;
+mod traits;
+mod tree;
+
+pub use self::render::{
+ write_fmt::{BufferWrite, FmtWrite},
+ PrettyFormatter, Render, RenderAnnotated,
+};
+#[cfg(feature = "std")]
+pub use crate::render::write_io::{IoWrite, TerminalWriter};
+pub use crate::{
+ providers::PrettyProvider,
+ traits::{printer::PrettyPrint, PrettyBuilder},
+ tree::PrettyTree,
+};
+pub use color_ansi::*;
+
+/// Concatenates a number of documents (or values that can be converted into a document via the
+/// `Pretty` trait, like `&str`)
+///
+/// ```
+/// use pretty_print::docs;
+/// let doc =
+/// docs!["let", arena.softline(), "x", arena.softline(), "=", arena.softline(), Some("123"),];
+/// assert_eq!(doc.1.pretty(80).to_string(), "let x = 123");
+/// ```
+#[macro_export]
+macro_rules! docs {
+ ($alloc: expr, $first: expr $(,)?) => {
+ $crate::Pretty::pretty($first, $alloc)
+ };
+ ($alloc: expr, $first: expr $(, $rest: expr)+ $(,)?) => {{
+ let mut doc = $crate::Pretty::pretty($first, $alloc);
+ $(
+ doc = doc.append($rest);
+ )*
+ doc
+ }}
+}
diff --git a/pretty-print/projects/pretty-print/src/providers/mod.rs b/pretty-print/projects/pretty-print/src/providers/mod.rs
new file mode 100644
index 0000000..91ea91e
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/providers/mod.rs
@@ -0,0 +1,236 @@
+use crate::{PrettyPrint, PrettyTree};
+use alloc::{borrow::Cow, rc::Rc};
+use color_ansi::AnsiStyle;
+use core::fmt::{Debug, Formatter};
+
+/// Represents a pretty-printable tree provider.
+pub struct PrettyProvider {
+ width: usize,
+ keyword: Rc,
+ string: Rc,
+ number: Rc,
+ macros: Rc,
+ argument: Rc,
+ argument_mut: Rc,
+ local: Rc,
+ local_mut: Rc,
+ operator: Rc,
+ structure: Rc,
+ variant: Rc,
+ interface: Rc,
+}
+
+impl Debug for PrettyProvider {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ f.debug_struct("PrettyProvider").finish()
+ }
+}
+
+impl PrettyProvider {
+ /// Creates a new pretty-printable tree provider.
+ pub fn new(width: usize) -> Self {
+ Self {
+ width,
+ keyword: AnsiStyle::rgb(197, 119, 207).into(),
+ string: AnsiStyle::rgb(152, 195, 121).into(),
+ number: AnsiStyle::rgb(206, 153, 100).into(),
+ macros: AnsiStyle::rgb(87, 182, 194).into(),
+ argument: AnsiStyle::rgb(239, 112, 117).into(),
+ argument_mut: AnsiStyle::rgb(239, 112, 117).with_underline().into(),
+ local: AnsiStyle::rgb(152, 195, 121).into(),
+ local_mut: AnsiStyle::rgb(152, 195, 121).with_underline().into(),
+ operator: AnsiStyle::rgb(90, 173, 238).into(),
+ structure: AnsiStyle::rgb(197, 119, 207).into(),
+ variant: AnsiStyle::rgb(239, 112, 117).into(),
+ interface: AnsiStyle::rgb(197, 119, 207).into(),
+ }
+ }
+}
+
+impl PrettyProvider {
+ /// Gets the width of the document.
+ pub fn get_width(&self) -> usize {
+ self.width
+ }
+ /// Sets the width of the document.
+ pub fn set_width(&mut self, width: usize) {
+ self.width = width;
+ }
+ /// Gets the width of the document.
+ pub fn text
(&self, text: S) -> PrettyTree
+ where
+ S: Into>,
+ {
+ PrettyTree::text(text)
+ }
+ /// Gets the width of the document.
+ pub fn custom(&self, text: S, style: Rc) -> PrettyTree
+ where
+ S: Into>,
+ {
+ PrettyTree::text(text).annotate(style)
+ }
+ /// Allocate a document containing the given text.
+ pub fn keyword(&self, text: S) -> PrettyTree
+ where
+ S: Into>,
+ {
+ PrettyTree::text(text).annotate(self.keyword.clone())
+ }
+ /// Allocate a document containing the given text.
+ pub fn identifier(&self, text: S) -> PrettyTree
+ where
+ S: Into>,
+ {
+ PrettyTree::text(text).annotate(self.operator.clone())
+ }
+ /// Allocate a document containing the given text.
+ pub fn generic(&self, text: S) -> PrettyTree
+ where
+ S: Into>,
+ {
+ PrettyTree::text(text).annotate(self.macros.clone())
+ }
+
+ /// Allocate a document containing the given text.
+ pub fn variable(&self, text: S, mutable: bool) -> PrettyTree
+ where
+ S: Into>,
+ {
+ if mutable {
+ PrettyTree::text(text).annotate(self.local_mut.clone())
+ }
+ else {
+ PrettyTree::text(text).annotate(self.local.clone())
+ }
+ }
+ /// Allocate a document containing the given text.
+ pub fn argument(&self, text: S, mutable: bool) -> PrettyTree
+ where
+ S: Into>,
+ {
+ if mutable {
+ PrettyTree::text(text).annotate(self.argument_mut.clone())
+ }
+ else {
+ PrettyTree::text(text).annotate(self.argument.clone())
+ }
+ }
+ /// Allocate a document containing the given text.
+ pub fn operator(&self, text: S) -> PrettyTree
+ where
+ S: Into>,
+ {
+ PrettyTree::text(text).annotate(self.operator.clone())
+ }
+ /// Allocate a document containing the given text.
+ pub fn string(&self, text: S) -> PrettyTree
+ where
+ S: Into>,
+ {
+ PrettyTree::text(text).annotate(self.string.clone())
+ }
+ /// Allocate a document containing the given text.
+ pub fn annotation(&self, text: S) -> PrettyTree
+ where
+ S: Into>,
+ {
+ PrettyTree::text(text).annotate(self.macros.clone())
+ }
+ /// Allocate a document containing the given text.
+ pub fn number(&self, text: S) -> PrettyTree
+ where
+ S: Into>,
+ {
+ PrettyTree::text(text).annotate(self.number.clone())
+ }
+ /// Allocate a document containing the given text.
+ pub fn structure(&self, text: S) -> PrettyTree
+ where
+ S: Into>,
+ {
+ PrettyTree::text(text).annotate(self.structure.clone())
+ }
+ /// Allocate a document containing the given text.
+ pub fn variant(&self, text: S) -> PrettyTree
+ where
+ S: Into>,
+ {
+ PrettyTree::text(text).annotate(self.variant.clone())
+ }
+
+ /// Allocate a document containing the given text.
+ pub fn interface(&self, text: S) -> PrettyTree
+ where
+ S: Into>,
+ {
+ PrettyTree::text(text).annotate(self.interface.clone())
+ }
+}
+
+impl PrettyProvider {
+ /// Allocate a document containing the given text.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use pretty_print::PrettyProvider;
+ /// let theme = PrettyProvider::new(80);
+ /// theme.join(vec!["a", "b", "c"], ", ");
+ /// ```
+ pub fn join(&self, iter: I, joint: T2) -> PrettyTree
+ where
+ I: IntoIterator- ,
+ T1: PrettyPrint,
+ T2: PrettyPrint,
+ {
+ PrettyTree::join(iter.into_iter().map(|x| x.pretty(self)), joint.pretty(self))
+ }
+ /// Allocate a document containing the given text.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use pretty_print::PrettyProvider;
+ /// let theme = PrettyProvider::new(80);
+ /// theme.join(&["a", "b", "c"], ", ");
+ /// ```
+ pub fn join_slice(&self, iter: &[I], joint: T) -> PrettyTree
+ where
+ I: PrettyPrint,
+ T: PrettyPrint,
+ {
+ PrettyTree::join(iter.iter().map(|s| s.pretty(self)), joint.pretty(self))
+ }
+ /// Allocate a document containing the given text.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use pretty_print::PrettyProvider;
+ /// let theme = PrettyProvider::new(80);
+ /// theme.concat(vec!["1", "2", "3"]);
+ /// ```
+ pub fn concat(&self, iter: I) -> PrettyTree
+ where
+ I: IntoIterator
- ,
+ T: PrettyPrint,
+ {
+ PrettyTree::concat(iter.into_iter().map(|x| x.pretty(self)))
+ }
+ /// Allocate a document containing the given text.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use pretty_print::PrettyProvider;
+ /// let theme = PrettyProvider::new(80);
+ /// theme.concat_slice(&["1", "2", "3"]);
+ /// ```
+ pub fn concat_slice(&self, iter: &[T]) -> PrettyTree
+ where
+ T: PrettyPrint,
+ {
+ PrettyTree::concat(iter.iter().map(|s| s.pretty(self)))
+ }
+}
diff --git a/pretty-print/projects/pretty-print/src/render/mod.rs b/pretty-print/projects/pretty-print/src/render/mod.rs
new file mode 100644
index 0000000..e92e0b2
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/render/mod.rs
@@ -0,0 +1,372 @@
+use crate::{BufferWrite, PrettyTree};
+use alloc::{rc::Rc, vec, vec::Vec};
+use color_ansi::AnsiStyle;
+use core::fmt::{Debug, Display, Formatter};
+
+#[cfg(feature = "std")]
+pub mod write_io;
+
+pub mod write_fmt;
+
+/// Trait representing the operations necessary to render a document
+pub trait Render {
+ /// The type of the output
+ type Error;
+
+ /// Write a string to the output
+ fn write_str(&mut self, s: &str) -> Result;
+
+ /// Write a character to the output
+ fn write_str_all(&mut self, mut s: &str) -> Result<(), Self::Error> {
+ while !s.is_empty() {
+ let count = self.write_str(s)?;
+ s = &s[count..];
+ }
+ Ok(())
+ }
+
+ /// Write a character to the output
+ fn fail_doc(&self) -> Self::Error;
+}
+
+/// The given text, which must not contain line breaks.
+#[derive(Debug)]
+pub struct PrettyFormatter<'a> {
+ tree: &'a PrettyTree,
+ width: usize,
+}
+
+impl<'a> Display for PrettyFormatter<'a> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ self.tree.render_fmt(self.width, f)
+ }
+}
+
+impl PrettyTree {
+ /// Returns a value which implements `std::fmt::Display`
+ ///
+ /// ```
+ /// use pretty::{BoxDoc, Doc};
+ /// let doc =
+ /// BoxDoc::<()>::group(BoxDoc::text("hello").append(Doc::line()).append(Doc::text("world")));
+ /// assert_eq!(format!("{}", doc.pretty(80)), "hello world");
+ /// ```
+ #[inline]
+ pub fn pretty(&self, width: usize) -> PrettyFormatter<'_> {
+ PrettyFormatter { tree: self, width }
+ }
+}
+
+/// Trait representing the operations necessary to write an annotated document.
+pub trait RenderAnnotated: Render {
+ /// Push an annotation onto the stack
+ fn push_annotation(&mut self, annotation: Rc) -> Result<(), Self::Error>;
+ /// Pop an annotation from the stack
+ fn pop_annotation(&mut self) -> Result<(), Self::Error>;
+}
+
+#[derive(Debug)]
+enum Annotation {
+ Push(Rc),
+ Pop,
+}
+
+macro_rules! make_spaces {
+ () => { "" };
+ ($s: tt $($t: tt)*) => { concat!(" ", make_spaces!($($t)*)) };
+}
+
+pub(crate) const SPACES: &str = make_spaces!(,,,,,,,,,,);
+
+fn append_docs2(ldoc: Rc, rdoc: Rc, mut consumer: impl FnMut(Rc)) -> Rc {
+ let d = append_docs(rdoc, &mut consumer);
+ consumer(d);
+ append_docs(ldoc, &mut consumer)
+}
+
+fn append_docs(mut doc: Rc, consumer: &mut impl FnMut(Rc)) -> Rc {
+ loop {
+ // Since appended documents often appear in sequence on the left side we
+ // gain a slight performance increase by batching these pushes (avoiding
+ // to push and directly pop `Append` documents)
+ match doc.as_ref() {
+ PrettyTree::Append { lhs, rhs } => {
+ let d = append_docs(rhs.clone(), consumer);
+ consumer(d);
+ doc = lhs.clone();
+ }
+ _ => return doc,
+ }
+ }
+}
+
+pub fn best(doc: Rc, width: usize, out: &mut W) -> Result<(), W::Error>
+where
+ W: RenderAnnotated,
+ W: ?Sized,
+{
+ Best {
+ pos: 0,
+ back_cmds: vec![RenderCommand { indent: 0, mode: Mode::Break, node: doc }],
+ front_cmds: vec![],
+ annotation_levels: vec![],
+ width,
+ }
+ .best(0, out)?;
+
+ Ok(())
+}
+
+#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
+enum Mode {
+ Break,
+ Flat,
+}
+
+struct RenderCommand {
+ indent: usize,
+ mode: Mode,
+ node: Rc,
+}
+
+fn write_newline(ind: usize, out: &mut W) -> Result<(), W::Error>
+where
+ W: ?Sized + Render,
+{
+ out.write_str_all("\n")?;
+ write_spaces(ind, out)
+}
+
+fn write_spaces(spaces: usize, out: &mut W) -> Result<(), W::Error>
+where
+ W: ?Sized + Render,
+{
+ let mut inserted = 0;
+ while inserted < spaces {
+ let insert = core::cmp::min(SPACES.len(), spaces - inserted);
+ inserted += out.write_str(&SPACES[..insert])?;
+ }
+
+ Ok(())
+}
+
+struct Best {
+ pos: usize,
+ back_cmds: Vec,
+ front_cmds: Vec>,
+ annotation_levels: Vec,
+ width: usize,
+}
+
+impl Best {
+ fn fitting(&mut self, next: Rc, mut pos: usize, ind: usize) -> bool {
+ let mut bidx = self.back_cmds.len();
+ self.front_cmds.clear(); // clear from previous calls from best
+ self.front_cmds.push(next);
+ let mut mode = Mode::Flat;
+
+ loop {
+ let mut doc = match self.front_cmds.pop() {
+ None => {
+ if bidx == 0 {
+ // All commands have been processed
+ return true;
+ } else {
+ bidx -= 1;
+ mode = Mode::Break;
+ self.back_cmds[bidx].node.clone()
+ }
+ }
+ Some(cmd) => cmd,
+ };
+
+ loop {
+ match doc.as_ref() {
+ PrettyTree::Nil => {}
+ PrettyTree::Append { lhs, rhs } => {
+ doc = append_docs2(lhs.clone(), rhs.clone(), |send| self.front_cmds.push(send));
+ continue;
+ }
+ // Newlines inside the group makes it not fit, but those outside lets it
+ // fit on the current line
+ PrettyTree::Hardline => return mode == Mode::Break,
+ PrettyTree::RenderLength { length: len, body: _ } => {
+ pos += len;
+ if pos > self.width {
+ return false;
+ }
+ }
+ PrettyTree::StaticText(str) => {
+ pos += str.len();
+ if pos > self.width {
+ return false;
+ }
+ }
+ PrettyTree::Text(ref str) => {
+ pos += str.len();
+ if pos > self.width {
+ return false;
+ }
+ }
+ PrettyTree::MaybeInline { block: flat, inline } => {
+ doc = match mode {
+ Mode::Break => flat.clone(),
+ Mode::Flat => inline.clone(),
+ };
+ continue;
+ }
+
+ PrettyTree::Column { invoke: function } => {
+ doc = Rc::new(function(pos));
+ continue;
+ }
+ PrettyTree::Nesting { invoke: function } => {
+ doc = Rc::new(function(ind));
+ continue;
+ }
+ PrettyTree::Nest { space: _, doc: next }
+ | PrettyTree::Group { items: next }
+ | PrettyTree::Annotated { style: _, body: next }
+ | PrettyTree::Union { lhs: _, rhs: next } => {
+ doc = next.clone();
+ continue;
+ }
+ PrettyTree::Fail => return false,
+ }
+ break;
+ }
+ }
+ }
+
+ fn best(&mut self, top: usize, out: &mut W) -> Result
+ where
+ W: RenderAnnotated,
+ W: ?Sized,
+ {
+ let mut fits = true;
+
+ while top < self.back_cmds.len() {
+ let mut cmd = self.back_cmds.pop().unwrap();
+ loop {
+ let RenderCommand { indent: ind, mode, node } = cmd;
+ match node.as_ref() {
+ PrettyTree::Nil => {}
+ PrettyTree::Append { lhs, rhs } => {
+ cmd.node = append_docs2(lhs.clone(), rhs.clone(), |send| {
+ self.back_cmds.push(RenderCommand { indent: ind, mode, node: send })
+ });
+ continue;
+ }
+ PrettyTree::MaybeInline { block, inline } => {
+ cmd.node = match mode {
+ Mode::Break => block.clone(),
+ Mode::Flat => inline.clone(),
+ };
+ continue;
+ }
+ PrettyTree::Group { items } => {
+ match mode {
+ Mode::Break if self.fitting(items.clone(), self.pos, ind) => {
+ cmd.mode = Mode::Flat;
+ }
+ _ => {}
+ }
+ cmd.node = items.clone();
+ continue;
+ }
+ PrettyTree::Nest { space, doc } => {
+ // Once https://doc.rust-lang.org/std/primitive.usize.html#method.saturating_add_signed is stable
+ // this can be replaced
+ let new_ind = if *space >= 0 {
+ ind.saturating_add(*space as usize)
+ } else {
+ ind.saturating_sub(space.unsigned_abs())
+ };
+ cmd = RenderCommand { indent: new_ind, mode, node: doc.clone() };
+ continue;
+ }
+ PrettyTree::Hardline => {
+ // The next document may have different indentation so we should use it if we can
+ match self.back_cmds.pop() {
+ Some(next) => {
+ write_newline(next.indent, out)?;
+ self.pos = next.indent;
+ cmd = next;
+ continue;
+ }
+ None => {
+ write_newline(ind, out)?;
+ self.pos = ind;
+ }
+ }
+ }
+ PrettyTree::RenderLength { length: len, body: doc } => match doc.as_ref() {
+ PrettyTree::Text(s) => {
+ out.write_str_all(s)?;
+ self.pos += len;
+ fits &= self.pos <= self.width;
+ }
+ PrettyTree::StaticText(s) => {
+ out.write_str_all(s)?;
+ self.pos += len;
+ fits &= self.pos <= self.width;
+ }
+ _ => unreachable!(),
+ },
+ PrettyTree::Text(ref s) => {
+ out.write_str_all(s)?;
+ self.pos += s.len();
+ fits &= self.pos <= self.width;
+ }
+ PrettyTree::StaticText(s) => {
+ out.write_str_all(s)?;
+ self.pos += s.len();
+ fits &= self.pos <= self.width;
+ }
+ PrettyTree::Annotated { style: color, body: doc } => {
+ out.push_annotation(color.clone())?;
+ self.annotation_levels.push(self.back_cmds.len());
+ cmd.node = doc.clone();
+ continue;
+ }
+ PrettyTree::Union { lhs: left, rhs: right } => {
+ let pos = self.pos;
+ let annotation_levels = self.annotation_levels.len();
+ let bcmds = self.back_cmds.len();
+
+ self.back_cmds.push(RenderCommand { indent: ind, mode, node: left.clone() });
+
+ let mut buffer = BufferWrite::new(0);
+
+ match self.best(bcmds, &mut buffer) {
+ Ok(true) => buffer.render(out)?,
+ Ok(false) | Err(_) => {
+ self.pos = pos;
+ self.back_cmds.truncate(bcmds);
+ self.annotation_levels.truncate(annotation_levels);
+ cmd.node = right.clone();
+ continue;
+ }
+ }
+ }
+ PrettyTree::Column { invoke: column } => {
+ cmd.node = Rc::new(column(self.pos));
+ continue;
+ }
+ PrettyTree::Nesting { invoke: nesting } => {
+ cmd.node = Rc::new(nesting(self.pos));
+ continue;
+ }
+ PrettyTree::Fail => return Err(out.fail_doc()),
+ }
+
+ break;
+ }
+ while self.annotation_levels.last() == Some(&self.back_cmds.len()) {
+ self.annotation_levels.pop();
+ out.pop_annotation()?;
+ }
+ }
+ Ok(fits)
+ }
+}
diff --git a/pretty-print/projects/pretty-print/src/render/write_fmt.rs b/pretty-print/projects/pretty-print/src/render/write_fmt.rs
new file mode 100644
index 0000000..89868a7
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/render/write_fmt.rs
@@ -0,0 +1,121 @@
+use crate::{render::Annotation, Render, RenderAnnotated};
+use alloc::rc::Rc;
+use color_ansi::AnsiStyle;
+use core::fmt::{Debug, Formatter};
+
+/// Writes to something implementing `std::fmt::Write`
+pub struct FmtWrite {
+ upstream: W,
+}
+/// Represents a terminal writer.
+#[derive(Debug)]
+pub struct BufferWrite {
+ buffer: String,
+ annotations: Vec<(usize, Annotation)>,
+}
+
+impl BufferWrite {
+ /// Creates a new terminal writer.
+ pub fn new(capacity: usize) -> Self {
+ BufferWrite { buffer: String::with_capacity(capacity), annotations: Vec::new() }
+ }
+ /// Creates a new terminal writer.
+ pub fn render(&mut self, render: &mut W) -> Result<(), W::Error>
+ where
+ W: RenderAnnotated,
+ W: ?Sized,
+ {
+ let mut start = 0;
+ for (end, annotation) in &self.annotations {
+ let s = &self.buffer[start..*end];
+ if !s.is_empty() {
+ render.write_str_all(s)?;
+ }
+ start = *end;
+ match annotation {
+ Annotation::Push(a) => render.push_annotation(a.clone())?,
+ Annotation::Pop => render.pop_annotation()?,
+ }
+ }
+ let s = &self.buffer[start..];
+ if !s.is_empty() {
+ render.write_str_all(s)?;
+ }
+ Ok(())
+ }
+}
+
+impl Render for BufferWrite {
+ type Error = core::fmt::Error;
+
+ fn write_str(&mut self, s: &str) -> Result {
+ self.buffer.push_str(s);
+ Ok(s.len())
+ }
+
+ fn write_str_all(&mut self, s: &str) -> Result<(), Self::Error> {
+ self.buffer.push_str(s);
+ Ok(())
+ }
+
+ fn fail_doc(&self) -> Self::Error {
+ core::fmt::Error::default()
+ }
+}
+
+impl RenderAnnotated for FmtWrite
+where
+ W: core::fmt::Write,
+{
+ fn push_annotation(&mut self, _: Rc) -> Result<(), Self::Error> {
+ Ok(())
+ }
+
+ fn pop_annotation(&mut self) -> Result<(), Self::Error> {
+ Ok(())
+ }
+}
+
+impl RenderAnnotated for BufferWrite {
+ fn push_annotation(&mut self, annotation: Rc) -> Result<(), Self::Error> {
+ self.annotations.push((self.buffer.len(), Annotation::Push(annotation)));
+ Ok(())
+ }
+
+ fn pop_annotation(&mut self) -> Result<(), Self::Error> {
+ self.annotations.push((self.buffer.len(), Annotation::Pop));
+ Ok(())
+ }
+}
+
+impl Debug for FmtWrite {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ f.debug_struct("IoWrite").finish()
+ }
+}
+
+impl FmtWrite {
+ /// Create a new `FmtWrite` from something implementing `std::fmt::Write`
+ pub fn new(upstream: W) -> FmtWrite {
+ FmtWrite { upstream }
+ }
+}
+
+impl Render for FmtWrite
+where
+ W: core::fmt::Write,
+{
+ type Error = core::fmt::Error;
+
+ fn write_str(&mut self, s: &str) -> Result {
+ self.write_str_all(s).map(|_| s.len())
+ }
+
+ fn write_str_all(&mut self, s: &str) -> core::fmt::Result {
+ self.upstream.write_str(s)
+ }
+
+ fn fail_doc(&self) -> Self::Error {
+ core::fmt::Error
+ }
+}
diff --git a/pretty-print/projects/pretty-print/src/render/write_io.rs b/pretty-print/projects/pretty-print/src/render/write_io.rs
new file mode 100644
index 0000000..e9c25a4
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/render/write_io.rs
@@ -0,0 +1,114 @@
+use crate::{Render, RenderAnnotated};
+use alloc::rc::Rc;
+use color_ansi::{AnsiAbility, AnsiStyle, AnsiWriter};
+use core::fmt::{Debug, Formatter};
+use std::io::{Error, ErrorKind, Write};
+/// Represents a terminal writer.
+pub struct TerminalWriter {
+ color_stack: Vec>,
+ upstream: AnsiWriter,
+}
+
+/// Writes to something implementing `std::io::Write`
+pub struct IoWrite {
+ upstream: W,
+}
+
+impl Debug for IoWrite {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ f.debug_struct("IoWrite").finish()
+ }
+}
+
+impl IoWrite {
+ /// Creates a new terminal writer.
+ pub fn new(upstream: W) -> IoWrite {
+ IoWrite { upstream }
+ }
+}
+
+#[cfg(feature = "std")]
+impl Render for IoWrite
+ where
+ W: std::io::Write,
+{
+ type Error = std::io::Error;
+
+ fn write_str(&mut self, s: &str) -> std::io::Result {
+ self.upstream.write(s.as_bytes())
+ }
+
+ fn write_str_all(&mut self, s: &str) -> std::io::Result<()> {
+ self.upstream.write_all(s.as_bytes())
+ }
+
+ fn fail_doc(&self) -> Self::Error {
+ std::io::Error::new(std::io::ErrorKind::Other, "Document failed to render")
+ }
+}
+
+#[cfg(feature = "std")]
+impl RenderAnnotated for IoWrite
+ where
+ W: std::io::Write,
+{
+ fn push_annotation(&mut self, _: Rc) -> Result<(), Self::Error> {
+ Ok(())
+ }
+
+ fn pop_annotation(&mut self) -> Result<(), Self::Error> {
+ Ok(())
+ }
+}
+
+impl Debug for TerminalWriter {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ f.debug_struct("TerminalWriter").finish()
+ }
+}
+
+impl TerminalWriter {
+ /// Creates a new terminal writer.
+ pub fn new(upstream: W) -> Self {
+ TerminalWriter { color_stack: Vec::new(), upstream: AnsiWriter::new(upstream) }
+ }
+ /// Creates a new terminal writer with a specific color.
+ pub fn with_color(mut self, color: AnsiAbility) -> Self {
+ self.upstream.set_ability(color);
+ self
+ }
+}
+
+impl Render for TerminalWriter
+where
+ W: Write,
+{
+ type Error = Error;
+
+ fn write_str(&mut self, s: &str) -> std::io::Result {
+ self.upstream.write(s.as_bytes())
+ }
+
+ fn write_str_all(&mut self, s: &str) -> std::io::Result<()> {
+ self.upstream.write_all(s.as_bytes())
+ }
+
+ fn fail_doc(&self) -> Self::Error {
+ Error::new(ErrorKind::Other, "Document failed to render")
+ }
+}
+
+impl RenderAnnotated for TerminalWriter {
+ fn push_annotation(&mut self, color: Rc) -> Result<(), Self::Error> {
+ self.color_stack.push(color.clone());
+ self.upstream.set_style(&color)
+ }
+
+ fn pop_annotation(&mut self) -> Result<(), Self::Error> {
+ self.color_stack.pop();
+ match self.color_stack.last() {
+ Some(previous) => self.upstream.set_style(previous),
+ None => self.upstream.reset_style(),
+ }
+ }
+}
diff --git a/pretty-print/projects/pretty-print/src/traits/mod.rs b/pretty-print/projects/pretty-print/src/traits/mod.rs
new file mode 100644
index 0000000..2f458b1
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/traits/mod.rs
@@ -0,0 +1,34 @@
+use crate::{providers::PrettyProvider, PrettyTree};
+use alloc::string::String;
+
+pub mod printer;
+
+/// The `PrettyPrint` trait is implemented by types that can be pretty-printed.
+pub trait PrettyBuilder {
+ /// Acts as `self` when laid out on multiple lines and acts as `that` when laid out on a single line.
+ ///
+ /// ```
+ /// use pretty::{Arena, DocAllocator};
+ ///
+ /// let arena = Arena::<()>::new();
+ /// let body = arena.line().append("x");
+ /// let doc = arena
+ /// .text("let")
+ /// .append(arena.line())
+ /// .append("x")
+ /// .group()
+ /// .append(body.clone().flat_alt(arena.line().append("in").append(body)))
+ /// .group();
+ ///
+ /// assert_eq!(doc.1.pretty(100).to_string(), "let x in x");
+ /// assert_eq!(doc.1.pretty(8).to_string(), "let x\nx");
+ /// ```
+ fn flat_alt(self, inline: E) -> PrettyTree
+ where
+ E: Into;
+ /// Acts as `self` when laid out on a single line and acts as `that` when laid out on multiple lines.
+ fn indent(self, indent: usize) -> PrettyTree;
+
+ /// Increase the indentation level of this document.
+ fn nest(self, offset: isize) -> PrettyTree;
+}
diff --git a/pretty-print/projects/pretty-print/src/traits/printer.rs b/pretty-print/projects/pretty-print/src/traits/printer.rs
new file mode 100644
index 0000000..ce40a89
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/traits/printer.rs
@@ -0,0 +1,37 @@
+use super::*;
+
+/// Marker trait for types that can be pretty printed.
+pub trait PrettyPrint {
+ /// Build a pretty tree for this type.
+ fn pretty(&self, theme: &PrettyProvider) -> PrettyTree;
+ /// Get a pretty string for this type.
+ fn pretty_string(&self, theme: &PrettyProvider) -> String {
+ let mut buffer = String::new();
+ if let Err(e) = self.pretty(&theme).render_fmt(theme.get_width(), &mut buffer) {
+ panic!("Error: {}", e);
+ }
+ buffer
+ }
+ /// Print a pretty string for this type.
+ fn pretty_colorful(&self, theme: &PrettyProvider) -> String {
+ let mut buffer = vec![];
+ if let Err(e) = self.pretty(&theme).render_colored(theme.get_width(), &mut buffer) {
+ panic!("Error: {}", e);
+ }
+ match String::from_utf8(buffer) {
+ Ok(s) => s,
+ Err(e) => panic!("Error: {}", e),
+ }
+ }
+}
+
+impl PrettyPrint for PrettyTree {
+ fn pretty(&self, _: &PrettyProvider) -> PrettyTree {
+ self.clone()
+ }
+}
+impl PrettyPrint for &'static str {
+ fn pretty(&self, _: &PrettyProvider) -> PrettyTree {
+ PrettyTree::StaticText(*self)
+ }
+}
diff --git a/pretty-print/projects/pretty-print/src/tree/display.rs b/pretty-print/projects/pretty-print/src/tree/display.rs
new file mode 100644
index 0000000..44b2745
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/tree/display.rs
@@ -0,0 +1,89 @@
+use super::*;
+
+impl Default for PrettyTree {
+ fn default() -> Self {
+ Self::Nil
+ }
+}
+
+impl Clone for PrettyTree {
+ fn clone(&self) -> Self {
+ match self {
+ Self::Nil => Self::Nil,
+ Self::Hardline => Self::Hardline,
+ Self::Text(s) => Self::Text(s.clone()),
+ Self::StaticText(s) => Self::StaticText(*s),
+ Self::Annotated { style: color, body: doc } => Self::Annotated { style: color.clone(), body: doc.clone() },
+ Self::Append { lhs, rhs } => Self::Append { lhs: lhs.clone(), rhs: rhs.clone() },
+ Self::Group { items } => Self::Group { items: items.clone() },
+ Self::MaybeInline { block, inline } => Self::MaybeInline { block: block.clone(), inline: inline.clone() },
+ Self::Nest { space, doc } => Self::Nest { space: *space, doc: doc.clone() },
+ Self::RenderLength { length: len, body: doc } => Self::RenderLength { length: *len, body: doc.clone() },
+ Self::Union { lhs: left, rhs: right } => Self::Union { lhs: left.clone(), rhs: right.clone() },
+ Self::Column { invoke: column } => Self::Column { invoke: column.clone() },
+ Self::Nesting { invoke: nesting } => Self::Nesting { invoke: nesting.clone() },
+ Self::Fail => Self::Fail,
+ }
+ }
+}
+
+impl Debug for PrettyTree {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ let is_line = |doc: &PrettyTree| match doc {
+ PrettyTree::MaybeInline { block: flat, inline: alt } => {
+ matches!((&**flat, &**alt), (PrettyTree::Hardline, PrettyTree::StaticText(" ")))
+ }
+ _ => false,
+ };
+ let is_line_ = |doc: &PrettyTree| match doc {
+ PrettyTree::MaybeInline { block: flat, inline: alt } => {
+ matches!((&**flat, &**alt), (PrettyTree::Hardline, PrettyTree::Nil))
+ }
+ _ => false,
+ };
+ match self {
+ PrettyTree::Nil => f.debug_tuple("Nil").finish(),
+ PrettyTree::Append { lhs: _, rhs: _ } => {
+ let mut f = f.debug_list();
+ append_docs(self, &mut |doc| {
+ f.entry(doc);
+ });
+ f.finish()
+ }
+ _ if is_line(self) => f.debug_tuple("Line").finish(),
+ _ if is_line_(self) => f.debug_tuple("Line?").finish(),
+ PrettyTree::MaybeInline { block, inline } => f.debug_tuple("FlatAlt").field(block).field(inline).finish(),
+ PrettyTree::Group { items } => {
+ if is_line(self) {
+ return f.debug_tuple("SoftLine").finish();
+ }
+ if is_line_(self) {
+ return f.debug_tuple("SoftLine?").finish();
+ }
+ f.debug_tuple("Group").field(items).finish()
+ }
+ PrettyTree::Nest { space, doc } => f.debug_tuple("Nest").field(&space).field(doc).finish(),
+ PrettyTree::Hardline => f.debug_tuple("Hardline").finish(),
+ PrettyTree::RenderLength { body: doc, .. } => doc.fmt(f),
+ PrettyTree::Text(s) => Debug::fmt(s, f),
+ PrettyTree::StaticText(s) => Debug::fmt(s, f),
+ PrettyTree::Annotated { style: color, body: doc } => f.debug_tuple("Annotated").field(color).field(doc).finish(),
+ PrettyTree::Union { lhs: left, rhs: right } => f.debug_tuple("Union").field(left).field(right).finish(),
+ PrettyTree::Column { .. } => f.debug_tuple("Column(..)").finish(),
+ PrettyTree::Nesting { .. } => f.debug_tuple("Nesting(..)").finish(),
+ PrettyTree::Fail => f.debug_tuple("Fail").finish(),
+ }
+ }
+}
+
+fn append_docs(mut doc: &PrettyTree, consumer: &mut impl FnMut(&PrettyTree)) {
+ loop {
+ match doc {
+ PrettyTree::Append { lhs, rhs } => {
+ append_docs(lhs, consumer);
+ doc = rhs;
+ }
+ _ => break consumer(doc),
+ }
+ }
+}
diff --git a/pretty-print/projects/pretty-print/src/tree/into.rs b/pretty-print/projects/pretty-print/src/tree/into.rs
new file mode 100644
index 0000000..8afff13
--- /dev/null
+++ b/pretty-print/projects/pretty-print/src/tree/into.rs
@@ -0,0 +1,50 @@
+use super::*;
+
+impl Add for PrettyTree
+where
+ T: Into,
+{
+ type Output = Self;
+ fn add(self, rhs: T) -> Self::Output {
+ self.append(rhs.into())
+ }
+}
+
+impl AddAssign for PrettyTree
+where
+ T: Into,
+{
+ fn add_assign(&mut self, rhs: T) {
+ *self = self.clone().append(rhs.into());
+ }
+}
+
+impl From