public CobolTextLine(ITextLine textLine, ColumnsLayout columnsLayout) { // Reuse the external text line object this.textLine = textLine; LineIndex = textLine.LineIndex; ColumnsLayout = columnsLayout; // Scan the line to find the indexes of the different areas // - 72 columns reference format if (columnsLayout == ColumnsLayout.CobolReferenceFormat) { MapVariableLengthLineWithReferenceFormat(); } // - free format and unlimited line length else { MapVariableLengthLineWithFreeFormat(); } // Study the indicator char to determine the type of the line ComputeLineTypeFromIndicator(); // First text analysis phase of the incremental compilation process CompilationStep = CompilationStep.Text; }
/// <summary> /// </summary> public ITextLine GetTextLineAt(Double yOffset) { if (Double.IsNaN(yOffset)) { throw new ArgumentOutOfRangeException("yOffset"); } IList <ITextLine> textLines = _textView.TextLines; Int32 count = textLines.Count; if (count == 0) { return(null); } for (Int32 i = 0; i < count; i++) { ITextLine line = textLines[i]; if (line.VerticalOffset <= yOffset) { if (((line.VerticalOffset + line.Height) + 0.01) >= yOffset) { return(line); } } else { return(null); } } return(textLines[count - 1]); }
public IAnchorPos CreateAnchor(ITextLine line) { var anchor = new BasicAnchor(line); _anchors.Add(anchor); return(anchor); }
private bool IsInTable(ITextLine obj) { var item = (ITextLine)table[obj.Line]; if (item != null) { return(item.IsEqualTo(obj)); } return(false); }
private static ITextLine SetComment(ITextLine line, bool? isComment) { if (isComment == true) return Comment(line); else if (isComment == false) return Uncomment(line); else // null return line; }
public override ITextLine Ensure(ITextLine line) { if (line is StringStorageLine) { return line; } else { IDecodedTextLine decodedLine = line.Decode_MustDispose(); return new StringStorageLine(decodedLine.Value); } }
private void UpdateCaret() { Int32 textInsertionIndex = this.Position.TextInsertionIndex; ITextLine textLineContaining = _textViewHelper.GetTextLineContaining(textInsertionIndex); if (textLineContaining == null) { base.Height = 0; if ((_editorView.TextLines.Count == 0) || (textInsertionIndex < _editorView.TextLines[0].LineSpan.Start)) { Canvas.SetTop(this, 0); } else { Canvas.SetTop(this, _editorView.ViewportHeight); } Canvas.SetLeft(this, 0); } else { Canvas.SetTop(this, textLineContaining.VerticalOffset); base.Height = textLineContaining.Height; Genetibase.Windows.Controls.Editor.Text.View.TextBounds characterBounds = textLineContaining.GetCharacterBounds(this.Position.CharacterIndex); if (!_overwriteMode) { if (this.Placement == CaretPlacement.LeftOfCharacter) { Canvas.SetLeft(this, characterBounds.Left); } else { Canvas.SetLeft(this, characterBounds.Right); } } else if (characterBounds.Right > characterBounds.Left) { base.Width = characterBounds.Right - characterBounds.Left; Canvas.SetLeft(this, characterBounds.Left); } else if (characterBounds.Left > characterBounds.Right) { base.Width = characterBounds.Left - characterBounds.Right; Canvas.SetLeft(this, characterBounds.Right); } else { base.Width = _defaultOverwriteCaretWidth; Canvas.SetLeft(this, characterBounds.Left); } _blinkAnimationClock.Controller.Begin(); } base.InvalidateVisual(); }
/// <summary> /// Access the document like a char array /// (first char at index 0 at the beginning of document) /// </summary> public char CharAt(int offset) { if (offset < 0 || offset >= charsCount) { throw new InvalidOperationException("offset must be a number between 0 and " + charsCount); } int indexOfCharInLine; ITextLine line = GetLineByOffset(offset, out indexOfCharInLine); return(line.Text[indexOfCharInLine]); }
private void Text_ScrollToLineRequested(object sender, ITextLine line) { if (_parentScrollViewer == null) { return; } var lineIndex = Text.GetLineNo(line); var verticalOffset = lineIndex * LineHeight; _parentScrollViewer.ChangeView(null, verticalOffset, null); }
public override ITextLine Ensure(ITextLine line) { if (line is Utf8GapStorageLine) { return line; } else { IDecodedTextLine decodedLine = line.Decode_MustDispose(); return new Utf8GapStorageLine(Encoding.UTF8.GetBytes(decodedLine.Value)); } }
/// <summary> /// Offset of the first char of this line in the document /// </summary> public int FindStartOffsetOfLine(ITextLine line) { IDocumentLine docLine = line.LineTrackingReferenceInSourceDocument as IDocumentLine; if (docLine != null) { return(docLine.Offset); } else { return(-1); } }
/// <summary> /// The first line has the index 0 /// </summary> public int FindIndexOfLine(ITextLine line) { IDocumentLine docLine = line.LineTrackingReferenceInSourceDocument as IDocumentLine; if (docLine != null) { return(docLine.LineNumber); } else { return(-1); } }
/// <summary> /// The first line has the index 0 /// </summary> public int FindIndexOfLine(ITextLine line) { ReadOnlyTextLine roTextLine = line as ReadOnlyTextLine; if (roTextLine != null) { return(roTextLine.LineIndex); } else { return(-1); } }
/// <summary> /// Offset of the first char of this line in the document /// </summary> public int FindStartOffsetOfLine(ITextLine line) { ReadOnlyTextLine roTextLine = line as ReadOnlyTextLine; if (roTextLine != null) { return(roTextLine.StartOffset); } else { return(-1); } }
private static ITextLine Comment(ITextLine line) { var cobol = line as CobolTextLine; if (cobol != null) { StringBuilder text = new StringBuilder(cobol.Text); text[6] = '*'; var lines = CobolTextLine.Create("*"+cobol.SourceText, cobol.ColumnsLayout, cobol.InitialLineIndex); foreach(var l in lines) return l;// there's only one in the collection throw new System.NotImplementedException("I should have at least one item!"); } else { return new TextLineSnapshot(line.InitialLineIndex, "*"+line.Text, null); } }
/// <summary> /// </summary> public Boolean FindTextLine(Int32 characterPosition, out Int32 index) { index = 0; IList <ITextLine> textLines = _textView.TextLines; Int32 count = textLines.Count; if (count != 0) { if (characterPosition == _textView.TextBuffer.Length) { ITextLine line = textLines[count - 1]; if ((line.NewlineLength == 0) && (characterPosition == line.LineSpan.End)) { index = count - 1; return(true); } } if (characterPosition < textLines[0].LineSpan.Start) { return(false); } if (characterPosition >= textLines[count - 1].LineSpan.End) { index = count; return(false); } Int32 num2 = 0; Int32 num3 = count - 1; while (num3 >= num2) { Int32 num4 = (num2 + num3) / 2; ITextLine line2 = textLines[num4]; if (characterPosition < line2.LineSpan.Start) { num3 = num4 - 1; } else { if (characterPosition >= line2.LineSpan.End) { num2 = num4 + 1; continue; } index = num4; return(true); } } index = num2; } return(false); }
private void ExpandBounds(ITextLine line, Span span, ref Double leftEdge, ref Double rightEdge) { foreach (TextBounds bounds in line.GetTextBounds(span)) { if (bounds.Left < leftEdge) { leftEdge = bounds.Left; } if (bounds.Right > rightEdge) { rightEdge = bounds.Right; } } }
/// <summary> /// Produces a commented or an uncommeneted text line from a text line /// </summary> /// <param name="line">the line</param> /// <param name="isComment">if null then this is the identity function, if true a commented line is produced, otherwise an uncommented line is produced.</param> /// <returns>if isComment is null the same line is return, if true a commneted line is returned otherwise an uncommented line</returns> private static ITextLine SetComment(ITextLine line, bool?isComment) { if (isComment == true) { return(Comment(line)); } else if (isComment == false) { return(Uncomment(line)); } else // null { return(line); } }
private static ITextLine Uncomment(ITextLine line) { var cobol = line as CobolTextLine; if (cobol != null) { StringBuilder text = new StringBuilder(cobol.Text); text[6] = ' '; var lines = CobolTextLine.Create(text.ToString(), cobol.ColumnsLayout, cobol.InitialLineIndex); foreach(var l in lines) return l;// there's only one in the collection throw new System.NotImplementedException("I should have at least one item!"); } else { StringBuilder text = new StringBuilder(line.Text); int index = line.Text.IndexOf('*'); text[index] = ' '; return new TextLineSnapshot(line.InitialLineIndex, text.ToString(), null); } }
private Int32 GetIndexOfTextLine(ITextLine textLine) { Int32 num; if (textLine == null) { throw new ArgumentNullException("textLine"); } IList <ITextLine> textLines = _textView.TextLines; if (!this.FindTextLine(textLine.LineSpan.Start, out num)) { throw new ArgumentException("textLine not found within textlines", "textLine"); } return(num); }
/// <summary> /// Document line factory for the compiler processing steps : create new line from text /// </summary> protected CodeElementsLine CreateNewDocumentLine(ITextLine textLine, ColumnsLayout columnsLayout) { // Ensure all document lines are read-only snapshots ITextLine textLineSnapshot; if (!textLine.IsReadOnly) { textLineSnapshot = new TextLineSnapshot(textLine); } else { textLineSnapshot = textLine; } return(new CodeElementsLine(textLineSnapshot, columnsLayout)); }
/// <summary> /// Produce an indented version of a text line. The indentation depends on the current Layout format /// ColumnsLayout.CobolReferenceFormat or ColumnsLayout.FreeTextFormat. /// </summary> /// <param name="line">The text line to be produced an indented version</param> /// <param name="isComment">if null then this is the identity function, if true a commented line is produced, otherwise an uncommented line is produced.</param> /// <returns>The idented line</returns> private IEnumerable <ITextLine> Indent(ITextLine line, bool?isComment) { var results = new List <ITextLine>(); var cobol = line as CobolTextLine; if (cobol != null) { if (Layout == ColumnsLayout.CobolReferenceFormat) { results.Add(SetComment(line, isComment)); } else if (Layout == ColumnsLayout.FreeTextFormat) { results.Add(SetComment(new TextLineSnapshot(-1, cobol.SourceText ?? "", null), isComment)); } else { throw new System.NotImplementedException("Unsuported columns layout: " + Layout); } } else { if (Layout == ColumnsLayout.CobolReferenceFormat) { var lines = CobolTextLine.Create(line.Text, Layout, line.LineIndex); foreach (var l in lines) { results.Add(SetComment(l, isComment)); } } else if (Layout == ColumnsLayout.FreeTextFormat) { results.Add(SetComment(line, isComment)); } else { throw new System.NotImplementedException("Unsuported columns layout: " + Layout); } } if (results.Count < 1) { throw new System.NotImplementedException("Unsuported ITextLine type: " + line.GetType()); } return(results); }
void ILineTracker.LineInserted(DocumentLine insertionPos, DocumentLine newLine) { if (sendNextChangeEvents) { int lineIndex = newLine.LineNumber - 1; ITextLine newTextLine = BuildTextLineFromDocumentLine(newLine); // Special case when you press enter at the end of a line in the middle of the document if (newLine.DelimiterLength == 0 && newLine.TotalLength >= 2) { if (newTextLine.Text.EndsWith("\r\n")) { newTextLine = BuildTextLineFromDocumentLine(newLine, newLine.TotalLength - 2); } } textChangedEvent.TextChanges.Add(new TextChange(TextChangeType.LineInserted, lineIndex, newTextLine)); } }
void ILineTracker.SetLineLength(DocumentLine line, int newTotalLength) { if (sendNextChangeEvents) { int lineIndex = line.LineNumber - 1; int delimiterLength = Math.Min(line.DelimiterLength, newTotalLength); ITextLine newTextLine = BuildTextLineFromDocumentLine(line, newTotalLength - delimiterLength); // Special case when you press enter at the end of the last line of the document if (delimiterLength == 0 && newTotalLength >= 2) { if (newTextLine.Text.EndsWith("\r\n")) { newTextLine = BuildTextLineFromDocumentLine(line, newTotalLength - 2); } } textChangedEvent.TextChanges.Add(new TextChange(TextChangeType.LineUpdated, lineIndex, newTextLine)); } }
public ITextLine GetLineFromPosition(int position) { if (position < 0 || position > this.Length) { throw new ArgumentOutOfRangeException("position"); } // After asking about a location on a particular line // it is common to ask about other position in the same line again. // try to see if this is the case. var lastFound = this.lastLineFoundForPosition; if (lastFound != null && lastFound.Start <= position && lastFound.EndIncludingLineBreak > position) { return(lastFound); } var lines = this.lines.Value; if (position == this.Length) { // this can happen when the user tried to get the line of items // that are at the absolute end of this text (i.e. the EndOfLine // token, or missing tokens that are at the end of the text). // In this case, we want the last line in the text. return(lines[lines.Count - 1]); } // Binary search to find the right line int lineNumber = lineStarts.Value.BinarySearch(position); if (lineNumber < 0) { lineNumber = (~lineNumber) - 1; } var result = lines[lineNumber]; this.lastLineFoundForPosition = result; return(result); }
public bool HasEnterEnd() { for (int i = Items.Count - 1; i >= 0; i--) { ITextItem item = Items[i]; if (item is ITextTrim) { ITextTrim trim = (ITextTrim)item; return(trim.GetEnterCount() > 0); } if (item is ITextLine) { ITextLine line = (ITextLine)item; return(line.HasEnterEnd()); } return(false); } return(false); }
public override ITextLine Combine( ITextLine lineA, int offsetA, int countA, ITextLine lineB, ITextLine lineC, int offsetC, int countC) { if ((lineA is StringStorageLine) && (lineC is StringStorageLine) && ((lineB == null) || (lineB is StringStorageLine))) { return new StringStorageLine( String.Concat( ((StringStorageLine)lineA).line.Substring(offsetA, countA), lineB != null ? ((StringStorageLine)lineB).line : String.Empty, ((StringStorageLine)lineC).line.Substring(offsetC, countC))); } return base.Combine(lineA, offsetA, countA, lineB, lineC, offsetC, countC); }
/// <summary> /// Produces a commented text line of a text line /// </summary> /// <param name="line">The text line to be procuded a commented text line </param> /// <returns>The commente dtext line</returns> private static ITextLine Comment(ITextLine line) { var cobol = line as CobolTextLine; if (cobol != null) { StringBuilder text = new StringBuilder(cobol.Text); text[6] = '*'; var lines = CobolTextLine.Create("*" + cobol.SourceText, cobol.ColumnsLayout, cobol.InitialLineIndex); foreach (var l in lines) { return(l); // there's only one in the collection } throw new System.NotImplementedException("I should have at least one item!"); } else { return(new TextLineSnapshot(line.InitialLineIndex, "*" + line.Text, null)); } }
/// <summary> /// Produces an uncommented text line from a commented text line /// </summary> /// <param name="line">The text line to produce the uncommented text line.</param> /// <returns>The uncommented text line</returns> private static ITextLine Uncomment(ITextLine line) { var cobol = line as CobolTextLine; if (cobol != null) { StringBuilder text = new StringBuilder(cobol.Text); text[6] = ' '; var lines = CobolTextLine.Create(text.ToString(), cobol.ColumnsLayout, cobol.LineIndex); foreach (var l in lines) { return(l);// there's only one in the collection } throw new System.NotImplementedException("I should have at least one item!"); } else { StringBuilder text = new StringBuilder(line.Text); int index = line.Text.IndexOf('*'); text[index] = ' '; return(new TextLineSnapshot(line.LineIndex, text.ToString(), null)); } }
public CobolTextLine(ITextLine textLine, ColumnsLayout columnsLayout) { // Reuse the external text line object this.textLine = textLine; ColumnsLayout = columnsLayout; // Scan the line to find the indexes of the different areas // - 72 columns reference format if (columnsLayout == ColumnsLayout.CobolReferenceFormat) { MapVariableLengthLineWithReferenceFormat(); } // - free format and unlimited line length else { MapVariableLengthLineWithFreeFormat(); } // Study the indicator char to determine the type of the line ComputeLineTypeFromIndicator(); // First text analysis phase of the incremental compilation process CompilationStep = CompilationStep.Text; }
protected override void InsertRange(int index, ITextLine[] linesToInsert) { string[] linesToInsert2 = new string[linesToInsert.Length]; for (int i = 0; i < linesToInsert.Length; i++) { if (!(linesToInsert[i] is StringStorageLine)) { throw new ArgumentException(); } linesToInsert2[i] = ((StringStorageLine)linesToInsert[i]).line; } lines.InsertRange(index, linesToInsert2); }
public abstract ITextLine Ensure( ITextLine line);
public virtual ITextLine Combine( ITextLine lineA, int offsetA, int countA, ITextLine lineB, ITextLine lineC, int offsetC, int countC) { IDecodedTextLine decodedLineA = lineA.Decode_MustDispose(); IDecodedTextLine decodedLineB = lineB != null ? lineB.Decode_MustDispose() : null; IDecodedTextLine decodedLineC = lineC.Decode_MustDispose(); return Encode( String.Concat( decodedLineA.Value.Substring(offsetA, countA), decodedLineB != null ? decodedLineB.Value : String.Empty, decodedLineC.Value.Substring(offsetC, countC))); }
public BasicAnchor(ITextLine line) { AdornedLine = line; }
/// <summary> /// Document line factory for the compiler processing steps : create new line from text /// </summary> protected CodeElementsLine CreateNewDocumentLine(ITextLine textLine, ColumnsLayout columnsLayout) { // Ensure all document lines are read-only snapshots ITextLine textLineSnapshot; if (!textLine.IsReadOnly) { textLineSnapshot = new TextLineSnapshot(textLine); } else { textLineSnapshot = textLine; } return new CodeElementsLine(textLineSnapshot, columnsLayout); }
public CodeElementsLine(ITextLine textLine, ColumnsLayout columnsLayout) : base(textLine, columnsLayout) { }
private bool IsInInput(ITextLine line) { int c = offset; while (c < Input.Count) { if (Input[c] == line) return true; c++; } return false; }
private Int32 GetIndexOfTextLine(ITextLine textLine) { Int32 num; if (textLine == null) { throw new ArgumentNullException("textLine"); } IList<ITextLine> textLines = _textView.TextLines; if (!this.FindTextLine(textLine.LineSpan.Start, out num)) { throw new ArgumentException("textLine not found within textlines", "textLine"); } return num; }
public override ITextLine Substring( ITextLine line, int offset, int count) { if (line is StringStorageLine) { return new StringStorageLine(((StringStorageLine)line).line.Substring(offset, count)); } return base.Substring(line, offset, count); }
/// <summary> /// Constructor /// </summary> /// <param name="type">Type of chage applied to the line</param> /// <param name="lineIndex">Index of the line which was changed</param> /// <param name="newLine">New line content after the update (null in case of a LineRemoved event)</param> public TextChange(TextChangeType type, int lineIndex, ITextLine newLine) { Type = type; LineIndex = lineIndex; NewLine = newLine; }
/// <summary> /// The first line has the index 0 /// </summary> public int FindIndexOfLine(ITextLine line) { ReadOnlyTextLine roTextLine = line as ReadOnlyTextLine; if (roTextLine != null) { return roTextLine.LineIndex; } else { return -1; } }
protected override void SetLine(int index, ITextLine line) { if (!(line is StringStorageLine)) { throw new ArgumentException(); } lines[index] = ((StringStorageLine)line).line; }
internal ProcessedTokensLine(ITextLine textLine, ColumnsLayout columnsLayout) : base(textLine, columnsLayout) { PreprocessingState = PreprocessorState.NeedsCompilerDirectiveParsing; }
protected override void SetLine(int index, ITextLine line) { if (!(line is Utf8GapStorageLine)) { throw new ArgumentException(); } byte[] bytes = ((Utf8GapStorageLine)line).bytes; buffer.SetLine(index, bytes); }
/// <summary>Reference immutable text line char array</summary> public TextLineCharStream(ITextLine line) { data = line; n = line.Length; }
protected override void InsertRange(int index, ITextLine[] linesToInsert) { Utf8GapStorageLine[] linesToInsert2 = new Utf8GapStorageLine[linesToInsert.Length]; for (int i = 0; i < linesToInsert.Length; i++) { if (!(linesToInsert[i] is Utf8GapStorageLine)) { throw new ArgumentException(); } linesToInsert2[i] = (Utf8GapStorageLine)linesToInsert[i]; } for (int i = 0; i < linesToInsert2.Length; i++) { Insert(index + i, linesToInsert2[i]); } }
public TextLineSnapshot(ITextLine mutableTextLine) { InitialLineIndex = mutableTextLine.InitialLineIndex; Text = mutableTextLine.Text; LineTrackingReferenceInSourceDocument = mutableTextLine.LineTrackingReferenceInSourceDocument; }
/// <summary> /// Write all lines between the last written line (ie. Input[offset-1]) and a given line. /// If line is contained in Input but before offset, all remaining Input will be written. /// In other words: don't fall in this case. /// </summary> /// <param name="line"></param> /// <returns>Number of lines written during this method call.</returns> private int WriteInputLinesUpTo(ITextLine line) { if (!IsInInput(line)) return 0; int lines = 0; while (offset < Input.Count) { var l = Input[offset]; if (l == line) break; if (ShouldCopy(l)) Write(l, null); // offset is normally increased by a call to Write, // so don't forget to do it to avoid infinite loop else offset++; lines++; } return lines; }
private IEnumerable<ITextLine> Indent(ITextLine line, bool? isComment) { var results = new List<ITextLine>(); var cobol = line as CobolTextLine; if (cobol != null) { if (Layout == ColumnsLayout.CobolReferenceFormat) { results.Add(SetComment(line, isComment)); } else if (Layout == ColumnsLayout.FreeTextFormat) { results.Add(SetComment(new TextLineSnapshot(-1, cobol.SourceText ?? "", null), isComment)); } else throw new System.NotImplementedException("Unsuported columns layout: "+Layout); } else { if (Layout == ColumnsLayout.CobolReferenceFormat) { var lines = CobolTextLine.Create(line.Text, Layout, line.InitialLineIndex); foreach(var l in lines) results.Add(SetComment(l, isComment)); } else if (Layout == ColumnsLayout.FreeTextFormat) { results.Add(SetComment(line, isComment)); } else throw new System.NotImplementedException("Unsuported columns layout: "+Layout); } if (results.Count < 1) throw new System.NotImplementedException("Unsuported ITextLine type: "+line.GetType()); return results; }
public void InsertLine(ITextLine line) { Lines.Add(line); }
public ITextLine GetLineFromPosition(int position) { if (position < 0 || position > this.Length) { throw new ArgumentOutOfRangeException("position"); } // After asking about a location on a particular line // it is common to ask about other position in the same line again. // try to see if this is the case. var lastFound = this.lastLineFoundForPosition; if (lastFound != null && lastFound.Start <= position && lastFound.EndIncludingLineBreak > position) { return lastFound; } var lines = this.Lines; if (position == this.Length) { // this can happen when the user tried to get the line of items // that are at the absolute end of this text (i.e. the EndOfLine // token, or missing tokens that are at the end of the text). // In this case, we want the last line in the text. return lines[lines.Count-1]; } // Binary search to find the right line int lineNumber = LineStarts.BinarySearch(position); if (lineNumber < 0) { lineNumber = (~lineNumber) - 1; } var result = lines[lineNumber]; this.lastLineFoundForPosition = result; return result; }
public void InsertLine(int index, ITextLine line) { Lines.Insert(index, line); }
public virtual ITextLine Substring( ITextLine line, int offset, int count) { IDecodedTextLine decodedLine = line.Decode_MustDispose(); return Encode(decodedLine.Value.Substring(offset, count)); }
/// <summary> /// Writes one line of Input as one or more lines in Output. /// A single line, once indented, can output as many lines, especially on 80 colons. /// The value of offset is increased once as part of the Write operation. /// </summary> /// <param name="line">Input[offset]</param> /// <param name="isComment">Must line be commented ?</param> private void Write(ITextLine line, bool? isComment) { if (line == lastline) return; int c = 0; foreach(var l in Indent(line, isComment)) { Output.WriteLine(l.Text); c++; } offset++; lastline = line; }
public TextLineSnapshot(ITextLine mutableTextLine) { LineIndex = mutableTextLine.LineIndex; Text = mutableTextLine.Text; LineTrackingReferenceInSourceDocument = mutableTextLine.LineTrackingReferenceInSourceDocument; }
protected override void Insert(int index, ITextLine line) { if (!(line is StringStorageLine)) { throw new ArgumentException(); } lines.Insert(index, ((StringStorageLine)line).line); }
/// <summary> /// Offset of the first char of this line in the document /// </summary> public int FindStartOffsetOfLine(ITextLine line) { ReadOnlyTextLine roTextLine = line as ReadOnlyTextLine; if (roTextLine != null) { return roTextLine.StartOffset; } else { return -1; } }