mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Autocompletion for raw language tags
This commit is contained in:
parent
a9fdff244a
commit
8e5f446544
@ -182,6 +182,7 @@ fn items() -> LangItems {
|
|||||||
}
|
}
|
||||||
node.pack()
|
node.pack()
|
||||||
},
|
},
|
||||||
|
raw_languages: text::RawNode::languages,
|
||||||
link: |url| meta::LinkNode::from_url(url).pack(),
|
link: |url| meta::LinkNode::from_url(url).pack(),
|
||||||
ref_: |target| meta::RefNode::new(target).pack(),
|
ref_: |target| meta::RefNode::new(target).pack(),
|
||||||
heading: |level, title| meta::HeadingNode::new(title).with_level(level).pack(),
|
heading: |level, title| meta::HeadingNode::new(title).with_level(level).pack(),
|
||||||
|
@ -103,6 +103,23 @@ pub struct RawNode {
|
|||||||
pub lang: Option<EcoString>,
|
pub lang: Option<EcoString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RawNode {
|
||||||
|
/// The supported language names and tags.
|
||||||
|
pub fn languages() -> Vec<(&'static str, Vec<&'static str>)> {
|
||||||
|
SYNTAXES
|
||||||
|
.syntaxes()
|
||||||
|
.iter()
|
||||||
|
.map(|syntax| {
|
||||||
|
(
|
||||||
|
syntax.name.as_str(),
|
||||||
|
syntax.file_extensions.iter().map(|s| s.as_str()).collect(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.chain([("Typst", vec!["typ"]), ("Typst (code)", vec!["typc"])])
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Prepare for RawNode {
|
impl Prepare for RawNode {
|
||||||
fn prepare(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
fn prepare(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
let mut node = self.clone().pack();
|
let mut node = self.clone().pack();
|
||||||
|
24
src/diag.rs
24
src/diag.rs
@ -177,30 +177,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format the parts separated with commas and a final "and" or "or".
|
|
||||||
pub(crate) fn comma_list<S>(buf: &mut String, parts: &[S], last: &str)
|
|
||||||
where
|
|
||||||
S: AsRef<str>,
|
|
||||||
{
|
|
||||||
for (i, part) in parts.iter().enumerate() {
|
|
||||||
match i {
|
|
||||||
0 => {}
|
|
||||||
1 if parts.len() == 2 => {
|
|
||||||
buf.push(' ');
|
|
||||||
buf.push_str(last);
|
|
||||||
buf.push(' ');
|
|
||||||
}
|
|
||||||
i if i + 1 == parts.len() => {
|
|
||||||
buf.push_str(", ");
|
|
||||||
buf.push_str(last);
|
|
||||||
buf.push(' ');
|
|
||||||
}
|
|
||||||
_ => buf.push_str(", "),
|
|
||||||
}
|
|
||||||
buf.push_str(part.as_ref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A result type with a file-related error.
|
/// A result type with a file-related error.
|
||||||
pub type FileResult<T> = Result<T, FileError>;
|
pub type FileResult<T> = Result<T, FileError>;
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use ecow::{eco_format, EcoVec};
|
|||||||
use super::{Array, Cast, Dict, Str, Value};
|
use super::{Array, Cast, Dict, Str, Value};
|
||||||
use crate::diag::{bail, At, SourceResult};
|
use crate::diag::{bail, At, SourceResult};
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
use crate::util::pretty_array;
|
use crate::util::pretty_array_like;
|
||||||
|
|
||||||
/// Evaluated arguments to a function.
|
/// Evaluated arguments to a function.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
@ -174,7 +174,7 @@ impl Debug for Args {
|
|||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
let pieces: Vec<_> =
|
let pieces: Vec<_> =
|
||||||
self.items.iter().map(|arg| eco_format!("{arg:?}")).collect();
|
self.items.iter().map(|arg| eco_format!("{arg:?}")).collect();
|
||||||
f.write_str(&pretty_array(&pieces, false))
|
f.write_str(&pretty_array_like(&pieces, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ use ecow::{eco_format, EcoString, EcoVec};
|
|||||||
|
|
||||||
use super::{ops, Args, Func, Value, Vm};
|
use super::{ops, Args, Func, Value, Vm};
|
||||||
use crate::diag::{bail, At, SourceResult, StrResult};
|
use crate::diag::{bail, At, SourceResult, StrResult};
|
||||||
use crate::util::pretty_array;
|
use crate::util::pretty_array_like;
|
||||||
|
|
||||||
/// Create a new [`Array`] from values.
|
/// Create a new [`Array`] from values.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
@ -343,7 +343,7 @@ impl Array {
|
|||||||
impl Debug for Array {
|
impl Debug for Array {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
let pieces: Vec<_> = self.iter().map(|value| eco_format!("{value:?}")).collect();
|
let pieces: Vec<_> = self.iter().map(|value| eco_format!("{value:?}")).collect();
|
||||||
f.write_str(&pretty_array(&pieces, self.len() == 1))
|
f.write_str(&pretty_array_like(&pieces, self.len() == 1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ use ecow::EcoString;
|
|||||||
use super::{Array, Str, Value};
|
use super::{Array, Str, Value};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
|
use crate::util::separated_list;
|
||||||
|
|
||||||
/// Cast from a value to a specific type.
|
/// Cast from a value to a specific type.
|
||||||
pub trait Cast<V = Value>: Sized {
|
pub trait Cast<V = Value>: Sized {
|
||||||
@ -284,7 +285,7 @@ impl CastInfo {
|
|||||||
msg.push_str(" nothing");
|
msg.push_str(" nothing");
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::diag::comma_list(&mut msg, &parts, "or");
|
msg.push_str(&separated_list(&parts, "or"));
|
||||||
|
|
||||||
if !matching_type {
|
if !matching_type {
|
||||||
msg.push_str(", found ");
|
msg.push_str(", found ");
|
||||||
|
@ -8,7 +8,7 @@ use ecow::{eco_format, EcoString};
|
|||||||
use super::{array, Array, Str, Value};
|
use super::{array, Array, Str, Value};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::syntax::is_ident;
|
use crate::syntax::is_ident;
|
||||||
use crate::util::{pretty_array, ArcExt};
|
use crate::util::{pretty_array_like, separated_list, ArcExt};
|
||||||
|
|
||||||
/// Create a new [`Dict`] from key-value pairs.
|
/// Create a new [`Dict`] from key-value pairs.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
@ -125,7 +125,7 @@ impl Dict {
|
|||||||
if let Some((key, _)) = self.iter().next() {
|
if let Some((key, _)) = self.iter().next() {
|
||||||
let parts: Vec<_> = expected.iter().map(|s| eco_format!("\"{s}\"")).collect();
|
let parts: Vec<_> = expected.iter().map(|s| eco_format!("\"{s}\"")).collect();
|
||||||
let mut msg = format!("unexpected key {key:?}, valid keys are ");
|
let mut msg = format!("unexpected key {key:?}, valid keys are ");
|
||||||
crate::diag::comma_list(&mut msg, &parts, "and");
|
msg.push_str(&separated_list(&parts, "and"));
|
||||||
return Err(msg.into());
|
return Err(msg.into());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -149,7 +149,7 @@ impl Debug for Dict {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
f.write_str(&pretty_array(&pieces, false))
|
f.write_str(&pretty_array_like(&pieces, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,8 @@ pub struct LangItems {
|
|||||||
pub emph: fn(body: Content) -> Content,
|
pub emph: fn(body: Content) -> Content,
|
||||||
/// Raw text with optional syntax highlighting: `` `...` ``.
|
/// Raw text with optional syntax highlighting: `` `...` ``.
|
||||||
pub raw: fn(text: EcoString, tag: Option<EcoString>, block: bool) -> Content,
|
pub raw: fn(text: EcoString, tag: Option<EcoString>, block: bool) -> Content,
|
||||||
|
/// The language names and tags supported by raw text.
|
||||||
|
pub raw_languages: fn() -> Vec<(&'static str, Vec<&'static str>)>,
|
||||||
/// A hyperlink: `https://typst.org`.
|
/// A hyperlink: `https://typst.org`.
|
||||||
pub link: fn(url: EcoString) -> Content,
|
pub link: fn(url: EcoString) -> Content,
|
||||||
/// A reference: `@target`.
|
/// A reference: `@target`.
|
||||||
|
@ -2,10 +2,14 @@ use std::collections::{BTreeSet, HashSet};
|
|||||||
|
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
|
use unscanny::Scanner;
|
||||||
|
|
||||||
use super::{analyze_expr, analyze_import, plain_docs_sentence, summarize_font_family};
|
use super::{analyze_expr, analyze_import, plain_docs_sentence, summarize_font_family};
|
||||||
use crate::eval::{methods_on, CastInfo, Scope, Value};
|
use crate::eval::{methods_on, CastInfo, Library, Scope, Value};
|
||||||
use crate::syntax::{ast, LinkedNode, Source, SyntaxKind};
|
use crate::syntax::{
|
||||||
|
ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind,
|
||||||
|
};
|
||||||
|
use crate::util::separated_list;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// Autocomplete a cursor position in a source file.
|
/// Autocomplete a cursor position in a source file.
|
||||||
@ -104,6 +108,22 @@ fn complete_markup(ctx: &mut CompletionContext) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Directly after a raw block.
|
||||||
|
let mut s = Scanner::new(&ctx.text);
|
||||||
|
s.jump(ctx.leaf.offset());
|
||||||
|
if s.eat_if("```") {
|
||||||
|
s.eat_while('`');
|
||||||
|
let start = s.cursor();
|
||||||
|
if s.eat_if(is_id_start) {
|
||||||
|
s.eat_while(is_id_continue);
|
||||||
|
}
|
||||||
|
if s.cursor() == ctx.cursor {
|
||||||
|
ctx.from = start;
|
||||||
|
ctx.raw_completions();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Anywhere: "|".
|
// Anywhere: "|".
|
||||||
if ctx.explicit {
|
if ctx.explicit {
|
||||||
ctx.from = ctx.cursor;
|
ctx.from = ctx.cursor;
|
||||||
@ -830,9 +850,11 @@ fn code_completions(ctx: &mut CompletionContext, hashtag: bool) {
|
|||||||
/// Context for autocompletion.
|
/// Context for autocompletion.
|
||||||
struct CompletionContext<'a> {
|
struct CompletionContext<'a> {
|
||||||
world: &'a (dyn World + 'static),
|
world: &'a (dyn World + 'static),
|
||||||
|
library: &'a Library,
|
||||||
source: &'a Source,
|
source: &'a Source,
|
||||||
global: &'a Scope,
|
global: &'a Scope,
|
||||||
math: &'a Scope,
|
math: &'a Scope,
|
||||||
|
text: &'a str,
|
||||||
before: &'a str,
|
before: &'a str,
|
||||||
after: &'a str,
|
after: &'a str,
|
||||||
leaf: LinkedNode<'a>,
|
leaf: LinkedNode<'a>,
|
||||||
@ -852,12 +874,15 @@ impl<'a> CompletionContext<'a> {
|
|||||||
explicit: bool,
|
explicit: bool,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let text = source.text();
|
let text = source.text();
|
||||||
|
let library = world.library();
|
||||||
let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||||
Some(Self {
|
Some(Self {
|
||||||
world,
|
world,
|
||||||
|
library,
|
||||||
source,
|
source,
|
||||||
global: &world.library().global.scope(),
|
global: &library.global.scope(),
|
||||||
math: &world.library().math.scope(),
|
math: &library.math.scope(),
|
||||||
|
text,
|
||||||
before: &text[..cursor],
|
before: &text[..cursor],
|
||||||
after: &text[cursor..],
|
after: &text[cursor..],
|
||||||
leaf,
|
leaf,
|
||||||
@ -908,6 +933,28 @@ impl<'a> CompletionContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add completions for raw block tags.
|
||||||
|
fn raw_completions(&mut self) {
|
||||||
|
for (name, mut tags) in (self.library.items.raw_languages)() {
|
||||||
|
let lower = name.to_lowercase();
|
||||||
|
if !tags.contains(&lower.as_str()) {
|
||||||
|
tags.push(lower.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
tags.retain(|tag| is_ident(tag));
|
||||||
|
if tags.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.completions.push(Completion {
|
||||||
|
kind: CompletionKind::Constant,
|
||||||
|
label: name.into(),
|
||||||
|
apply: Some(tags[0].into()),
|
||||||
|
detail: Some(separated_list(&tags, " or ").into()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a completion for a specific value.
|
/// Add a completion for a specific value.
|
||||||
fn value_completion(
|
fn value_completion(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -12,7 +12,7 @@ use super::{node, Guard, Recipe, Style, StyleMap};
|
|||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::eval::{cast_from_value, Args, FuncInfo, Str, Value, Vm};
|
use crate::eval::{cast_from_value, Args, FuncInfo, Str, Value, Vm};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::util::pretty_array;
|
use crate::util::pretty_array_like;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// Composable representation of styled content.
|
/// Composable representation of styled content.
|
||||||
@ -261,7 +261,7 @@ impl Debug for Content {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
f.write_str(name)?;
|
f.write_str(name)?;
|
||||||
f.write_str(&pretty_array(&pieces, false))
|
f.write_str(&pretty_array_like(&pieces, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ use super::{Content, Label, Node, NodeId};
|
|||||||
use crate::diag::{SourceResult, Trace, Tracepoint};
|
use crate::diag::{SourceResult, Trace, Tracepoint};
|
||||||
use crate::eval::{cast_from_value, Args, Cast, Dict, Func, Regex, Value};
|
use crate::eval::{cast_from_value, Args, Cast, Dict, Func, Regex, Value};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::util::pretty_array;
|
use crate::util::pretty_array_like;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// A map of style properties.
|
/// A map of style properties.
|
||||||
@ -86,7 +86,7 @@ impl Debug for StyleMap {
|
|||||||
|
|
||||||
let pieces: Vec<_> =
|
let pieces: Vec<_> =
|
||||||
self.0.iter().map(|value| eco_format!("{value:?}")).collect();
|
self.0.iter().map(|value| eco_format!("{value:?}")).collect();
|
||||||
f.write_str(&pretty_array(&pieces, false))
|
f.write_str(&pretty_array_like(&pieces, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -644,13 +644,13 @@ pub fn is_ident(string: &str) -> bool {
|
|||||||
|
|
||||||
/// Whether a character can start an identifier.
|
/// Whether a character can start an identifier.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn is_id_start(c: char) -> bool {
|
pub fn is_id_start(c: char) -> bool {
|
||||||
c.is_xid_start() || c == '_'
|
c.is_xid_start() || c == '_'
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether a character can continue an identifier.
|
/// Whether a character can continue an identifier.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn is_id_continue(c: char) -> bool {
|
pub fn is_id_continue(c: char) -> bool {
|
||||||
c.is_xid_continue() || c == '_' || c == '-'
|
c.is_xid_continue() || c == '_' || c == '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ use std::hash::Hash;
|
|||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ecow::EcoString;
|
|
||||||
use siphasher::sip128::{Hasher128, SipHasher};
|
use siphasher::sip128::{Hasher128, SipHasher};
|
||||||
|
|
||||||
/// Turn a closure into a struct implementing [`Debug`].
|
/// Turn a closure into a struct implementing [`Debug`].
|
||||||
@ -133,10 +132,66 @@ impl PathExt for Path {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format something as a a comma-separated list that support horizontal
|
/// Format pieces separated with commas and a final "and" or "or".
|
||||||
/// formatting but falls back to vertical formatting if the pieces are too long.
|
pub fn separated_list(pieces: &[impl AsRef<str>], last: &str) -> String {
|
||||||
pub fn pretty_array(pieces: &[EcoString], trailing_comma: bool) -> String {
|
let mut buf = String::new();
|
||||||
let list = pretty_comma_list(&pieces, trailing_comma);
|
for (i, part) in pieces.iter().enumerate() {
|
||||||
|
match i {
|
||||||
|
0 => {}
|
||||||
|
1 if pieces.len() == 2 => {
|
||||||
|
buf.push(' ');
|
||||||
|
buf.push_str(last);
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
i if i + 1 == pieces.len() => {
|
||||||
|
buf.push_str(", ");
|
||||||
|
buf.push_str(last);
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
_ => buf.push_str(", "),
|
||||||
|
}
|
||||||
|
buf.push_str(part.as_ref());
|
||||||
|
}
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format a comma-separated list.
|
||||||
|
///
|
||||||
|
/// Tries to format horizontally, but falls back to vertical formatting if the
|
||||||
|
/// pieces are too long.
|
||||||
|
pub fn pretty_comma_list(pieces: &[impl AsRef<str>], trailing_comma: bool) -> String {
|
||||||
|
const MAX_WIDTH: usize = 50;
|
||||||
|
|
||||||
|
let mut buf = String::new();
|
||||||
|
let len = pieces.iter().map(|s| s.as_ref().len()).sum::<usize>()
|
||||||
|
+ 2 * pieces.len().saturating_sub(1);
|
||||||
|
|
||||||
|
if len <= MAX_WIDTH {
|
||||||
|
for (i, piece) in pieces.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
buf.push_str(", ");
|
||||||
|
}
|
||||||
|
buf.push_str(piece.as_ref());
|
||||||
|
}
|
||||||
|
if trailing_comma {
|
||||||
|
buf.push(',');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for piece in pieces {
|
||||||
|
buf.push_str(piece.as_ref().trim());
|
||||||
|
buf.push_str(",\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format an array-like construct.
|
||||||
|
///
|
||||||
|
/// Tries to format horizontally, but falls back to vertical formatting if the
|
||||||
|
/// pieces are too long.
|
||||||
|
pub fn pretty_array_like(parts: &[impl AsRef<str>], trailing_comma: bool) -> String {
|
||||||
|
let list = pretty_comma_list(&parts, trailing_comma);
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
buf.push('(');
|
buf.push('(');
|
||||||
if list.contains('\n') {
|
if list.contains('\n') {
|
||||||
@ -150,35 +205,6 @@ pub fn pretty_array(pieces: &[EcoString], trailing_comma: bool) -> String {
|
|||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format something as a a comma-separated list that support horizontal
|
|
||||||
/// formatting but falls back to vertical formatting if the pieces are too long.
|
|
||||||
pub fn pretty_comma_list(pieces: &[EcoString], trailing_comma: bool) -> String {
|
|
||||||
const MAX_WIDTH: usize = 50;
|
|
||||||
|
|
||||||
let mut buf = String::new();
|
|
||||||
let len = pieces.iter().map(|s| s.len()).sum::<usize>()
|
|
||||||
+ 2 * pieces.len().saturating_sub(1);
|
|
||||||
|
|
||||||
if len <= MAX_WIDTH {
|
|
||||||
for (i, piece) in pieces.iter().enumerate() {
|
|
||||||
if i > 0 {
|
|
||||||
buf.push_str(", ");
|
|
||||||
}
|
|
||||||
buf.push_str(piece);
|
|
||||||
}
|
|
||||||
if trailing_comma {
|
|
||||||
buf.push(',');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for piece in pieces {
|
|
||||||
buf.push_str(piece.trim());
|
|
||||||
buf.push_str(",\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Indent a string by two spaces.
|
/// Indent a string by two spaces.
|
||||||
pub fn indent(text: &str, amount: usize) -> String {
|
pub fn indent(text: &str, amount: usize) -> String {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user