forked from scala-ide/scala-ide
-
Notifications
You must be signed in to change notification settings - Fork 2
/
FormatterPreferencePage.scala
448 lines (371 loc) · 15.2 KB
/
FormatterPreferencePage.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
package scala.tools.eclipse.formatter
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.net.URL
import java.util.HashMap
import java.util.Properties
import net.miginfocom.layout._
import net.miginfocom.swt.MigLayout
import org.eclipse.core.resources.IProject
import org.eclipse.jdt.core.IJavaProject
import org.eclipse.jdt.internal.ui.JavaPlugin
import org.eclipse.jdt.internal.ui.javaeditor.JavaSourceViewer
import org.eclipse.jdt.internal.ui.preferences.OverlayPreferenceStore
import org.eclipse.jdt.internal.ui.preferences.PreferencesMessages
import org.eclipse.jdt.ui.PreferenceConstants
import org.eclipse.jdt.ui.text.IJavaPartitions
import org.eclipse.jface.dialogs.MessageDialog
import org.eclipse.jface.preference._
import org.eclipse.jface.resource.JFaceResources
import org.eclipse.jface.text._
import org.eclipse.jface.util._
import org.eclipse.swt.SWT
import org.eclipse.swt.events._
import org.eclipse.swt.layout._
import org.eclipse.swt.widgets._
import org.eclipse.ui._
import org.eclipse.ui.dialogs.PreferencesUtil
import org.eclipse.ui.dialogs.PropertyPage
import org.eclipse.ui.editors.text.TextEditor
import scala.tools.eclipse.ScalaPlugin
import scala.tools.eclipse.ScalaPreviewerFactory
import scala.tools.eclipse.ScalaSourceViewerConfiguration
import scala.tools.eclipse.formatter.FormatterPreferences._
import scala.tools.eclipse.lexical.ScalaDocumentPartitioner
import scala.tools.eclipse.properties.PropertyStore
import scala.tools.eclipse.util.FileUtils._
import scala.tools.eclipse.util.SWTUtils._
import scalariform.formatter._
import scalariform.formatter.preferences._
import scala.tools.eclipse.logging.HasLogger
class FormatterPreferencePage extends PropertyPage with IWorkbenchPreferencePage with HasLogger {
import FormatterPreferencePage._
private var isWorkbenchPage = false
private var allEnableDisableControls: Set[Control] = Set()
override def init(workbench: IWorkbench) {
isWorkbenchPage = true
}
lazy val overlayStore = {
import OverlayPreferenceStore._
val keys =
for (preference <- AllPreferences.preferences)
yield preference.preferenceType match {
case BooleanPreference => new OverlayKey(BOOLEAN, preference.eclipseKey)
case IntegerPreference(_, _) => new OverlayKey(INT, preference.eclipseKey)
}
val overlayStore = new OverlayPreferenceStore(getPreferenceStore, keys.toArray)
overlayStore.load()
overlayStore.start()
overlayStore
}
abstract class PrefTab(tabName: String, previewText: String) {
protected var previewDocument: IDocument = _
def build(tabFolder: TabFolder) {
val tabItem = new TabItem(tabFolder, SWT.NONE)
tabItem.setText(tabName)
val tabComposite = new Composite(tabFolder, SWT.NONE)
tabItem.setControl(tabComposite)
buildContents(tabComposite)
}
protected def buildContents(composite: Composite)
private def formatPreviewText: String = ScalaFormatter.format(previewText, getPreferences(overlayStore))
protected def addCheckBox(parent: Composite, text: String, preference: BooleanPreferenceDescriptor) {
val checkBox = new Button(parent, SWT.CHECK | SWT.WRAP)
checkBox.setText(text)
checkBox.setToolTipText(preference.description + " (" + preference.key + ")")
checkBox.setSelection(overlayStore(preference))
checkBox.setLayoutData(new CC().spanX(2).growX.wrap)
checkBox.addSelectionListener { e: SelectionEvent =>
overlayStore(preference) = checkBox.getSelection
previewDocument.set(formatPreviewText)
}
overlayStore.addPropertyChangeListener { e: PropertyChangeEvent =>
if (e.getProperty == preference.eclipseKey)
checkBox.setSelection(overlayStore(preference))
}
allEnableDisableControls += checkBox
}
protected def addNumericField(parent: Composite, text: String, preference: PreferenceDescriptor[Int]) {
val IntegerPreference(min, max) = preference.preferenceType
val label = new Label(parent, SWT.LEFT)
label.setText(text)
label.setToolTipText(preference.description + " (" + preference.key + ")")
label.setLayoutData(new CC())
val field = new Text(parent, SWT.SINGLE | SWT.BORDER)
field.setText(overlayStore(preference).toString)
field.setLayoutData(new CC().sizeGroupX("numfield").alignX("right").minWidth("40px").wrap)
def validateNumber(s: String) =
try Integer.parseInt(s) match {
case n if n < min || n > max => None
case n => Some(n)
} catch {
case _: NumberFormatException => None
}
def valueChanged() {
validateNumber(field.getText) match {
case Some(n) =>
overlayStore(preference) = n
previewDocument.set(formatPreviewText)
setErrorMessage(null)
case None =>
setErrorMessage("Number must be an integer between " + min + " and " + max)
}
}
field.onKeyReleased { valueChanged() }
field.onFocusLost { valueChanged() }
overlayStore.addPropertyChangeListener { e: PropertyChangeEvent =>
if (e.getProperty == preference.eclipseKey)
field.setText(overlayStore(preference).toString)
}
allEnableDisableControls ++= Set(label, field)
}
protected def addPreview(parent: Composite) {
val previewLabel = new Label(parent, SWT.LEFT)
allEnableDisableControls += previewLabel
previewLabel.setText("Preview:")
previewLabel.setLayoutData(new CC().spanX(2).wrap)
val previewer = createPreviewer(parent)
previewer.setLayoutData(new CC().spanX(2).grow)
}
protected def createPreviewer(parent: Composite): Control = {
val previewer = ScalaPreviewerFactory.createPreviewer(parent, getPreferenceStore, formatPreviewText)
previewDocument = previewer.getDocument
val control = previewer.getControl
allEnableDisableControls += control
control
}
}
object IndentPrefTab extends PrefTab("Indentation && Alignment", INDENT_PREVIEW_TEXT) {
def buildContents(composite: Composite) {
composite.setLayout(new MigLayout(new LC().fill, new AC, new AC().index(9).grow(1)))
addNumericField(composite, "Spaces to indent:", IndentSpaces)
addCheckBox(composite, "Indent using tabs", IndentWithTabs)
addCheckBox(composite, "Align parameters", AlignParameters)
addCheckBox(composite, "Double indent class declaration", DoubleIndentClassDeclaration)
addCheckBox(composite, "Align single-line case statements", AlignSingleLineCaseStatements)
addNumericField(composite, "Max arrow indent:", AlignSingleLineCaseStatements.MaxArrowIndent)
addCheckBox(composite, "Indent package blocks", IndentPackageBlocks)
addCheckBox(composite, "Indent local defs", IndentLocalDefs)
addPreview(composite)
}
}
object SpacesPrefTab extends PrefTab("Spaces", SPACES_PREVIEW_TEXT) {
def buildContents(composite: Composite) {
composite.setLayout(new MigLayout(new LC().fill, new AC, new AC().index(7).grow(1)))
addCheckBox(composite, "Space before colons", SpaceBeforeColon)
addCheckBox(composite, "Compact string concatenation", CompactStringConcatenation)
addCheckBox(composite, "Space inside brackets", SpaceInsideBrackets)
addCheckBox(composite, "Space inside parentheses", SpaceInsideParentheses)
addCheckBox(composite, "Preserve space before arguments", PreserveSpaceBeforeArguments)
addCheckBox(composite, "Spaces within pattern binders", SpacesWithinPatternBinders)
addPreview(composite)
}
}
object MiscPrefTab extends PrefTab("Miscellaneous", MISC_PREVIEW_TEXT) {
def buildContents(composite: Composite) {
composite.setLayout(new MigLayout(new LC().fill, new AC, new AC().index(5).grow(1)))
addCheckBox(composite, "Format XML", FormatXml)
addCheckBox(composite, "Rewrite arrow tokens", RewriteArrowSymbols)
addCheckBox(composite, "Preserve dangling close parenthesis", PreserveDanglingCloseParenthesis)
addCheckBox(composite, "Use Compact Control Readability style", CompactControlReadability)
addPreview(composite)
}
}
object ScaladocPrefTab extends PrefTab("Scaladoc", SCALADOC_PREVIEW_TEXT) {
def buildContents(composite: Composite) {
composite.setLayout(new MigLayout(new LC().fill, new AC, new AC().index(3).grow(1)))
addCheckBox(composite, "Multiline Scaladoc comments start on first line", MultilineScaladocCommentsStartOnFirstLine)
addCheckBox(composite, "Align asterisks beneath second asterisk", PlaceScaladocAsterisksBeneathSecondAsterisk)
addPreview(composite)
}
}
private def initUnderlyingPreferenceStore() {
val pluginId = ScalaPlugin.plugin.pluginId
val scalaPrefStore = ScalaPlugin.plugin.getPreferenceStore
setPreferenceStore(getElement match {
case project: IProject => new PropertyStore(project, scalaPrefStore, pluginId)
case project: IJavaProject => new PropertyStore(project.getProject, scalaPrefStore, pluginId)
case _ => scalaPrefStore
})
}
def createContents(parent: Composite): Control = {
initUnderlyingPreferenceStore() // done here to ensure that getElement will have been set
val control = new Composite(parent, SWT.NONE)
val rowConstraints = if (isWorkbenchPage)
new AC().index(0).grow(0).index(1).grow
else
new AC().index(0).grow(0).index(1).grow(0).index(2).grow(0).index(3).grow
control.setLayout(new MigLayout(new LC().insetsAll("0").fill, new AC(), rowConstraints))
if (!isWorkbenchPage) {
val projectSpecificButton = new Button(control, SWT.CHECK | SWT.WRAP)
projectSpecificButton.setText("Enable project specific settings")
projectSpecificButton.setSelection(getPreferenceStore.getBoolean(USE_PROJECT_SPECIFIC_SETTINGS_KEY))
projectSpecificButton.addSelectionListener { e: SelectionEvent =>
val enabled = projectSpecificButton.getSelection
getPreferenceStore.setValue(USE_PROJECT_SPECIFIC_SETTINGS_KEY, enabled)
allEnableDisableControls foreach { _.setEnabled(enabled) }
}
projectSpecificButton.setLayoutData(new CC)
val link = new Link(control, SWT.NONE)
link.setText("<a>" + PreferencesMessages.PropertyAndPreferencePage_useworkspacesettings_change + "</a>")
link.addSelectionListener {
PreferencesUtil.createPreferenceDialogOn(getShell, PAGE_ID, Array(PAGE_ID), null).open()
}
link.setLayoutData(new CC().alignX("right").wrap)
val horizontalLine = new Label(control, SWT.SEPARATOR | SWT.HORIZONTAL)
horizontalLine.setLayoutData(new CC().spanX(2).grow.wrap)
}
{ // Manual link + import / export buttons
val buttonPanel = new Composite(control, SWT.NONE)
buttonPanel.setLayout(
new MigLayout(
new LC().insetsAll("0"),
new AC()
.index(0).grow.align("left")
.index(1).grow(0).align("right")
.index(2).grow(0).align("right")))
val link = new Link(buttonPanel, SWT.NONE)
link.setText("<a>Scalariform manual</a>")
link.addSelectionListener { e: SelectionEvent =>
val url = new URL(SCALARIFORM_DOC_URL)
PlatformUI.getWorkbench.getBrowserSupport.createBrowser(null).openURL(url)
}
link.setLayoutData(new CC)
val importButton = new Button(buttonPanel, SWT.PUSH)
importButton.setText("Import...")
importButton.setLayoutData(new CC().sizeGroupX("button"))
importButton.addSelectionListener { importPreferences() }
val exportButton = new Button(buttonPanel, SWT.PUSH)
exportButton.setText("Export...")
exportButton.setLayoutData(new CC().sizeGroupX("button").wrap)
exportButton.addSelectionListener { exportPreferences() }
buttonPanel.setLayoutData(new CC().spanX(2).growX.wrap)
allEnableDisableControls ++= Set(link, importButton, exportButton)
}
val tabFolder = new TabFolder(control, SWT.TOP)
tabFolder.setLayoutData(new CC().spanX(2).grow)
IndentPrefTab.build(tabFolder)
SpacesPrefTab.build(tabFolder)
ScaladocPrefTab.build(tabFolder)
MiscPrefTab.build(tabFolder)
allEnableDisableControls += tabFolder
if (!isWorkbenchPage) {
val enabled = getPreferenceStore.getBoolean(USE_PROJECT_SPECIFIC_SETTINGS_KEY)
allEnableDisableControls foreach { _.setEnabled(enabled) }
}
control
}
override def performOk() = {
super.performOk()
overlayStore.propagate()
ScalaPlugin.plugin.savePluginPreferences()
true
}
override def dispose() {
overlayStore.stop()
super.dispose()
}
override def performDefaults() {
overlayStore.loadDefaults()
super.performDefaults()
}
private def getPreferenceFileNameViaDialog(title: String, initialFileName: String = ""): Option[String] = {
val dialog = new FileDialog(getShell, SWT.SAVE)
dialog.setText(title)
dialog.setFileName(initialFileName)
val dialogSettings = ScalaPlugin.plugin.getDialogSettings
Option(dialogSettings get IMPORT_EXPORT_DIALOG_PATH) foreach dialog.setFilterPath
val fileName = dialog.open()
if (fileName == null)
None
else {
dialogSettings.put(IMPORT_EXPORT_DIALOG_PATH, dialog.getFilterPath)
Some(fileName)
}
}
private def exportPreferences() {
for (fileName <- getPreferenceFileNameViaDialog("Export formatter preferences", DEFAULT_PREFERENCE_FILE_NAME)) {
val preferences = FormatterPreferences.getPreferences(overlayStore)
try
PreferencesImporterExporter.savePreferences(fileName, preferences)
catch {
case e: IOException =>
eclipseLog.error(e)
MessageDialog.openError(getShell, "Error writing to " + fileName, e.getMessage)
}
}
}
private def importPreferences() {
for (fileName <- getPreferenceFileNameViaDialog("Import formatter preferences")) {
val preferences = try
PreferencesImporterExporter.loadPreferences(fileName)
catch {
case e: IOException =>
eclipseLog.error(e)
MessageDialog.openError(getShell, "Error opening " + fileName, e.getMessage)
return
}
overlayStore.importPreferences(preferences)
}
}
}
object FormatterPreferencePage {
val DEFAULT_PREFERENCE_FILE_NAME = "formatterPreferences.properties"
val PAGE_ID = "scala.tools.eclipse.formatter.FormatterPreferencePage"
val IMPORT_EXPORT_DIALOG_PATH = "formatter.importExportDialogPath"
val SCALARIFORM_DOC_URL = "http://mdr.github.com/scalariform/"
val SPACES_PREVIEW_TEXT = """class ClassName[T](name: String) {
println("hello"+name+"world")
stack.pop() should equal (2)
x match {
case elem@Multi(values@_*) =>
}
}
"""
val INDENT_PREVIEW_TEXT = """package foo {
class Bar(param: Int)
extends Foo with Baz {
def method(s: String,
n: Int) = {
def localDef {
// ..
}
s match {
case "wibble" => 42
case "foo" => 123
case _ => 100
}
}
}
}"""
val MISC_PREVIEW_TEXT = """val xml = <foo>
<bar/>
<baz attr= "value" />
</foo>
for (n <- 1 to 10)
n match {
case _ => 42
}
val book = Book(
name = "Name",
author = "Author",
rating = 5
)
if (condition) {
// do something
}
else if (condition2) {
// do something else
}
else {
// last ditch
}
"""
val SCALADOC_PREVIEW_TEXT = """/**
* Multiline Scaladoc
* comment
*/
class A
"""
}