|
6 | 6 | using StructuredXmlEditor.View; |
7 | 7 | using System; |
8 | 8 | using System.Collections.Generic; |
| 9 | +using System.Diagnostics; |
9 | 10 | using System.IO; |
10 | 11 | using System.Linq; |
11 | 12 | using System.Reflection; |
12 | 13 | using System.Text; |
13 | 14 | using System.Threading.Tasks; |
| 15 | +using System.Windows.Input; |
14 | 16 | using System.Windows.Media; |
15 | 17 | using System.Xml; |
16 | 18 | using System.Xml.Linq; |
@@ -93,8 +95,10 @@ public DataTransformerTool(Workspace workspace) : base(workspace, "Data Transfor |
93 | 95 | TextEditor.Text = "{el||}"; |
94 | 96 | TextEditor.Foreground = new SolidColorBrush(Color.FromRgb(255, 255, 255)); |
95 | 97 | TextEditor.Background = Brushes.Transparent; |
96 | | - TextEditor.Document.TextChanged += (e, args) => |
| 98 | + TextEditor.TextArea.TextEntered += (e, args) => |
97 | 99 | { |
| 100 | + Autocomplete(e, args); |
| 101 | + |
98 | 102 | UpdatePreview(); |
99 | 103 | }; |
100 | 104 |
|
@@ -226,6 +230,161 @@ public void SaveAll() |
226 | 230 | } |
227 | 231 | Return(); |
228 | 232 | } |
| 233 | + |
| 234 | + //----------------------------------------------------------------------- |
| 235 | + public void Autocomplete(object sender, TextCompositionEventArgs e) |
| 236 | + { |
| 237 | + if (e.Text == ">") |
| 238 | + { |
| 239 | + //auto-insert closing element |
| 240 | + int offset = TextEditor.CaretOffset; |
| 241 | + string s = GetElementAtCursor(TextEditor.Text, offset - 1); |
| 242 | + if (!string.IsNullOrWhiteSpace(s) && "!--" != s) |
| 243 | + { |
| 244 | + if (!IsClosingElement(TextEditor.Text, offset - 1, s)) |
| 245 | + { |
| 246 | + string endElement = "</" + s + ">"; |
| 247 | + var rightOfCursor = TextEditor.Text.Substring(offset, Math.Max(0, Math.Min(endElement.Length + 50, TextEditor.Text.Length) - offset - 1)).TrimStart(); |
| 248 | + if (!rightOfCursor.StartsWith(endElement)) |
| 249 | + { |
| 250 | + TextEditor.TextArea.Document.Insert(offset, endElement); |
| 251 | + TextEditor.CaretOffset = offset; |
| 252 | + } |
| 253 | + } |
| 254 | + } |
| 255 | + } |
| 256 | + else if (e.Text == "/") |
| 257 | + { |
| 258 | + int offset = TextEditor.CaretOffset; |
| 259 | + if (TextEditor.Text.Length > offset + 2 && TextEditor.Text[offset] == '>') |
| 260 | + { |
| 261 | + //remove closing tag if exist |
| 262 | + string s = GetElementAtCursor(TextEditor.Text, offset - 1); |
| 263 | + if (!string.IsNullOrWhiteSpace(s)) |
| 264 | + { |
| 265 | + //search closing end tag. Element must be empty (whitespace allowed) |
| 266 | + //"<hallo> </hallo>" --> enter '/' --> "<hallo/> " |
| 267 | + string expectedEndTag = "</" + s + ">"; |
| 268 | + for (int i = offset + 1; i < TextEditor.Text.Length - expectedEndTag.Length + 1; i++) |
| 269 | + { |
| 270 | + if (!char.IsWhiteSpace(TextEditor.Text[i])) |
| 271 | + { |
| 272 | + if (TextEditor.Text.Substring(i, expectedEndTag.Length) == expectedEndTag) |
| 273 | + { |
| 274 | + //remove already existing endTag |
| 275 | + TextEditor.Document.Remove(i, expectedEndTag.Length); |
| 276 | + } |
| 277 | + break; |
| 278 | + } |
| 279 | + } |
| 280 | + } |
| 281 | + } |
| 282 | + } |
| 283 | + else if (e.Text == "{") |
| 284 | + { |
| 285 | + int offset = TextEditor.CaretOffset; |
| 286 | + TextEditor.TextArea.Document.Insert(offset, "el||}"); |
| 287 | + TextEditor.CaretOffset = offset+3; |
| 288 | + } |
| 289 | + } |
| 290 | + |
| 291 | + //----------------------------------------------------------------------- |
| 292 | + /// <summary> |
| 293 | + /// Source: https://xsemmel.codeplex.com |
| 294 | + /// </summary> |
| 295 | + /// <param name="xml"></param> |
| 296 | + /// <param name="offset"></param> |
| 297 | + /// <returns></returns> |
| 298 | + public static string GetElementAtCursor(string xml, int offset) |
| 299 | + { |
| 300 | + if (offset == xml.Length) |
| 301 | + { |
| 302 | + offset--; |
| 303 | + } |
| 304 | + int startIdx = xml.LastIndexOf('<', offset); |
| 305 | + if (startIdx < 0) return null; |
| 306 | + |
| 307 | + if (startIdx < xml.Length && xml[startIdx + 1] == '/') |
| 308 | + { |
| 309 | + startIdx = startIdx + 1; |
| 310 | + } |
| 311 | + |
| 312 | + int endIdx1 = xml.IndexOf(' ', startIdx); |
| 313 | + if (endIdx1 == -1 /*|| endIdx1 > offset*/) endIdx1 = int.MaxValue; |
| 314 | + |
| 315 | + int endIdx2 = xml.IndexOf('>', startIdx); |
| 316 | + if (endIdx2 == -1 /*|| endIdx2 > offset*/) |
| 317 | + { |
| 318 | + endIdx2 = int.MaxValue; |
| 319 | + } |
| 320 | + else |
| 321 | + { |
| 322 | + if (endIdx2 < xml.Length && xml[endIdx2 - 1] == '/') |
| 323 | + { |
| 324 | + endIdx2 = endIdx2 - 1; |
| 325 | + } |
| 326 | + } |
| 327 | + |
| 328 | + int endIdx = Math.Min(endIdx1, endIdx2); |
| 329 | + if (endIdx2 > 0 && endIdx2 < int.MaxValue && endIdx > startIdx) |
| 330 | + { |
| 331 | + return xml.Substring(startIdx + 1, endIdx - startIdx - 1); |
| 332 | + } |
| 333 | + else |
| 334 | + { |
| 335 | + return null; |
| 336 | + } |
| 337 | + } |
| 338 | + |
| 339 | + //----------------------------------------------------------------------- |
| 340 | + /// <summary> |
| 341 | + /// Source: https://xsemmel.codeplex.com |
| 342 | + /// Liefert true falls das Element beim offset ein schließendes Element ist, |
| 343 | + /// also </x> oder <x/> |
| 344 | + /// </summary> |
| 345 | + /// <param name="xml"></param> |
| 346 | + /// <param name="offset"></param> |
| 347 | + /// <param name="elementName">optional, elementName = GetElementAtCursor(xml, offset)</param> |
| 348 | + /// <returns></returns> |
| 349 | + public static bool IsClosingElement(string xml, int offset, string elementName = null) |
| 350 | + { |
| 351 | + if (elementName == null) |
| 352 | + { |
| 353 | + elementName = GetElementAtCursor(xml, offset); |
| 354 | + } |
| 355 | + else |
| 356 | + { |
| 357 | + Debug.Assert(GetElementAtCursor(xml, offset) == elementName); |
| 358 | + } |
| 359 | + |
| 360 | + if (offset >= xml.Length || offset < 0) |
| 361 | + { |
| 362 | + return false; |
| 363 | + } |
| 364 | + int idxOpen = xml.LastIndexOf('<', offset); |
| 365 | + if (idxOpen < 0) |
| 366 | + { |
| 367 | + return false; |
| 368 | + } |
| 369 | + |
| 370 | + int idxClose = xml.LastIndexOf('>', offset); |
| 371 | + if (idxClose > 0) |
| 372 | + { |
| 373 | + if (idxClose > idxOpen && idxClose < offset - 1) |
| 374 | + { |
| 375 | + return false; |
| 376 | + } |
| 377 | + } |
| 378 | + |
| 379 | + string prefix = xml.Substring(idxOpen, offset - idxOpen); |
| 380 | + if (prefix.Contains("/")) |
| 381 | + { |
| 382 | + return true; |
| 383 | + } |
| 384 | + |
| 385 | + |
| 386 | + return false; |
| 387 | + } |
229 | 388 | } |
230 | 389 |
|
231 | 390 | //----------------------------------------------------------------------- |
|
0 commit comments