@@ -130,22 +130,57 @@ function _find_inline_section(source::String, kind::Symbol)
130130 line_start = findprev (isequal (' \n ' ), source, first (begin_idx))
131131 line_start = line_start === nothing ? 1 : line_start + 1
132132
133+ # Check if there's a #region marker on the line before
134+ region_marker = " #region $(kind_str) "
135+ if line_start > 1
136+ prev_line_end = line_start - 1 # This is the newline character
137+ prev_line_start = findprev (isequal (' \n ' ), source, prev_line_end - 1 )
138+ prev_line_start = prev_line_start === nothing ? 1 : prev_line_start + 1
139+ prev_line = source[prev_line_start: prev_line_end- 1 ]
140+ if strip (prev_line) == region_marker
141+ line_start = prev_line_start
142+ end
143+ end
144+
133145 # Determine format by checking if there's a #= after the begin marker
134146 # Look at content between begin and end markers
135147 content_start = last (begin_idx) + 1
136148 content_end = first (end_idx) - 1
137149 content_between = source[content_start: content_end]
138150 format = contains (content_between, " #=" ) ? :multiline : :line
139151
140- # Find the newline after the end marker (if it exists)
152+ # Find the newline after the end marker
141153 char_after_end = last (end_idx) < lastindex (source) ? source[nextind (source, last (end_idx))] : nothing
154+ included_newline = false
142155 span_end_pos = if char_after_end == ' \n ' || (char_after_end == ' \r ' && last (end_idx) + 1 < lastindex (source) && source[last (end_idx) + 2 ] == ' \n ' )
143156 # Include the newline in the span
157+ included_newline = true
144158 char_after_end == ' \r ' ? last (end_idx) + 2 : last (end_idx) + 1
145159 else
146160 last (end_idx)
147161 end
148162
163+ # Check if there's a #endregion marker on the next line after end marker
164+ endregion_marker = " #endregion $(kind_str) "
165+ if included_newline && span_end_pos < lastindex (source)
166+ # If we included a newline, start looking at the next character
167+ next_line_start = span_end_pos + 1
168+ next_line_end = findnext (isequal (' \n ' ), source, next_line_start)
169+ if next_line_end != = nothing
170+ next_line = source[next_line_start: next_line_end- 1 ]
171+ if strip (next_line) == endregion_marker
172+ # Include the #endregion line and its newline in the span
173+ span_end_pos = next_line_end + 1
174+ end
175+ elseif next_line_start <= lastindex (source)
176+ # No newline found, check if rest of file is the endregion marker
177+ next_line = source[next_line_start: end ]
178+ if strip (next_line) == endregion_marker
179+ span_end_pos = lastindex (source)
180+ end
181+ end
182+ end
183+
149184 return (
150185 span_start = line_start,
151186 span_end = span_end_pos,
@@ -154,21 +189,43 @@ function _find_inline_section(source::String, kind::Symbol)
154189 )
155190end
156191
157- function update_inline_project ! (path:: AbstractString , toml:: String )
192+ function _update_inline_section ! (path:: AbstractString , kind :: Symbol , toml:: String )
158193 source = read (path, String)
159- section = _find_inline_section (source, :project )
194+ section = _find_inline_section (source, kind )
160195
161196 if section === nothing
162- # No existing section, add one at the beginning
197+ # No existing section, add appropriately
163198 newline = contains (source, " \r\n " ) ? " \r\n " : " \n "
164- replacement = _render_inline_block (:project , toml, newline, :line )
165- new_source = isempty (source) ? replacement : replacement * newline * source
199+ replacement = _render_inline_block (kind, toml, newline, :line )
200+
201+ if kind === :project
202+ # Project goes at the beginning
203+ new_source = isempty (source) ? replacement : replacement * newline * source
204+ else
205+ # Manifest goes at the bottom
206+ project_section = _find_inline_section (source, :project )
207+ if project_section === nothing
208+ # No project section either, add empty project at top and manifest at bottom
209+ project_block = _render_inline_block (:project , " " , newline, :line )
210+ if isempty (source)
211+ new_source = project_block * newline * replacement
212+ else
213+ new_source = project_block * newline * source * newline * replacement
214+ end
215+ else
216+ # Add manifest at the bottom of the file
217+ replacement = _render_inline_block (kind, toml, project_section. newline, project_section. format)
218+ new_source = source * project_section. newline * replacement
219+ end
220+ end
166221 else
167222 # Replace existing section
168- replacement = _render_inline_block (:project , toml, section. newline, section. format)
223+ replacement = _render_inline_block (kind , toml, section. newline, section. format)
169224 prefix = section. span_start == firstindex (source) ? " " : source[firstindex (source): prevind (source, section. span_start)]
170225 suffix = section. span_end == lastindex (source) ? " " : source[nextind (source, section. span_end): lastindex (source)]
171- new_source = prefix * replacement * suffix
226+ # Add a blank line after the section if there's content after it
227+ separator = ! isempty (suffix) && ! startswith (suffix, section. newline) ? section. newline : " "
228+ new_source = prefix * replacement * separator * suffix
172229 end
173230
174231 open (path, " w" ) do io
@@ -177,42 +234,29 @@ function update_inline_project!(path::AbstractString, toml::String)
177234 return nothing
178235end
179236
180- function update_inline_manifest ! (path:: AbstractString , toml :: String )
237+ function remove_inline_section ! (path:: AbstractString , kind :: Symbol )
181238 source = read (path, String)
182- project_section = _find_inline_section (source, :project )
183- manifest_section = _find_inline_section (source, :manifest )
184-
185- if manifest_section === nothing
186- # No existing manifest section, add one at the bottom
187- if project_section === nothing
188- # No project section either, add project at top and manifest at bottom
189- newline = contains (source, " \r\n " ) ? " \r\n " : " \n "
190- project_block = _render_inline_block (:project , " " , newline, :line )
191- manifest_block = _render_inline_block (:manifest , toml, newline, :line )
192- if isempty (source)
193- new_source = project_block * newline * manifest_block
194- else
195- new_source = project_block * newline * source * newline * manifest_block
196- end
197- else
198- # Add manifest at the bottom of the file
199- replacement = _render_inline_block (:manifest , toml, project_section. newline, project_section. format)
200- new_source = source * project_section. newline * replacement
201- end
202- else
203- # Replace existing manifest section
204- replacement = _render_inline_block (:manifest , toml, manifest_section. newline, manifest_section. format)
205- prefix = manifest_section. span_start == firstindex (source) ? " " : source[firstindex (source): prevind (source, manifest_section. span_start)]
206- suffix = manifest_section. span_end == lastindex (source) ? " " : source[nextind (source, manifest_section. span_end): lastindex (source)]
207- new_source = prefix * replacement * suffix
208- end
239+ section = _find_inline_section (source, kind)
209240
210- open (path, " w" ) do io
211- write (io, new_source)
241+ if section != = nothing
242+ prefix = section. span_start == firstindex (source) ? " " : source[firstindex (source): prevind (source, section. span_start)]
243+ suffix = section. span_end >= lastindex (source) ? " " : source[nextind (source, section. span_end): lastindex (source)]
244+ new_source = prefix * suffix
245+ open (path, " w" ) do io
246+ write (io, new_source)
247+ end
212248 end
213249 return nothing
214250end
215251
252+ function update_inline_project! (path:: AbstractString , toml:: String )
253+ return _update_inline_section! (path, :project , toml)
254+ end
255+
256+ function update_inline_manifest! (path:: AbstractString , toml:: String )
257+ return _update_inline_section! (path, :manifest , toml)
258+ end
259+
216260# ##############
217261# PackageSpec #
218262# ##############
@@ -592,10 +636,38 @@ function EnvCache(env::Union{Nothing, String} = nothing)
592636 end
593637
594638 dir = abspath (project_dir)
595- # For .jl files, always use the same file for both project and manifest (inline)
639+
640+ # Save the original project before any modifications
641+ original_project = deepcopy (project)
642+
643+ # For .jl files, handle inline_manifest flag and fix inconsistent states
596644 if endswith (project_file, " .jl" )
597- manifest_file = project_file
645+ inline_manifest = get (project. other, " inline_manifest" , true ):: Bool
646+
647+ # Case 1: inline_manifest=false but no manifest path
648+ # User wants external manifest but hasn't set it up yet
649+ if ! inline_manifest && project. manifest === nothing
650+ # Generate a new UUID and set manifest path
651+ script_uuid = string (uuid4 ())
652+ script_name = splitext (basename (project_file))[1 ]
653+ manifest_file = joinpath (depots1 (), " environments" , " scripts" , " $(script_name) _$(script_uuid) " , " Manifest.toml" )
654+ project. manifest = manifest_file
655+ # Case 2: inline_manifest=true (or default) but has manifest path
656+ # User wants inline manifest but still has external path set
657+ elseif inline_manifest && project. manifest != = nothing
658+ # Load from external path for reading this time
659+ manifest_file = isabspath (project. manifest) ? project. manifest : abspath (dir, project. manifest)
660+ # But clear the path so it gets written inline later
661+ # (We'll clean up the external file in write_env)
662+ # Case 3: inline_manifest=false and has manifest path (consistent state)
663+ elseif ! inline_manifest && project. manifest != = nothing
664+ manifest_file = isabspath (project. manifest) ? project. manifest : abspath (dir, project. manifest)
665+ # Case 4: inline_manifest=true and no manifest path (consistent state, default)
666+ else
667+ manifest_file = project_file
668+ end
598669 else
670+ # For regular .toml files, use standard logic
599671 manifest_file = manifest_file != = nothing ?
600672 (isabspath (manifest_file) ? manifest_file : abspath (dir, manifest_file)) :
601673 manifestfile_path (dir):: String
@@ -611,7 +683,7 @@ function EnvCache(env::Union{Nothing, String} = nothing)
611683 project,
612684 workspace,
613685 manifest,
614- deepcopy (project) ,
686+ original_project ,
615687 deepcopy (manifest),
616688 )
617689
@@ -1565,12 +1637,45 @@ function write_env(
15651637 pkgerror (" Cannot modify a readonly environment. The project at $(env. project_file) is marked as readonly." )
15661638 end
15671639
1640+ # Handle transitions for portable scripts
1641+ transitioning_to_inline = false
1642+ if endswith (env. project_file, " .jl" )
1643+ inline_manifest = get (env. project. other, " inline_manifest" , true ):: Bool
1644+
1645+ # If transitioning to inline and we had an external manifest, clean it up
1646+ if inline_manifest && env. project. manifest != = nothing
1647+ transitioning_to_inline = true
1648+ external_manifest_path = isabspath (env. project. manifest) ? env. project. manifest :
1649+ abspath (dirname (env. project_file), env. project. manifest)
1650+ # Clear the manifest path so it writes inline
1651+ env. project. manifest = nothing
1652+ # Update manifest_file to point to the script file for inline writing
1653+ env. manifest_file = env. project_file
1654+ # Clean up external manifest directory
1655+ external_dir = dirname (external_manifest_path)
1656+ if isdir (external_dir)
1657+ rm (external_dir; recursive= true , force= true )
1658+ end
1659+ end
1660+ end
1661+
15681662 if (env. project != env. original_project) && (! skip_writing_project)
15691663 write_project (env, skip_readonly_check)
15701664 end
1571- if env. manifest != env. original_manifest
1665+ # Force manifest write when transitioning to inline, even if manifest hasn't changed
1666+ if env. manifest != env. original_manifest || transitioning_to_inline
15721667 write_manifest (env)
15731668 end
1669+
1670+ # Remove inline manifest section if we have external manifest
1671+ if endswith (env. project_file, " .jl" )
1672+ inline_manifest = get (env. project. other, " inline_manifest" , true ):: Bool
1673+ if ! inline_manifest
1674+ # Remove the inline manifest section since we're using external
1675+ remove_inline_section! (env. project_file, :manifest )
1676+ end
1677+ end
1678+
15741679 return update_undo && Pkg. API. add_snapshot_to_undo (env)
15751680end
15761681
0 commit comments