@@ -9,10 +9,13 @@ import { Config } from "../config/config"
99import { mergeDeep } from "remeda"
1010import { Instance } from "../project/instance"
1111import { Process } from "../util/process"
12+ import { InstanceContext } from "@/effect/instance-context"
13+ import { Effect , Layer , ServiceMap } from "effect"
14+ import { runPromiseInstance } from "@/effect/runtime"
1215
13- export namespace Format {
14- const log = Log . create ( { service : "format" } )
16+ const log = Log . create ( { service : "format" } )
1517
18+ export namespace Format {
1619 export const Status = z
1720 . object ( {
1821 name : z . string ( ) ,
@@ -24,113 +27,133 @@ export namespace Format {
2427 } )
2528 export type Status = z . infer < typeof Status >
2629
27- const state = Instance . state ( async ( ) => {
28- const cache : Record < string , string [ ] | false > = { }
29- const cfg = await Config . get ( )
30+ export async function init ( ) {
31+ return runPromiseInstance ( FormatService . use ( ( s ) => s . init ( ) ) )
32+ }
33+
34+ export async function status ( ) {
35+ return runPromiseInstance ( FormatService . use ( ( s ) => s . status ( ) ) )
36+ }
37+ }
38+
39+ export namespace FormatService {
40+ export interface Service {
41+ readonly init : ( ) => Effect . Effect < void >
42+ readonly status : ( ) => Effect . Effect < Format . Status [ ] >
43+ }
44+ }
45+
46+ export class FormatService extends ServiceMap . Service < FormatService , FormatService . Service > ( ) ( "@opencode/Format" ) {
47+ static readonly layer = Layer . effect (
48+ FormatService ,
49+ Effect . gen ( function * ( ) {
50+ const instance = yield * InstanceContext
51+
52+ const cache : Record < string , string [ ] | false > = { }
53+ const formatters : Record < string , Formatter . Info > = { }
54+
55+ const cfg = yield * Effect . promise ( ( ) => Config . get ( ) )
56+
57+ if ( cfg . formatter !== false ) {
58+ for ( const item of Object . values ( Formatter ) ) {
59+ formatters [ item . name ] = item
60+ }
61+ for ( const [ name , item ] of Object . entries ( cfg . formatter ?? { } ) ) {
62+ if ( item . disabled ) {
63+ delete formatters [ name ]
64+ continue
65+ }
66+ const result = mergeDeep ( formatters [ name ] ?? { } , {
67+ extensions : [ ] ,
68+ ...item ,
69+ } ) as Formatter . Info
70+
71+ result . enabled = async ( ) => item . command ?? false
72+ result . name = name
73+ formatters [ name ] = result
74+ }
75+ } else {
76+ log . info ( "all formatters are disabled" )
77+ }
3078
31- const formatters : Record < string , Formatter . Info > = { }
32- if ( cfg . formatter === false ) {
33- log . info ( "all formatters are disabled" )
34- return {
35- cache,
36- formatters,
79+ async function resolveCommand ( item : Formatter . Info ) {
80+ let command = cache [ item . name ]
81+ if ( command === undefined ) {
82+ command = await item . enabled ( )
83+ cache [ item . name ] = command
84+ }
85+ return command
3786 }
38- }
39-
40- for ( const item of Object . values ( Formatter ) ) {
41- formatters [ item . name ] = item
42- }
43- for ( const [ name , item ] of Object . entries ( cfg . formatter ?? { } ) ) {
44- if ( item . disabled ) {
45- delete formatters [ name ]
46- continue
87+
88+ async function getFormatter ( ext : string ) {
89+ const result : { info : Formatter . Info ; command : string [ ] } [ ] = [ ]
90+ for ( const item of Object . values ( formatters ) ) {
91+ log . info ( "checking" , { name : item . name , ext } )
92+ if ( ! item . extensions . includes ( ext ) ) continue
93+ const command = await resolveCommand ( item )
94+ if ( ! command ) continue
95+ log . info ( "enabled" , { name : item . name , ext } )
96+ result . push ( { info : item , command } )
97+ }
98+ return result
4799 }
48- const result : Formatter . Info = mergeDeep ( formatters [ name ] ?? { } , {
49- extensions : [ ] ,
50- ...item ,
51- } )
52100
53- result . enabled = async ( ) => item . command ?? false
54- result . name = name
55- formatters [ name ] = result
56- }
57-
58- return {
59- cache,
60- formatters,
61- }
62- } )
63-
64- async function resolveCommand ( item : Formatter . Info ) {
65- const s = await state ( )
66- let command = s . cache [ item . name ]
67- if ( command === undefined ) {
68- log . info ( "resolving command" , { name : item . name } )
69- command = await item . enabled ( )
70- s . cache [ item . name ] = command
71- }
72- return command
73- }
101+ const unsubscribe = Bus . subscribe (
102+ File . Event . Edited ,
103+ Instance . bind ( async ( payload ) => {
104+ const file = payload . properties . file
105+ log . info ( "formatting" , { file } )
106+ const ext = path . extname ( file )
74107
75- async function getFormatter ( ext : string ) {
76- const formatters = await state ( ) . then ( ( x ) => x . formatters )
77- const result : { info : Formatter . Info ; command : string [ ] } [ ] = [ ]
78- for ( const item of Object . values ( formatters ) ) {
79- if ( ! item . extensions . includes ( ext ) ) continue
80- const command = await resolveCommand ( item )
81- if ( ! command ) continue
82- log . info ( "enabled" , { name : item . name , ext } )
83- result . push ( { info : item , command } )
84- }
85- return result
86- }
108+ for ( const { info, command } of await getFormatter ( ext ) ) {
109+ log . info ( "running" , { command } )
110+ try {
111+ const proc = Process . spawn (
112+ command . map ( ( x ) => x . replace ( "$FILE" , file ) ) ,
113+ {
114+ cwd : instance . directory ,
115+ env : { ...process . env , ...info . environment } ,
116+ stdout : "ignore" ,
117+ stderr : "ignore" ,
118+ } ,
119+ )
120+ const exit = await proc . exited
121+ if ( exit !== 0 )
122+ log . error ( "failed" , {
123+ command,
124+ ...info . environment ,
125+ } )
126+ } catch ( error ) {
127+ log . error ( "failed to format file" , {
128+ error,
129+ command,
130+ ...info . environment ,
131+ file,
132+ } )
133+ }
134+ }
135+ } ) ,
136+ )
87137
88- export async function status ( ) {
89- const s = await state ( )
90- const result : Status [ ] = [ ]
91- for ( const formatter of Object . values ( s . formatters ) ) {
92- const command = await resolveCommand ( formatter )
93- result . push ( {
94- name : formatter . name ,
95- extensions : formatter . extensions ,
96- enabled : ! ! command ,
97- } )
98- }
99- return result
100- }
138+ yield * Effect . addFinalizer ( ( ) => Effect . sync ( unsubscribe ) )
139+ log . info ( "init" )
101140
102- export function init ( ) {
103- log . info ( "init" )
104- Bus . subscribe ( File . Event . Edited , async ( payload ) => {
105- const file = payload . properties . file
106- log . info ( "formatting" , { file } )
107- const ext = path . extname ( file )
108-
109- for ( const { info, command } of await getFormatter ( ext ) ) {
110- const replaced = command . map ( ( x ) => x . replace ( "$FILE" , file ) )
111- log . info ( "running" , { replaced } )
112- try {
113- const proc = Process . spawn ( replaced , {
114- cwd : Instance . directory ,
115- env : { ...process . env , ...info . environment } ,
116- stdout : "ignore" ,
117- stderr : "ignore" ,
118- } )
119- const exit = await proc . exited
120- if ( exit !== 0 )
121- log . error ( "failed" , {
122- command,
123- ...info . environment ,
124- } )
125- } catch ( error ) {
126- log . error ( "failed to format file" , {
127- error,
128- command,
129- ...info . environment ,
130- file,
141+ const init = Effect . fn ( "FormatService.init" ) ( function * ( ) { } )
142+
143+ const status = Effect . fn ( "FormatService.status" ) ( function * ( ) {
144+ const result : Format . Status [ ] = [ ]
145+ for ( const formatter of Object . values ( formatters ) ) {
146+ const command = yield * Effect . promise ( ( ) => resolveCommand ( formatter ) )
147+ result . push ( {
148+ name : formatter . name ,
149+ extensions : formatter . extensions ,
150+ enabled : ! ! command ,
131151 } )
132152 }
133- }
134- } )
135- }
153+ return result
154+ } )
155+
156+ return FormatService . of ( { init, status } )
157+ } ) ,
158+ )
136159}
0 commit comments