1291 // --- Command implementations --- |
1303 // --- Command implementations --- |
1292 |
1304 |
1293 private static final String[] SET_SUBCOMMANDS = new String[]{ |
1305 private static final String[] SET_SUBCOMMANDS = new String[]{ |
1294 "format", "truncation", "feedback", "mode", "prompt", "editor", "start"}; |
1306 "format", "truncation", "feedback", "mode", "prompt", "editor", "start"}; |
1295 |
1307 |
1296 private static final String[] RETAIN_SUBCOMMANDS = new String[]{ |
|
1297 "feedback", "mode", "editor", "start"}; |
|
1298 |
|
1299 final boolean cmdSet(String arg) { |
1308 final boolean cmdSet(String arg) { |
1300 String cmd = "/set"; |
1309 String cmd = "/set"; |
1301 ArgTokenizer at = new ArgTokenizer(cmd, arg.trim()); |
1310 ArgTokenizer at = new ArgTokenizer(cmd, arg.trim()); |
1302 String which = subCommand(cmd, at, SET_SUBCOMMANDS); |
1311 String which = subCommand(cmd, at, SET_SUBCOMMANDS); |
1303 if (which == null) { |
1312 if (which == null) { |
1304 return false; |
1313 return false; |
1305 } |
1314 } |
1306 switch (which) { |
1315 switch (which) { |
|
1316 case "_retain": { |
|
1317 errormsg("jshell.err.setting.to.retain.must.be.specified", at.whole()); |
|
1318 return false; |
|
1319 } |
|
1320 case "_blank": { |
|
1321 // show top-level settings |
|
1322 new SetEditor().set(); |
|
1323 showSetStart(); |
|
1324 setFeedback(this, at); // no args so shows feedback setting |
|
1325 hardmsg("jshell.msg.set.show.mode.settings"); |
|
1326 return true; |
|
1327 } |
1307 case "format": |
1328 case "format": |
1308 return feedback.setFormat(this, at); |
1329 return feedback.setFormat(this, at); |
1309 case "truncation": |
1330 case "truncation": |
1310 return feedback.setTruncation(this, at); |
1331 return feedback.setTruncation(this, at); |
1311 case "feedback": |
1332 case "feedback": |
1312 return feedback.setFeedback(this, at); |
1333 return setFeedback(this, at); |
1313 case "mode": |
1334 case "mode": |
1314 return feedback.setMode(this, at); |
1335 return feedback.setMode(this, at, |
|
1336 retained -> prefs.put(MODE_KEY, retained)); |
1315 case "prompt": |
1337 case "prompt": |
1316 return feedback.setPrompt(this, at); |
1338 return feedback.setPrompt(this, at); |
1317 case "editor": |
1339 case "editor": |
1318 return setEditor(at, true); |
1340 return new SetEditor(at).set(); |
1319 case "start": |
1341 case "start": |
1320 return setStart(cmd, at, true); |
1342 return setStart(at); |
1321 default: |
1343 default: |
1322 errormsg("jshell.err.arg", cmd, at.val()); |
1344 errormsg("jshell.err.arg", cmd, at.val()); |
1323 return false; |
1345 return false; |
1324 } |
1346 } |
1325 } |
1347 } |
1326 |
1348 |
1327 final boolean cmdRetain(String arg) { |
1349 boolean setFeedback(MessageHandler messageHandler, ArgTokenizer at) { |
1328 String cmd = "/retain"; |
1350 return feedback.setFeedback(messageHandler, at, |
1329 ArgTokenizer at = new ArgTokenizer(cmd, arg.trim()); |
1351 fb -> prefs.put(FEEDBACK_KEY, fb)); |
1330 String which = subCommand(cmd, at, RETAIN_SUBCOMMANDS); |
1352 } |
1331 if (which == null) { |
1353 |
1332 return false; |
1354 // Find which, if any, sub-command matches. |
1333 } |
1355 // Return null on error |
1334 switch (which) { |
|
1335 case "feedback": { |
|
1336 String fb = feedback.retainFeedback(this, at); |
|
1337 if (fb != null) { |
|
1338 // If a feedback mode has been set now, or in the past, retain it |
|
1339 prefs.put(FEEDBACK_KEY, fb); |
|
1340 return true; |
|
1341 } |
|
1342 return false; |
|
1343 } |
|
1344 case "mode": |
|
1345 String retained = feedback.retainMode(this, at); |
|
1346 if (retained != null) { |
|
1347 // Retain this mode and all previously retained modes |
|
1348 prefs.put(MODE_KEY, retained); |
|
1349 return true; |
|
1350 } |
|
1351 return false; |
|
1352 case "editor": |
|
1353 if (!setEditor(at, false)) { |
|
1354 return false; |
|
1355 } |
|
1356 // retain editor setting |
|
1357 prefs.put(EDITOR_KEY, (editor == null) |
|
1358 ? "" |
|
1359 : (editorWait? "-" : "*") + String.join(RECORD_SEPARATOR, editor)); |
|
1360 return true; |
|
1361 case "start": { |
|
1362 if (!setStart(cmd, at, false)) { |
|
1363 return false; |
|
1364 } |
|
1365 // retain startup setting |
|
1366 prefs.put(STARTUP_KEY, startup); |
|
1367 return true; |
|
1368 } |
|
1369 default: |
|
1370 errormsg("jshell.err.arg", cmd, at.val()); |
|
1371 return false; |
|
1372 } |
|
1373 } |
|
1374 |
|
1375 // Print the help doc for the specified sub-command |
|
1376 boolean printSubCommandHelp(String cmd, ArgTokenizer at, String helpPrefix, String[] subs) { |
|
1377 String which = subCommand(cmd, at, subs); |
|
1378 if (which == null) { |
|
1379 return false; |
|
1380 } |
|
1381 hardrb(helpPrefix + which); |
|
1382 return true; |
|
1383 } |
|
1384 |
|
1385 // Find which, if any, sub-command matches |
|
1386 String subCommand(String cmd, ArgTokenizer at, String[] subs) { |
1356 String subCommand(String cmd, ArgTokenizer at, String[] subs) { |
1387 String[] matches = at.next(subs); |
1357 at.allowedOptions("-retain"); |
1388 if (matches == null) { |
1358 String sub = at.next(); |
|
1359 if (sub == null) { |
1389 // No sub-command was given |
1360 // No sub-command was given |
1390 errormsg("jshell.err.sub.arg", cmd); |
1361 return at.hasOption("-retain") |
1391 return null; |
1362 ? "_retain" |
1392 } |
1363 : "_blank"; |
|
1364 } |
|
1365 String[] matches = Arrays.stream(subs) |
|
1366 .filter(s -> s.startsWith(sub)) |
|
1367 .toArray(size -> new String[size]); |
1393 if (matches.length == 0) { |
1368 if (matches.length == 0) { |
1394 // There are no matching sub-commands |
1369 // There are no matching sub-commands |
1395 errormsg("jshell.err.arg", cmd, at.val()); |
1370 errormsg("jshell.err.arg", cmd, sub); |
1396 fluffmsg("jshell.msg.use.one.of", Arrays.stream(subs) |
1371 fluffmsg("jshell.msg.use.one.of", Arrays.stream(subs) |
1397 .collect(Collectors.joining(", ")) |
1372 .collect(Collectors.joining(", ")) |
1398 ); |
1373 ); |
1399 return null; |
1374 return null; |
1400 } |
1375 } |
1401 if (matches.length > 1) { |
1376 if (matches.length > 1) { |
1402 // More than one sub-command matches the initial characters provided |
1377 // More than one sub-command matches the initial characters provided |
1403 errormsg("jshell.err.sub.ambiguous", cmd, at.val()); |
1378 errormsg("jshell.err.sub.ambiguous", cmd, sub); |
1404 fluffmsg("jshell.msg.use.one.of", Arrays.stream(matches) |
1379 fluffmsg("jshell.msg.use.one.of", Arrays.stream(matches) |
1405 .collect(Collectors.joining(", ")) |
1380 .collect(Collectors.joining(", ")) |
1406 ); |
1381 ); |
1407 return null; |
1382 return null; |
1408 } |
1383 } |
1409 return matches[0]; |
1384 return matches[0]; |
1410 } |
1385 } |
1411 |
1386 |
1412 // The sub-command: /set editor <editor-command-line>> |
1387 static class EditorSetting { |
1413 boolean setEditor(ArgTokenizer at, boolean argsRequired) { |
1388 |
1414 at.allowedOptions("-default", "-wait"); |
1389 static String BUILT_IN_REP = "-default"; |
1415 String prog = at.next(); |
1390 static char WAIT_PREFIX = '-'; |
1416 List<String> ed = new ArrayList<>(); |
1391 static char NORMAL_PREFIX = '*'; |
1417 while (at.val() != null) { |
1392 |
1418 ed.add(at.val()); |
1393 final String[] cmd; |
1419 at.nextToken(); |
1394 final boolean wait; |
1420 } |
1395 |
1421 if (!checkOptionsAndRemainingInput(at)) { |
1396 EditorSetting(String[] cmd, boolean wait) { |
1422 return false; |
1397 this.wait = wait; |
1423 } |
1398 this.cmd = cmd; |
1424 boolean defaultOption = at.hasOption("-default"); |
1399 } |
1425 boolean waitOption = at.hasOption("-wait"); |
1400 |
1426 if (prog != null) { |
1401 // returns null if not stored in preferences |
1427 if (defaultOption) { |
1402 static EditorSetting fromPrefs(Preferences prefs) { |
|
1403 // Read retained editor setting (if any) |
|
1404 String editorString = prefs.get(EDITOR_KEY, ""); |
|
1405 if (editorString == null || editorString.isEmpty()) { |
|
1406 return null; |
|
1407 } else if (editorString.equals(BUILT_IN_REP)) { |
|
1408 return BUILT_IN_EDITOR; |
|
1409 } else { |
|
1410 boolean wait = false; |
|
1411 char waitMarker = editorString.charAt(0); |
|
1412 if (waitMarker == WAIT_PREFIX || waitMarker == NORMAL_PREFIX) { |
|
1413 wait = waitMarker == WAIT_PREFIX; |
|
1414 editorString = editorString.substring(1); |
|
1415 } |
|
1416 String[] cmd = editorString.split(RECORD_SEPARATOR); |
|
1417 return new EditorSetting(cmd, wait); |
|
1418 } |
|
1419 } |
|
1420 |
|
1421 void toPrefs(Preferences prefs) { |
|
1422 prefs.put(EDITOR_KEY, (this == BUILT_IN_EDITOR) |
|
1423 ? BUILT_IN_REP |
|
1424 : (wait ? WAIT_PREFIX : NORMAL_PREFIX) + String.join(RECORD_SEPARATOR, cmd)); |
|
1425 } |
|
1426 |
|
1427 @Override |
|
1428 public boolean equals(Object o) { |
|
1429 if (o instanceof EditorSetting) { |
|
1430 EditorSetting ed = (EditorSetting) o; |
|
1431 return Arrays.equals(cmd, ed.cmd) && wait == ed.wait; |
|
1432 } else { |
|
1433 return false; |
|
1434 } |
|
1435 } |
|
1436 |
|
1437 @Override |
|
1438 public int hashCode() { |
|
1439 int hash = 7; |
|
1440 hash = 71 * hash + Arrays.deepHashCode(this.cmd); |
|
1441 hash = 71 * hash + (this.wait ? 1 : 0); |
|
1442 return hash; |
|
1443 } |
|
1444 } |
|
1445 |
|
1446 class SetEditor { |
|
1447 |
|
1448 private final ArgTokenizer at; |
|
1449 private final String[] command; |
|
1450 private final boolean hasCommand; |
|
1451 private final boolean defaultOption; |
|
1452 private final boolean waitOption; |
|
1453 private final boolean retainOption; |
|
1454 |
|
1455 SetEditor(ArgTokenizer at) { |
|
1456 at.allowedOptions("-default", "-wait", "-retain"); |
|
1457 String prog = at.next(); |
|
1458 List<String> ed = new ArrayList<>(); |
|
1459 while (at.val() != null) { |
|
1460 ed.add(at.val()); |
|
1461 at.nextToken(); // so that options are not interpreted as jshell options |
|
1462 } |
|
1463 this.at = at; |
|
1464 this.command = ed.toArray(new String[ed.size()]); |
|
1465 this.hasCommand = command.length > 0; |
|
1466 this.defaultOption = at.hasOption("-default"); |
|
1467 this.waitOption = at.hasOption("-wait"); |
|
1468 this.retainOption = at.hasOption("-retain"); |
|
1469 } |
|
1470 |
|
1471 SetEditor() { |
|
1472 this(new ArgTokenizer("", "")); |
|
1473 } |
|
1474 |
|
1475 boolean set() { |
|
1476 if (!check()) { |
|
1477 return false; |
|
1478 } |
|
1479 if (!hasCommand && !defaultOption && !retainOption) { |
|
1480 // No settings or -retain, so this is a query |
|
1481 EditorSetting retained = EditorSetting.fromPrefs(prefs); |
|
1482 if (retained != null) { |
|
1483 // retained editor is set |
|
1484 hard("/set editor -retain %s", format(retained)); |
|
1485 } |
|
1486 if (retained == null || !retained.equals(editor)) { |
|
1487 // editor is not retained or retained is different from set |
|
1488 hard("/set editor %s", format(editor)); |
|
1489 } |
|
1490 return true; |
|
1491 } |
|
1492 install(); |
|
1493 if (retainOption) { |
|
1494 editor.toPrefs(prefs); |
|
1495 fluffmsg("jshell.msg.set.editor.retain", format(editor)); |
|
1496 } |
|
1497 return true; |
|
1498 } |
|
1499 |
|
1500 private boolean check() { |
|
1501 if (!checkOptionsAndRemainingInput(at)) { |
|
1502 return false; |
|
1503 } |
|
1504 if (hasCommand && defaultOption) { |
1428 errormsg("jshell.err.default.option.or.program", at.whole()); |
1505 errormsg("jshell.err.default.option.or.program", at.whole()); |
1429 return false; |
1506 return false; |
1430 } |
1507 } |
1431 editor = ed.toArray(new String[ed.size()]); |
1508 if (waitOption && !hasCommand) { |
1432 editorWait = waitOption; |
|
1433 fluffmsg("jshell.msg.set.editor.set", prog); |
|
1434 } else if (defaultOption) { |
|
1435 if (waitOption) { |
|
1436 errormsg("jshell.err.wait.applies.to.external.editor", at.whole()); |
1509 errormsg("jshell.err.wait.applies.to.external.editor", at.whole()); |
1437 return false; |
1510 return false; |
1438 } |
1511 } |
1439 editor = null; |
1512 return true; |
1440 } else if (argsRequired) { |
1513 } |
1441 errormsg("jshell.err.set.editor.arg"); |
1514 |
1442 return false; |
1515 private void install() { |
1443 } |
1516 if (hasCommand) { |
1444 return true; |
1517 editor = new EditorSetting(command, waitOption); |
|
1518 } else if (defaultOption) { |
|
1519 editor = BUILT_IN_EDITOR; |
|
1520 } else { |
|
1521 return; |
|
1522 } |
|
1523 fluffmsg("jshell.msg.set.editor.set", format(editor)); |
|
1524 } |
|
1525 |
|
1526 private String format(EditorSetting ed) { |
|
1527 if (ed == BUILT_IN_EDITOR) { |
|
1528 return "-default"; |
|
1529 } else { |
|
1530 Stream<String> elems = Arrays.stream(ed.cmd); |
|
1531 if (ed.wait) { |
|
1532 elems = Stream.concat(Stream.of("-wait"), elems); |
|
1533 } |
|
1534 return elems.collect(joining(" ")); |
|
1535 } |
|
1536 } |
1445 } |
1537 } |
1446 |
1538 |
1447 // The sub-command: /set start <start-file> |
1539 // The sub-command: /set start <start-file> |
1448 boolean setStart(String cmd, ArgTokenizer at, boolean argsRequired) { |
1540 boolean setStart(ArgTokenizer at) { |
1449 at.allowedOptions("-default", "-none"); |
1541 at.allowedOptions("-default", "-none", "-retain"); |
1450 String fn = at.next(); |
1542 String fn = at.next(); |
1451 if (!checkOptionsAndRemainingInput(at)) { |
1543 if (!checkOptionsAndRemainingInput(at)) { |
1452 return false; |
1544 return false; |
1453 } |
1545 } |
1454 int argCount = at.optionCount() + ((fn != null) ? 1 : 0); |
1546 boolean defaultOption = at.hasOption("-default"); |
1455 if (argCount > 1 || argsRequired && argCount == 0) { |
1547 boolean noneOption = at.hasOption("-none"); |
|
1548 boolean retainOption = at.hasOption("-retain"); |
|
1549 boolean hasFile = fn != null; |
|
1550 |
|
1551 int argCount = (defaultOption ? 1 : 0) + (noneOption ? 1 : 0) + (hasFile ? 1 : 0); |
|
1552 if (argCount > 1) { |
1456 errormsg("jshell.err.option.or.filename", at.whole()); |
1553 errormsg("jshell.err.option.or.filename", at.whole()); |
1457 return false; |
1554 return false; |
1458 } |
1555 } |
1459 if (fn != null) { |
1556 if (argCount == 0 && !retainOption) { |
1460 String init = readFile(fn, cmd + " start"); |
1557 // no options or filename, show current setting |
|
1558 showSetStart(); |
|
1559 return true; |
|
1560 } |
|
1561 if (hasFile) { |
|
1562 String init = readFile(fn, "/set start"); |
1461 if (init == null) { |
1563 if (init == null) { |
1462 return false; |
1564 return false; |
1463 } else { |
1565 } |
1464 startup = init; |
1566 startup = init; |
1465 return true; |
1567 } else if (defaultOption) { |
1466 } |
|
1467 } else if (at.hasOption("-default")) { |
|
1468 startup = DEFAULT_STARTUP; |
1568 startup = DEFAULT_STARTUP; |
1469 } else if (at.hasOption("-none")) { |
1569 } else if (noneOption) { |
1470 startup = ""; |
1570 startup = ""; |
1471 } |
1571 } |
|
1572 if (retainOption) { |
|
1573 // retain startup setting |
|
1574 prefs.put(STARTUP_KEY, startup); |
|
1575 } |
1472 return true; |
1576 return true; |
|
1577 } |
|
1578 |
|
1579 void showSetStart() { |
|
1580 String retained = prefs.get(STARTUP_KEY, null); |
|
1581 if (retained != null) { |
|
1582 showSetStart(true, retained); |
|
1583 } |
|
1584 if (retained == null || !startup.equals(retained)) { |
|
1585 showSetStart(false, startup); |
|
1586 } |
|
1587 } |
|
1588 |
|
1589 void showSetStart(boolean isRetained, String start) { |
|
1590 String cmd = "/set start" + (isRetained ? " -retain " : " "); |
|
1591 String stset; |
|
1592 if (start.equals(DEFAULT_STARTUP)) { |
|
1593 stset = cmd + "-default"; |
|
1594 } else if (start.isEmpty()) { |
|
1595 stset = cmd + "-none"; |
|
1596 } else { |
|
1597 stset = prefix("startup.jsh:\n" + start + "\n" + cmd + "startup.jsh", ""); |
|
1598 } |
|
1599 hard(stset); |
1473 } |
1600 } |
1474 |
1601 |
1475 boolean cmdClasspath(String arg) { |
1602 boolean cmdClasspath(String arg) { |
1476 if (arg.isEmpty()) { |
1603 if (arg.isEmpty()) { |
1477 errormsg("jshell.err.classpath.arg"); |
1604 errormsg("jshell.err.classpath.arg"); |