2021-04-04 17:05:06 +03:00
module main
import os
import time
2021-04-27 16:29:02 +03:00
import term
2021-04-28 12:23:23 +03:00
import flag
2021-04-04 17:05:06 +03:00
2021-06-19 16:51:40 +03:00
const scan_timeout_s = get_scan_timeout_seconds ( )
2021-04-04 17:05:06 +03:00
const max_v_cycles = 1000
const scan_frequency_hz = 4
const scan_period_ms = 1000 / scan_frequency_hz
const max_scan_cycles = scan_timeout_s * scan_frequency_hz
2021-06-19 16:51:40 +03:00
fn get_scan_timeout_seconds ( ) int {
env_vw_timeout := os . getenv ( ' V W A T C H _ T I M E O U T ' ) . int ( )
if env_vw_timeout == 0 {
2021-06-19 22:07:56 +03:00
$ if gcboehm ? {
return 35000000 // over 1 year
} $ else {
return 5 * 60
}
2021-06-19 16:51:40 +03:00
}
return env_vw_timeout
}
2021-04-04 17:05:06 +03:00
//
2021-06-19 16:51:40 +03:00
// Implements `v watch file.v` , `v watch run file.v` etc.
2021-04-04 17:05:06 +03:00
// With this command, V will collect all .v files that are needed for the
// compilation, then it will enter an infinite loop, monitoring them for
// changes.
//
// When a change is detected, it will stop the current process, if it is
// still running, then rerun/recompile/etc.
//
// In effect, this makes it easy to have an editor session and a separate
2021-06-19 16:51:40 +03:00
// terminal, running just `v watch run file.v`, and you will see your
2021-04-04 17:05:06 +03:00
// changes right after you save your .v file in your editor.
//
//
// Since -gc boehm is not available on all platforms yet,
// and this program leaks ~8MB/minute without it, the implementation here
// is done similarly to vfmt in 2 modes, in the same executable:
//
// a) A parent/manager process that only manages a single worker
// process. The parent process does mostly nothing except restarting
// workers, thus it does not leak much.
//
// b) A worker process, doing the actual monitoring/polling.
2022-03-06 20:01:22 +03:00
// Note: *workers are started with the --vwatchworker option*
2021-04-04 17:05:06 +03:00
//
// Worker processes will run for a limited number of iterations, then
// they will do exit(255), and then the parent will start a new worker.
// Exiting by any other code will cause the parent to also exit with the
// same error code. This limits the potential leak that a worker process
// can do, even without using the garbage collection mode.
//
struct VFileStat {
path string
2021-10-22 17:08:08 +03:00
mtime i64
2021-04-04 17:05:06 +03:00
}
[ unsafe ]
fn ( mut vfs VFileStat ) free ( ) {
unsafe { vfs . path . free ( ) }
}
enum RerunCommand {
restart
quit
}
struct Context {
mut :
pid int // the pid of the current process; useful while debugging manager/worker interactions
is_worker bool // true in the workers, false in the manager process
check_period_ms int = scan_period_ms
vexe string
affected_paths [ ] string
vfiles [ ] VFileStat
opts [ ] string
rerun_channel chan RerunCommand
2022-09-15 07:59:31 +03:00
child_process & os . Process = unsafe { nil }
2021-04-27 17:05:14 +03:00
is_exiting bool // set by SIGINT/Ctrl-C
v_cycles int // how many times the worker has restarted the V compiler
scan_cycles int // how many times the worker has scanned for source file changes
clear_terminal bool // whether to clear the terminal before each re-run
2022-06-15 18:00:02 +03:00
keep_running bool // when true, re-run the program automatically if it exits on its own. Useful for gg apps.
2021-05-16 13:23:27 +03:00
silent bool // when true, watch will not print a timestamp line before each re-run
2021-04-27 17:05:14 +03:00
add_files [ ] string // path to additional files that have to be watched for changes
2021-04-28 09:22:25 +03:00
ignore_exts [ ] string // extensions of files that will be ignored, even if they change (useful for sqlite.db files for example)
2021-05-16 17:50:10 +03:00
cmd_before_run string // a command to run before each re-run
cmd_after_run string // a command to run after each re-run
2023-07-27 09:50:26 +03:00
only_watch [ ] string // If not empty, *all* files that trigger updates, should match *at least one* of these s.match_glob() patterns. This is also triggered for vweb apps, to monitor for just *.v,*.js,*.css,*.html in vweb projects.
2021-04-04 17:05:06 +03:00
}
2021-06-22 10:46:43 +03:00
[ if debug_vwatch ? ]
2021-04-04 17:05:06 +03:00
fn ( mut context Context ) elog ( msg string ) {
2022-11-15 16:53:13 +03:00
eprintln ( ' > v w a t c h $ { context . pid } , $ { msg } ' )
2021-04-04 17:05:06 +03:00
}
fn ( context & Context ) str ( ) string {
2022-11-15 16:53:13 +03:00
return ' C o n t e x t { p i d : $ { context . pid } , i s _ w o r k e r : $ { context . is_worker } , c h e c k _ p e r i o d _ m s : $ { context . check_period_ms } , v e x e : $ { context . vexe } , o p t s : $ { context . opts } , i s _ e x i t i n g : $ { context . is_exiting } , v f i l e s : $ { context . vfiles } '
2021-04-04 17:05:06 +03:00
}
2023-07-27 09:50:26 +03:00
fn ( mut context Context ) is_ext_ignored ( pf string , pf_ext string ) bool {
for ipattern in context . ignore_exts {
if pf_ext . match_glob ( ipattern ) {
return true
}
}
if pf_ext in [ ' ' , ' . s o ' , ' . a ' ] {
// on unix, the executables saved by compilers, usually do not have extensions at all, and shared libs are .so
return true
}
if pf_ext in [ ' . e x e ' , ' . d l l ' , ' . d e f ' ] {
// on windows, files with these extensions will be generated by the compiler
return true
}
// ignore common backup files saved by editors like emacs/jed/vim:
if pf_ext == ' . b a k ' {
return true
}
if pf . starts_with ( ' . # ' ) {
return true
}
if pf . ends_with ( ' ~ ' ) {
return true
}
return false
}
2021-04-04 17:05:06 +03:00
fn ( mut context Context ) get_stats_for_affected_vfiles ( ) [ ] VFileStat {
if context . affected_paths . len == 0 {
mut apaths := map [ string ] bool { }
// The next command will make V parse the program, and print all .v files,
// needed for its compilation, without actually compiling it.
copts := context . opts . join ( ' ' )
2023-04-06 00:44:52 +03:00
cmd := ' " $ { context . vexe } " - s i l e n t - p r i n t - w a t c h e d - f i l e s $ { copts } '
// context.elog('> cmd: ${cmd}')
2021-04-27 17:05:14 +03:00
mut paths := [ ] string { }
if context . add_files . len > 0 && context . add_files [ 0 ] != ' ' {
paths << context . add_files
}
vfiles := os . execute ( cmd )
2021-04-04 17:05:06 +03:00
if vfiles . exit_code == 0 {
paths_trimmed := vfiles . output . trim_space ( )
2023-07-27 09:50:26 +03:00
reported_used_files := paths_trimmed . split_any ( ' \n ' )
2023-04-06 00:44:52 +03:00
$ if trace_reported_used_files ? {
context . elog ( ' r e p o r t e d _ u s e d _ f i l e s : $ { reported_used_files } ' )
}
paths << reported_used_files
2021-04-27 17:05:14 +03:00
}
2023-07-27 09:50:26 +03:00
mut is_vweb_found := false
2021-04-27 17:05:14 +03:00
for vf in paths {
apaths [ os . real_path ( os . dir ( vf ) ) ] = true
2023-07-27 09:50:26 +03:00
if vf . contains ( ' v w e b . v ' ) {
is_vweb_found = true
}
}
if is_vweb_found {
if ! os . args . any ( it . starts_with ( ' - - o n l y - w a t c h ' ) ) {
// vweb is often used with SQLite .db or .sqlite3 files right next to the executable/source,
// that are updated by the vweb app, causing a restart of the app, which in turn causes the
// browser to reload the current page, that probably triggered the update in the first place.
// Note that the problem is not specific to SQLite, any database that stores its files in the
// current (project) folder, will also cause this.
println ( ' ` v w a t c h ` d e t e c t e d t h a t y o u a r e c o m p i l i n g a v w e b p r o j e c t . ' )
println ( ' B e c a u s e o f t h a t , t h e ` - - o n l y - w a t c h = * . v , * . h t m l , * . c s s , * . j s ` f l a g w a s a l s o i m p l i e d . ' )
println ( ' I n r e s u l t , ` v w a t c h ` w i l l i g n o r e c h a n g e s t o o t h e r f i l e s . ' )
println ( ' A d d y o u r o w n - - o n l y - w a t c h f i l t e r , i f y o u w i s h t o o v e r r i d e t h a t c h o i c e . ' )
println ( ' ' )
context . only_watch = ' * . v , * . h t m l , * . c s s , * . j s ' . split_any ( ' , ' )
}
2021-04-04 17:05:06 +03:00
}
context . affected_paths = apaths . keys ( )
// context.elog('vfiles paths to be scanned: $context.affected_paths')
}
2023-07-27 09:50:26 +03:00
// scan all files in the found folders:
2021-04-04 17:05:06 +03:00
mut newstats := [ ] VFileStat { }
for path in context . affected_paths {
mut files := os . ls ( path ) or { [ ] string { } }
2023-07-27 09:50:26 +03:00
next_file : for pf in files {
pf_path := os . join_path_single ( path , pf )
if context . only_watch . len > 0 {
// in the whitelist mode, first only allow files, which match at least one of the patterns in context.only_watch:
mut matched_pattern_idx := - 1
for ow_pattern_idx , ow_pattern in context . only_watch {
if pf_path . match_glob ( ow_pattern ) {
matched_pattern_idx = ow_pattern_idx
context . elog ( ' > $ { @METHOD } m a t c h e d - - o n l y - w a t c h p a t t e r n : $ { ow_pattern } , f o r f i l e : $ { pf_path } ' )
break
}
}
if matched_pattern_idx == - 1 {
context . elog ( ' > $ { @METHOD } - - o n l y - w a t c h i g n o r e d f i l e : $ { pf_path } ' )
continue
}
2021-04-04 17:05:06 +03:00
}
2023-07-27 09:50:26 +03:00
// by default allow everything, except very specific extensions (backup files, executables etc):
pf_ext := os . file_ext ( pf ) . to_lower ( )
if context . is_ext_ignored ( pf , pf_ext ) {
context . elog ( ' > $ { @METHOD } i g n o r e d e x t e n s i o n : $ { pf_ext } , f o r f i l e : $ { pf_path } ' )
2021-04-04 17:05:06 +03:00
continue
}
f := os . join_path ( path , pf )
fullpath := os . real_path ( f )
mtime := os . file_last_mod_unix ( fullpath )
newstats << VFileStat { fullpath , mtime }
}
}
2021-04-05 11:04:31 +03:00
// always add the v compiler itself, so that if it is recompiled with `v self`
// the watcher will rerun the compilation too
newstats << VFileStat { context . vexe , os . file_last_mod_unix ( context . vexe ) }
2021-04-04 17:05:06 +03:00
return newstats
}
fn ( mut context Context ) get_changed_vfiles ( ) int {
mut changed := 0
newfiles := context . get_stats_for_affected_vfiles ( )
for vfs in newfiles {
mut found := false
for existing_vfs in context . vfiles {
if existing_vfs . path == vfs . path {
found = true
if existing_vfs . mtime != vfs . mtime {
2022-11-15 16:53:13 +03:00
context . elog ( ' > n e w u p d a t e s f o r f i l e : $ { vfs } ' )
2021-04-04 17:05:06 +03:00
changed ++
}
break
}
}
if ! found {
changed ++
continue
}
}
context . vfiles = newfiles
if changed > 0 {
2022-11-15 16:53:13 +03:00
context . elog ( ' > g e t _ c h a n g e d _ v f i l e s : $ { changed } ' )
2021-04-04 17:05:06 +03:00
}
return changed
}
fn change_detection_loop ( ocontext & Context ) {
2021-05-07 15:58:48 +03:00
mut context := unsafe { ocontext }
2021-04-04 17:05:06 +03:00
for {
if context . v_cycles >= max_v_cycles || context . scan_cycles >= max_scan_cycles {
context . is_exiting = true
context . kill_pgroup ( )
time . sleep ( 50 * time . millisecond )
exit ( 255 )
}
if context . is_exiting {
return
}
changes := context . get_changed_vfiles ( )
if changes > 0 {
context . rerun_channel <- RerunCommand . restart
}
time . sleep ( context . check_period_ms * time . millisecond )
context . scan_cycles ++
}
}
fn ( mut context Context ) kill_pgroup ( ) {
2022-05-20 18:30:16 +03:00
if unsafe { context . child_process == 0 } {
2021-04-04 17:05:06 +03:00
return
}
if context . child_process . is_alive ( ) {
context . child_process . signal_pgkill ( )
}
context . child_process . wait ( )
}
2021-05-16 17:50:10 +03:00
fn ( mut context Context ) run_before_cmd ( ) {
if context . cmd_before_run != ' ' {
2022-11-15 16:53:13 +03:00
context . elog ( ' > r u n _ b e f o r e _ c m d : " $ { context . cmd_before_run } " ' )
2021-05-16 17:50:10 +03:00
os . system ( context . cmd_before_run )
}
}
fn ( mut context Context ) run_after_cmd ( ) {
if context . cmd_after_run != ' ' {
2022-11-15 16:53:13 +03:00
context . elog ( ' > r u n _ a f t e r _ c m d : " $ { context . cmd_after_run } " ' )
2021-05-16 17:50:10 +03:00
os . system ( context . cmd_after_run )
}
}
2021-04-04 17:05:06 +03:00
fn ( mut context Context ) compilation_runner_loop ( ) {
2022-11-15 16:53:13 +03:00
cmd := ' " $ { context . vexe } " $ { context . opts . join ( ' ' ) } '
2021-04-04 17:05:06 +03:00
_ := <- context . rerun_channel
for {
2022-11-15 16:53:13 +03:00
context . elog ( ' > > l o o p : v _ c y c l e s : $ { context . v_cycles } ' )
2021-05-16 17:50:10 +03:00
if context . clear_terminal {
term . clear ( )
}
context . run_before_cmd ( )
2021-04-04 17:05:06 +03:00
timestamp := time . now ( ) . format_ss_milli ( )
context . child_process = os . new_process ( context . vexe )
context . child_process . use_pgroup = true
context . child_process . set_args ( context . opts )
context . child_process . run ( )
2021-05-16 13:23:27 +03:00
if ! context . silent {
2022-11-15 16:53:13 +03:00
eprintln ( ' $ { timestamp } : $ { cmd } | p i d : $ { context . child_process . pid : 7 d } | r e l o a d c y c l e : $ { context . v_cycles : 5 d } ' )
2021-05-16 13:23:27 +03:00
}
2021-04-04 17:05:06 +03:00
for {
2021-05-16 17:50:10 +03:00
mut notalive_count := 0
2021-04-04 17:05:06 +03:00
mut cmds := [ ] RerunCommand { }
for {
if context . is_exiting {
return
}
if ! context . child_process . is_alive ( ) {
context . child_process . wait ( )
2021-05-16 17:50:10 +03:00
notalive_count ++
if notalive_count == 1 {
// a short lived process finished, do cleanup:
context . run_after_cmd ( )
2022-06-15 18:00:02 +03:00
if context . keep_running {
break
}
2021-05-16 17:50:10 +03:00
}
2021-04-04 17:05:06 +03:00
}
select {
action := <- context . rerun_channel {
cmds << action
if action == . quit {
context . kill_pgroup ( )
return
}
}
2021-07-23 23:24:27 +03:00
100 * time . millisecond {
2021-04-04 17:05:06 +03:00
should_restart := RerunCommand . restart in cmds
cmds = [ ]
if should_restart {
// context.elog('>>>>>>>> KILLING $context.child_process.pid')
context . kill_pgroup ( )
break
}
}
}
}
if ! context . child_process . is_alive ( ) {
2022-11-15 16:53:13 +03:00
context . elog ( ' > c h i l d _ p r o c e s s i s n o l o n g e r a l i v e | n o t a l i v e _ c o u n t : $ { notalive_count } ' )
2021-04-04 17:05:06 +03:00
context . child_process . wait ( )
2021-05-09 21:31:04 +03:00
context . child_process . close ( )
2021-05-16 17:50:10 +03:00
if notalive_count == 0 {
// a long running process was killed, do cleanup:
context . run_after_cmd ( )
}
2021-04-04 17:05:06 +03:00
break
}
}
context . v_cycles ++
}
}
const ccontext = Context {
child_process : 0
}
fn main ( ) {
2021-04-25 09:18:06 +03:00
mut context := unsafe { & Context ( voidptr ( & ccontext ) ) }
2021-04-04 17:05:06 +03:00
context . pid = os . getpid ( )
context . vexe = os . getenv ( ' V E X E ' )
2021-04-28 12:23:23 +03:00
2023-03-16 23:00:47 +03:00
watch_pos := os . args . index ( ' w a t c h ' )
all_args_before_watch_cmd := os . args # [ 1 .. watch_pos ]
all_args_after_watch_cmd := os . args # [ watch_pos + 1 .. ]
// dump(os.getpid())
// dump(all_args_before_watch_cmd)
// dump(all_args_after_watch_cmd)
// Options after `run` should be ignored, since they are intended for the user program, not for the watcher.
// For example, `v watch run x.v -a -b -k', should pass all of -a -b -k to the compiled and run program.
only_watch_options , has_run := all_before ( ' r u n ' , all_args_after_watch_cmd )
2023-07-27 09:50:26 +03:00
2023-03-16 23:00:47 +03:00
mut fp := flag . new_flag_parser ( only_watch_options )
2021-04-28 12:23:23 +03:00
fp . application ( ' v w a t c h ' )
fp . version ( ' 0 . 0 . 2 ' )
fp . description ( ' C o l l e c t a l l . v f i l e s n e e d e d f o r a c o m p i l a t i o n , t h e n r e - r u n t h e c o m p i l a t i o n w h e n a n y o f t h e s o u r c e c h a n g e s . ' )
2021-05-16 13:23:27 +03:00
fp . arguments_description ( ' [ - - s i l e n t ] [ - - c l e a r ] [ - - i g n o r e . d b ] [ - - a d d / p a t h / t o / a / f i l e . v ] [ r u n ] p r o g r a m . v ' )
2021-04-28 12:23:23 +03:00
fp . allow_unknown_args ( )
2023-07-27 09:50:26 +03:00
2021-04-28 12:23:23 +03:00
context . is_worker = fp . bool ( ' v w a t c h w o r k e r ' , 0 , false , ' I n t e r n a l f l a g . U s e d t o d i s t i n g u i s h v w a t c h m a n a g e r a n d w o r k e r p r o c e s s e s . ' )
2021-05-16 13:23:27 +03:00
context . silent = fp . bool ( ' s i l e n t ' , ` s ` , false , ' B e m o r e s i l e n t ; d o n o t p r i n t t h e w a t c h t i m e s t a m p b e f o r e e a c h r e - r u n . ' )
2021-04-28 12:23:23 +03:00
context . clear_terminal = fp . bool ( ' c l e a r ' , ` c ` , false , ' C l e a r s t h e t e r m i n a l b e f o r e e a c h r e - r u n . ' )
2022-06-15 18:00:02 +03:00
context . keep_running = fp . bool ( ' k e e p ' , ` k ` , false , ' K e e p t h e p r o g r a m r u n n i n g . R e s t a r t i t a u t o m a t i c a l l y , i f i t e x i t s b y i t s e l f . U s e f u l f o r g g / u i a p p s . ' )
2023-07-27 09:50:26 +03:00
context . add_files = fp . string ( ' a d d ' , ` a ` , ' ' , ' A d d m o r e f i l e s t o b e w a t c h e d . U s e f u l w i t h ` v w a t c h - - a d d = / t m p / f e a t u r e . v r u n c m d / v / t m p / f e a t u r e . v ` , i f y o u c h a n g e * b o t h * t h e c o m p i l e r , a n d t h e f e a t u r e . v f i l e . ' ) . split_any ( ' , ' )
context . ignore_exts = fp . string ( ' i g n o r e ' , ` i ` , ' ' , ' I g n o r e f i l e s h a v i n g t h e s e e x t e n s i o n s . U s e f u l w i t h ` v w a t c h - - i g n o r e = . d b r u n s e r v e r . v ` , i f y o u r s e r v e r w r i t e s t o a n s q l i t e . d b f i l e i n t h e s a m e f o l d e r . ' ) . split_any ( ' , ' )
context . only_watch = fp . string ( ' o n l y - w a t c h ' , ` o ` , ' ' , ' W a t c h o n l y f i l e s m a t c h i n g t h e s e g l o b e p a t t e r n s . E x a m p l e f o r a m a r k d o w n r e n d e r e r p r o j e c t : ` v w a t c h - - o n l y - w a t c h = * . v , * . m d r u n . ` ' ) . split_any ( ' , ' )
2021-04-28 12:23:23 +03:00
show_help := fp . bool ( ' h e l p ' , ` h ` , false , ' S h o w t h i s h e l p s c r e e n . ' )
2021-05-16 17:50:10 +03:00
context . cmd_before_run = fp . string ( ' b e f o r e ' , 0 , ' ' , ' A c o m m a n d t o e x e c u t e * b e f o r e * e a c h r e - r u n . ' )
context . cmd_after_run = fp . string ( ' a f t e r ' , 0 , ' ' , ' A c o m m a n d t o e x e c u t e * a f t e r * e a c h r e - r u n . ' )
2021-04-28 12:23:23 +03:00
if show_help {
println ( fp . usage ( ) )
exit ( 0 )
}
remaining_options := fp . finalize ( ) or {
2022-11-15 16:53:13 +03:00
eprintln ( ' E r r o r : $ { err } ' )
2021-04-28 12:23:23 +03:00
exit ( 1 )
}
2023-03-16 23:00:47 +03:00
context . opts = [ ]
context . opts << all_args_before_watch_cmd
context . opts << remaining_options
if has_run {
2023-07-27 09:50:26 +03:00
context . opts << ' r u n '
2023-03-16 23:00:47 +03:00
context . opts << all_after ( ' r u n ' , all_args_after_watch_cmd )
}
2022-11-15 16:53:13 +03:00
context . elog ( ' > > > c o n t e x t . p i d : $ { context . pid } ' )
context . elog ( ' > > > c o n t e x t . v e x e : $ { context . vexe } ' )
context . elog ( ' > > > c o n t e x t . o p t s : $ { context . opts } ' )
context . elog ( ' > > > c o n t e x t . i s _ w o r k e r : $ { context . is_worker } ' )
context . elog ( ' > > > c o n t e x t . c l e a r _ t e r m i n a l : $ { context . clear_terminal } ' )
context . elog ( ' > > > c o n t e x t . a d d _ f i l e s : $ { context . add_files } ' )
context . elog ( ' > > > c o n t e x t . i g n o r e _ e x t s : $ { context . ignore_exts } ' )
2023-07-27 09:50:26 +03:00
context . elog ( ' > > > c o n t e x t . o n l y _ w a t c h : $ { context . only_watch } ' )
2021-04-04 17:05:06 +03:00
if context . is_worker {
context . worker_main ( )
} else {
2023-03-16 23:00:47 +03:00
context . manager_main ( all_args_before_watch_cmd , all_args_after_watch_cmd )
2021-04-04 17:05:06 +03:00
}
}
2023-03-16 23:00:47 +03:00
fn ( mut context Context ) manager_main ( all_args_before_watch_cmd [ ] string , all_args_after_watch_cmd [ ] string ) {
2021-04-04 17:05:06 +03:00
myexecutable := os . executable ( )
2023-03-16 23:00:47 +03:00
mut worker_opts := all_args_before_watch_cmd . clone ( )
worker_opts << [ ' w a t c h ' , ' - - v w a t c h w o r k e r ' ]
worker_opts << all_args_after_watch_cmd
2021-04-04 17:05:06 +03:00
for {
mut worker_process := os . new_process ( myexecutable )
worker_process . set_args ( worker_opts )
worker_process . run ( )
for {
if ! worker_process . is_alive ( ) {
worker_process . wait ( )
break
}
time . sleep ( 200 * time . millisecond )
}
if ! ( worker_process . code == 255 && worker_process . status == . exited ) {
2021-05-09 21:31:04 +03:00
worker_process . close ( )
2021-04-04 17:05:06 +03:00
break
}
2021-05-09 21:31:04 +03:00
worker_process . close ( )
2021-04-04 17:05:06 +03:00
}
}
fn ( mut context Context ) worker_main ( ) {
context . rerun_channel = chan RerunCommand { cap : 10 }
2021-05-09 21:31:04 +03:00
os . signal_opt ( . int , fn ( _ os . Signal ) {
2021-04-25 09:18:06 +03:00
mut context := unsafe { & Context ( voidptr ( & ccontext ) ) }
2021-04-04 17:05:06 +03:00
context . is_exiting = true
context . kill_pgroup ( )
2021-05-09 21:31:04 +03:00
} ) or { panic ( err ) }
2022-11-05 10:46:40 +03:00
spawn context . compilation_runner_loop ( )
2021-04-04 17:05:06 +03:00
change_detection_loop ( context )
}
2023-03-16 23:00:47 +03:00
fn all_before ( needle string , all [ ] string ) ( [ ] string , bool ) {
needle_pos := all . index ( needle )
if needle_pos == - 1 {
return all , false
}
2023-07-27 09:50:26 +03:00
return all # [ .. needle_pos ] , true
2023-03-16 23:00:47 +03:00
}
fn all_after ( needle string , all [ ] string ) [ ] string {
needle_pos := all . index ( needle )
if needle_pos == - 1 {
return all
}
return all # [ needle_pos + 1 .. ]
}