git.delta.rocks / jrsonnet / refs/commits / 752087cb9057

difftreelog

feat more formatter options

uqnknwssYaroslav Bolyukin2026-05-06parent: #aa77bfb.patch.diff
in: master

3 files changed

modifiedbindings/jrsonnet-web/src/lib.rsdiffbeforeafterboth
before · bindings/jrsonnet-web/src/lib.rs
1#![allow(clippy::future_not_send, reason = "we work with js promises anyway")]23use std::{cell::RefCell, result::Result};45use jrsonnet_evaluator::{6	IStr, NumValue, ObjValue, Result as JrResult, SourcePath, SourceUrl, State, StateBuilder, Val,7	async_import::{ResolvedImportResolver, async_import},8	error,9	function::builtin::{NativeCallback, NativeCallbackHandler},10	manifest::{JsonFormat, ManifestFormat, StringFormat, ToStringFormat, YamlStreamFormat},11	tla::{TlaArg, apply_tla},12	trace::PathResolver,13	val::ArrValue,14	with_state,15};16use jrsonnet_formatter::FormatOptions;17use jrsonnet_gcmodule::Trace;18use jrsonnet_stdlib::{IniFormat, TomlFormat, XmlJsonmlFormat, YamlFormat};19use jrsonnet_types::ValType;20use js_sys::Reflect::get;21use rustc_hash::FxHashMap;22use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*};2324#[wasm_bindgen]25#[derive(Clone, Copy)]26pub enum ValKind {27	Null,28	Bool,29	Num,30	Str,31	Arr,32	Obj,33	Func,34	BigInt,35}3637thread_local! {38	static ERR_FACTORY: RefCell<Option<js_sys::Function>> = const { RefCell::new(None) };39}40#[wasm_bindgen(js_name = setErrorFactory)]41pub fn set_error_factory(f: js_sys::Function) {42	ERR_FACTORY.with(|c| *c.borrow_mut() = Some(f));43}44fn make_jrsonnet_error(message: &str, frames: js_sys::Array, cause: &JsValue) -> JsValue {45	ERR_FACTORY.with(|c| {46		c.borrow().as_ref().map_or_else(47			|| js_sys::Error::new(message).into(),48			|f| {49				let args = js_sys::Array::new();50				args.push(&JsValue::from_str(message));51				args.push(&frames);52				args.push(cause);53				f.apply(&JsValue::NULL, &args)54					.unwrap_or_else(|e| js_sys::Error::new(&format!("{e:?}")).into())55			},56		)57	})58}5960fn js_error_message(e: &JsValue) -> String {61	e.dyn_ref::<js_sys::Error>().map_or_else(62		|| e.as_string().unwrap_or_else(|| format!("{e:?}")),63		|err| String::from(err.message()),64	)65}6667fn unwrap_val_ref(value: &JsValue) -> Result<<WasmVal as RefFromWasmAbi>::Anchor, JsValue> {68	let ptr = get(value, &JsValue::from_str("__wbg_ptr"))69		.ok()70		.and_then(|v| v.as_f64())71		.ok_or_else(|| JsValue::from_str("expected a Val instance"))? as u32;72	if ptr == 0 {73		return Err(JsValue::from_str("Val has been freed"));74	}75	Ok(unsafe { <WasmVal as RefFromWasmAbi>::ref_from_abi(ptr) })76}7778fn js_resolver_error(prefix: &str, e: JsValue) -> JsValue {79	let msg = format!("{prefix}: {}", js_error_message(&e));80	let frames = js_sys::Array::new();81	let frame = js_sys::Object::new();82	let _ = js_sys::Reflect::set(83		&frame,84		&JsValue::from_str("desc"),85		&JsValue::from_str(prefix),86	);87	frames.push(&frame);88	make_jrsonnet_error(&msg, frames, &e)89}9091fn jrsonnet_js_error(e: &jrsonnet_evaluator::Error) -> JsValue {92	let msg = e.error().to_string();93	// let msg = format.format(e).unwrap_or_else(|_| e.to_string());94	let frames = js_sys::Array::new();95	for el in &e.trace().0 {96		let frame = js_sys::Object::new();97		let _ = js_sys::Reflect::set(98			&frame,99			&JsValue::from_str("desc"),100			&JsValue::from_str(&el.desc),101		);102		if let Some(loc) = &el.location {103			let path = loc.0.source_path().to_string();104			let _ = js_sys::Reflect::set(105				&frame,106				&JsValue::from_str("path"),107				&JsValue::from_str(&path),108			);109			let mapped = loc.0.map_source_locations(&[loc.1, loc.2]);110			let _ = js_sys::Reflect::set(111				&frame,112				&JsValue::from_str("line"),113				&JsValue::from(mapped[0].line),114			);115			let _ = js_sys::Reflect::set(116				&frame,117				&JsValue::from_str("column"),118				&JsValue::from(mapped[0].column),119			);120		}121		frames.push(&frame);122	}123	make_jrsonnet_error(&msg, frames, &JsValue::UNDEFINED)124}125126impl From<ValType> for ValKind {127	fn from(v: ValType) -> Self {128		match v {129			ValType::Null => Self::Null,130			ValType::Bool => Self::Bool,131			ValType::Num => Self::Num,132			ValType::Str => Self::Str,133			ValType::Arr => Self::Arr,134			ValType::Obj => Self::Obj,135			ValType::Func => Self::Func,136			#[cfg(feature = "exp-bigint")]137			ValType::BigInt => Self::BigInt,138		}139	}140}141142#[wasm_bindgen(js_name = Val)]143pub struct WasmVal {144	val: Val,145	state: Option<State>,146}147148impl WasmVal {149	fn new(val: Val) -> Self {150		Self { val, state: None }151	}152	fn with_state(val: Val, state: State) -> Self {153		Self {154			val,155			state: Some(state),156		}157	}158	fn run<R>(&self, f: impl FnOnce(&Val) -> R) -> R {159		if let Some(state) = &self.state {160			let _guard = state.try_enter();161			f(&self.val)162		} else {163			f(&self.val)164		}165	}166	fn manifest_with(&self, format: impl ManifestFormat) -> Result<String, JsValue> {167		self.run(|v| v.manifest(format))168			.map_err(|e| jrsonnet_js_error(&e))169	}170}171172#[wasm_bindgen(js_class = Val)]173impl WasmVal {174	pub fn null() -> Self {175		Self::new(Val::Null)176	}177	pub fn bool(b: bool) -> Self {178		Self::new(Val::Bool(b))179	}180	pub fn num(n: f64) -> Result<Self, JsError> {181		let n = NumValue::new(n)182			.ok_or_else(|| JsError::new("only finite numbers are supported by jsonnet"))?;183		Ok(Self::new(Val::num(n)))184	}185	pub fn string(s: String) -> Self {186		Self::new(Val::string(s))187	}188	pub fn bigint(value: js_sys::BigInt) -> Result<Self, JsError> {189		#[cfg(feature = "exp-bigint")]190		{191			let s: String = value192				.to_string(10)193				.map_err(|_| JsError::new("invalid bigint"))?194				.into();195			let bi = s196				.parse::<num_bigint::BigInt>()197				.map_err(|e| JsError::new(&format!("failed to parse bigint: {e}")))?;198			Ok(Self::new(Val::BigInt(Box::new(bi))))199		}200		#[cfg(not(feature = "exp-bigint"))]201		{202			let _ = value;203			Err(JsError::new(204				"bigint support is not enabled in this build (exp-bigint feature)",205			))206		}207	}208	pub fn arr(items: Vec<WasmVal>) -> Self {209		Self::new(Val::arr(210			items.into_iter().map(|v| v.val).collect::<Vec<_>>(),211		))212	}213	pub fn func(214		params: Vec<String>,215216		#[wasm_bindgen(unchecked_param_type = "(...args: Val[]) => Val")]217		callback: js_sys::Function,218	) -> Self {219		#[allow(deprecated)]220		Self::new(Val::function(NativeCallback::new(221			params,222			JsHandler { func: callback },223		)))224	}225226	#[wasm_bindgen(getter)]227	pub fn kind(&self) -> ValKind {228		self.val.value_type().into()229	}230	#[wasm_bindgen(js_name = asBool)]231	pub fn as_bool(&self) -> Option<bool> {232		self.val.as_bool()233	}234	#[wasm_bindgen(js_name = asNum)]235	pub fn as_num(&self) -> Option<f64> {236		self.val.as_num()237	}238	#[wasm_bindgen(js_name = asBigint)]239	pub fn as_bigint(&self) -> Result<Option<js_sys::BigInt>, JsError> {240		#[cfg(feature = "exp-bigint")]241		{242			let Some(bi) = self.val.as_bigint() else {243				return Ok(None);244			};245			let big = js_sys::BigInt::new(&JsValue::from_str(&bi.to_string()))246				.map_err(|e| JsError::new(&format!("{e:?}")))?;247			Ok(Some(big))248		}249		#[cfg(not(feature = "exp-bigint"))]250		{251			Err(JsError::new(252				"bigint support is not enabled in this build (exp-bigint feature)",253			))254		}255	}256	#[wasm_bindgen(js_name = asString)]257	pub fn as_string(&self) -> Option<String> {258		self.val.as_str().map(|s| s.to_string())259	}260	#[wasm_bindgen(js_name = asArr)]261	pub fn as_arr(&self) -> Option<WasmArrValue> {262		self.val.as_arr().map(|arr| WasmArrValue {263			arr,264			state: self.state.clone(),265		})266	}267	#[wasm_bindgen(js_name = asObj)]268	pub fn as_obj(&self) -> Option<WasmObjValue> {269		self.val.as_obj().map(|obj| WasmObjValue {270			obj,271			state: self.state.clone(),272		})273	}274275	#[wasm_bindgen(js_name = applyTla)]276	pub fn apply_tla(277		&self,278		#[wasm_bindgen(unchecked_param_type = "Record<string, Val>")] args: &js_sys::Object,279	) -> Result<WasmVal, JsValue> {280		let mut map: FxHashMap<IStr, TlaArg> = FxHashMap::default();281		for entry in js_sys::Object::entries(args).iter() {282			let pair: js_sys::Array = entry283				.dyn_into()284				.map_err(|_| JsValue::from_str("expected [key, value] entry"))?;285			let key = pair286				.get(0)287				.as_string()288				.ok_or_else(|| JsValue::from_str("TLA arg key must be a string"))?;289			let value = unwrap_val_ref(&pair.get(1))?;290			map.insert(key.into(), TlaArg::Val(value.val.clone()));291		}292		let val = self.val.clone();293		self.run(|_| apply_tla(&map, val))294			.map(|v| WasmVal {295				val: v,296				state: self.state.clone(),297			})298			.map_err(|e| jrsonnet_js_error(&e))299	}300301	#[wasm_bindgen(js_name = manifestJson)]302	pub fn manifest_json(&self, indent: u32) -> Result<String, JsValue> {303		self.manifest_with(JsonFormat::cli(304			indent as usize,305			#[cfg(feature = "exp-preserve-order")]306			false,307		))308	}309	#[wasm_bindgen(js_name = manifestToString)]310	pub fn manifest_to_string(&self) -> Result<String, JsValue> {311		self.manifest_with(ToStringFormat)312	}313	#[wasm_bindgen(js_name = manifestString)]314	pub fn manifest_string(&self) -> Result<String, JsValue> {315		self.manifest_with(StringFormat)316	}317	#[wasm_bindgen(js_name = manifestYaml)]318	pub fn manifest_yaml(&self, indent: u32, quote_keys: bool) -> Result<String, JsValue> {319		self.manifest_with(YamlFormat::std_to_yaml(320			indent != 0,321			quote_keys,322			#[cfg(feature = "exp-preserve-order")]323			false,324		))325	}326	#[wasm_bindgen(js_name = manifestYamlStream)]327	pub fn manifest_yaml_stream(328		&self,329		indent: u32,330		quote_keys: bool,331		c_document_end: bool,332	) -> Result<String, JsValue> {333		self.manifest_with(YamlStreamFormat::std_yaml_stream(334			YamlFormat::std_to_yaml(335				indent != 0,336				quote_keys,337				#[cfg(feature = "exp-preserve-order")]338				false,339			),340			c_document_end,341		))342	}343	#[wasm_bindgen(js_name = manifestXmlJsonml)]344	pub fn manifest_xml_jsonml(&self) -> Result<String, JsValue> {345		self.manifest_with(XmlJsonmlFormat::std_to_xml())346	}347	#[wasm_bindgen(js_name = manifestToml)]348	pub fn manifest_toml(&self, indent: u32) -> Result<String, JsValue> {349		self.manifest_with(TomlFormat::std_to_toml(350			" ".repeat(indent as usize),351			#[cfg(feature = "exp-preserve-order")]352			false,353		))354	}355	#[wasm_bindgen(js_name = manifestIni)]356	pub fn manifest_ini(&self) -> Result<String, JsValue> {357		self.manifest_with(IniFormat::std(358			#[cfg(feature = "exp-preserve-order")]359			false,360		))361	}362}363364#[wasm_bindgen(js_name = ArrValue)]365pub struct WasmArrValue {366	arr: ArrValue,367	state: Option<State>,368}369370#[wasm_bindgen(js_class = ArrValue)]371impl WasmArrValue {372	#[wasm_bindgen(getter)]373	pub fn length(&self) -> u32 {374		self.arr.len()375	}376	pub fn at(&self, index: u32) -> Result<Option<WasmVal>, JsValue> {377		let result = self.state.as_ref().map_or_else(378			|| self.arr.get(index),379			|state| {380				let _guard = state.try_enter();381				self.arr.get(index)382			},383		);384		result385			.map(|opt: Option<Val>| {386				opt.map(|v| WasmVal {387					val: v,388					state: self.state.clone(),389				})390			})391			.map_err(|e| jrsonnet_js_error(&e))392	}393}394395#[wasm_bindgen(js_name = ObjValue)]396pub struct WasmObjValue {397	obj: ObjValue,398	state: Option<State>,399}400401#[wasm_bindgen(js_class = ObjValue)]402impl WasmObjValue {403	pub fn keys(&self) -> Vec<String> {404		self.obj405			.fields(406				#[cfg(feature = "exp-preserve-order")]407				false,408			)409			.into_iter()410			.map(|s| s.to_string())411			.collect()412	}413	pub fn get(&self, key: String) -> Result<Option<WasmVal>, JsValue> {414		let result = if let Some(state) = &self.state {415			let _guard = state.try_enter();416			self.obj.get(key.into())417		} else {418			self.obj.get(key.into())419		};420		result421			.map(|opt: Option<Val>| {422				opt.map(|v| WasmVal {423					val: v,424					state: self.state.clone(),425				})426			})427			.map_err(|e| jrsonnet_js_error(&e))428	}429}430431#[derive(Trace)]432struct JsHandler {433	#[trace(skip)]434	func: js_sys::Function,435}436437#[wasm_bindgen(inline_js = r"438export function js_invoke_val_callback(cb, args) {439	return cb.apply(null, args);440}441")]442extern "C" {443	#[wasm_bindgen(catch)]444	fn js_invoke_val_callback(445		cb: &js_sys::Function,446		args: &js_sys::Array,447	) -> Result<WasmVal, JsValue>;448}449450impl NativeCallbackHandler for JsHandler {451	fn call(&self, args: &[Val]) -> JrResult<Val> {452		let js_args = js_sys::Array::new();453		let state = with_state(|s| s);454		for arg in args {455			js_args.push(&JsValue::from(WasmVal::with_state(456				arg.clone(),457				state.clone(),458			)));459		}460		let result = js_invoke_val_callback(&self.func, &js_args).map_err(|e| {461			let msg = e462				.as_string()463				.or_else(|| {464					e.dyn_ref::<js_sys::Error>()465						.map(|err| String::from(err.message()))466				})467				.unwrap_or_else(|| format!("{e:?}"));468			error!("js callback threw: {msg}")469		})?;470		Ok(result.val)471	}472}473474#[wasm_bindgen(js_name = State)]475pub struct WasmState {476	state: State,477	resolver: Option<JsAsyncResolver>,478}479#[wasm_bindgen(js_class = State)]480impl WasmState {481	#[wasm_bindgen(constructor)]482	pub fn new(resolver: Option<ImportResolverJs>) -> Self {483		console_error_panic_hook::set_once();484		let mut state = StateBuilder::default();485		state.import_resolver(ResolvedImportResolver::new());486		let std = jrsonnet_stdlib::ContextInitializer::new(PathResolver::Absolute);487		state.context_initializer(std);488		let state = state.build();489		Self {490			state,491			resolver: resolver.map(|js| JsAsyncResolver { js }),492		}493	}494495	#[wasm_bindgen(js_name = evaluateSnippet)]496	pub fn evaluate_snippet(&self, name: &str, snippet: &str) -> Result<WasmVal, JsValue> {497		let _guard = self.state.enter();498		self.state499			.evaluate_snippet(name, snippet)500			.map(|v| WasmVal::with_state(v, self.state.clone()))501			.map_err(|e| jrsonnet_js_error(&e))502	}503504	#[wasm_bindgen(js_name = evaluateFile)]505	pub async fn evaluate_file(&self, path: String) -> Result<WasmVal, JsValue> {506		self.evaluate_file_from_impl(None, path).await507	}508509	#[wasm_bindgen(js_name = evaluateFileFrom)]510	pub async fn evaluate_file_from(&self, from: String, path: String) -> Result<WasmVal, JsValue> {511		self.evaluate_file_from_impl(Some(from), path).await512	}513}514515impl WasmState {516	async fn evaluate_file_from_impl(517		&self,518		from: Option<String>,519		path: String,520	) -> Result<WasmVal, JsValue> {521		let resolver = self522			.resolver523			.clone()524			.ok_or_else(|| JsValue::from_str("file evaluation requires an ImportResolver"))?;525		let from = match from {526			Some(s) => {527				let url = url::Url::parse(&s).map_err(|e| JsValue::from_str(&e.to_string()))?;528				SourcePath::new(SourceUrl::new(url))529			}530			None => SourcePath::default(),531		};532		let path = async_import(self.state.clone(), resolver, &from, &path.as_str()).await?;533		let _guard = self.state.enter();534		self.state535			.import_resolved(path)536			.map(|v| WasmVal::with_state(v, self.state.clone()))537			.map_err(|e| jrsonnet_js_error(&e))538	}539}540541#[wasm_bindgen]542extern "C" {543	#[wasm_bindgen(typescript_type = "ImportResolver")]544	#[derive(Clone)]545	pub type ImportResolverJs;546547	#[wasm_bindgen(catch, method, structural, js_name = resolveFrom)]548	fn resolve_from(549		this: &ImportResolverJs,550		from: Option<String>,551		path: &str,552	) -> Result<js_sys::Promise, JsValue>;553554	#[wasm_bindgen(catch, method, structural, js_name = loadFileContents)]555	fn load_file_contents(556		this: &ImportResolverJs,557		resolved: &str,558	) -> Result<js_sys::Promise, JsValue>;559}560561#[wasm_bindgen(typescript_custom_section)]562const TS_IMPORT_RESOLVER: &'static str = r"563export interface ImportResolver {564	resolveFrom(from: string | undefined, path: string): Promise<string>;565	loadFileContents(resolved: string): Promise<Uint8Array>;566}567";568569#[derive(Clone)]570struct JsAsyncResolver {571	js: ImportResolverJs,572}573574impl jrsonnet_evaluator::async_import::AsyncImportResolver for JsAsyncResolver {575	type Error = JsValue;576577	async fn resolve_from(578		&self,579		from: &SourcePath,580		path: &dyn jrsonnet_evaluator::AsPathLike,581	) -> Result<SourcePath, JsValue> {582		let from_js = (!from.is_default()).then(|| from.to_string());583		let path_str = path.as_path().as_ref().to_string_lossy().into_owned();584		let promise = self585			.js586			.resolve_from(from_js, &path_str)587			.map_err(|e| js_resolver_error("resolveFrom", e))?;588		let resolved_js = wasm_bindgen_futures::JsFuture::from(promise)589			.await590			.map_err(|e| js_resolver_error("resolveFrom", e))?;591		let resolved_str = resolved_js592			.as_string()593			.ok_or_else(|| JsValue::from_str("resolveFrom must return string"))?;594		let url = url::Url::parse(&resolved_str).map_err(|e| JsValue::from_str(&e.to_string()))?;595		Ok(SourcePath::new(SourceUrl::new(url)))596	}597598	async fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>, JsValue> {599		let resolved_str = resolved.to_string();600		let promise = self601			.js602			.load_file_contents(&resolved_str)603			.map_err(|e| js_resolver_error("loadFileContents", e))?;604		let bytes_js = wasm_bindgen_futures::JsFuture::from(promise)605			.await606			.map_err(|e| js_resolver_error("loadFileContents", e))?;607		let arr = bytes_js608			.dyn_into::<js_sys::Uint8Array>()609			.map_err(|_| JsValue::from_str("loadFileContents must return Uint8Array"))?;610		Ok(arr.to_vec())611	}612}613614#[wasm_bindgen(js_name = FormatOptions)]615pub struct WasmFormatOptions {616	indent: u8,617}618#[wasm_bindgen(js_class = FormatOptions)]619impl WasmFormatOptions {620	#[wasm_bindgen(constructor)]621	pub fn new() -> Self {622		Self { indent: 0 }623	}624625	fn build(&self) -> FormatOptions {626		FormatOptions {627			indent: self.indent,628		}629	}630}631632impl Default for WasmFormatOptions {633	fn default() -> Self {634		Self::new()635	}636}637638#[wasm_bindgen]639pub fn format(src: &str, opts: &WasmFormatOptions) -> Result<String, String> {640	match jrsonnet_formatter::format(src, &opts.build()) {641		Ok(v) => Ok(v),642		Err(e) => {643			let e = e.build();644			Err(hi_doc::source_to_ansi(&e))645		}646	}647}
modifiedcmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet-fmt/src/main.rs
+++ b/cmds/jrsonnet-fmt/src/main.rs
@@ -25,11 +25,14 @@
 	#[arg(long)]
 	test: bool,
 	/// Number of spaces to indent with
