2222import java .util .HashMap ;
2323import java .util .List ;
2424import java .util .Map ;
25+ import java .util .Set ;
2526import java .util .UUID ;
2627import java .util .stream .Stream ;
2728
@@ -49,6 +50,12 @@ public static Path rename(ScratchProject project, Path asset, String newBaseName
4950
5051 public static Path rename (ScratchProject project , Path asset , String newBaseName ,
5152 FileUsages .Mode mode ) throws IOException {
53+ return rename (project , asset , newBaseName , mode , Set .of ());
54+ }
55+
56+ /** {@link #rename(ScratchProject, Path, String, FileUsages.Mode)}, leaving the {@code skipped} usages as they are. */
57+ public static Path rename (ScratchProject project , Path asset , String newBaseName ,
58+ FileUsages .Mode mode , Set <FileUsages .Usage > skipped ) throws IOException {
5259 Path source = validate (project , asset );
5360 if (!newBaseName .matches ("[A-Za-z0-9_][A-Za-z0-9_ -]*" )) {
5461 throw new IOException ("Use letters, digits, spaces, hyphens or underscores" );
@@ -60,7 +67,7 @@ public static Path rename(ScratchProject project, Path asset, String newBaseName
6067 if (source .equals (target ) || Files .exists (target )) {
6168 throw new IOException ("Choose an unused asset name" );
6269 }
63- return relocate (project , source , target , mode );
70+ return relocate (project , source , target , mode , skipped );
6471 }
6572
6673 /**
@@ -98,6 +105,12 @@ static List<FileUsages.Usage> usages(ScratchProject project, Path path) throws I
98105 return distinct (usages );
99106 }
100107
108+ /** Whether the user turned off the usage of this string (its path sits inside the quotes). */
109+ private static boolean isSkipped (Set <FileUsages .Usage > skipped , Path file , Span span ) {
110+ return skipped .stream ().anyMatch (u -> u .updatable () && u .file ().equals (file )
111+ && u .start () == span .start () + 1 );
112+ }
113+
101114 private static List <FileUsages .Usage > distinct (List <FileUsages .Usage > usages ) {
102115 Map <String , FileUsages .Usage > seen = new java .util .LinkedHashMap <>();
103116 for (FileUsages .Usage usage : usages ) {
@@ -129,15 +142,25 @@ static Path relocate(ScratchProject project, Path source, Path target) throws IO
129142 */
130143 static Path relocate (ScratchProject project , Path source , Path target , FileUsages .Mode mode )
131144 throws IOException {
132- Path moved = relocateFiles (project , source , target , mode );
145+ return relocate (project , source , target , mode , Set .of ());
146+ }
147+
148+ /**
149+ * {@link #relocate(ScratchProject, Path, Path, FileUsages.Mode)}, leaving the
150+ * {@code skipped} usages (from {@link FileUsages#find}) as they are: the user
151+ * turned them off in the preview.
152+ */
153+ static Path relocate (ScratchProject project , Path source , Path target , FileUsages .Mode mode ,
154+ Set <FileUsages .Usage > skipped ) throws IOException {
155+ Path moved = relocateFiles (project , source , target , mode , skipped );
133156 // the editors' undo steps go along
134157 org .openpatch .scratch4j .core .io .EditorHistory .move (project .root ().toRealPath (), source ,
135158 moved );
136159 return moved ;
137160 }
138161
139162 private static Path relocateFiles (ScratchProject project , Path source , Path target ,
140- FileUsages .Mode mode ) throws IOException {
163+ FileUsages .Mode mode , Set < FileUsages . Usage > skipped ) throws IOException {
141164 boolean folder = Files .isDirectory (source );
142165 if (folder ) {
143166 for (Path file : files (source )) {
@@ -154,7 +177,8 @@ private static Path relocateFiles(ScratchProject project, Path source, Path targ
154177 }
155178 Map <Path , String > original = new HashMap <>();
156179 Map <Path , List <Span >> spans = new HashMap <>();
157- Map <Span , String > replacements = new HashMap <>();
180+ // per file: two files can have a path at the same offsets
181+ Map <Path , Map <Span , String >> replacements = new HashMap <>();
158182 Map <String , String > refs = new HashMap <>();
159183 List <FileUsages .Usage > manual = new ArrayList <>();
160184 for (Path file : files (source )) {
@@ -166,9 +190,13 @@ private static Path relocateFiles(ScratchProject project, Path source, Path targ
166190 manual .addAll (scan .manual ());
167191 original .putAll (scan .original ());
168192 for (Map .Entry <Path , List <Span >> entry : scan .literals ().entrySet ()) {
169- spans .computeIfAbsent (entry .getKey (), key -> new ArrayList <>())
170- .addAll (entry .getValue ());
171- entry .getValue ().forEach (span -> replacements .put (span , "\" " + newRef + "\" " ));
193+ List <Span > kept = entry .getValue ().stream ()
194+ .filter (span -> !isSkipped (skipped , entry .getKey (), span ))
195+ .toList ();
196+ if (kept .isEmpty ()) continue ;
197+ spans .computeIfAbsent (entry .getKey (), key -> new ArrayList <>()).addAll (kept );
198+ kept .forEach (span -> replacements .computeIfAbsent (entry .getKey (), key -> new HashMap <>())
199+ .put (span , "\" " + newRef + "\" " ));
172200 }
173201 }
174202 if (folder ) {
@@ -184,11 +212,13 @@ private static Path relocateFiles(ScratchProject project, Path source, Path targ
184212 StringBuilder code = new StringBuilder (original .get (entry .getKey ()));
185213 entry .getValue ().stream ().distinct ()
186214 .sorted (Comparator .comparingInt (Span ::start ).reversed ())
187- .forEach (span -> code .replace (span .start (), span .end (), replacements .get (span )));
215+ .forEach (span -> code .replace (span .start (), span .end (),
216+ replacements .get (entry .getKey ()).get (span )));
188217 changes .put (entry .getKey (), code .toString ());
189218 }
190219 String oldSplash = project .settings ().splashLogo ;
191- String newSplash = oldSplash == null ? null : refs .get (oldSplash );
220+ boolean keepSplash = skipped .stream ().anyMatch (u -> u .updatable () && u .line () == 0 );
221+ String newSplash = oldSplash == null || keepSplash ? null : refs .get (oldSplash );
192222 List <Path > written = new ArrayList <>();
193223 boolean done = false ;
194224 try {
0 commit comments