@@ -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,131 @@ 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 resolve ( item : Formatter . Info ) {
80+ let command = cache [ item . name ]
81+ if ( command === undefined ) {
82+ log . info ( "resolving command" , { name : item . name } )
83+ command = await item . enabled ( )
84+ cache [ item . name ] = command
85+ }
86+ return command
3787 }
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
88+
89+ async function getFormatter ( ext : string ) {
90+ const result : { info : Formatter . Info ; command : string [ ] } [ ] = [ ]
91+ for ( const item of Object . values ( formatters ) ) {
92+ if ( ! item . extensions . includes ( ext ) ) continue
93+ const command = await resolve ( 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+ const replaced = command . map ( ( x ) => x . replace ( "$FILE" , file ) )
110+ log . info ( "running" , { replaced } )
111+ try {
112+ const proc = Process . spawn ( replaced , {
113+ cwd : instance . directory ,
114+ env : { ...process . env , ...info . environment } ,
115+ stdout : "ignore" ,
116+ stderr : "ignore" ,
117+ } )
118+ const exit = await proc . exited
119+ if ( exit !== 0 )
120+ log . error ( "failed" , {
121+ command,
122+ ...info . environment ,
123+ } )
124+ } catch ( error ) {
125+ log . error ( "failed to format file" , {
126+ error,
127+ command,
128+ ...info . environment ,
129+ file,
130+ } )
131+ }
132+ }
133+ } ) ,
134+ )
87135
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- }
136+ yield * Effect . addFinalizer ( ( ) => Effect . sync ( unsubscribe ) )
137+ log . info ( "init" )
101138
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,
139+ const init = Effect . fn ( "FormatService.init" ) ( function * ( ) { } )
140+
141+ const status = Effect . fn ( "FormatService.status" ) ( function * ( ) {
142+ const result : Format . Status [ ] = [ ]
143+ for ( const formatter of Object . values ( formatters ) ) {
144+ const command = yield * Effect . promise ( ( ) => resolve ( formatter ) )
145+ result . push ( {
146+ name : formatter . name ,
147+ extensions : formatter . extensions ,
148+ enabled : ! ! command ,
131149 } )
132150 }
133- }
134- } )
135- }
151+ return result
152+ } )
153+
154+ return FormatService . of ( { init, status } )
155+ } ) ,
156+ )
136157}
0 commit comments