-	#[arg(long, default_value = "2")]
+	#[arg(long, default_value = "4")]
 	indent: u8,
 	/// Force hard tab for indentation
-	#[arg(long)]
-	hard_tabs: bool,
+	#[arg(long, default_value = "true")]
+	use_tabs: bool,
+	/// Max formatted source width
+	#[arg(long, default_value = "100")]
+	max_width: u32,
 
 	/// Debug option: how many times to call reformatting in case of unstable dprint output resolution.
 	///
@@ -51,13 +54,7 @@
 }
 
 fn main_result() -> Result<(), Error> {
-	eprintln!(
-		"jrsonnet-fmt is a prototype of a jsonnet code formatter, do not expect it to produce meaningful results right now."
-	);
-	eprintln!(
-		"It is not expected for its output to match other implementations, it will be completly separate implementation with maybe different name."
-	);
-	let mut opts = Opts::parse();
+	let opts = Opts::parse();
 	let input = if opts.exec {
 		if opts.in_place {
 			return Err(Error::InPlaceExec);
@@ -66,12 +63,6 @@
 	} else {
 		fs::read_to_string(&opts.input)?
 	};
-
-	if opts.indent == 0 {
-		// Sane default.
-		// TODO: Implement actual guessing.
-		opts.hard_tabs = true;
-	}
 
 	let mut iteration = 0;
 	let mut formatted = input.clone();
@@ -81,11 +72,9 @@
 		let reformatted = match format(
 			&formatted,
 			&FormatOptions {
-				indent: if opts.indent == 0 || opts.hard_tabs {
-					0
-				} else {
-					opts.indent
-				},
+				indent: opts.indent,
+				use_tabs: opts.use_tabs,
+				max_width: opts.max_width,
 			},
 		) {
 			Ok(v) => v,
modifiedcrates/jrsonnet-formatter/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-formatter/src/lib.rs
+++ b/crates/jrsonnet-formatter/src/lib.rs
@@ -901,14 +901,25 @@
 	}
 }
 
-#[derive(Default)]
 pub struct FormatOptions {
-	// 0 for hard tabs, otherwise number of spaces
 	pub indent: u8,
+	pub use_tabs: bool,
+	pub max_width: u32,
 }
+
 impl FormatOptions {
 	pub fn new() -> Self {
-		Self::default()
+		Self {
+			indent: 4,
+			use_tabs: true,
+			max_width: 100,
+		}
+	}
+}
+
+impl Default for FormatOptions {
+	fn default() -> Self {
+		Self::new()
 	}
 }
 
@@ -947,14 +958,9 @@
 			out
 		},
 		PrintOptions {
-			indent_width: if opts.indent == 0 {
-				// Reasonable max length for both 2 and 4 space sized tabs.
-				3
-			} else {
-				opts.indent
-			},
-			max_width: 100,
-			use_tabs: opts.indent == 0,
+			indent_width: opts.indent,
+			max_width: opts.max_width,
+			use_tabs: opts.use_tabs,
 			new_line_text: "\n",
 		},
 	))